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 * <fx:deploy width="600" height="400" 55 * outdir="web-dist" outfile="Fish"> 56 * <info title="Sample application"/> 57 * <fx:application refid="myapp"/> 58 * <fx:resources refid="myresources"/> 59 * </fx:deploy> 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 * <fx:deploy width="600" height="400" 68 * outdir="web-dist" outfile="Fish"> 69 * <info title="Sample application"/> 70 * <fx:application name="SampleApp" 71 * mainClass="testapp.MainApp" 72 * preloaderClass="testpreloader.Preloader"/> 73 * <fx:resources> 74 * <fx:fileset requiredFor="preloader" dir="dist"> 75 * <include name="preloader.jar"/> 76 * </fx:fileset> 77 * <fx:fileset dir="dist"> 78 * <include name="helloworld.jar"/> 79 * </fx:fileset> 80 * </fx:resources> 81 * </fx:deploy> 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 * <template file="App_template.html" tofile="App.html"/> 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 }