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