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