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