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