1 /* 2 * Copyright (c) 2011, 2019, 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 jdk.jpackage.internal; 27 28 import java.io.File; 29 import java.nio.file.Files; 30 import java.nio.file.Path; 31 import java.nio.file.InvalidPathException; 32 import java.text.MessageFormat; 33 import java.util.ArrayList; 34 import java.util.Arrays; 35 import java.util.Collection; 36 import java.util.LinkedHashMap; 37 import java.util.LinkedHashSet; 38 import java.util.LinkedList; 39 import java.util.List; 40 import java.util.Map; 41 import java.util.Set; 42 import java.util.TreeMap; 43 import java.util.TreeSet; 44 45 /** 46 * DeployParams 47 * 48 * This class is generated and used in Arguments.processArguments() as 49 * intermediate step in generating the BundleParams and ultimately the Bundles 50 */ 51 public class DeployParams { 52 53 final List<RelativeFileSet> resources = new ArrayList<>(); 54 55 String id; 56 String title; 57 String vendor; 58 String email; 59 String description; 60 String category; 61 String licenseType; 62 String copyright; 63 String version; 64 Boolean systemWide; 65 Boolean serviceHint; 66 Boolean signBundle; 67 Boolean installdirChooser; 68 69 String applicationClass; 70 71 List<Param> params; 72 List<String> arguments; //unnamed arguments 73 74 // Java 9 modules support 75 String addModules = null; 76 String limitModules = null; 77 Boolean stripNativeCommands = null; 78 String modulePath = null; 79 String module = null; 80 String debugPort = null; 81 82 boolean jreInstaller = false; 83 84 File outdir = null; 85 86 String appId = null; 87 88 // list of jvm args 89 // (in theory string can contain spaces and need to be escaped 90 List<String> jvmargs = new LinkedList<>(); 91 92 // raw arguments to the bundler 93 Map<String, ? super Object> bundlerArguments = new LinkedHashMap<>(); 94 95 void setCategory(String category) { 96 this.category = category; 97 } 98 99 void setLicenseType(String licenseType) { 100 this.licenseType = licenseType; 101 } 102 103 void setCopyright(String copyright) { 104 this.copyright = copyright; 105 } 106 107 void setVersion(String version) { 108 this.version = version; 109 } 110 111 void setSystemWide(Boolean systemWide) { 112 this.systemWide = systemWide; 113 } 114 115 void setInstalldirChooser(Boolean installdirChooser) { 116 this.installdirChooser = installdirChooser; 117 } 118 119 void setSignBundle(Boolean signBundle) { 120 this.signBundle = signBundle; 121 } 122 123 void addJvmArg(String v) { 124 jvmargs.add(v); 125 } 126 127 void setArguments(List<String> args) { 128 this.arguments = args; 129 } 130 131 List<String> getArguments() { 132 return this.arguments; 133 } 134 135 void addArgument(String arg) { 136 this.arguments.add(arg); 137 } 138 139 void addAddModule(String value) { 140 if (addModules == null) { 141 addModules = value; 142 } 143 else { 144 addModules += "," + value; 145 } 146 } 147 148 void addLimitModule(String value) { 149 if (limitModules == null) { 150 limitModules = value; 151 } 152 else { 153 limitModules += "," + value; 154 } 155 } 156 157 String getModulePath() { 158 return this.modulePath; 159 } 160 161 void setModulePath(String value) { 162 this.modulePath = value; 163 } 164 165 void setModule(String value) { 166 this.module = value; 167 } 168 169 void setDebug(String value) { 170 this.debugPort = value; 171 } 172 173 void setStripNativeCommands(boolean value) { 174 this.stripNativeCommands = value; 175 } 176 177 void setDescription(String description) { 178 this.description = description; 179 } 180 181 public void setAppId(String id) { 182 appId = id; 183 } 184 185 void setParams(List<Param> params) { 186 this.params = params; 187 } 188 189 void setTitle(String title) { 190 this.title = title; 191 } 192 193 void setVendor(String vendor) { 194 this.vendor = vendor; 195 } 196 197 void setEmail(String email) { 198 this.email = email; 199 } 200 201 void setApplicationClass(String applicationClass) { 202 this.applicationClass = applicationClass; 203 } 204 205 void setJreInstaller(boolean value) { 206 jreInstaller = value; 207 } 208 209 File getOutput() { 210 return outdir; 211 } 212 213 public void setOutput(File output) { 214 outdir = output; 215 } 216 217 static class Template { 218 File in; 219 File out; 220 221 Template(File in, File out) { 222 this.in = in; 223 this.out = out; 224 } 225 } 226 227 // we need to expand as in some cases 228 // (most notably jpackage) 229 // we may get "." as filename and assumption is we include 230 // everything in the given folder 231 // (IOUtils.copyfiles() have recursive behavior) 232 List<File> expandFileset(File root) { 233 List<File> files = new LinkedList<>(); 234 if (!Files.isSymbolicLink(root.toPath())) { 235 if (root.isDirectory()) { 236 File[] children = root.listFiles(); 237 if (children != null) { 238 for (File f : children) { 239 files.addAll(expandFileset(f)); 240 } 241 } 242 } else { 243 files.add(root); 244 } 245 } 246 return files; 247 } 248 249 public void addResource(File baseDir, String path) { 250 File file = new File(baseDir, path); 251 // normalize top level dir 252 // to strip things like "." in the path 253 // or it can confuse symlink detection logic 254 file = file.getAbsoluteFile(); 255 256 if (baseDir == null) { 257 baseDir = file.getParentFile(); 258 } 259 resources.add(new RelativeFileSet( 260 baseDir, new LinkedHashSet<>(expandFileset(file)))); 261 } 262 263 public void addResource(File baseDir, File file) { 264 // normalize initial file 265 // to strip things like "." in the path 266 // or it can confuse symlink detection logic 267 file = file.getAbsoluteFile(); 268 269 if (baseDir == null) { 270 baseDir = file.getParentFile(); 271 } 272 resources.add(new RelativeFileSet( 273 baseDir, new LinkedHashSet<>(expandFileset(file)))); 274 } 275 276 void setClasspath() { 277 String classpath = ""; 278 for (RelativeFileSet resource : resources) { 279 for (String file : resource.getIncludedFiles()) { 280 if (file.endsWith(".jar")) { 281 classpath += file + File.pathSeparator; 282 } 283 } 284 } 285 addBundleArgument( 286 StandardBundlerParam.CLASSPATH.getID(), classpath); 287 } 288 289 private static File createFile(final File baseDir, final String path) { 290 final File testFile = new File(path); 291 return testFile.isAbsolute() ? 292 testFile : new File(baseDir == null ? 293 null : baseDir.getAbsolutePath(), path); 294 } 295 296 static void validateName(String s, boolean forApp) 297 throws PackagerException { 298 299 String exceptionKey = forApp ? 300 "ERR_InvalidAppName" : "ERR_InvalidSLName"; 301 302 if (s == null) { 303 if (forApp) { 304 return; 305 } else { 306 throw new PackagerException(exceptionKey, s); 307 } 308 } 309 if (s.charAt(s.length() - 1) == '\\') { 310 throw new PackagerException(exceptionKey, s); 311 } 312 try { 313 // name must be valid path element for this file system 314 Path p = (new File(s)).toPath(); 315 // and it must be a single name element in a path 316 if (p.getNameCount() != 1) { 317 throw new PackagerException(exceptionKey, s); 318 } 319 } catch (InvalidPathException ipe) { 320 throw new PackagerException(ipe, exceptionKey, s); 321 } 322 323 for (int i = 0; i < s.length(); i++) { 324 char a = s.charAt(i); 325 // We check for ASCII codes first which we accept. If check fails, 326 // check if it is acceptable extended ASCII or unicode character. 327 if (a < ' ' || a > '~') { 328 // Accept anything else including special chars like copyright 329 // symbols. Note: space will be included by ASCII check above, 330 // but other whitespace like tabs or new line will be rejected. 331 if (Character.isISOControl(a) || 332 Character.isWhitespace(a)) { 333 throw new PackagerException(exceptionKey, s); 334 } 335 } else if (a == '"' || a == '%') { 336 throw new PackagerException(exceptionKey, s); 337 } 338 } 339 } 340 341 public void validate() throws PackagerException { 342 if (outdir == null) { 343 throw new PackagerException("ERR_MissingArgument", "--output"); 344 } 345 346 boolean hasModule = (bundlerArguments.get( 347 Arguments.CLIOptions.MODULE.getId()) != null); 348 boolean hasImage = (bundlerArguments.get( 349 Arguments.CLIOptions.PREDEFINED_APP_IMAGE.getId()) != null); 350 boolean hasClass = (bundlerArguments.get( 351 Arguments.CLIOptions.APPCLASS.getId()) != null); 352 boolean hasMain = (bundlerArguments.get( 353 Arguments.CLIOptions.MAIN_JAR.getId()) != null); 354 boolean hasRuntimeImage = (bundlerArguments.get( 355 Arguments.CLIOptions.PREDEFINED_RUNTIME_IMAGE.getId()) != null); 356 boolean hasInput = (bundlerArguments.get( 357 Arguments.CLIOptions.INPUT.getId()) != null); 358 boolean hasModulePath = (bundlerArguments.get( 359 Arguments.CLIOptions.MODULE_PATH.getId()) != null); 360 boolean hasAppImage = (bundlerArguments.get( 361 Arguments.CLIOptions.PREDEFINED_APP_IMAGE.getId()) != null); 362 363 if (getBundleType() == BundlerType.IMAGE) { 364 // Module application requires --runtime-image or --module-path 365 if (hasModule) { 366 if (!hasModulePath && !hasRuntimeImage) { 367 throw new PackagerException("ERR_MissingArgument", 368 "--runtime-image or --module-path"); 369 } 370 } else { 371 if (!hasInput) { 372 throw new PackagerException( 373 "ERR_MissingArgument", "--input"); 374 } 375 } 376 } else if (getBundleType() == BundlerType.INSTALLER) { 377 if (!Arguments.isJreInstaller()) { 378 if (hasModule) { 379 if (!hasModulePath && !hasRuntimeImage && !hasAppImage) { 380 throw new PackagerException("ERR_MissingArgument", 381 "--runtime-image, --module-path or --app-image"); 382 } 383 } else { 384 if (!hasInput && !hasAppImage) { 385 throw new PackagerException("ERR_MissingArgument", 386 "--input or --app-image"); 387 } 388 } 389 } 390 } 391 392 // if bundling non-modular image, or installer without app-image 393 // then we need some resources and a main class 394 if (!hasModule && !hasImage && !jreInstaller) { 395 if (resources.isEmpty()) { 396 throw new PackagerException("ERR_MissingAppResources"); 397 } 398 if (!hasClass) { 399 throw new PackagerException("ERR_MissingArgument", "--class"); 400 } 401 if (!hasMain) { 402 throw new PackagerException("ERR_MissingArgument", 403 "--main-jar"); 404 } 405 } 406 407 String name = (String)bundlerArguments.get( 408 Arguments.CLIOptions.NAME.getId()); 409 validateName(name, true); 410 411 // Validate app image if set 412 String appImage = (String)bundlerArguments.get( 413 Arguments.CLIOptions.PREDEFINED_APP_IMAGE.getId()); 414 if (appImage != null) { 415 File appImageDir = new File(appImage); 416 if (!appImageDir.exists()) { 417 throw new PackagerException("ERR_AppImageNotExist", appImage); 418 } 419 420 File appImageAppDir = new File(appImage + File.separator + "app"); 421 File appImageRuntimeDir = new File(appImage 422 + File.separator + "runtime"); 423 if (!appImageAppDir.exists() || !appImageRuntimeDir.exists()) { 424 throw new PackagerException("ERR_AppImageInvalid", appImage); 425 } 426 } 427 428 // Validate build-root 429 String root = (String)bundlerArguments.get( 430 Arguments.CLIOptions.BUILD_ROOT.getId()); 431 if (root != null) { 432 String [] contents = (new File(root)).list(); 433 434 if (contents != null && contents.length > 0) { 435 throw new PackagerException("ERR_BuildRootInvalid", root); 436 } 437 } 438 439 // Validate license file if set 440 String license = (String)bundlerArguments.get( 441 Arguments.CLIOptions.LICENSE_FILE.getId()); 442 if (license != null) { 443 File licenseFile = new File(license); 444 if (!licenseFile.exists()) { 445 throw new PackagerException("ERR_LicenseFileNotExit"); 446 } 447 } 448 } 449 450 boolean validateForBundle() { 451 boolean result = false; 452 453 // Success 454 if (((applicationClass != null && !applicationClass.isEmpty()) || 455 (module != null && !module.isEmpty()))) { 456 result = true; 457 } 458 459 return result; 460 } 461 462 BundlerType bundleType = BundlerType.NONE; 463 String targetFormat = null; //means any 464 465 void setBundleType(BundlerType type) { 466 bundleType = type; 467 } 468 469 BundlerType getBundleType() { 470 return bundleType; 471 } 472 473 void setTargetFormat(String t) { 474 targetFormat = t; 475 } 476 477 String getTargetFormat() { 478 return targetFormat; 479 } 480 481 private String getArch() { 482 String arch = System.getProperty("os.arch").toLowerCase(); 483 484 if ("x86".equals(arch) || "i386".equals(arch) || "i486".equals(arch) 485 || "i586".equals(arch) || "i686".equals(arch)) { 486 arch = "x86"; 487 } else if ("x86_64".equals(arch) || "amd64".equals("arch")) { 488 arch = "x86_64"; 489 } 490 491 return arch; 492 } 493 494 static final Set<String> multi_args = new TreeSet<>(Arrays.asList( 495 StandardBundlerParam.JVM_OPTIONS.getID(), 496 StandardBundlerParam.ARGUMENTS.getID(), 497 StandardBundlerParam.MODULE_PATH.getID(), 498 StandardBundlerParam.ADD_MODULES.getID(), 499 StandardBundlerParam.LIMIT_MODULES.getID(), 500 StandardBundlerParam.FILE_ASSOCIATIONS.getID() 501 )); 502 503 @SuppressWarnings("unchecked") 504 public void addBundleArgument(String key, Object value) { 505 // special hack for multi-line arguments 506 if (multi_args.contains(key)) { 507 Object existingValue = bundlerArguments.get(key); 508 if (existingValue instanceof String && value instanceof String) { 509 bundlerArguments.put(key, existingValue + "\n\n" + value); 510 } else if (existingValue instanceof List && value instanceof List) { 511 ((List)existingValue).addAll((List)value); 512 } else if (existingValue instanceof Map && 513 value instanceof String && ((String)value).contains("=")) { 514 String[] mapValues = ((String)value).split("=", 2); 515 ((Map)existingValue).put(mapValues[0], mapValues[1]); 516 } else { 517 bundlerArguments.put(key, value); 518 } 519 } else { 520 bundlerArguments.put(key, value); 521 } 522 } 523 524 BundleParams getBundleParams() { 525 BundleParams bundleParams = new BundleParams(); 526 527 // construct app resources relative to output folder! 528 String currentOS = System.getProperty("os.name").toLowerCase(); 529 String currentArch = getArch(); 530 531 bundleParams.setAppResourcesList(resources); 532 533 bundleParams.setIdentifier(id); 534 535 bundleParams.setApplicationClass(applicationClass); 536 bundleParams.setAppVersion(version); 537 bundleParams.setType(bundleType); 538 bundleParams.setBundleFormat(targetFormat); 539 bundleParams.setVendor(vendor); 540 bundleParams.setEmail(email); 541 bundleParams.setInstalldirChooser(installdirChooser); 542 bundleParams.setCopyright(copyright); 543 bundleParams.setApplicationCategory(category); 544 bundleParams.setDescription(description); 545 bundleParams.setTitle(title); 546 547 bundleParams.setJvmargs(jvmargs); 548 bundleParams.setArguments(arguments); 549 550 if (addModules != null && !addModules.isEmpty()) { 551 bundleParams.setAddModules(addModules); 552 } 553 554 if (limitModules != null && !limitModules.isEmpty()) { 555 bundleParams.setLimitModules(limitModules); 556 } 557 558 if (stripNativeCommands != null) { 559 bundleParams.setStripNativeCommands(stripNativeCommands); 560 } 561 562 if (modulePath != null && !modulePath.isEmpty()) { 563 bundleParams.setModulePath(modulePath); 564 } 565 566 if (module != null && !module.isEmpty()) { 567 bundleParams.setMainModule(module); 568 } 569 570 if (debugPort != null && !debugPort.isEmpty()) { 571 bundleParams.setDebug(debugPort); 572 } 573 574 Map<String, String> paramsMap = new TreeMap<>(); 575 if (params != null) { 576 for (Param p : params) { 577 paramsMap.put(p.name, p.value); 578 } 579 } 580 581 Map<String, String> unescapedHtmlParams = new TreeMap<>(); 582 Map<String, String> escapedHtmlParams = new TreeMap<>(); 583 584 // check for collisions 585 TreeSet<String> keys = new TreeSet<>(bundlerArguments.keySet()); 586 keys.retainAll(bundleParams.getBundleParamsAsMap().keySet()); 587 588 if (!keys.isEmpty()) { 589 throw new RuntimeException("Deploy Params and Bundler Arguments " 590 + "overlap in the following values:" + keys.toString()); 591 } 592 593 bundleParams.addAllBundleParams(bundlerArguments); 594 595 return bundleParams; 596 } 597 598 Map<String, ? super Object> getBundlerArguments() { 599 return this.bundlerArguments; 600 } 601 602 void putUnlessNull(String param, Object value) { 603 if (value != null) { 604 bundlerArguments.put(param, value); 605 } 606 } 607 608 void putUnlessNullOrEmpty(String param, Map<?, ?> value) { 609 if (value != null && !value.isEmpty()) { 610 bundlerArguments.put(param, value); 611 } 612 } 613 614 void putUnlessNullOrEmpty(String param, Collection<?> value) { 615 if (value != null && !value.isEmpty()) { 616 bundlerArguments.put(param, value); 617 } 618 } 619 620 @Override 621 public String toString() { 622 return "DeployParams {" + "output: " + outdir 623 + " resources: {" + resources + "}}"; 624 } 625 626 }