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