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