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