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