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