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 setModulePath(String value) {
 140         modulePath = value;
 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.setStripNativeCommands(app.getStripExecutables());
 197         }
 198 
 199         if (appInfo != null) {
 200             deployParams.setTitle(appInfo.title);
 201             deployParams.setVendor(appInfo.vendor);
 202             deployParams.setDescription(appInfo.appDescription);
 203             deployParams.setCategory(appInfo.category);
 204             deployParams.setLicenseType(appInfo.licenseType);
 205             deployParams.setCopyright(appInfo.copyright);
 206             deployParams.setEmail(appInfo.email);
 207 
 208             for (Info.Icon i: appInfo.icons) {
 209                 if (i instanceof Info.Splash) {
 210                    deployParams.addIcon(i.href, i.kind, i.width, i.height, i.depth,
 211                         ((Info.Splash) i).mode);
 212                 } else {
 213                    deployParams.addIcon(i.href, i.kind, i.width, i.height, i.depth,
 214                         DeployParams.RunMode.WEBSTART);
 215                 }
 216             }
 217 
 218             deployParams.addBundleArgument(StandardBundlerParam.FILE_ASSOCIATIONS.getID(),
 219                     appInfo.fileAssociations.stream()
 220                         .map(FileAssociation::createLauncherMap)
 221                         .collect(Collectors.toList()));
 222         }
 223 
 224         deployParams.setUpdateMode(updateMode);
 225         deployParams.setExtension(isExtension);
 226         deployParams.setIncludeDT(includeDT);
 227 
 228         if (platform != null) {
 229             Platform pl = platform.get();
 230             if (pl.j2se != null) {
 231                 deployParams.setJRE(pl.j2se);
 232             }
 233             if (pl.javafx != null) {
 234                 deployParams.setJavafx(pl.javafx);
 235             }
 236 
 237             //only pass it further if it was explicitly set
 238             // as we do not want to override default
 239             if (pl.javaRoot != null) {
 240                 if (Platform.USE_SYSTEM_JRE.equals(pl.javaRoot)) {
 241                     deployParams.setJavaRuntimeSource(null);
 242                 } else {
 243                     deployParams.setJavaRuntimeSource(new File(pl.javaRoot));
 244                 }
 245             }
 246             for (Property p: pl.properties) {
 247                 deployParams.addJvmProperty(p.name, p.value);
 248             }
 249             for (Jvmarg a: pl.jvmargs) {
 250                 deployParams.addJvmArg(a.value);
 251             }
 252             for (Property a: pl.jvmUserArgs) {
 253                 deployParams.addJvmUserArg(a.name, a.value);
 254             }
 255         }
 256 
 257         if (callbacks != null) {
 258             deployParams.setCallbacks(callbacks.callbacks);
 259         }
 260 
 261         if (prefs != null) {
 262             deployParams.setNeedShortcut(prefs.getShortcut());
 263             deployParams.setNeedInstall(prefs.getInstall());
 264             deployParams.setNeedMenu(prefs.getMenu());
 265             deployParams.setSystemWide(prefs.getSystemInstall());
 266             deployParams.setInstalldirChooser(prefs.getInstalldirChooser());
 267         }
 268 
 269         for (Template t: templateList) {
 270             deployParams.addTemplate(t.infile, t.outfile);
 271         }
 272 
 273         for (BundleArgument ba : bundleArgumentList) {
 274             deployParams.addBundleArgument(ba.arg, ba.value);
 275         }
 276 
 277         deployParams.setPlaceholder(placeholder);
 278 
 279         if (resources != null) {
 280             for (FileSet fs: resources.getResources()) {
 281                    Utils.addResources(deployParams, fs);
 282             }
 283         }
 284 
 285         List<Map<String, ? super Object>> launchersAsMap = new ArrayList<>();
 286         for (SecondaryLauncher sl : secondaryLaunchers) {
 287             launchersAsMap.add(sl.createLauncherMap());
 288         }
 289 
 290         deployParams.addBundleArgument(
 291                 StandardBundlerParam.SECONDARY_LAUNCHERS.getID(),
 292                 launchersAsMap);
 293 
 294         deployParams.setBundleType(nativeBundles);
 295         deployParams.setTargetFormat(bundleFormat);
 296 
 297         Log.setLogger(new AntLog(this.getProject()));
 298 
 299         try {
 300             packager.generateDeploymentPackages(deployParams);
 301         } catch (PackagerException pe) {
 302             if (pe.getCause() != null) {
 303                throw new BuildException(pe.getCause().getMessage(),
 304                         pe.getCause());
 305             } else {
 306                 throw new BuildException(pe.getMessage(),
 307                         pe);
 308             }
 309         } catch (Exception e) {
 310             throw new BuildException(e.getMessage(), e);
 311         } finally {
 312             Log.setLogger(null);
 313         }
 314     }
 315 
 316     /**
 317      * Set to true if we are generating an 'extension' JNLP.
 318      *
 319      * @ant.not-required Default is false.
 320      */
 321     public void setExtension(boolean v) {
 322         isExtension = v;
 323     }
 324 
 325     public void setNativeBundles(String v) {
 326         if ("false".equals(v) || "none".equals(v)) {
 327             nativeBundles = BundleType.NONE;
 328         } else if ("all".equals(v) || "true".equals(v)) {
 329             nativeBundles = BundleType.ALL;
 330         } else if ("image".equals(v)) {
 331             nativeBundles = BundleType.IMAGE;
 332         } else if ("installer".equals(v)) {
 333             nativeBundles = BundleType.INSTALLER;
 334         } else {
 335             //assume it is request to build only specific format (like exe or msi)
 336             nativeBundles = BundleType.INSTALLER;
 337             bundleFormat = (v != null) ? v.toLowerCase() : null;
 338         }
 339     }
 340 
 341     /**
 342      * Indicates the preferences for when checks for application updates
 343      * are performed. Supported modes are always, timeout and background.
 344      *
 345      * @ant.not-required Default is background.
 346      */
 347     public void setUpdateMode(String v) {
 348         String l = v.toLowerCase();
 349         if ("eager".equals(l)) {
 350             //workaround for doc bug in 2.0
 351             l="always";
 352         }
 353         if (!"always".equals(l) && !"background".equals(l)
 354                 && !"timeout".equals(l)) {
 355             throw new BuildException("Unknown update mode: ["+l+"]." +
 356                     "Supported modes are: 'always', 'timeout' and 'background'");
 357         }
 358         updateMode = l;
 359     }
 360 
 361     /**
 362      * Indicates if the application can be launched offline.
 363      *
 364      * If application is already downloaded and update mode is eager then
 365      * the check will timeout after a few seconds, in which case the cached
 366      * application will be launched instead.
 367      *
 368      * Given a reasonably fast server connection,
 369      * the latest version of the application will usually be run,
 370      * but it is not guaranteed. The application, however, can be run offline.
 371      *
 372      * @ant.not-required Default is true.
 373      */
 374     public void setOfflineAllowed(boolean v) {
 375         offlineAllowed = v;
 376     }
 377 
 378     /**
 379      * Application width for embedding application into Web page
 380      *
 381      * @ant.optional
 382      */
 383     public void setEmbeddedWidth(String w) {
 384         embeddedWidth = w;
 385     }
 386 
 387     /**
 388      * Application width. Used for webstart and embedded applications
 389      * unless emdeddedWidth is specified
 390      *
 391      * @ant.required
 392      */
 393     public void setWidth(String v) {
 394         width = v;
 395     }
 396 
 397     /**
 398      * Application width for embedding application into Web page
 399      *
 400      * @ant.optional
 401      */
 402     public void setEmbeddedHeight(String w) {
 403         embeddedHeight = w;
 404     }
 405 
 406     /**
 407      * Application height. Used for webstart and embedded applications
 408      * unless emdeddedHeight is specified
 409      *
 410      * @ant.required
 411      */
 412     public void setHeight(String v) {
 413         height = v;
 414     }
 415 
 416     /**
 417      * Enable embedding JNLP descriptor into Web page.
 418      * Reduces number of network connections to be made on startup and
 419      * help to improve startup time.
 420      *
 421      * @ant.not-required Default is false.
 422      */
 423     public void setEmbedJNLP(boolean v) {
 424         embedJNLP = v;
 425     }
 426 
 427     /**
 428      * Directory where application package will be saved.
 429      *
 430      * @ant.required
 431      */
 432     public void setOutdir(String v) {
 433         outdir = v;
 434     }
 435 
 436     /**
 437      * Prefix to be used for new generated files.
 438      *
 439      * @ant.required
 440      */
 441     public void setOutfile(String v) {
 442         outfile = v;
 443     }
 444 
 445     /**
 446      * If true then web deployment is done using javascript files
 447      * on java.com. Otherwise copy of javascript file is included into
 448      * application package.
 449      *
 450      * @ant.not-required Before FCS default is false. For FCS default is true.
 451      */
 452     public void setIncludeDT(Boolean v) {
 453         includeDT = v;
 454     }
 455 
 456     /**
 457      * Placeholder in the web page where application will be embedded.
 458      * This is expected to be Javascript DOM object.
 459      *
 460      * @ant.required Either reference or id of placeholder is required.
 461      */
 462     public void setPlaceholderRef(String p) {
 463         this.placeholder = p;
 464     }
 465 
 466     /**
 467      * Id of the placeholder in the web page where application will be embedded.
 468      * Javascript's document.getElementById() is expected to be able to resolve it.
 469      *
 470      * @ant.required Either reference or id of placeholder is required.
 471      */
 472     public void setPlaceholderId(String id) {
 473         //raw id of the placeholder, need to escape it
 474         this.placeholder = "'"+id+"'";
 475     }
 476 
 477     public void setSignBundle(boolean signBundle) {
 478         this.signBundle = signBundle;
 479     }
 480 
 481     public Info createInfo() {
 482         appInfo = new Info();
 483         return appInfo;
 484     }
 485 
 486     public Application createApplication() {
 487         app = new Application();
 488         return app;
 489     }
 490 
 491     public Preferences createPreferences() {
 492         prefs = new Preferences();
 493         return prefs;
 494     }
 495 
 496     public Callbacks createCallbacks() {
 497         if (callbacks != null) {
 498             throw new BuildException("Only one callbacks element is supported.");
 499         }
 500         callbacks = new Callbacks();
 501         return callbacks;
 502     }
 503 
 504     public Resources createResources() {
 505         if (resources != null) {
 506             throw new BuildException("Only one resources element is supported.");
 507         }
 508         resources = new Resources();
 509         return resources;
 510     }
 511 
 512     List<Template> templateList = new LinkedList<>();
 513 
 514     public Template createTemplate() {
 515         Template t = new Template();
 516         templateList.add(t);
 517         return t;
 518     }
 519 
 520     Platform platform;
 521 
 522     public Platform createPlatform() {
 523         platform = new Platform();
 524         return platform;
 525     }
 526 
 527     private Permissions perms = null;
 528 
 529     public Permissions createPermissions() {
 530         perms = new Permissions();
 531         return perms;
 532     }
 533 
 534     List<BundleArgument> bundleArgumentList = new LinkedList<>();
 535 
 536     public BundleArgument createBundleArgument() {
 537         BundleArgument ba = new BundleArgument();
 538         bundleArgumentList.add(ba);
 539         return ba;
 540     }
 541 
 542     private List<SecondaryLauncher> secondaryLaunchers = new ArrayList<>();
 543 
 544     public SecondaryLauncher createSecondaryLauncher() {
 545         SecondaryLauncher sl = new SecondaryLauncher();
 546         secondaryLaunchers.add(sl);
 547         return sl;
 548     }
 549 
 550 
 551     @Override
 552     public void setDynamicAttribute(String name, String value) throws BuildException {
 553         //Use qName and value - can't really validate anything until we know which bundlers we have, so this has
 554         //to done (way) downstream
 555         bundleArgumentList.add(new BundleArgument(name, value));
 556     }
 557 
 558     /**
 559      * Template to preprocess.
 560      * <p>
 561      * Template is the HTML file containing markers to be replaced with
 562      * javascript or HTML snippets needed to deploy JavaFX application on the
 563      * Web page. This allows to deploy application into "real" Web pages
 564      * and simplify development process if application is tightly
 565      * integrated with the page (e.g. uses javascript to communicate to it).
 566      * <p>
 567      * Marker has the form of #XXX# or #XXX(id)#. Where id is identifier
 568      * of an application and XXX is one of following:
 569      * <ul>
 570      *   <li>DT.SCRIPT.URL - location of dtjava.js
 571      *   <li>DT.SCRIPT.CODE - script element to include dtjava.js
 572      *   <li>DT.EMBED.CODE.DYNAMIC - code to embed application into given placeholder
 573      *         It is expected it will be wrapped into function()
 574      *   <li>DT.EMBED.CODE.ONLOAD - all code needed to embed application into Web page
 575      *               using onload hook (except inclusion of dtjava.js)
 576      *   <li>DT.LAUNCH.CODE - code need to launch application.
 577      *          Expected to be wrappend into function().
 578      * </ul>
 579      *
 580      * Page with multiple different applications can be processed multiple times
 581      * - one per application. To avoid confusion markers need to use
 582      * application ids  (alphanumeric string no spaces).
 583      * <p>
 584      * If input and output files are the same then template is processed in place.
 585      * <p>
 586      * Example:
 587      * <pre>
 588      *     &lt;template file="App_template.html" tofile="App.html"/&gt;
 589      * </pre>
 590      *
 591      * @ant.type name="Template" category="javafx"
 592      */
 593     public static class Template extends DataType {
 594         File infile = null;
 595         File outfile = null;
 596 
 597         /**
 598          * Input file.
 599          *
 600          * @ant.required
 601          */
 602         public void setFile(File f) {
 603             infile = f;
 604         }
 605 
 606         /**
 607          * Output file (after preprocessing).
 608          *
 609          * @ant.not-required Default is the same as input file.
 610          */
 611         public void setTofile(File f) {
 612             outfile = f;
 613         }
 614     }
 615 
 616     /**
 617      * An argument to be passed off to the bundlers.
 618      *
 619      * Each bundler uses a set of arguments that may be shared across
 620      * the different bundlers or it may be specific to each bundler.
 621      *
 622      * Some bundlers declare argument types that are not known to the JDK
 623      * and may be specific to the particular bundler (such as Mac App Store
 624      * categories).  These arguments allow you to set and adjust these a
 625      * rguments.
 626      *
 627      * @ant.type name="BundleArgument" category="javafx"
 628      */
 629     public static class BundleArgument extends DataType {
 630         String arg = null;
 631         String value = null;
 632 
 633         BundleArgument() {
 634 
 635         }
 636 
 637         BundleArgument(String arg, String value) {
 638             this.arg = arg;
 639             this.value = value;
 640         }
 641 
 642         /**
 643          * Name of the bundle argument.
 644          *
 645          * @ant.required
 646          */
 647         public void setArg(String arg) {
 648             this.arg = arg;
 649         }
 650 
 651         /**
 652          * Value for the bundle argument.
 653          *
 654          * @ant.not-required Default is a literal null
 655          */
 656         public void setValue(String value) {
 657             this.value = value;
 658         }
 659     }
 660 }