1 /*
   2  * Copyright (c) 2006, 2019, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.
   8  *
   9  * This code is distributed in the hope that it will be useful, but WITHOUT
  10  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  11  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  12  * version 2 for more details (a copy is included in the LICENSE file that
  13  * accompanied this code).
  14  *
  15  * You should have received a copy of the GNU General Public License version
  16  * 2 along with this work; if not, write to the Free Software Foundation,
  17  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  18  *
  19  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  20  * or visit www.oracle.com if you need additional information or have any
  21  * questions.
  22  */
  23 
  24 package jnlp.converter.parser;
  25 
  26 import java.net.URL;
  27 import java.util.Arrays;
  28 import java.util.ArrayList;
  29 import jnlp.converter.JNLPConverter;
  30 import jnlp.converter.parser.exception.MissingFieldException;
  31 import jnlp.converter.parser.exception.BadFieldException;
  32 import jnlp.converter.parser.exception.JNLParseException;
  33 import jnlp.converter.parser.xml.XMLEncoding;
  34 import jnlp.converter.parser.xml.XMLParser;
  35 import jnlp.converter.parser.xml.XMLNode;
  36 import jnlp.converter.parser.JNLPDesc.AssociationDesc;
  37 import jnlp.converter.parser.JNLPDesc.IconDesc;
  38 import jnlp.converter.parser.JNLPDesc.InformationDesc;
  39 import jnlp.converter.parser.JNLPDesc.ShortcutDesc;
  40 import jnlp.converter.parser.ResourcesDesc.JARDesc;
  41 import jnlp.converter.parser.ResourcesDesc.JREDesc;
  42 import jnlp.converter.Log;
  43 import jnlp.converter.parser.ResourcesDesc.ExtensionDesc;
  44 import jnlp.converter.parser.ResourcesDesc.PropertyDesc;
  45 import org.xml.sax.SAXParseException;
  46 
  47 public class XMLFormat {
  48 
  49     public static XMLNode parseBits(byte[] bits) throws JNLParseException {
  50         return parse(decode(bits));
  51     }
  52 
  53     private static String decode(byte[] bits) throws JNLParseException {
  54         try {
  55             return XMLEncoding.decodeXML(bits);
  56         } catch (Exception e) {
  57             throw new JNLParseException(e,
  58                 "exception determining encoding of jnlp file", 0);
  59         }
  60     }
  61 
  62     private static XMLNode parse(String source) throws JNLParseException {
  63         try {
  64             return (new XMLParser(source).parse());
  65         } catch (SAXParseException spe) {
  66             throw new JNLParseException(spe,
  67                         "exception parsing jnlp file", spe.getLineNumber());
  68         } catch (Exception e) {
  69             throw new JNLParseException(e,
  70                         "exception parsing jnlp file", 0);
  71         }
  72     }
  73 
  74     /**
  75      * thisCodebase, if set, is used to determine the codebase,
  76      *     if JNLP codebase is not absolute.
  77      *
  78      * @param thisCodebase base URL of this JNLPDesc location
  79      */
  80     public static JNLPDesc parse(byte[] bits, URL thisCodebase, String jnlp)
  81             throws Exception {
  82 
  83         JNLPDesc jnlpd = new JNLPDesc();
  84         String source = decode(bits).trim();
  85         XMLNode root = parse(source);
  86 
  87         if (root == null || root.getName() == null) {
  88             throw new JNLParseException(null, null, 0);
  89         }
  90 
  91         // Check that root element is a <jnlp> tag
  92         if (!root.getName().equals("jnlp")) {
  93             throw (new MissingFieldException(source, "<jnlp>"));
  94         }
  95 
  96         // Read <jnlp> attributes (path is empty, i.e., "")
  97         // (spec, version, codebase, href)
  98         String specVersion = XMLUtils.getAttribute(root, "", "spec", "1.0+");
  99         jnlpd.setSpecVersion(specVersion);
 100         String version = XMLUtils.getAttribute(root, "", "version");
 101         jnlpd.setVersion(version);
 102 
 103         // Make sure the codebase URL ends with a '/'.
 104         //
 105         // Regarding the JNLP spec,
 106         // the thisCodebase is used to determine the codebase.
 107         //      codebase = new URL(thisCodebase, codebase)
 108         URL codebase = GeneralUtil.asPathURL(XMLUtils.getAttributeURL(source,
 109             thisCodebase, root, "", "codebase"));
 110         if (codebase == null && thisCodebase != null) {
 111             codebase = thisCodebase;
 112         }
 113         jnlpd.setCodebase(codebase.toExternalForm());
 114 
 115         // Get href for JNLP file
 116         URL href = XMLUtils.getAttributeURL(source, codebase, root, "", "href");
 117         jnlpd.setHref(href.toExternalForm());
 118 
 119         // Read <security> attributes
 120         if (XMLUtils.isElementPath(root, "<security><all-permissions>")) {
 121             jnlpd.setIsSandbox(false);
 122         } else if (XMLUtils.isElementPath(root,
 123                 "<security><j2ee-application-client-permissions>")) {
 124             jnlpd.setIsSandbox(false);
 125         }
 126 
 127         // We can be fxapp, and also be applet, or application, or neither
 128         boolean isFXApp = false;
 129         boolean isApplet = false;
 130         if (XMLUtils.isElementPath(root, "<javafx-desc>")) {
 131             // no new type for javafx-desc - needs one of the others
 132             buildFXAppDesc(source, root, "<javafx-desc>", jnlpd);
 133             jnlpd.setIsFXApp(true);
 134             isFXApp = true;
 135         }
 136 
 137         /*
 138          * Note - the jnlp specification says there must be exactly one of
 139          * the descriptor types.  This code has always violated (or at least
 140          * not checked for) that condition.
 141          * Instead it uses precedent order app, component, installer, applet
 142          * and ignores any other descriptors given.
 143          */
 144         if (XMLUtils.isElementPath(root, "<application-desc>")) {
 145             buildApplicationDesc(source, root, jnlpd);
 146         } else if (XMLUtils.isElementPath(root, "<component-desc>")) {
 147             jnlpd.setIsLibrary(true);
 148         } else if (XMLUtils.isElementPath(root, "<installer-desc>")) {
 149             Log.warning("<installer-desc> is not supported and will be ignored in " + jnlp);
 150             jnlpd.setIsInstaller(true);
 151         } else if (XMLUtils.isElementPath(root, "<applet-desc>")) {
 152             isApplet = true;
 153         } else {
 154             if (!isFXApp) {
 155                 throw (new MissingFieldException(source,
 156                     "<jnlp>(<application-desc>|<applet-desc>|" +
 157                     "<installer-desc>|<component-desc>)"));
 158             }
 159         }
 160 
 161         if (isApplet && !isFXApp) {
 162             Log.error("Applet based applications deployed with <applet-desc> element are not supported.");
 163         }
 164 
 165         if (!jnlpd.isLibrary() && !jnlpd.isInstaller()) {
 166             buildInformationDesc(source, codebase, root, jnlpd);
 167         }
 168 
 169         if (!jnlpd.isInstaller()) {
 170             buildResourcesDesc(source, codebase, root, false, jnlpd);
 171         }
 172 
 173         if (!jnlpd.isLibrary() && !jnlpd.isInstaller()) {
 174             jnlpd.parseResourceDesc();
 175         }
 176 
 177         if (!jnlpd.isInstaller()) {
 178             if (jnlpd.isSandbox()) {
 179                 if (jnlpd.isLibrary()) {
 180                     Log.warning(jnlp + " is sandbox extension. JNLPConverter does not support sandbox environment and converted application will run without security manager.");
 181                 } else {
 182                     Log.warning("This is sandbox Web-Start application. JNLPConverter does not support sandbox environment and converted application will run without security manager.");
 183                 }
 184             }
 185         }
 186 
 187         return jnlpd;
 188     }
 189 
 190     /**
 191      * Create a combine informationDesc in the two informationDesc.
 192      * The information present in id1 overwrite the information present in id2
 193      */
 194     private static InformationDesc combineInformationDesc(
 195                                    InformationDesc id1, InformationDesc id2) {
 196         if (id1 == null) {
 197             return id2;
 198         }
 199         if (id2 == null) {
 200             return id1;
 201         }
 202 
 203         String t1 = id1.getTitle();
 204         String title  = (t1 != null && t1.length() > 0) ?
 205             t1 : id2.getTitle();
 206         String v1 = id1.getVendor();
 207         String vendor = (v1 != null && v1.length() > 0) ?
 208             v1 : id2.getVendor();
 209 
 210         /** Copy descriptions */
 211         String[] descriptions = new String[InformationDesc.NOF_DESC];
 212         for (int i = 0; i < descriptions.length; i++) {
 213             descriptions[i] = (id1.getDescription(i) != null)
 214                     ? id1.getDescription(i) : id2.getDescription(i);
 215         }
 216 
 217         /** Icons */
 218         ArrayList<IconDesc> iconList = new ArrayList<>();
 219         if (id2.getIcons() != null) {
 220             iconList.addAll(Arrays.asList(id2.getIcons()));
 221         }
 222         if (id1.getIcons() != null) {
 223             iconList.addAll(Arrays.asList(id1.getIcons()));
 224         }
 225         IconDesc[] icons = new IconDesc[iconList.size()];
 226         icons = iconList.toArray(icons);
 227 
 228         ShortcutDesc hints = (id1.getShortcut() != null) ?
 229                              id1.getShortcut() : id2.getShortcut();
 230 
 231         AssociationDesc[] asd = ( AssociationDesc[] ) addArrays(
 232             (Object[])id1.getAssociations(), (Object[])id2.getAssociations());
 233 
 234         return new InformationDesc(title,
 235                                    vendor,
 236                                    descriptions,
 237                                    icons,
 238                                    hints,
 239                                    asd);
 240     }
 241 
 242     /** Extract data from <information> tag */
 243     private static void buildInformationDesc(final String source, final URL codebase, XMLNode root, JNLPDesc jnlpd)
 244         throws MissingFieldException, BadFieldException {
 245         final ArrayList<InformationDesc> list = new ArrayList<>();
 246 
 247         // Iterates over all <information> nodes ignoring the type
 248         XMLUtils.visitElements(root,
 249             "<information>", new XMLUtils.ElementVisitor() {
 250             @Override
 251             public void visitElement(XMLNode e) throws
 252                 BadFieldException, MissingFieldException {
 253 
 254                 // Check for right os, arch, and locale
 255                 String[] os = GeneralUtil.getStringList(
 256                             XMLUtils.getAttribute(e, "", "os", null));
 257                 String[] arch = GeneralUtil.getStringList(
 258                             XMLUtils.getAttribute(e, "", "arch", null));
 259                 String[] locale = GeneralUtil.getStringList(
 260                             XMLUtils.getAttribute(e, "", "locale", null));
 261                 if (GeneralUtil.prefixMatchStringList(
 262                                 os, GeneralUtil.getOSFullName()) &&
 263                     GeneralUtil.prefixMatchArch(arch) &&
 264                     matchDefaultLocale(locale))
 265                 {
 266                     // Title, vendor
 267                     String title = XMLUtils.getElementContents(e, "<title>");
 268                     String vendor = XMLUtils.getElementContents(e, "<vendor>");
 269 
 270                     // Descriptions
 271                     String[] descriptions =
 272                                 new String[InformationDesc.NOF_DESC];
 273                     descriptions[InformationDesc.DESC_DEFAULT] =
 274                         XMLUtils.getElementContentsWithAttribute(
 275                         e, "<description>", "kind", "", null);
 276                     descriptions[InformationDesc.DESC_ONELINE] =
 277                         XMLUtils.getElementContentsWithAttribute(
 278                         e, "<description>", "kind", "one-line", null);
 279                     descriptions[InformationDesc.DESC_SHORT] =
 280                         XMLUtils.getElementContentsWithAttribute(
 281                         e, "<description>", "kind", "short", null);
 282                     descriptions[InformationDesc.DESC_TOOLTIP] =
 283                         XMLUtils.getElementContentsWithAttribute(
 284                         e, "<description>", "kind", "tooltip", null);
 285 
 286                     // Icons
 287                     IconDesc[] icons = getIconDescs(source, codebase, e);
 288 
 289                     // Shortcut hints
 290                     ShortcutDesc shortcuts = getShortcutDesc(e);
 291 
 292                     // Association hints
 293                     AssociationDesc[] associations = getAssociationDesc(
 294                                                         source, codebase, e);
 295 
 296                     list.add(new InformationDesc(
 297                         title, vendor, descriptions, icons,
 298                         shortcuts, associations));
 299                 }
 300             }
 301         });
 302 
 303         /* Combine all information desc. information in a single one for
 304          * the current locale using the following priorities:
 305          *   1. locale == language_country_variant
 306          *   2. locale == lauguage_country
 307          *   3. locale == lauguage
 308          *   4. no or empty locale
 309          */
 310         InformationDesc normId = new InformationDesc(null, null, null, null, null, null);
 311         for (InformationDesc id : list) {
 312             normId = combineInformationDesc(id, normId);
 313         }
 314 
 315         jnlpd.setTitle(normId.getTitle());
 316         jnlpd.setVendor(normId.getVendor());
 317         jnlpd.setDescriptions(normId.getDescription());
 318         jnlpd.setIcons(normId.getIcons());
 319         jnlpd.setShortcuts(normId.getShortcut());
 320         jnlpd.setAssociations(normId.getAssociations());
 321     }
 322 
 323     private static Object[] addArrays (Object[] a1, Object[] a2) {
 324         if (a1 == null) {
 325             return a2;
 326         }
 327         if (a2 == null) {
 328             return a1;
 329         }
 330         ArrayList<Object> list = new ArrayList<>();
 331         int i;
 332         for (i=0; i<a1.length; list.add(a1[i++]));
 333         for (i=0; i<a2.length; list.add(a2[i++]));
 334         return list.toArray(a1);
 335     }
 336 
 337     public static boolean matchDefaultLocale(String[] localeStr) {
 338         return GeneralUtil.matchLocale(localeStr, GeneralUtil.getDefaultLocale());
 339     }
 340 
 341     /** Extract data from <resources> tag. There is only one. */
 342     static void buildResourcesDesc(final String source,
 343             final URL codebase, XMLNode root, final boolean ignoreJres, JNLPDesc jnlpd)
 344             throws MissingFieldException, BadFieldException {
 345         // Extract classpath directives
 346         final ResourcesDesc rdesc = new ResourcesDesc();
 347 
 348         // Iterate over all entries
 349         XMLUtils.visitElements(root, "<resources>",
 350                 new XMLUtils.ElementVisitor() {
 351             @Override
 352             public void visitElement(XMLNode e)
 353                     throws MissingFieldException, BadFieldException {
 354                 // Check for right os, archictecture, and locale
 355                 String[] os = GeneralUtil.getStringList(
 356                         XMLUtils.getAttribute(e, "", "os", null));
 357                 final String arch = XMLUtils.getAttribute(e, "", "arch", null);
 358                 String[] locale = GeneralUtil.getStringList(
 359                         XMLUtils.getAttribute(e, "", "locale", null));
 360                 if (GeneralUtil.prefixMatchStringList(
 361                         os, GeneralUtil.getOSFullName())
 362                         && matchDefaultLocale(locale)) {
 363                     // Now visit all children in this node
 364                     XMLUtils.visitChildrenElements(e,
 365                             new XMLUtils.ElementVisitor() {
 366                         @Override
 367                         public void visitElement(XMLNode e2)
 368                                 throws MissingFieldException, BadFieldException {
 369                             handleResourceElement(source, codebase,
 370                                     e2, rdesc, ignoreJres, arch, jnlpd);
 371                         }
 372                     });
 373                 }
 374             }
 375         });
 376 
 377         if (!rdesc.isEmpty()) {
 378             jnlpd.setResourcesDesc(rdesc);
 379         }
 380     }
 381 
 382     private static IconDesc[] getIconDescs(final String source,
 383             final URL codebase, XMLNode e)
 384             throws MissingFieldException, BadFieldException {
 385         final ArrayList<IconDesc> answer = new ArrayList<>();
 386         XMLUtils.visitElements(e, "<icon>", new XMLUtils.ElementVisitor() {
 387             @Override
 388             public void visitElement(XMLNode icon) throws
 389                     MissingFieldException, BadFieldException {
 390                 String kindStr = XMLUtils.getAttribute(icon, "", "kind", "");
 391                 URL href = XMLUtils.getRequiredURL(source, codebase, icon, "", "href");
 392 
 393                 if (href != null) {
 394                     if (!JNLPConverter.isIconSupported(href.toExternalForm())) {
 395                         return;
 396                     }
 397                 }
 398 
 399                 int kind;
 400                 if (kindStr == null || kindStr.isEmpty() || kindStr.equals("default")) {
 401                     kind = IconDesc.ICON_KIND_DEFAULT;
 402                 } else if (kindStr.equals("shortcut")) {
 403                     kind = IconDesc.ICON_KIND_SHORTCUT;
 404                 } else {
 405                     Log.warning("Ignoring unsupported icon \"" + href + "\" with kind \"" + kindStr + "\".");
 406                     return;
 407                 }
 408 
 409                 answer.add(new IconDesc(href, kind));
 410             }
 411         });
 412         return answer.toArray(new IconDesc[answer.size()]);
 413     }
 414 
 415     private static ShortcutDesc getShortcutDesc(XMLNode e)
 416                 throws MissingFieldException, BadFieldException {
 417         final ArrayList<ShortcutDesc> shortcuts = new ArrayList<>();
 418 
 419         XMLUtils.visitElements(e, "<shortcut>", new XMLUtils.ElementVisitor() {
 420             @Override
 421             public void visitElement(XMLNode shortcutNode)
 422                 throws MissingFieldException, BadFieldException {
 423                 boolean desktopHinted =
 424                     XMLUtils.isElementPath(shortcutNode, "<desktop>");
 425                 boolean menuHinted =
 426                     XMLUtils.isElementPath(shortcutNode, "<menu>");
 427                 String submenuHinted =
 428                     XMLUtils.getAttribute(shortcutNode, "<menu>", "submenu");
 429                 shortcuts.add(new ShortcutDesc(desktopHinted, menuHinted, submenuHinted));
 430             }
 431         });
 432 
 433         if (shortcuts.size() > 0) {
 434             return shortcuts.get(0);
 435         }
 436         return null;
 437     }
 438 
 439     private static AssociationDesc[] getAssociationDesc(final String source,
 440         final URL codebase, XMLNode e)
 441                 throws MissingFieldException, BadFieldException {
 442         final ArrayList<AssociationDesc> answer = new ArrayList<>();
 443         XMLUtils.visitElements(e, "<association>",
 444             new XMLUtils.ElementVisitor() {
 445             @Override
 446             public void visitElement(XMLNode node)
 447                 throws MissingFieldException, BadFieldException {
 448 
 449                 String extensions = XMLUtils.getAttribute(
 450                                        node, "", "extensions");
 451 
 452                 String mimeType = XMLUtils.getAttribute(
 453                                        node, "", "mime-type");
 454                 String description = XMLUtils.getElementContents(
 455                                         node, "<description>");
 456 
 457                 URL icon = XMLUtils.getAttributeURL(
 458                                 source, codebase, node, "<icon>", "href");
 459 
 460                 if (!JNLPConverter.isIconSupported(icon.toExternalForm())) {
 461                     icon = null;
 462                 }
 463 
 464                 if (extensions == null && mimeType == null) {
 465                     throw new MissingFieldException(source,
 466                                  "<association>(<extensions><mime-type>)");
 467                 } else if (extensions == null) {
 468                     throw new MissingFieldException(source,
 469                                      "<association><extensions>");
 470                 } else if (mimeType == null) {
 471                     throw new MissingFieldException(source,
 472                                      "<association><mime-type>");
 473                 }
 474 
 475                 // don't support uppercase extension and mime-type on gnome.
 476                 if ("gnome".equals(System.getProperty("sun.desktop"))) {
 477                     extensions = extensions.toLowerCase();
 478                     mimeType = mimeType.toLowerCase();
 479                 }
 480 
 481                 answer.add(new AssociationDesc(extensions, mimeType,
 482                                                 description, icon));
 483             }
 484         });
 485         return answer.toArray(
 486                 new AssociationDesc[answer.size()]);
 487     }
 488 
 489     /** Handle the individual entries in a resource desc */
 490     private static void handleResourceElement(String source, URL codebase,
 491         XMLNode e, ResourcesDesc rdesc, boolean ignoreJres, String arch, JNLPDesc jnlpd)
 492         throws MissingFieldException, BadFieldException {
 493 
 494         String tag = e.getName();
 495 
 496         boolean matchArch = GeneralUtil.prefixMatchArch(
 497             GeneralUtil.getStringList(arch));
 498 
 499 
 500         if (matchArch && (tag.equals("jar") || tag.equals("nativelib"))) {
 501             /*
 502              * jar/nativelib elements
 503              */
 504             URL href = XMLUtils.getRequiredURL(source, codebase, e, "", "href");
 505             String version = XMLUtils.getAttribute(e, "", "version", null);
 506 
 507             String mainStr = XMLUtils.getAttribute(e, "", "main");
 508             boolean isNativeLib = tag.equals("nativelib");
 509 
 510             boolean isMain = "true".equalsIgnoreCase(mainStr);
 511 
 512             JARDesc jd = new JARDesc(href, version, isMain, isNativeLib, rdesc);
 513             rdesc.addResource(jd);
 514         } else if (matchArch && tag.equals("property")) {
 515             /*
 516              *  property tag
 517              */
 518             String name  = XMLUtils.getRequiredAttribute(source, e, "", "name");
 519             String value = XMLUtils.getRequiredAttributeEmptyOK(
 520                     source, e, "", "value");
 521 
 522             rdesc.addResource(new PropertyDesc(name, value));
 523         } else if (matchArch && tag.equals("extension")) {
 524             URL href = XMLUtils.getRequiredURL(source, codebase, e, "", "href");
 525             String version = XMLUtils.getAttribute(e, "", "version", null);
 526             rdesc.addResource(new ExtensionDesc(href, version));
 527         } else if ((tag.equals("java") || tag.equals("j2se")) && !ignoreJres) {
 528             /*
 529              * j2se element
 530              */
 531             String version  =
 532                 XMLUtils.getRequiredAttribute(source, e, "", "version");
 533             String minheapstr =
 534                 XMLUtils.getAttribute(e, "", "initial-heap-size");
 535             String maxheapstr =
 536                 XMLUtils.getAttribute(e, "", "max-heap-size");
 537 
 538             String vmargs =
 539                 XMLUtils.getAttribute(e, "", "java-vm-args");
 540 
 541             if (jnlpd.isJRESet()) {
 542                 if (vmargs == null) {
 543                     vmargs = "none";
 544                 }
 545                 Log.warning("Ignoring repeated element <" + tag + "> with version " + version +
 546                         " and java-vm-args: " + vmargs);
 547                 return;
 548             }
 549 
 550             long minheap = GeneralUtil.heapValToLong(minheapstr);
 551             long maxheap = GeneralUtil.heapValToLong(maxheapstr);
 552 
 553             ResourcesDesc cbs = null;
 554             buildResourcesDesc(source, codebase, e, true, null);
 555 
 556             // JRE
 557             JREDesc jreDesc = new JREDesc(
 558                 version,
 559                 minheap,
 560                 maxheap,
 561                 vmargs,
 562                 cbs,
 563                 arch);
 564 
 565             rdesc.addResource(jreDesc);
 566 
 567             jnlpd.setIsJRESet(true);
 568         }
 569     }
 570 
 571     /** Extract data from the application-desc tag */
 572     private static void buildApplicationDesc(final String source,
 573         XMLNode root, JNLPDesc jnlpd) throws MissingFieldException, BadFieldException {
 574 
 575         String mainclass = XMLUtils.getClassName(source, root,
 576                            "<application-desc>", "main-class", false);
 577         String appType = XMLUtils.getAttribute(root, "<application-desc>",
 578                                                "type", "Java");
 579         String progressclass  = XMLUtils.getClassName(source, root,
 580                                 "<application-desc>", "progress-class", false);
 581         if (progressclass != null && !progressclass.isEmpty()) {
 582             Log.warning("JNLPConverter does not support progress indication. \"" + progressclass + "\" will not be loaded and will be ignored.");
 583         }
 584 
 585         if (!("Java".equalsIgnoreCase(appType) ||
 586             "JavaFx".equalsIgnoreCase(appType))) {
 587             throw new BadFieldException(source, XMLUtils.getPathString(root) +
 588                 "<application-desc>type", appType);
 589         }
 590 
 591         if ("JavaFx".equalsIgnoreCase(appType)) {
 592             jnlpd.setIsFXApp(true);
 593         }
 594 
 595         XMLUtils.visitElements(root, "<application-desc><argument>", new XMLUtils.ElementVisitor() {
 596             @Override
 597             public void visitElement(XMLNode e) throws MissingFieldException, BadFieldException {
 598                 String arg = XMLUtils.getElementContents(e, "", null);
 599                 if (arg == null) {
 600                     throw new BadFieldException(source, XMLUtils.getPathString(e), "");
 601                 }
 602                 jnlpd.addArguments(arg);
 603             }
 604         });
 605 
 606         XMLUtils.visitElements(root, "<application-desc><param>",
 607             new XMLUtils.ElementVisitor() {
 608             @Override
 609             public void visitElement(XMLNode e) throws MissingFieldException,
 610                 BadFieldException {
 611                 String pn = XMLUtils.getRequiredAttribute(
 612                             source, e, "", "name");
 613                 String pv = XMLUtils.getRequiredAttributeEmptyOK(
 614                             source, e, "", "value");
 615                 jnlpd.setProperty(pn, pv);
 616             }
 617         });
 618         jnlpd.setMainClass(mainclass, false);
 619     }
 620 
 621     /** Extract data from the javafx-desc tag */
 622     private static void buildFXAppDesc(final String source,
 623         XMLNode root, String element, JNLPDesc jnlpd)
 624              throws MissingFieldException, BadFieldException {
 625         String mainclass = XMLUtils.getClassName(source, root, element,
 626                                                  "main-class", true);
 627         String name = XMLUtils.getRequiredAttribute(source, root,
 628                                         "<javafx-desc>", "name");
 629 
 630         /* extract arguments */
 631         XMLUtils.visitElements(root, "<javafx-desc><argument>", new XMLUtils.ElementVisitor() {
 632             @Override
 633             public void visitElement(XMLNode e) throws MissingFieldException, BadFieldException {
 634                 String arg = XMLUtils.getElementContents(e, "", null);
 635                 if (arg == null) {
 636                     throw new BadFieldException(source, XMLUtils.getPathString(e), "");
 637                 }
 638                 jnlpd.addArguments(arg);
 639             }
 640         });
 641 
 642         /* extract parameters */
 643         XMLUtils.visitElements(root, "<javafx-desc><param>",
 644             new XMLUtils.ElementVisitor() {
 645             @Override
 646             public void visitElement(XMLNode e) throws MissingFieldException,
 647                 BadFieldException {
 648                 String pn = XMLUtils.getRequiredAttribute(
 649                             source, e, "", "name");
 650                 String pv = XMLUtils.getRequiredAttributeEmptyOK(
 651                             source, e, "", "value");
 652                 jnlpd.setProperty(pn, pv);
 653             }
 654         });
 655 
 656         jnlpd.setMainClass(mainclass, true);
 657         jnlpd.setName(name);
 658     }
 659 }