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