1 /*
   2  * Copyright (c) 2011, 2016, 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.  Oracle designates this
   8  * particular file as subject to the "Classpath" exception as provided
   9  * by Oracle in the LICENSE file that accompanied this code.
  10  *
  11  * This code is distributed in the hope that it will be useful, but WITHOUT
  12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  14  * version 2 for more details (a copy is included in the LICENSE file that
  15  * accompanied this code).
  16  *
  17  * You should have received a copy of the GNU General Public License version
  18  * 2 along with this work; if not, write to the Free Software Foundation,
  19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  20  *
  21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  22  * or visit www.oracle.com if you need additional information or have any
  23  * questions.
  24  */
  25 
  26 package com.sun.javafx.tools.ant;
  27 
  28 import java.io.File;
  29 import java.util.ArrayList;
  30 import java.util.LinkedList;
  31 import java.util.List;
  32 import java.util.Map;
  33 import java.util.stream.Collectors;
  34 
  35 import com.oracle.tools.packager.StandardBundlerParam;
  36 import com.sun.javafx.tools.ant.Platform.Jvmarg;
  37 import com.sun.javafx.tools.ant.Platform.Property;
  38 import com.sun.javafx.tools.packager.DeployParams;
  39 import com.oracle.tools.packager.Log;
  40 import com.sun.javafx.tools.packager.PackagerException;
  41 import com.sun.javafx.tools.packager.PackagerLib;
  42 import com.sun.javafx.tools.packager.bundlers.Bundler;
  43 import com.sun.javafx.tools.packager.bundlers.Bundler.Bundle;
  44 import com.sun.javafx.tools.packager.bundlers.Bundler.BundleType;
  45 import org.apache.tools.ant.BuildException;
  46 import org.apache.tools.ant.DynamicAttribute;
  47 import org.apache.tools.ant.Task;
  48 import org.apache.tools.ant.types.DataType;
  49 
  50 /**
  51  * Generates package for Web deployment and redistribution of application.
  52  * Package includes of set of jar files, JNLP file and HTML file.
  53  *
  54  * Minimal example:
  55  * <pre>
  56  *   &lt;fx:deploy width="600" height="400"
  57  *                 outdir="web-dist" outfile="Fish"&gt;
  58  *       &lt;info title="Sample application"/&gt;
  59  *       &lt;fx:application refid="myapp"/&gt;
  60  *       &lt;fx:resources refid="myresources"/&gt;
  61  *   &lt;/fx:deploy&gt;
  62  * </pre>
  63  * Above example will generate HTML/JNLP files into the web-dist directory
  64  * and use "Fish" as prefix for generated files. Details about application and
  65  * its resources are defined elsewhere in the application and resource elements.
  66  * <p>
  67  * Minimal complete example:
  68  * <pre>
  69  *   &lt;fx:deploy width="600" height="400"
  70  *                 outdir="web-dist" outfile="Fish"&gt;
  71  *       &lt;info title="Sample application"/&gt;
  72  *       &lt;fx:application name="SampleApp"
  73  *              mainClass="testapp.MainApp"
  74  *              preloaderClass="testpreloader.Preloader"/&gt;
  75  *       &lt;fx:resources&gt;
  76  *              &lt;fx:fileset requiredFor="preloader" dir="dist"&gt;
  77  *                &lt;include name="preloader.jar"/&gt;
  78  *             &lt;/fx:fileset&gt;
  79  *              &lt;fx:fileset dir="dist"&gt;
  80  *                &lt;include name="helloworld.jar"/&gt;
  81  *             &lt;/fx:fileset&gt;
  82  *       &lt;/fx:resources&gt;
  83  *   &lt;/fx:deploy&gt;
  84  * </pre>
  85  * Same as above but here application and resource details are defined in place.
  86  * Note that using references helps with reducing code duplication as fx:jar need
  87  * to be used for double clickable jars.
  88  *
  89  * @ant.task name="deploy" category="javafx"
  90  */
  91 public class DeployFXTask extends Task implements DynamicAttribute {
  92     private String width = null;
  93     private String height = null;
  94     private String embeddedWidth = null;
  95     private String embeddedHeight = null;
  96     private String outfile = null;
  97     private String outdir = null;
  98     private boolean embedJNLP;
  99     private boolean isExtension = false;
 100     private Boolean signBundle;
 101 
 102     //Before FCS default is to include DT files with app
 103     // to ensure tests are using latest and compatible.
 104     //After FCS default is to use shared copy.
 105     private boolean includeDT = false;
 106 
 107     private String updateMode="background";
 108     private Info appInfo = null;
 109     private Application app = null;
 110     private Resources resources = null;
 111     private Preferences prefs = null;
 112     private String codebase = null;
 113 
 114     //container to embed application into
 115     //could be either string id or js code. If it is string id then it needs to
 116     //be escaped
 117     private String placeholder;
 118 
 119     private PackagerLib packager;
 120     private DeployParams deployParams;
 121 
 122     private Callbacks callbacks;
 123 
 124     boolean offlineAllowed = true;
 125 
 126     //default native bundle settings
 127     // use NONE to avoid large disk space and build time overhead
 128     BundleType nativeBundles = BundleType.NONE;
 129     String bundleFormat = null;
 130 
 131     private boolean verbose = false;
 132     public void setVerbose(boolean v) {
 133         verbose = v;
 134     }
 135 
 136     public void setCodebase(String str) {
 137         codebase = str;
 138     }
 139 
 140     public DeployFXTask() {
 141         packager = new PackagerLib();
 142         deployParams = new DeployParams();
 143     }
 144 
 145     @Override
 146     public void execute() {
 147         boolean isModular = (app.getModule() != null) && !app.getModule().isEmpty();
 148         deployParams.setOutfile(outfile);
 149         deployParams.setOutdir(new File(outdir));
 150 
 151         if (!isModular &&
 152             (nativeBundles == BundleType.JNLP ||
 153              nativeBundles == BundleType.ALL)) {
 154             deployParams.setOfflineAllowed(offlineAllowed);
 155             deployParams.setVerbose(verbose);
 156             deployParams.setCodebase(codebase);
 157             deployParams.setSignBundle(signBundle);
 158 
 159             if (width != null) {
 160                 deployParams.setWidth(Integer.valueOf(width));
 161             }
 162 
 163             if (height != null) {
 164                 deployParams.setHeight(Integer.valueOf(height));
 165             }
 166 
 167             if (embeddedWidth != null && embeddedHeight != null) {
 168                 deployParams.setEmbeddedDimensions(embeddedWidth, embeddedHeight);
 169             }
 170 
 171             deployParams.setEmbedJNLP(embedJNLP);
 172             if (perms != null) {
 173                deployParams.setAllPermissions(perms.getElevated());
 174             }
 175 
 176             deployParams.setUpdateMode(updateMode);
 177             deployParams.setExtension(isExtension);
 178             deployParams.setIncludeDT(includeDT);
 179 
 180             if (platform != null) {
 181                 Platform pl = platform.get();
 182                 if (pl.j2se != null) {
 183                     deployParams.setJRE(pl.j2se);
 184                 }
 185                 if (pl.javafx != null) {
 186                     deployParams.setJavafx(pl.javafx);
 187                 }
 188 
 189                 //only pass it further if it was explicitly set
 190                 // as we do not want to override default
 191                 if (pl.javaRoot != null) {
 192                     if (Platform.USE_SYSTEM_JRE.equals(pl.javaRoot)) {
 193                         deployParams.setJavaRuntimeSource(null);
 194                     } else {
 195                         deployParams.setJavaRuntimeSource(new File(pl.javaRoot));
 196                     }
 197                 }
 198 
 199                 for (Property p: pl.properties) {
 200                     deployParams.addJvmProperty(p.name, p.value);
 201                 }
 202                 for (Jvmarg a: pl.jvmargs) {
 203                     deployParams.addJvmArg(a.value);
 204                 }
 205                 for (Property a: pl.jvmUserArgs) {
 206                     deployParams.addJvmUserArg(a.name, a.value);
 207                 }
 208             }
 209 
 210             if (callbacks != null) {
 211                 for (Callback cb: callbacks.callbacks) {
 212                     deployParams.addCallback(cb.getName(), cb.getCmd());
 213                 }
 214             }
 215 
 216             if (prefs != null) {
 217                 deployParams.setNeedShortcut(prefs.getShortcut());
 218                 deployParams.setNeedInstall(prefs.getInstall());
 219                 deployParams.setNeedMenu(prefs.getMenu());
 220                 deployParams.setSystemWide(prefs.getSystemInstall());
 221                 deployParams.setInstalldirChooser(prefs.getInstalldirChooser());
 222             }
 223 
 224             for (Template t: templateList) {
 225                 deployParams.addTemplate(t.infile, t.outfile);
 226             }
 227         }
 228 
 229         if (isModular &&
 230             (nativeBundles == BundleType.NATIVE ||
 231              nativeBundles == BundleType.IMAGE ||
 232              nativeBundles == BundleType.INSTALLER ||
 233              nativeBundles == BundleType.ALL)) {
 234             if (app != null) {
 235                 if (app.getModule() == null) {
 236                     deployParams.setApplicationClass(app.get().mainClass);
 237                 }
 238                 else {
 239                     int index = app.getModule().indexOf("/");
 240 
 241                     if (index > 0) {
 242                         deployParams.setModule(app.getModule());
 243                     }
 244                     else {
 245                         deployParams.setModule(app.getModule() + "/" + app.get().mainClass);
 246                     }
 247                 }
 248 
 249                 deployParams.setPreloader(app.get().preloaderClass);
 250                 deployParams.setAppId(app.get().id);
 251                 deployParams.setAppName(app.get().name);
 252                 deployParams.setParams(app.get().parameters);
 253                 deployParams.setArguments(app.get().getArguments());
 254                 deployParams.setHtmlParams(app.get().htmlParameters);
 255                 deployParams.setFallback(app.get().fallbackApp);
 256                 deployParams.setSwingAppWithEmbeddedJavaFX(app.get().embeddedIntoSwing);
 257                 deployParams.setVersion(app.get().version);
 258                 deployParams.setId(app.get().id);
 259                 deployParams.setServiceHint(app.get().daemon);
 260 
 261                 if (runtime != null) {
 262                     for (String s : runtime.getAddModules()) {
 263                         deployParams.addAddModule(s);
 264                     }
 265 
 266                     for (String s : runtime.getLimitModules()) {
 267                         deployParams.addLimitModule(s);
 268                     }
 269 
 270                     deployParams.setModulePath(runtime.getModulePath());
 271 
 272                     Boolean stripNativeCommands = runtime.getStripNativeCommands();
 273 
 274                     if (stripNativeCommands != null) {
 275                         deployParams.setStripNativeCommands(stripNativeCommands);
 276                     }
 277 
 278                     Boolean detectModules = runtime.getDetectModules();
 279 
 280                     if (detectModules != null) {
 281                         deployParams.setDetectModules(detectModules);
 282                     }
 283                 }
 284             }
 285 
 286             if (appInfo != null) {
 287                 deployParams.setTitle(appInfo.title);
 288                 deployParams.setVendor(appInfo.vendor);
 289                 deployParams.setDescription(appInfo.appDescription);
 290                 deployParams.setCategory(appInfo.category);
 291                 deployParams.setLicenseType(appInfo.licenseType);
 292                 deployParams.setCopyright(appInfo.copyright);
 293                 deployParams.setEmail(appInfo.email);
 294 
 295                 for (Info.Icon i: appInfo.icons) {
 296                     if (i instanceof Info.Splash) {
 297                        deployParams.addIcon(i.href, i.kind, i.width, i.height, i.depth,
 298                             ((Info.Splash) i).mode);
 299                     } else {
 300                        deployParams.addIcon(i.href, i.kind, i.width, i.height, i.depth,
 301                             DeployParams.RunMode.WEBSTART);
 302                     }
 303                 }
 304 
 305                 deployParams.addBundleArgument(StandardBundlerParam.FILE_ASSOCIATIONS.getID(),
 306                         appInfo.fileAssociations.stream()
 307                             .map(FileAssociation::createLauncherMap)
 308                             .collect(Collectors.toList()));
 309             }
 310         }
 311 
 312         for (BundleArgument ba : bundleArgumentList) {
 313             deployParams.addBundleArgument(ba.arg, ba.value);
 314         }
 315 
 316         deployParams.setPlaceholder(placeholder);
 317 
 318         if (resources != null) {
 319             for (FileSet fs: resources.getResources()) {
 320                    Utils.addResources(deployParams, fs);
 321             }
 322         }
 323 
 324         List<Map<String, ? super Object>> launchersAsMap = new ArrayList<>();
 325         for (SecondaryLauncher sl : secondaryLaunchers) {
 326             launchersAsMap.add(sl.createLauncherMap());
 327         }
 328 
 329         deployParams.addBundleArgument(
 330                 StandardBundlerParam.SECONDARY_LAUNCHERS.getID(),
 331                 launchersAsMap);
 332 
 333         deployParams.setBundleType(nativeBundles);
 334         deployParams.setTargetFormat(bundleFormat);
 335 
 336         Log.setLogger(new AntLog(this.getProject()));
 337 
 338         try {
 339             packager.generateDeploymentPackages(deployParams);
 340         } catch (PackagerException pe) {
 341             if (pe.getCause() != null) {
 342                throw new BuildException(pe.getCause().getMessage(),
 343                         pe.getCause());
 344             } else {
 345                 throw new BuildException(pe.getMessage(),
 346                         pe);
 347             }
 348         } catch (Exception e) {
 349             throw new BuildException(e.getMessage(), e);
 350         } finally {
 351             Log.setLogger(null);
 352         }
 353     }
 354 
 355     /**
 356      * Set to true if we are generating an 'extension' JNLP.
 357      *
 358      * @ant.not-required Default is false.
 359      */
 360     public void setExtension(boolean v) {
 361         isExtension = v;
 362     }
 363 
 364     public void setNativeBundles(String v) {
 365         Bundle bundle = Bundler.stringToBundle(v);
 366         this.nativeBundles = bundle.type;
 367         this.bundleFormat = bundle.format;
 368     }
 369 
 370     /**
 371      * Indicates the preferences for when checks for application updates
 372      * are performed. Supported modes are always, timeout and background.
 373      *
 374      * @ant.not-required Default is background.
 375      */
 376     public void setUpdateMode(String v) {
 377         String l = v.toLowerCase();
 378         if ("eager".equals(l)) {
 379             //workaround for doc bug in 2.0
 380             l="always";
 381         }
 382         if (!"always".equals(l) && !"background".equals(l)
 383                 && !"timeout".equals(l)) {
 384             throw new BuildException("Unknown update mode: ["+l+"]." +
 385                     "Supported modes are: 'always', 'timeout' and 'background'");
 386         }
 387         updateMode = l;
 388     }
 389 
 390     /**
 391      * Indicates if the application can be launched offline.
 392      *
 393      * If application is already downloaded and update mode is eager then
 394      * the check will timeout after a few seconds, in which case the cached
 395      * application will be launched instead.
 396      *
 397      * Given a reasonably fast server connection,
 398      * the latest version of the application will usually be run,
 399      * but it is not guaranteed. The application, however, can be run offline.
 400      *
 401      * @ant.not-required Default is true.
 402      */
 403     public void setOfflineAllowed(boolean v) {
 404         offlineAllowed = v;
 405     }
 406 
 407     /**
 408      * Application width for embedding application into Web page
 409      *
 410      * @ant.optional
 411      */
 412     public void setEmbeddedWidth(String w) {
 413         embeddedWidth = w;
 414     }
 415 
 416     /**
 417      * Application width. Used for webstart and embedded applications
 418      * unless emdeddedWidth is specified
 419      *
 420      * @ant.required
 421      */
 422     public void setWidth(String v) {
 423         width = v;
 424     }
 425 
 426     /**
 427      * Application width for embedding application into Web page
 428      *
 429      * @ant.optional
 430      */
 431     public void setEmbeddedHeight(String w) {
 432         embeddedHeight = w;
 433     }
 434 
 435     /**
 436      * Application height. Used for webstart and embedded applications
 437      * unless emdeddedHeight is specified
 438      *
 439      * @ant.required
 440      */
 441     public void setHeight(String v) {
 442         height = v;
 443     }
 444 
 445     /**
 446      * Enable embedding JNLP descriptor into Web page.
 447      * Reduces number of network connections to be made on startup and
 448      * help to improve startup time.
 449      *
 450      * @ant.not-required Default is false.
 451      */
 452     public void setEmbedJNLP(boolean v) {
 453         embedJNLP = v;
 454     }
 455 
 456     /**
 457      * Directory where application package will be saved.
 458      *
 459      * @ant.required
 460      */
 461     public void setOutdir(String v) {
 462         outdir = v;
 463     }
 464 
 465     /**
 466      * Prefix to be used for new generated files.
 467      *
 468      * @ant.required
 469      */
 470     public void setOutfile(String v) {
 471         outfile = v;
 472     }
 473 
 474     /**
 475      * If true then web deployment is done using javascript files
 476      * on java.com. Otherwise copy of javascript file is included into
 477      * application package.
 478      *
 479      * @ant.not-required Before FCS default is false. For FCS default is true.
 480      */
 481     public void setIncludeDT(Boolean v) {
 482         includeDT = v;
 483     }
 484 
 485     /**
 486      * Placeholder in the web page where application will be embedded.
 487      * This is expected to be Javascript DOM object.
 488      *
 489      * @ant.required Either reference or id of placeholder is required.
 490      */
 491     public void setPlaceholderRef(String p) {
 492         this.placeholder = p;
 493     }
 494 
 495     /**
 496      * Id of the placeholder in the web page where application will be embedded.
 497      * Javascript's document.getElementById() is expected to be able to resolve it.
 498      *
 499      * @ant.required Either reference or id of placeholder is required.
 500      */
 501     public void setPlaceholderId(String id) {
 502         //raw id of the placeholder, need to escape it
 503         this.placeholder = "'"+id+"'";
 504     }
 505 
 506     public void setSignBundle(boolean signBundle) {
 507         this.signBundle = signBundle;
 508     }
 509 
 510     public Info createInfo() {
 511         appInfo = new Info();
 512         return appInfo;
 513     }
 514 
 515     public Application createApplication() {
 516         app = new Application();
 517         return app;
 518     }
 519 
 520     public Preferences createPreferences() {
 521         prefs = new Preferences();
 522         return prefs;
 523     }
 524 
 525     public Callbacks createCallbacks() {
 526         if (callbacks != null) {
 527             throw new BuildException("Only one callbacks element is supported.");
 528         }
 529         callbacks = new Callbacks();
 530         return callbacks;
 531     }
 532 
 533     public Resources createResources() {
 534         if (resources != null) {
 535             throw new BuildException("Only one resources element is supported.");
 536         }
 537         resources = new Resources();
 538         return resources;
 539     }
 540 
 541     List<Template> templateList = new LinkedList<>();
 542 
 543     public Template createTemplate() {
 544         Template t = new Template();
 545         templateList.add(t);
 546         return t;
 547     }
 548 
 549     Platform platform;
 550 
 551     public Platform createPlatform() {
 552         platform = new Platform();
 553         return platform;
 554     }
 555 
 556     private Permissions perms = null;
 557 
 558     public Permissions createPermissions() {
 559         perms = new Permissions();
 560         return perms;
 561     }
 562 
 563     List<BundleArgument> bundleArgumentList = new LinkedList<>();
 564 
 565     public BundleArgument createBundleArgument() {
 566         BundleArgument ba = new BundleArgument();
 567         bundleArgumentList.add(ba);
 568         return ba;
 569     }
 570 
 571     private List<SecondaryLauncher> secondaryLaunchers = new ArrayList<>();
 572 
 573     public SecondaryLauncher createSecondaryLauncher() {
 574         SecondaryLauncher sl = new SecondaryLauncher();
 575         secondaryLaunchers.add(sl);
 576         return sl;
 577     }
 578 
 579     private Runtime runtime = null;
 580 
 581     public Runtime createRuntime() {
 582         runtime = new Runtime();
 583         return runtime;
 584     }
 585 
 586     @Override
 587     public void setDynamicAttribute(String name, String value) throws BuildException {
 588         //Use qName and value - can't really validate anything until we know which bundlers we have, so this has
 589         //to done (way) downstream
 590         bundleArgumentList.add(new BundleArgument(name, value));
 591     }
 592 
 593     /**
 594      * Template to preprocess.
 595      * <p>
 596      * Template is the HTML file containing markers to be replaced with
 597      * javascript or HTML snippets needed to deploy JavaFX application on the
 598      * Web page. This allows to deploy application into "real" Web pages
 599      * and simplify development process if application is tightly
 600      * integrated with the page (e.g. uses javascript to communicate to it).
 601      * <p>
 602      * Marker has the form of #XXX# or #XXX(id)#. Where id is identifier
 603      * of an application and XXX is one of following:
 604      * <ul>
 605      *   <li>DT.SCRIPT.URL - location of dtjava.js
 606      *   <li>DT.SCRIPT.CODE - script element to include dtjava.js
 607      *   <li>DT.EMBED.CODE.DYNAMIC - code to embed application into given placeholder
 608      *         It is expected it will be wrapped into function()
 609      *   <li>DT.EMBED.CODE.ONLOAD - all code needed to embed application into Web page
 610      *               using onload hook (except inclusion of dtjava.js)
 611      *   <li>DT.LAUNCH.CODE - code need to launch application.
 612      *          Expected to be wrappend into function().
 613      * </ul>
 614      *
 615      * Page with multiple different applications can be processed multiple times
 616      * - one per application. To avoid confusion markers need to use
 617      * application ids  (alphanumeric string no spaces).
 618      * <p>
 619      * If input and output files are the same then template is processed in place.
 620      * <p>
 621      * Example:
 622      * <pre>
 623      *     &lt;template file="App_template.html" tofile="App.html"/&gt;
 624      * </pre>
 625      *
 626      * @ant.type name="Template" category="javafx"
 627      */
 628     public static class Template extends DataType {
 629         File infile = null;
 630         File outfile = null;
 631 
 632         /**
 633          * Input file.
 634          *
 635          * @ant.required
 636          */
 637         public void setFile(File f) {
 638             infile = f;
 639         }
 640 
 641         /**
 642          * Output file (after preprocessing).
 643          *
 644          * @ant.not-required Default is the same as input file.
 645          */
 646         public void setTofile(File f) {
 647             outfile = f;
 648         }
 649     }
 650 
 651     /**
 652      * An argument to be passed off to the bundlers.
 653      *
 654      * Each bundler uses a set of arguments that may be shared across
 655      * the different bundlers or it may be specific to each bundler.
 656      *
 657      * Some bundlers declare argument types that are not known to the JDK
 658      * and may be specific to the particular bundler (such as Mac App Store
 659      * categories).  These arguments allow you to set and adjust these a
 660      * rguments.
 661      *
 662      * @ant.type name="BundleArgument" category="javafx"
 663      */
 664     public static class BundleArgument extends DataType {
 665         String arg = null;
 666         String value = null;
 667 
 668         BundleArgument() {
 669 
 670         }
 671 
 672         BundleArgument(String arg, String value) {
 673             this.arg = arg;
 674             this.value = value;
 675         }
 676 
 677         /**
 678          * Name of the bundle argument.
 679          *
 680          * @ant.required
 681          */
 682         public void setArg(String arg) {
 683             this.arg = arg;
 684         }
 685 
 686         /**
 687          * Value for the bundle argument.
 688          *
 689          * @ant.not-required Default is a literal null
 690          */
 691         public void setValue(String value) {
 692             this.value = value;
 693         }
 694     }
 695 }