1 /*
   2  * Copyright (c) 2011, 2014, 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.packager;
  27 
  28 import com.oracle.tools.packager.*;
  29 import com.oracle.tools.packager.RelativeFileSet;
  30 import com.oracle.tools.packager.jnlp.JNLPBundler;
  31 import com.sun.javafx.tools.ant.Callback;
  32 import com.sun.javafx.tools.packager.bundlers.*;
  33 import com.sun.javafx.tools.packager.bundlers.Bundler.BundleType;
  34 import com.sun.javafx.tools.resource.DeployResource;
  35 import java.io.File;
  36 import java.io.IOException;
  37 import java.util.ArrayList;
  38 import java.util.Arrays;
  39 import java.util.Collection;
  40 import java.util.HashMap;
  41 import java.util.LinkedHashMap;
  42 import java.util.LinkedHashSet;
  43 import java.util.LinkedList;
  44 import java.util.List;
  45 import java.util.Map;
  46 import java.util.Set;
  47 import java.util.TreeMap;
  48 import java.util.TreeSet;
  49 import java.util.stream.Collectors;
  50 
  51 import static com.oracle.tools.packager.jnlp.JNLPBundler.*;
  52 
  53 public class DeployParams extends CommonParams {
  54     public enum RunMode {
  55         WEBSTART, EMBEDDED, STANDALONE, ALL
  56     }
  57 
  58     final List<DeployResource> resources = new ArrayList<>();
  59 
  60     String id;
  61     String title;
  62     String vendor;
  63     String email;
  64     String description;
  65     String category;
  66     String licenseType;
  67     String copyright;
  68     String version;
  69     Boolean systemWide;
  70     Boolean serviceHint;
  71     Boolean signBundle;
  72 
  73     String applicationClass;
  74     String preloader;
  75 
  76     List<Param> params;
  77     List<HtmlParam> htmlParams;
  78     List<String> arguments; //unnamed arguments
  79 
  80     int width;
  81     int height;
  82     String embeddedWidth = null;
  83     String embeddedHeight = null;
  84 
  85     String appName;
  86     String codebase;
  87 
  88     boolean embedJNLP = true;
  89     @Deprecated final boolean embedCertificates = false;
  90     boolean allPermissions = false;
  91     String updateMode = "background";
  92     boolean isExtension = false;
  93     boolean isSwingApp = false;
  94 
  95     Boolean needShortcut = null;
  96     Boolean needMenu = null;
  97     boolean needInstall = false;
  98 
  99     String outfile;
 100     //if true then we cobundle js and image files needed
 101     // for web deployment with the application
 102     boolean includeDT;
 103 
 104     String placeholder = "'javafx-app-placeholder'";
 105     String appId = null;
 106 
 107     // didn't have a setter...
 108     boolean offlineAllowed = true;
 109 
 110     List<JSCallback> callbacks;
 111 
 112     //list of HTML templates to process
 113     List<Template> templates = new LinkedList<>();
 114 
 115     String jrePlatform = "1.6+";
 116     String fxPlatform = PackagerLib.JAVAFX_VERSION+"+";
 117     File javaRuntimeToUse = null;
 118     boolean javaRuntimeWasSet = false;
 119 
 120     //list of jvm args (in theory string can contain spaces and need to be escaped
 121     List<String> jvmargs = new LinkedList<>();
 122     Map<String, String> jvmUserArgs = new HashMap<>();
 123 
 124     //list of jvm properties (can also be passed as VM args
 125     // but keeping them separate make it a bit more convinient for JNLP generation)
 126     Map<String, String> properties = new HashMap<>();
 127     
 128     // raw arguments to the bundler
 129     Map<String, ? super Object> bundlerArguments = new HashMap<>();
 130 
 131     String fallbackApp = null;
 132 
 133     public void setJavaRuntimeSource(File src) {
 134         javaRuntimeToUse = src;
 135         javaRuntimeWasSet = true;
 136     }
 137 
 138     public void setCodebase(String codebase) {
 139         this.codebase = codebase;
 140     }
 141 
 142     public void setId(String id) {
 143         this.id = id;
 144     }
 145 
 146     public void setCategory(String category) {
 147         this.category = category;
 148     }
 149 
 150     public void setLicenseType(String licenseType) {
 151         this.licenseType = licenseType;
 152     }
 153 
 154     public void setCopyright(String copyright) {
 155         this.copyright = copyright;
 156     }
 157 
 158     public void setVersion(String version) {
 159         this.version = version;
 160     }
 161 
 162     public void setSystemWide(Boolean systemWide) {
 163         this.systemWide = systemWide;
 164     }
 165 
 166     public void setServiceHint(Boolean serviceHint) {
 167         this.serviceHint = serviceHint;
 168     }
 169 
 170     public void setSignBundle(Boolean signBundle) {
 171         this.signBundle = signBundle;
 172     }
 173 
 174     public void setJRE(String v) {
 175         jrePlatform = v;
 176     }
 177 
 178     public void setSwingAppWithEmbeddedJavaFX(boolean v) {
 179         isSwingApp = v;
 180     }
 181 
 182     public void setNeedInstall(boolean b) {
 183         needInstall = b;
 184     }
 185 
 186     public void setOfflineAllowed(boolean b) {
 187         offlineAllowed = b;
 188     }
 189 
 190     public void setNeedShortcut(Boolean b) {
 191         needShortcut = b;
 192     }
 193 
 194     public void setNeedMenu(Boolean b) {
 195         needMenu = b;
 196     }
 197 
 198     public void setEmbeddedDimensions(String w, String h) {
 199         embeddedWidth = w;
 200         embeddedHeight = h;
 201     }
 202 
 203     public void setFallback(String v) {
 204         if (v == null) {
 205             return;
 206         }
 207 
 208         if ("none".equals(v) || "null".equals(v)) {
 209             fallbackApp = null;
 210         } else {
 211             fallbackApp = v;
 212         }
 213     }
 214 
 215     public void setJavafx(String v) {
 216         fxPlatform = v;
 217     }
 218 
 219     public void addJvmArg(String v) {
 220         jvmargs.add(v);
 221     }
 222 
 223     public void addJvmUserArg(String n, String v) {
 224         jvmUserArgs.put(n, v);
 225     }
 226 
 227     public void addJvmProperty(String n, String v) {
 228         properties.put(n, v);
 229     }
 230 
 231     public void setAllPermissions(boolean allPermissions) {
 232         this.allPermissions = allPermissions;
 233     }
 234 
 235     public void setAppName(String appName) {
 236         this.appName = appName;
 237     }
 238 
 239     public void setArguments(List<String> args) {
 240         this.arguments = args;
 241     }
 242 
 243     public void setDescription(String description) {
 244         this.description = description;
 245     }
 246 
 247     public void setEmbedJNLP(boolean embedJNLP) {
 248         this.embedJNLP = embedJNLP;
 249     }
 250 
 251     @Deprecated
 252     public void setEmbedCertifcates(boolean v) {
 253         if (v) {
 254             System.out.println("JavaFX Packager no longer supports embedding certificates in JNLP files.  Setting will be ignored.");
 255         }
 256     }
 257 
 258     public void setPlaceholder(String p) {
 259         placeholder = p;
 260     }
 261 
 262     public void setAppId(String id) {
 263         appId = id;
 264     }
 265 
 266     public void setHeight(int height) {
 267         this.height = height;
 268     }
 269 
 270     public void setHtmlParams(List<HtmlParam> htmlParams) {
 271         this.htmlParams = htmlParams;
 272     }
 273 
 274     public void setOutfile(String outfile) {
 275         this.outfile = outfile;
 276     }
 277 
 278     public void setParams(List<Param> params) {
 279         this.params = params;
 280     }
 281 
 282     public void setPreloader(String preloader) {
 283         this.preloader = preloader;
 284     }
 285 
 286     public void setTitle(String title) {
 287         this.title = title;
 288     }
 289 
 290     public void setUpdateMode(String updateMode) {
 291         this.updateMode = updateMode;
 292     }
 293 
 294     public void setVendor(String vendor) {
 295         this.vendor = vendor;
 296     }
 297 
 298     public void setEmail(String email) {
 299         this.email = email;
 300     }
 301 
 302     public void setWidth(int width) {
 303         this.width = width;
 304     }
 305 
 306     public void setExtension(boolean isExtension) {
 307         this.isExtension = isExtension;
 308     }
 309 
 310     public void setApplicationClass(String applicationClass) {
 311         this.applicationClass = applicationClass;
 312     }
 313 
 314     public void setIncludeDT(boolean doEmbed) {
 315         includeDT = doEmbed;
 316     }
 317 
 318     public void setJSCallbacks(List<JSCallback> list) {
 319         callbacks = list;
 320     }
 321 
 322     public void setCallbacks(List<Callback> list) {
 323         List<JSCallback> jslist = new ArrayList<>(list.size());
 324         for (Callback cb: list) {
 325             jslist.add(new JSCallback(cb.getName(), cb.getCmd()));
 326         }
 327         callbacks = jslist;
 328     }
 329 
 330     static class Template {
 331         File in;
 332         File out;
 333 
 334         Template(File in, File out) {
 335             this.in = in;
 336             this.out = out;
 337         }
 338     }
 339 
 340     public void addTemplate(File in, File out) {
 341         templates.add(new Template(in, out));
 342     }
 343 
 344     //we need to expand as in some cases
 345     // (most notably javapackager)
 346     //we may get "." as filename and assumption is we include
 347     // everything in the given folder
 348     // (IOUtils.copyfiles() have recursive behavior)
 349     List<File> expandFileset(File root) {
 350         List<File> files = new LinkedList<>();
 351         if (com.oracle.tools.packager.IOUtils.isNotSymbolicLink(root)) {
 352            if (root.isDirectory()) {
 353                File[] children = root.listFiles();
 354                if (children != null) {
 355                    for (File f : children) {
 356                        files.addAll(expandFileset(f));
 357                    }
 358                }
 359            } else {
 360                files.add(root);
 361            }
 362         }
 363         return files;
 364     }
 365 
 366     @Override
 367     public void addResource(File baseDir, String path) {
 368         File file = new File(baseDir, path);
 369         try {
 370             //normalize top level dir
 371             // to strip things like "." in the path
 372             // or it can confuse symlink detection logic
 373             file = file.getCanonicalFile();
 374         } catch (IOException ignored) {}
 375         for (File f: expandFileset(file)) {
 376            resources.add(new DeployResource(baseDir, f));
 377         }
 378     }
 379 
 380     @Override
 381     public void addResource(File baseDir, File file) {
 382         try {
 383             //normalize initial file
 384             // to strip things like "." in the path
 385             // or it can confuse symlink detection logic
 386             file = file.getCanonicalFile();
 387         } catch (IOException ignored) {}
 388         for (File f: expandFileset(file)) {
 389            resources.add(new DeployResource(baseDir, f));
 390         }
 391     }
 392 
 393     public void addResource(File baseDir, String path, String type) {
 394         resources.add(new DeployResource(baseDir, path, type));
 395     }
 396 
 397     public void addResource(File baseDir, File file, String type) {
 398         resources.add(new DeployResource(baseDir, file, type));
 399     }
 400 
 401     public void addResource(File baseDir, File file, String mode, String type, String os, String arch) {
 402         resources.add(new DeployResource(baseDir, file, mode, type, os, arch));
 403     }
 404 
 405     @Override
 406     public void validate() throws PackagerException {
 407         if (outdir == null) {
 408             throw new PackagerException("ERR_MissingArgument", "-outdir");
 409         }
 410         if (outfile == null) {
 411             throw new PackagerException("ERR_MissingArgument", "-outfile");
 412         }
 413         if (resources.isEmpty()) {
 414             throw new PackagerException("ERR_MissingAppResources");
 415         }
 416         if (applicationClass == null) {
 417             throw new PackagerException("ERR_MissingArgument", "-appclass");
 418         }
 419     }
 420 
 421     //could be icon or splash
 422     static class Icon {
 423         final static int UNDEFINED = -1;
 424 
 425         String href;
 426         String kind;
 427         int width = UNDEFINED;
 428         int height = UNDEFINED;
 429         int depth = UNDEFINED;
 430         RunMode mode = RunMode.WEBSTART;
 431 
 432         Icon(String href, String kind, int w, int h, int d, RunMode m) {
 433             mode = m;
 434             this.href = href;
 435             this.kind = kind;
 436             if (w > 0) {
 437                 width = w;
 438             }
 439             if (h > 0) {
 440                 height = h;
 441             }
 442             if (d > 0) {
 443                 depth = d;
 444             }
 445         }
 446     }
 447 
 448     List<Icon> icons = new LinkedList<>();
 449 
 450     public void addIcon(String href, String kind, int w, int h, int d, RunMode m) {
 451         icons.add(new Icon(href, kind, w, h, d, m));
 452     }
 453 
 454     BundleType bundleType = BundleType.NONE;
 455     String targetFormat = null; //means any
 456 
 457     public void setBundleType(BundleType type) {
 458         bundleType = type;
 459     }
 460     
 461     public BundleType getBundleType() {
 462         return bundleType;
 463     }
 464 
 465     public void setTargetFormat(String t) {
 466         targetFormat = t;
 467     }
 468 
 469     public String getTargetFormat() {
 470         return targetFormat;
 471     }
 472 
 473     private String getArch() {
 474         String arch = System.getProperty("os.arch").toLowerCase();
 475 
 476         if ("x86".equals(arch) || "i386".equals(arch) || "i486".equals(arch)
 477                 || "i586".equals(arch) || "i686".equals(arch)) {
 478             arch = "x86";
 479         } else if ("x86_64".equals(arch) || "amd64".equals("arch")) {
 480             arch = "x86_64";
 481         }
 482 
 483         return arch;
 484     }
 485 
 486     static final Set<String> multi_args = new TreeSet<>(Arrays.asList(
 487             StandardBundlerParam.JVM_PROPERTIES.getID(),
 488             StandardBundlerParam.JVM_OPTIONS.getID(),
 489             StandardBundlerParam.USER_JVM_OPTIONS.getID(),
 490             StandardBundlerParam.ARGUMENTS.getID()
 491     ));
 492 
 493     @SuppressWarnings("unchecked")
 494     public void addBundleArgument(String key, Object value) {
 495         // special hack for multi-line arguments
 496         if (multi_args.contains(key) && value instanceof String) {
 497             Object existingValue = bundlerArguments.get(key);
 498             if (existingValue instanceof String) {
 499                 bundlerArguments.put(key, existingValue + "\n\n" + value);
 500             } else if (existingValue instanceof List) {
 501                 ((List)existingValue).add(value);
 502             } else if (existingValue instanceof Map && ((String)value).contains("=")) {
 503                 String[] mapValues = ((String)value).split("=", 2);
 504                 ((Map)existingValue).put(mapValues[0], mapValues[1]);
 505             } else {
 506                 bundlerArguments.put(key, value);
 507             }
 508         } else {
 509             bundlerArguments.put(key, value);
 510         }
 511     }
 512 
 513     public BundleParams getBundleParams() {
 514         BundleParams bundleParams = new BundleParams();
 515 
 516         //construct app resources
 517         //  relative to output folder!
 518         String currentOS = System.getProperty("os.name").toLowerCase();
 519         String currentArch = getArch();
 520 
 521         Map<File, Set<File>> fileResources = new LinkedHashMap<>();
 522         
 523         for (DeployResource r: resources) {
 524             String os = r.getOs();
 525             String arch = r.getArch();
 526             //skip resources for other OS
 527             // and nativelib jars (we are including raw libraries)
 528             if ((os == null || currentOS.contains(os.toLowerCase())) &&
 529                     (arch == null || currentArch.startsWith(arch.toLowerCase()))
 530                     && r.getType() != DeployResource.Type.nativelib) {
 531                 if (r.getType() == DeployResource.Type.license) {
 532                     bundleParams.addLicenseFile(r.getRelativePath());
 533                 }
 534                 Set<File> files = fileResources.get(r.getBaseDir());
 535                 if (files == null) {
 536                     fileResources.put(r.getBaseDir(), files = new LinkedHashSet<>());
 537                 }
 538                 files.add(new File(r.getBaseDir(), r.getRelativePath()));
 539             }
 540         }
 541         
 542         bundleParams.setAppResourcesList(fileResources.entrySet().stream()
 543                 .map(e -> new RelativeFileSet(e.getKey(), e.getValue()))
 544                 .collect(Collectors.toList()));
 545 
 546         bundleParams.setIdentifier(id);
 547 
 548         if (javaRuntimeWasSet) {
 549             bundleParams.setRuntime(javaRuntimeToUse);
 550         }
 551         bundleParams.setApplicationClass(applicationClass);
 552         bundleParams.setPrelaoderClass(preloader);
 553         bundleParams.setName(this.appName);
 554         bundleParams.setAppVersion(version);
 555         bundleParams.setType(bundleType);
 556         bundleParams.setBundleFormat(targetFormat);
 557         bundleParams.setVendor(vendor);
 558         bundleParams.setEmail(email);
 559         bundleParams.setShortcutHint(needShortcut);
 560         bundleParams.setMenuHint(needMenu);
 561         bundleParams.setSystemWide(systemWide);
 562         bundleParams.setServiceHint(serviceHint);
 563         bundleParams.setSignBundle(signBundle);
 564         bundleParams.setCopyright(copyright);
 565         bundleParams.setApplicationCategory(category);
 566         bundleParams.setLicenseType(licenseType);
 567         bundleParams.setDescription(description);
 568         bundleParams.setTitle(title);
 569         if (verbose) bundleParams.setVerbose(true);
 570 
 571         bundleParams.setJvmProperties(properties);
 572         bundleParams.setJvmargs(jvmargs);
 573         bundleParams.setJvmUserArgs(jvmUserArgs);
 574         bundleParams.setArguments(arguments);
 575 
 576         File appIcon = null;
 577         for (Icon ic: icons) {
 578             //NB: in theory we should be paying attention to RunMode but
 579             // currently everything is marked as webstart internally and runmode
 580             // is not publicly documented property
 581             if (/* (ic.mode == RunMode.ALL || ic.mode == RunMode.STANDALONE) && */
 582                 (ic.kind == null || ic.kind.equals("default"))) {
 583                 //could be full path or something relative to the output folder
 584                 appIcon = new File(ic.href);
 585                 if (!appIcon.exists()) {
 586                     com.oracle.tools.packager.Log.debug("Icon [" + ic.href + "] is not valid absolute path. " +
 587                             "Assume it is relative to the output dir.");
 588                     appIcon = new File(outdir, ic.href);
 589                 }
 590             }
 591         }
 592 
 593         bundleParams.setIcon(appIcon);
 594 
 595         Map<String, String> paramsMap = new TreeMap<>();
 596         if (params != null) {
 597             for (Param p : params) {
 598                 paramsMap.put(p.name, p.value);
 599             }
 600         }
 601         putUnlessNullOrEmpty(JNLPBundler.APP_PARAMS.getID(), paramsMap);
 602         
 603         Map<String, String> unescapedHtmlParams = new TreeMap<>();
 604         Map<String, String> escapedHtmlParams = new TreeMap<>();
 605         if (htmlParams != null) {
 606             for (HtmlParam hp : htmlParams) {
 607                 if (hp.needEscape) {
 608                     escapedHtmlParams.put(hp.name, hp.value);
 609                 } else {
 610                     unescapedHtmlParams.put(hp.name, hp.value);
 611                 }
 612             }
 613         }
 614         putUnlessNullOrEmpty(JNLPBundler.APPLET_PARAMS.getID(), unescapedHtmlParams);
 615         putUnlessNullOrEmpty(ESCAPED_APPLET_PARAMS.getID(), escapedHtmlParams);
 616         
 617 
 618         putUnlessNull(WIDTH.getID(), width);
 619         putUnlessNull(HEIGHT.getID(), height);
 620         putUnlessNull(EMBEDDED_WIDTH.getID(), embeddedWidth);
 621         putUnlessNull(EMBEDDED_HEIGHT.getID(), embeddedHeight);
 622         
 623         putUnlessNull(CODEBASE.getID(), codebase);
 624         putUnlessNull(EMBED_JNLP.getID(), embedJNLP);
 625         // embedCertificates
 626         putUnlessNull(ALL_PERMISSIONS.getID(), allPermissions);
 627         putUnlessNull(UPDATE_MODE.getID(), updateMode);
 628         putUnlessNull(EXTENSION.getID(), isExtension);
 629         putUnlessNull(SWING_APP.getID(), isSwingApp);
 630 
 631         putUnlessNull(OUT_FILE.getID(), outfile);
 632         putUnlessNull(INCLUDE_DT.getID(), includeDT);
 633         putUnlessNull(PLACEHOLDER.getID(), placeholder);
 634         putUnlessNull(OFFLINE_ALLOWED.getID(), offlineAllowed);
 635         
 636         Map<String, String> callbacksMap = new TreeMap<>();
 637         if (callbacks != null) {
 638             for (JSCallback callback : callbacks) {
 639                 callbacksMap.put(callback.getName(), callback.getCmd());
 640             }
 641         }
 642         putUnlessNull(JS_CALLBACKS.getID(), callbacksMap);
 643 
 644         Map<File, File> templatesMap = new TreeMap<>();
 645         if (templates != null) {
 646             for (Template template : templates) {
 647                 templatesMap.put(template.in, template.out);
 648             }
 649         }
 650         putUnlessNull(TEMPLATES.getID(), templatesMap);
 651 
 652         putUnlessNull(FX_PLATFORM.getID(), fxPlatform);
 653         putUnlessNull(JRE_PLATFORM.getID(), jrePlatform);
 654 
 655         putUnlessNull(FALLBACK_APP.getID(), fallbackApp);
 656         
 657         // check for collisions
 658         TreeSet<String> keys = new TreeSet<>(bundlerArguments.keySet());
 659         keys.retainAll(bundleParams.getBundleParamsAsMap().keySet());
 660 
 661         if (!keys.isEmpty()) {
 662             throw new RuntimeException("Deploy Params and Bundler Arguments overlap in the following values:" + keys.toString());
 663         }
 664         
 665         bundleParams.addAllBundleParams(bundlerArguments);
 666         
 667         return bundleParams;
 668     }
 669 
 670     public void putUnlessNull(String param, Object value) {
 671         if (value != null) {
 672             bundlerArguments.put(param, value);
 673         }
 674     }
 675 
 676     public void putUnlessNullOrEmpty(String param, Map value) {
 677         if (value != null && !value.isEmpty()) {
 678             bundlerArguments.put(param, value);
 679         }
 680     }
 681 }