1 /* 2 * Copyright (c) 2012, 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.bundlers.Bundlers; 29 import com.sun.javafx.tools.ant.Utils; 30 import com.sun.javafx.tools.packager.DeployParams.Icon; 31 import com.sun.javafx.tools.packager.JarSignature.InputStreamSource; 32 import com.sun.javafx.tools.packager.bundlers.*; 33 import com.sun.javafx.tools.resource.DeployResource; 34 import com.sun.javafx.tools.resource.PackagerResource; 35 import java.io.BufferedReader; 36 import java.io.ByteArrayOutputStream; 37 import java.io.File; 38 import java.io.FileInputStream; 39 import java.io.FileNotFoundException; 40 import java.io.FileOutputStream; 41 import java.io.FileWriter; 42 import java.io.IOException; 43 import java.io.InputStream; 44 import java.io.InputStreamReader; 45 import java.io.OutputStream; 46 import java.io.PrintStream; 47 import java.io.Writer; 48 import java.lang.reflect.Method; 49 import java.net.MalformedURLException; 50 import java.net.URL; 51 import java.net.URLClassLoader; 52 import java.nio.file.Files; 53 import java.nio.file.StandardCopyOption; 54 import java.security.CodeSigner; 55 import java.security.InvalidKeyException; 56 import java.security.KeyStore; 57 import java.security.KeyStoreException; 58 import java.security.NoSuchAlgorithmException; 59 import java.security.PrivateKey; 60 import java.security.SignatureException; 61 import java.security.UnrecoverableKeyException; 62 import java.security.cert.CertPath; 63 import java.security.cert.Certificate; 64 import java.security.cert.CertificateEncodingException; 65 import java.security.cert.CertificateException; 66 import java.security.cert.X509Certificate; 67 import java.text.MessageFormat; 68 import java.util.ArrayList; 69 import java.util.Collection; 70 import java.util.EnumMap; 71 import java.util.Enumeration; 72 import java.util.HashSet; 73 import java.util.Iterator; 74 import java.util.List; 75 import java.util.Map; 76 import java.util.Map.Entry; 77 import java.util.ResourceBundle; 78 import java.util.Set; 79 import java.util.jar.Attributes; 80 import java.util.jar.JarEntry; 81 import java.util.jar.JarFile; 82 import java.util.jar.JarOutputStream; 83 import java.util.jar.Manifest; 84 import java.util.regex.Matcher; 85 import java.util.regex.Pattern; 86 import java.util.zip.ZipEntry; 87 import java.util.zip.ZipOutputStream; 88 import sun.misc.BASE64Encoder; 89 90 import static com.oracle.bundlers.StandardBundlerParam.*; 91 92 93 public class PackagerLib { 94 public static final String JAVAFX_VERSION = "2.2"; 95 96 private static final ResourceBundle bundle = 97 ResourceBundle.getBundle("com/sun/javafx/tools/packager/Bundle"); 98 99 private static final String dtFX = "dtjava.js"; 100 101 private static final String webfilesDir = "web-files"; 102 //Note: leading "." is important for IE8 103 private static final String EMBEDDED_DT = "./"+webfilesDir+"/"+dtFX; 104 105 private static final String PUBLIC_DT = "http://java.com/js/dtjava.js"; 106 107 private CreateJarParams createJarParams; 108 private DeployParams deployParams; 109 private CreateBSSParams createBssParams; 110 private File bssTmpDir; 111 private boolean isSignedJNLP; 112 113 114 private enum Filter {ALL, CLASSES_ONLY, RESOURCES}; 115 116 private ClassLoader classLoader; 117 118 private ClassLoader getClassLoader() throws PackagerException { 119 if (classLoader == null) { 120 try { 121 URL[] urls = {new URL(getJfxrtPath())}; 122 classLoader = URLClassLoader.newInstance(urls); 123 } catch (MalformedURLException ex) { 124 throw new PackagerException(ex, "ERR_CantFindRuntime"); 125 } 126 } 127 return classLoader; 128 } 129 130 public static final String MANIFEST_JAVAFX_MAIN ="JavaFX-Application-Class"; 131 132 // if set of input resources consist of SINGLE element and 133 // this element is jar file then we expect this to be request to 134 // "update" jar file 135 // Input jar file MUST be executable jar file 136 // 137 // Check if we are in "special case" scenario 138 private File jarFileToUpdate(CreateJarParams params) { 139 if (params.resources.size() == 1) { 140 PackagerResource p = params.resources.get(0); 141 File f = p.getFile(); 142 if (!f.isFile() || !f.getAbsolutePath().toLowerCase().endsWith(".jar")) { 143 return null; 144 } 145 JarFile jf = null; 146 try { 147 jf = new JarFile(f); 148 Manifest m = jf.getManifest(); //try to read manifest to validate it is jar 149 return f; 150 } catch (Exception e) { 151 //treat any excepion as "not a special case" scenario 152 Log.verbose(e); 153 } finally { 154 if (jf != null) { 155 try { 156 jf.close(); 157 } catch (IOException ex) { 158 } 159 } 160 } 161 } 162 return null; 163 } 164 165 public void packageAsJar(CreateJarParams createJarParams) throws PackagerException { 166 if (createJarParams == null) { 167 throw new IllegalArgumentException("Parameters must not be null"); 168 } 169 170 if (createJarParams.outfile == null) { 171 throw new IllegalArgumentException("Output file is not specified"); 172 } 173 174 this.createJarParams = createJarParams; 175 176 //Special case: could be request for "update jar file" 177 File jarToUpdate = jarFileToUpdate(createJarParams); 178 Manifest m = null; 179 180 if (jarToUpdate != null) { 181 JarFile jf = null; 182 try { 183 //extract data we want to preserve 184 Log.info(MessageFormat.format(bundle.getString("MSG_UpdatingJar"), jarToUpdate.getAbsolutePath())); 185 jf = new JarFile(jarToUpdate); 186 m = jf.getManifest(); 187 if (m != null) { 188 Attributes attrs = m.getMainAttributes(); 189 if (createJarParams.applicationClass == null) { 190 createJarParams.applicationClass = 191 attrs.getValue(Attributes.Name.MAIN_CLASS); 192 } 193 if (createJarParams.classpath == null) { 194 createJarParams.classpath = 195 attrs.getValue(Attributes.Name.CLASS_PATH); 196 } 197 } 198 } catch (IOException ex) { 199 throw new PackagerException( 200 ex, "ERR_FileReadFailed", jarToUpdate.getAbsolutePath()); 201 } finally { 202 if (jf != null) { 203 try { 204 jf.close(); 205 } catch (IOException ex) { 206 } 207 } 208 } 209 } 210 211 if (createJarParams.applicationClass == null) { 212 throw new IllegalArgumentException( 213 "Main application class is not specified"); 214 } 215 216 //NOTE: This should be a save-to-temp file, then rename operation 217 File applicationJar = new File(createJarParams.outdir, 218 createJarParams.outfile.endsWith(".jar") 219 ? createJarParams.outfile 220 : createJarParams.outfile + ".jar"); 221 222 if (jarToUpdate != null && 223 applicationJar.getAbsoluteFile().equals(jarToUpdate.getAbsoluteFile())) { 224 try { 225 File newInputJar = File.createTempFile("tempcopy", ".jar"); 226 Files.move(jarToUpdate.toPath(), newInputJar.toPath(), 227 StandardCopyOption.REPLACE_EXISTING); 228 jarToUpdate = newInputJar; 229 } catch (IOException ioe) { 230 throw new PackagerException( 231 ioe, "ERR_FileCopyFailed", jarToUpdate.getAbsolutePath()); 232 } 233 } 234 235 File parentFile = applicationJar.getParentFile(); 236 if (parentFile != null) { 237 parentFile.mkdirs(); 238 } 239 240 if (m == null) { 241 m = new Manifest(); 242 } 243 Attributes attr = m.getMainAttributes(); 244 attr.put(Attributes.Name.MANIFEST_VERSION, "1.0"); 245 attr.put(new Attributes.Name("Created-By"), "JavaFX Packager"); 246 247 if (createJarParams.manifestAttrs != null) { 248 for (Entry<String, String> e: createJarParams.manifestAttrs.entrySet()) { 249 attr.put(new Attributes.Name(e.getKey()), e.getValue()); 250 } 251 } 252 253 attr.put(Attributes.Name.MAIN_CLASS, createJarParams.applicationClass); 254 if (createJarParams.classpath != null) { 255 // Allow comma or semicolon as delimeter (turn them into spaces) 256 String cp = createJarParams.classpath; 257 cp = cp.replace(';', ' ').replace(',', ' '); 258 attr.put(new Attributes.Name("Class-Path"), cp); 259 } 260 261 attr.put(new Attributes.Name("JavaFX-Version"), createJarParams.fxVersion); 262 263 if (createJarParams.preloader != null) { 264 attr.put(new Attributes.Name("JavaFX-Preloader-Class"), createJarParams.preloader); 265 } 266 267 268 if (createJarParams.arguments != null) { 269 int idx = 1; 270 for (String arg: createJarParams.arguments) { 271 attr.put(new Attributes.Name("JavaFX-Argument-"+idx), 272 encodeAsBase64(arg.getBytes())); 273 idx++; 274 } 275 } 276 if (createJarParams.params != null) { 277 int idx = 1; 278 for (Param p : createJarParams.params) { 279 if (p.name != null) { //otherwise it is something weird and we skip it 280 attr.put(new Attributes.Name("JavaFX-Parameter-Name-" + idx), 281 encodeAsBase64(p.name.getBytes())); 282 if (p.value != null) { //legal, means not value specified 283 attr.put(new Attributes.Name("JavaFX-Parameter-Value-" + idx), 284 encodeAsBase64(p.value.getBytes())); 285 } 286 idx++; 287 } 288 } 289 } 290 291 292 if (createJarParams.css2bin) { 293 try { 294 bssTmpDir = File.createTempFile("bssfiles", ""); 295 } catch (IOException ex) { 296 throw new PackagerException(ex, "ERR_CreatingTempFileFailed"); 297 } 298 bssTmpDir.delete(); 299 } 300 301 if (applicationJar.exists() && !applicationJar.delete()) { 302 throw new PackagerException( 303 "ERR_CantDeleteFile", createJarParams.outfile); 304 } 305 try { 306 jar(m, createJarParams.resources, jarToUpdate, 307 new JarOutputStream(new FileOutputStream(applicationJar)), 308 Filter.ALL); 309 } catch (IOException ex) { 310 throw new PackagerException( 311 ex, "ERR_CreatingJarFailed", createJarParams.outfile); 312 } 313 314 // cleanup 315 deleteDirectory(bssTmpDir); 316 this.createJarParams = null; 317 } 318 319 private String readTextFile(File in) throws PackagerException { 320 StringBuilder sb = new StringBuilder(); 321 InputStreamReader isr = null; 322 try { 323 char[] buf = new char[16384]; 324 int len; 325 isr = new InputStreamReader(new FileInputStream(in)); 326 while ((len = isr.read(buf)) > 0) { 327 sb.append(buf, sb.length(), len); 328 } 329 } catch (IOException ex) { 330 throw new PackagerException(ex, "ERR_FileReadFailed", 331 in.getAbsolutePath()); 332 } finally { 333 if (isr != null) { 334 try { 335 isr.close(); 336 } catch (IOException ex) { 337 } 338 } 339 } 340 return sb.toString(); 341 } 342 343 private String processTemplate(String inpText, 344 Map<TemplatePlaceholders, String> templateStrings) { 345 //Core pattern matches 346 // #DT.SCRIPT# 347 // #DT.EMBED.CODE.ONLOAD# 348 // #DT.EMBED.CODE.ONLOAD(App2)# 349 String corePattern = "(#[\\w\\.\\(\\)]+#)"; 350 //This will match 351 // "/*", "//" or "<!--" with arbitrary number of spaces 352 String prefixGeneric = "[\\/\\*-<\\!]*[ \\t]*"; 353 //This will match 354 // "/*", "//" or "<!--" with arbitrary number of spaces 355 String suffixGeneric = "[ \\t]*[\\*\\/>-]*"; 356 357 //NB: result core match is group number 1 358 Pattern mainPattern = Pattern.compile( 359 prefixGeneric + corePattern + suffixGeneric); 360 361 Matcher m = mainPattern.matcher(inpText); 362 StringBuffer result = new StringBuffer(); 363 while (m.find()) { 364 String match = m.group(); 365 String coreMatch = m.group(1); 366 //have match, not validate it is not false positive ... 367 // e.g. if we matched just some spaces in prefix/suffix ... 368 boolean inComment = 369 (match.startsWith("<!--") && match.endsWith("-->")) || 370 (match.startsWith("//")) || 371 (match.startsWith("/*") && match.endsWith(" */")); 372 373 //try to find if we have match 374 String coreReplacement = null; 375 //map with rules have no template ids 376 //int p = coreMatch.indexOf("\\("); 377 //strip leading/trailing #, then split of id part 378 String parts[] = coreMatch.substring(1, coreMatch.length()-1).split("[\\(\\)]"); 379 String rulePart = parts[0]; 380 String idPart = (parts.length == 1) ? 381 //strip trailing ')' 382 null : parts[1]; 383 if (templateStrings.containsKey( 384 TemplatePlaceholders.fromString(rulePart)) 385 && (idPart == null /* it is ok for templeteId to be not null, e.g. DT.SCRIPT.CODE */ 386 || idPart.equals(deployParams.appId))) { 387 coreReplacement = templateStrings.get( 388 TemplatePlaceholders.fromString(rulePart)); 389 } 390 391 if (coreReplacement != null) { 392 if (inComment || coreMatch.length() == match.length()) { 393 m.appendReplacement(result, coreReplacement); 394 } else { // pattern matched something that is not comment 395 // Very unlikely but lets play it safe 396 int pp = match.indexOf(coreMatch); 397 String v = match.substring(0, pp) + 398 coreReplacement + 399 match.substring(pp + coreMatch.length()); 400 m.appendReplacement(result, v); 401 } 402 } 403 } 404 m.appendTail(result); 405 return result.toString(); 406 } 407 408 private static enum Mode {FX, APPLET, SwingAPP}; 409 410 public void generateDeploymentPackages(DeployParams deployParams) throws PackagerException { 411 if (deployParams == null) { 412 throw new IllegalArgumentException("Parameters must not be null."); 413 } 414 this.deployParams = deployParams; 415 boolean templateOn = !deployParams.templates.isEmpty(); 416 Map<TemplatePlaceholders, String> templateStrings = null; 417 if (templateOn) { 418 templateStrings = 419 new EnumMap<TemplatePlaceholders, String>(TemplatePlaceholders.class); 420 } 421 try { 422 //In case of FX app we will have one JNLP and one HTML 423 //In case of Swing with FX we will have 2 JNLP files and one HTML 424 String jnlp_filename_webstart = deployParams.outfile + ".jnlp"; 425 String jnlp_filename_browser 426 = deployParams.isSwingApp ? 427 (deployParams.outfile + "_browser.jnlp") : jnlp_filename_webstart; 428 String html_filename = deployParams.outfile + ".html"; 429 430 //create out dir 431 File odir = deployParams.outdir; 432 odir.mkdirs(); 433 434 if (deployParams.includeDT && !extractWebFiles()) { 435 throw new PackagerException("ERR_NoEmbeddedDT"); 436 } 437 438 ByteArrayOutputStream jnlp_bos_webstart = new ByteArrayOutputStream(); 439 ByteArrayOutputStream jnlp_bos_browser = new ByteArrayOutputStream(); 440 441 //for swing case we need to generate 2 JNLP files 442 if (deployParams.isSwingApp) { 443 PrintStream jnlp_ps = new PrintStream(jnlp_bos_webstart); 444 generateJNLP(jnlp_ps, jnlp_filename_webstart, Mode.SwingAPP); 445 jnlp_ps.close(); 446 //save JNLP 447 save(jnlp_filename_webstart, jnlp_bos_webstart.toByteArray()); 448 449 jnlp_ps = new PrintStream(jnlp_bos_browser); 450 generateJNLP(jnlp_ps, jnlp_filename_browser, Mode.APPLET); 451 jnlp_ps.close(); 452 //save JNLP 453 save(jnlp_filename_browser, jnlp_bos_browser.toByteArray()); 454 455 } else { 456 PrintStream jnlp_ps = new PrintStream(jnlp_bos_browser); 457 generateJNLP(jnlp_ps, jnlp_filename_browser, Mode.FX); 458 jnlp_ps.close(); 459 460 //save JNLP 461 save(jnlp_filename_browser, jnlp_bos_browser.toByteArray()); 462 463 jnlp_bos_webstart = jnlp_bos_browser; 464 } 465 466 //we do not need html if this is component and not main app 467 if (!deployParams.isExtension) { 468 ByteArrayOutputStream html_bos = 469 new ByteArrayOutputStream(); 470 PrintStream html_ps = new PrintStream(html_bos); 471 generateHTML(html_ps, 472 jnlp_bos_browser.toByteArray(), jnlp_filename_browser, 473 jnlp_bos_webstart.toByteArray(), jnlp_filename_webstart, 474 templateStrings, deployParams.isSwingApp); 475 html_ps.close(); 476 477 //process template file 478 if (templateOn) { 479 for (DeployParams.Template t: deployParams.templates) { 480 File out = t.out; 481 if (out == null) { 482 System.out.println( 483 "Perform inplace substitution for " + 484 t.in.getAbsolutePath()); 485 out = t.in; 486 } 487 save(out, processTemplate( 488 readTextFile(t.in), templateStrings).getBytes()); 489 } 490 } else { 491 //save HTML 492 save(html_filename, html_bos.toByteArray()); 493 } 494 } 495 496 //copy jar files 497 for (DeployResource resource: deployParams.resources) { 498 copyFiles(resource, deployParams.outdir); 499 } 500 } catch (Exception ex) { 501 throw new PackagerException(ex, "ERR_DeployFailed"); 502 } 503 504 BundleParams bp = deployParams.getBundleParams(); 505 if (bp != null) { 506 generateNativeBundles(deployParams.outdir, bp.getBundleParamsAsMap(), deployParams.getBundleType(), deployParams.getTargetFormat(), deployParams.verbose); 507 } 508 509 this.deployParams = null; 510 } 511 512 private void generateNativeBundles(File outdir, Map<String, ? super Object> params, BundleType bundleType, String bundleFormat, boolean verbose) { 513 outdir = new File(outdir, "bundles"); 514 515 if (params.containsKey(RUNTIME.getID())) { 516 RelativeFileSet runtime = RUNTIME.fetchFrom(params); 517 if (runtime == null) { 518 Log.info(bundle.getString("MSG_NoJREPackaged")); 519 } else { 520 Log.info(MessageFormat.format(bundle.getString("MSG_UserProvidedJRE"), runtime.getBaseDirectory().getAbsolutePath())); 521 if (Log.isDebug()) { 522 runtime.dump(); 523 } 524 } 525 } else { 526 Log.info(bundle.getString("MSG_UseSystemJRE")); 527 } 528 529 for (com.oracle.bundlers.Bundler bundler : Bundlers.createBundlersInstance().getBundlers(bundleType)) { 530 // if they specify the bundle format, require we match the ID 531 if (bundleFormat != null && !bundleFormat.equals(bundler.getID())) continue; 532 533 try { 534 if (bundler.validate(params)) { 535 bundler.execute(params, outdir); 536 } 537 538 } catch (UnsupportedPlatformException e) { 539 Log.debug(MessageFormat.format(bundle.getString("MSG_BundlerPlatformException"), bundler.getName())); 540 } catch (ConfigException e) { 541 Log.info(MessageFormat.format(bundle.getString("MSG_BundlerConfigException"), bundler.getName(), e.getMessage(), e.getAdvice())); 542 } catch (RuntimeException re) { 543 Log.info(MessageFormat.format(bundle.getString("MSG_BundlerRuntimeException"), bundler.getName(), re.toString())); 544 Log.debug(re); 545 } 546 } 547 } 548 549 private static void copyFiles(DeployResource resource, File outdir) throws IOException, PackagerException { 550 551 if (resource.getFile().isDirectory()) { 552 final File baseDir = resource.getBaseDir(); 553 File[] children = resource.getFile().listFiles(); 554 if (children != null) { 555 for (File file : children) { 556 copyFiles(new DeployResource(baseDir, file), outdir); 557 } 558 } 559 } else { 560 final File srcFile = resource.getFile(); 561 if (srcFile.exists() && srcFile.isFile()) { 562 //skip file copying if jar is in the same location 563 final File destFile = 564 new File(outdir, resource.getRelativePath()); 565 566 if (!srcFile.getCanonicalFile().equals( 567 destFile.getCanonicalFile())) { 568 copyFileToOutDir(new FileInputStream(srcFile), 569 destFile); 570 } else { 571 Log.verbose(MessageFormat.format(bundle.getString("MSG_JarNoSelfCopy"), resource.getRelativePath())); 572 } 573 } 574 } 575 } 576 577 public void generateBSS(CreateBSSParams params) throws PackagerException { 578 if (params == null) { 579 throw new IllegalArgumentException("Parameters must not be null."); 580 } 581 this.createBssParams = params; 582 createBinaryCss(createBssParams.resources, createBssParams.outdir); 583 this.createBssParams = null; 584 } 585 586 public void signJar(SignJarParams params) throws PackagerException { 587 try { 588 JarSignature signature = retrieveSignature(params); 589 590 for (PackagerResource pr: params.resources) { 591 signFile(pr, signature, params.outdir, params.verbose); 592 } 593 594 } catch (Exception ex) { 595 Log.verbose(ex); 596 throw new PackagerException("ERR_SignFailed", ex); 597 } 598 599 } 600 601 602 private JarSignature retrieveSignature(SignJarParams params) throws KeyStoreException, 603 NoSuchAlgorithmException, UnrecoverableKeyException, IOException, 604 CertificateException, InvalidKeyException { 605 if (params.keyPass == null) { 606 params.keyPass = params.storePass; 607 } 608 609 if (params.keyStore == null) { 610 throw new IOException("No keystore specified"); 611 } 612 613 if (params.storePass == null) { 614 throw new IOException("No store password specified"); 615 } 616 617 if (params.storeType == null) { 618 throw new IOException("No store type is specified"); 619 } 620 621 KeyStore store = KeyStore.getInstance(params.storeType); 622 store.load(new FileInputStream(params.keyStore), params.storePass.toCharArray()); 623 624 Certificate[] chain = store.getCertificateChain(params.alias); 625 X509Certificate certChain[] = new X509Certificate[chain.length]; 626 for (int i=0; i<chain.length; i++) { 627 certChain[i] = (X509Certificate) chain[i]; 628 } 629 630 PrivateKey privateKey = (PrivateKey) 631 store.getKey(params.alias, params.keyPass.toCharArray()); 632 633 return JarSignature.create(privateKey, certChain); 634 } 635 636 private void signFile( 637 PackagerResource pr, JarSignature signature, File outdir, boolean verbose) 638 throws NoSuchAlgorithmException, IOException, SignatureException { 639 if (pr.getFile().isDirectory()) { 640 File[] children = pr.getFile().listFiles(); 641 if (children != null) { 642 for (File innerFile : children) { 643 signFile(new PackagerResource( 644 pr.getBaseDir(), innerFile), signature, outdir, verbose); 645 } 646 } 647 } else { 648 File jar = pr.getFile(); 649 File parent = jar.getParentFile(); 650 String name = "bsigned_" + jar.getName(); 651 File signedJar = new File(parent, name); 652 653 System.out.println("Signing (BLOB) " + jar.getPath()); 654 655 signAsBLOB(jar, signedJar, signature); 656 657 File destJar; 658 if (outdir != null) { 659 destJar = new File(outdir, pr.getRelativePath()); 660 } else { 661 // in-place 662 jar.delete(); 663 destJar = jar; 664 } 665 destJar.delete(); 666 destJar.getParentFile().mkdirs(); 667 signedJar.renameTo(destJar); 668 if (verbose) { 669 System.out.println("Signed as " + destJar.getPath()); 670 } 671 } 672 } 673 674 private void signAsBLOB(final File jar, File signedJar, JarSignature signature) 675 throws IOException, NoSuchAlgorithmException, SignatureException 676 { 677 if (signature == null) { 678 throw new IllegalStateException("Should retrieve signature first"); 679 } 680 681 InputStreamSource in = new InputStreamSource() { 682 @Override 683 public InputStream getInputStream() throws IOException { 684 return new FileInputStream(jar); 685 } 686 }; 687 if (!signedJar.isFile()) { 688 signedJar.createNewFile(); 689 } 690 FileOutputStream fos = new FileOutputStream(signedJar); 691 signature.signJarAsBLOB(in, new ZipOutputStream(fos)); 692 } 693 694 695 696 public void makeAll(MakeAllParams makeAllParams) throws PackagerException { 697 final String exe = 698 System.getProperty("os.name").startsWith("Windows") ? ".exe" : ""; 699 String jHome = System.getenv("JAVA_HOME"); 700 if (jHome == null) { 701 jHome = System.getProperty("java.home"); 702 } 703 if (jHome == null) { 704 throw new PackagerException("ERR_MissingJavaHome"); 705 } 706 707 final File javac = new File(new File(jHome), "bin/javac" + exe); 708 709 String jfxHome = System.getenv("JAVAFX_HOME"); 710 if (jfxHome == null) { 711 jfxHome = System.getProperty("javafx.home"); 712 } 713 if (jfxHome == null) { 714 throw new PackagerException("ERR_MissingJavaFxHome"); 715 } 716 717 final String srcDirName = "src"; 718 final String compiledDirName = "compiled"; 719 final String distDirName = "dist"; 720 final String outfileName = "dist"; 721 final String jarName = outfileName + ".jar"; 722 723 final File distDir = new File(distDirName); 724 725 final File compiledDir = new File(compiledDirName); 726 compiledDir.mkdir(); 727 728 try { 729 final File tmpFile = File.createTempFile("javac", "sources", new File(".")); 730 tmpFile.deleteOnExit(); 731 final FileWriter sources = new FileWriter(tmpFile); 732 try { 733 scanAndCopy(new PackagerResource(new File(srcDirName), "."), sources, compiledDir); 734 } finally { 735 sources.close(); 736 } 737 String classpath = jfxHome + "/../rt/lib/ext/jfxrt.jar"; 738 if (makeAllParams.classpath != null) { 739 classpath += File.pathSeparator + makeAllParams.classpath; 740 } 741 if (makeAllParams.verbose) { 742 System.out.println("Executing javac:"); 743 System.out.printf("%s %s %s %s %s %s%n", 744 javac.getAbsolutePath(), 745 "-d", compiledDirName, 746 "-cp", classpath, 747 "@" + tmpFile.getAbsolutePath()); 748 } 749 int ret = execute( 750 javac.getAbsolutePath(), 751 "-d", compiledDirName, 752 "-cp", classpath, 753 "@" + tmpFile.getAbsolutePath()); 754 if (ret != 0) { 755 throw new PackagerException("ERR_JavacFailed", Integer.toString(ret)); 756 } 757 } catch (PackagerException e) { 758 throw e; 759 } catch (Exception e) { 760 throw new PackagerException(e, "ERR_MakeAllJavacFailed"); 761 } 762 763 CreateJarParams cjp = new CreateJarParams(); 764 cjp.applicationClass = makeAllParams.appClass; 765 cjp.preloader = makeAllParams.preloader; 766 cjp.classpath = makeAllParams.classpath; 767 cjp.css2bin = false; 768 cjp.outdir = distDir; 769 cjp.outfile = jarName; 770 cjp.addResource(compiledDir, "."); 771 772 packageAsJar(cjp); 773 774 DeployParams dp = new DeployParams(); 775 dp.applicationClass = makeAllParams.appClass; 776 dp.appName = makeAllParams.appName; 777 dp.description = "Application description"; 778 dp.height = makeAllParams.height; 779 dp.width = makeAllParams.width; 780 dp.vendor = "Application vendor"; 781 dp.outdir = distDir; 782 dp.outfile = outfileName; 783 dp.addResource(distDir, jarName); 784 dp.setBundleType(BundleType.ALL); 785 786 generateDeploymentPackages(dp); 787 788 deleteDirectory(compiledDir); 789 } 790 791 private static int execute(Object ... args) throws IOException, InterruptedException { 792 final ArrayList<String> argsList = new ArrayList(); 793 for (Object a : args) { 794 if (a instanceof List) { 795 argsList.addAll((List)a); 796 } else if (a instanceof String) { 797 argsList.add((String)a); 798 } 799 } 800 final Process p = Runtime.getRuntime().exec(argsList.toArray(new String[argsList.size()])); 801 final BufferedReader in = new BufferedReader(new InputStreamReader(p.getInputStream())); 802 Thread t = new Thread(new Runnable() { 803 @Override 804 public void run() { 805 try { 806 String line; 807 while ((line = in.readLine()) != null) { 808 System.out.println(line); 809 } 810 } catch (IOException ioe) { 811 Log.verbose(ioe); 812 } 813 } 814 }); 815 t.setDaemon(true); 816 t.start(); 817 final BufferedReader err = new BufferedReader(new InputStreamReader(p.getErrorStream())); 818 t = new Thread(new Runnable() { 819 @Override 820 public void run() { 821 try { 822 String line; 823 while ((line = err.readLine()) != null) { 824 System.err.println(line); 825 } 826 } catch (IOException ioe) { 827 Log.verbose(ioe); 828 } 829 } 830 }); 831 t.setDaemon(true); 832 t.start(); 833 return p.waitFor(); 834 } 835 836 private static void scanAndCopy(PackagerResource dir, Writer out, File outdir) throws PackagerException { 837 if (!dir.getFile().exists()) { 838 throw new PackagerException("ERR_MissingDirectory", dir.getFile().getName()); 839 } 840 if ((dir.getFile().listFiles() == null) || (dir.getFile().listFiles().length == 0)) { 841 throw new PackagerException("ERR_EmptySourceDirectory", dir.getFile().getName()); 842 } 843 try { 844 for (File f : dir.getFile().listFiles()) { 845 if (f.isDirectory()) { 846 scanAndCopy(new PackagerResource(dir.getBaseDir(), f), out, outdir); 847 } else if (f.getName().endsWith(".java")) { 848 out.write('\'' + f.getAbsolutePath().replace('\\', '/') + "\'\n"); 849 } else { 850 copyFileToOutDir(new FileInputStream(f), 851 new File(outdir.getPath() + File.separator 852 + dir.getRelativePath() + File.separator 853 + f.getName())); 854 } 855 } 856 } catch (IOException ex) { 857 throw new PackagerException("ERR_FileCopyFailed", dir.getFile().getName()); 858 } 859 } 860 861 //return null if args are default 862 private String getJvmArguments(boolean includeProperties) { 863 StringBuilder sb = new StringBuilder(); 864 for(String v: deployParams.jvmargs) { 865 sb.append(v); //may need to escape if parameter has spaces 866 sb.append(" "); 867 } 868 if (includeProperties) { 869 for(String k: deployParams.properties.keySet()) { 870 sb.append("-D"); 871 sb.append(k); 872 sb.append("="); 873 sb.append(deployParams.properties.get(k)); //may need to escape if value has spaces 874 sb.append(" "); 875 } 876 } 877 if (sb.length() > 0) { 878 return sb.toString(); 879 } 880 return null; 881 } 882 883 private void generateJNLP(PrintStream out, String jnlp_filename, Mode m) 884 throws IOException, CertificateEncodingException 885 { 886 out.println("<?xml version=\"1.0\" encoding=\"utf-8\"?>"); 887 //have to use "old" spec version or old javaws will fail 888 // with "unknown" version exception ... 889 out.println("<jnlp spec=\"1.0\" xmlns:jfx=\"http://javafx.com\"" + 890 (deployParams.codebase != null ? 891 " codebase=\"" + deployParams.codebase + "\"" : "") + 892 " href=\""+jnlp_filename+"\">"); 893 out.println(" <information>"); 894 out.println(" <title>" + 895 ((deployParams.title != null) 896 ? deployParams.title : "Sample JavaFX Application") + 897 "</title>"); 898 out.println(" <vendor>" + 899 ((deployParams.vendor != null) 900 ? deployParams.vendor : "Unknown vendor") + 901 "</vendor>"); 902 out.println(" <description>" + 903 ((deployParams.description != null) 904 ? deployParams.description : "Sample JavaFX 2.0 application.") + 905 "</description>"); 906 for (Iterator<Icon> it = deployParams.icons.iterator(); it.hasNext();) { 907 DeployParams.Icon i = it.next(); 908 if (i.mode == DeployParams.RunMode.WEBSTART || 909 i.mode == DeployParams.RunMode.ALL) { 910 out.println(" <icon href=\"" + i.href+"\" " + 911 ((i.kind != null) ? " kind=\"" + i.kind + "\"" : "") + 912 ((i.width != DeployParams.Icon.UNDEFINED) ? 913 " width=\"" + i.width + "\"" : "") + 914 ((i.height != DeployParams.Icon.UNDEFINED) ? 915 " height=\"" + i.height + "\"" : "") + 916 ((i.depth != DeployParams.Icon.UNDEFINED) ? 917 " depth=\"" + i.depth + "\"" : "") + 918 "/>"); 919 } 920 } 921 922 if (deployParams.offlineAllowed && !deployParams.isExtension) { 923 out.println(" <offline-allowed/>"); 924 } 925 out.println(" </information>"); 926 927 if (!deployParams.isExtension) { 928 //FX is platfrom specific (soon will be available for Mac and Linux too) 929 out.println(" <resources>"); 930 out.println(" <jfx:javafx-runtime version=\"" + 931 deployParams.fxPlatform + 932 "\" href=\"http://javadl.sun.com/webapps/download/GetFile/javafx-latest/windows-i586/javafx2.jnlp\"/>"); 933 out.println(" </resources>"); 934 } 935 936 boolean needToCloseResourceTag = false; 937 //jre is available for all platforms 938 if (!deployParams.isExtension) { 939 out.println(" <resources>"); 940 needToCloseResourceTag = true; 941 942 String vmargs = getJvmArguments(false); 943 vmargs = (vmargs == null) ? "" : " java-vm-args=\""+vmargs+"\" "; 944 out.println(" <j2se version=\"" + deployParams.jrePlatform + "\"" + 945 vmargs + " href=\"http://java.sun.com/products/autodl/j2se\"/>"); 946 for (String k : deployParams.properties.keySet()) { 947 out.println(" <property name=\"" + k + 948 "\" value=\"" + deployParams.properties.get(k) + "\"/>"); 949 } 950 } 951 String currentOS = null, currentArch = null; 952 //NOTE: This should sort the list by os+arch; it will reduce the number of resource tags 953 String pendingPrint = null; 954 for (DeployResource resource: deployParams.resources) { 955 //if not same OS or arch then open new resources element 956 if (!needToCloseResourceTag || 957 ((currentOS == null && resource.getOs() != null) || 958 currentOS != null && !currentOS.equals(resource.getOs())) || 959 ((currentArch == null && resource.getArch() != null) || 960 currentArch != null && !currentArch.equals(resource.getArch()))) { 961 962 //we do not print right a way as it may be empty block 963 // Not all resources make sense for JNLP (e.g. data or license) 964 if (needToCloseResourceTag) { 965 pendingPrint = " </resources>\n"; 966 } else { 967 pendingPrint = ""; 968 } 969 currentOS = resource.getOs(); 970 currentArch = resource.getArch(); 971 pendingPrint += " <resources" + 972 ((currentOS != null) ? " os=\"" + currentOS + "\"" : "") + 973 ((currentArch != null) ? " arch=\""+currentArch+"\"" : "") + 974 ">\n"; 975 } 976 final File srcFile = resource.getFile(); 977 if (srcFile.exists() && srcFile.isFile()) { 978 final String relativePath = resource.getRelativePath(); 979 DeployResource.Type type = resource.getType(); 980 switch (type) { 981 case jar: 982 if (pendingPrint != null) { 983 out.print(pendingPrint); 984 pendingPrint = null; 985 needToCloseResourceTag = true; 986 } 987 out.print(" <jar href=\"" + relativePath + "\" size=\"" 988 + srcFile.length() + "\""); 989 out.print(" download=\"" + resource.getMode() + "\" "); 990 out.println("/>"); 991 break; 992 case jnlp: 993 if (pendingPrint != null) { 994 out.print(pendingPrint); 995 pendingPrint = null; 996 needToCloseResourceTag = true; 997 } 998 out.println(" <extension href=\"" + relativePath + "\"/>"); 999 break; 1000 case nativelib: 1001 if (pendingPrint != null) { 1002 out.print(pendingPrint); 1003 needToCloseResourceTag = true; 1004 pendingPrint = null; 1005 } 1006 out.println(" <nativelib href=\"" + relativePath + "\"/>"); 1007 break; 1008 } 1009 } 1010 } 1011 if (needToCloseResourceTag) { 1012 out.println(" </resources>"); 1013 } 1014 1015 if (deployParams.allPermissions) { 1016 out.println("<security>"); 1017 out.println(" <all-permissions/>"); 1018 processEmbeddedCertificates(out); 1019 out.println("</security>"); 1020 } 1021 1022 if (deployParams.needShortcut) { 1023 out.println(" <shortcut><desktop/></shortcut>"); 1024 1025 // //TODO: Add support for a more sophisticated shortcut tag. 1026 // <shortcut/> // install no shortcuts, and do not consider "installed" 1027 // <shortcut installed="true"/> // install no shortcuts, but consider "installed" 1028 // <shortcut installed="false"><desktop/></shortcut> // install desktop shortcut, but do not consider the app "installed" 1029 // <shortcut installed="true"><menu/></shortcut> // install menu shortcut, and consider app "installed" 1030 } 1031 1032 if (!deployParams.isExtension) { 1033 if (m == Mode.APPLET) { 1034 out.print(" <applet-desc width=\"" + deployParams.width 1035 + "\" height=\"" + deployParams.height + "\""); 1036 1037 out.print(" main-class=\"" + deployParams.applicationClass + "\" "); 1038 out.println(" name=\"" + deployParams.appName + "\" >"); 1039 if (deployParams.params != null) { 1040 for (Param p : deployParams.params) { 1041 out.println(" <param name=\"" + p.name + "\"" 1042 + (p.value != null 1043 ? (" value=\"" + p.value + "\"") : "") 1044 + "/>"); 1045 } 1046 } 1047 out.println(" </applet-desc>"); 1048 } else if (m == Mode.SwingAPP) { 1049 out.print(" <application-desc main-class=\"" + deployParams.applicationClass + "\" "); 1050 out.println(" name=\"" + deployParams.appName + "\" >"); 1051 if (deployParams.arguments != null) { 1052 for (String a : deployParams.arguments) { 1053 out.println(" <argument>" + a + "</argument>"); 1054 } 1055 } 1056 out.println(" </application-desc>"); 1057 } else { //JavaFX application 1058 //embed fallback application 1059 if (deployParams.fallbackApp != null) { 1060 out.print(" <applet-desc width=\"" + deployParams.width 1061 + "\" height=\"" + deployParams.height + "\""); 1062 1063 out.print(" main-class=\"" + deployParams.fallbackApp + "\" "); 1064 out.println(" name=\"" + deployParams.appName + "\" >"); 1065 out.println(" <param name=\"requiredFXVersion\" value=\"" 1066 + deployParams.fxPlatform + "\"/>"); 1067 out.println(" </applet-desc>"); 1068 } 1069 1070 //javafx application descriptor 1071 out.print(" <jfx:javafx-desc width=\"" + deployParams.width 1072 + "\" height=\"" + deployParams.height + "\""); 1073 1074 out.print(" main-class=\"" + deployParams.applicationClass + "\" "); 1075 out.print(" name=\"" + deployParams.appName + "\" "); 1076 if (deployParams.preloader != null) { 1077 out.print(" preloader-class=\"" + deployParams.preloader + "\""); 1078 } 1079 if (((deployParams.params == null) || deployParams.params.isEmpty()) 1080 && (deployParams.arguments == null || deployParams.arguments.isEmpty())) { 1081 out.println("/>"); 1082 } else { 1083 out.println(">"); 1084 if (deployParams.params != null) { 1085 for (Param p : deployParams.params) { 1086 out.println(" <fx:param name=\"" + p.name + "\"" 1087 + (p.value != null 1088 ? (" value=\"" + p.value + "\"") : "") 1089 + "/>"); 1090 } 1091 } 1092 if (deployParams.arguments != null) { 1093 for (String a : deployParams.arguments) { 1094 out.println(" <fx:argument>" + a + "</fx:argument>"); 1095 } 1096 } 1097 out.println(" </jfx:javafx-desc>"); 1098 } 1099 } 1100 } else { 1101 out.println("<component-desc/>"); 1102 } 1103 1104 out.println(" <update check=\"" + deployParams.updateMode + "\"/>"); 1105 out.println("</jnlp>"); 1106 } 1107 1108 1109 1110 private void addToList(List<String> l, String name, String value, boolean isString) { 1111 String s = isString ? "'" : ""; 1112 String v = name +" : " + s + value + s; 1113 l.add(v); 1114 } 1115 1116 private String listToString(List<String> lst, String offset) { 1117 StringBuilder b = new StringBuilder(); 1118 if (lst == null || lst.isEmpty()) { 1119 return offset + "{}"; 1120 } 1121 1122 b.append(offset).append("{\n"); 1123 boolean first = true; 1124 for (String s : lst) { 1125 if (!first) { 1126 b.append(",\n"); 1127 } 1128 first = false; 1129 b.append(offset).append(" "); 1130 b.append(s); 1131 } 1132 b.append("\n"); 1133 b.append(offset).append("}"); 1134 return b.toString(); 1135 } 1136 1137 private String encodeAsBase64(byte inp[]) { 1138 BASE64Encoder encoder = new BASE64Encoder(); 1139 return encoder.encode(inp); 1140 } 1141 1142 private void generateHTML(PrintStream out, 1143 byte[] jnlp_bytes_browser, String jnlpfile_browser, 1144 byte[] jnlp_bytes_webstart, String jnlpfile_webstart, 1145 Map<TemplatePlaceholders, String> templateStrings, 1146 boolean swingMode) { 1147 String poff = " "; 1148 String poff2 = poff + poff; 1149 String poff3 = poff2 + poff; 1150 1151 StringBuilder out_embed_dynamic = new StringBuilder(); 1152 StringBuilder out_embed_onload = new StringBuilder(); 1153 StringBuilder out_launch_code = new StringBuilder(); 1154 1155 String appletParams = getAppletParameters(); 1156 String jnlp_content_browser = null; 1157 String jnlp_content_webstart = null; 1158 1159 if (deployParams.embedJNLP) { 1160 jnlp_content_browser = 1161 encodeAsBase64(jnlp_bytes_browser).replaceAll("\\r|\\n", ""); 1162 jnlp_content_webstart = 1163 encodeAsBase64(jnlp_bytes_webstart).replaceAll("\\r|\\n", ""); 1164 } 1165 1166 out.println("<html><head>"); 1167 String dtURL = deployParams.includeDT ? EMBEDDED_DT : PUBLIC_DT; 1168 String includeDtString = "<SCRIPT src=\"" + dtURL + "\"></SCRIPT>"; 1169 if (templateStrings != null) { 1170 templateStrings.put(TemplatePlaceholders.SCRIPT_URL, dtURL); 1171 templateStrings.put(TemplatePlaceholders.SCRIPT_CODE, includeDtString); 1172 } 1173 out.println(" " + includeDtString); 1174 1175 String webstartError = "System is not setup to launch JavaFX applications. " + 1176 "Make sure that you have a recent Java runtime, then install JavaFX Runtime 2.0 "+ 1177 "and check that JavaFX is enabled in the Java Control Panel."; 1178 1179 List w_app = new ArrayList(); 1180 List w_platform = new ArrayList(); 1181 List w_callback = new ArrayList(); 1182 1183 addToList(w_app, "url", jnlpfile_webstart, true); 1184 if (jnlp_content_webstart != null) { 1185 addToList(w_app, "jnlp_content", jnlp_content_webstart, true); 1186 } 1187 1188 addToList(w_platform, "javafx", deployParams.fxPlatform, true); 1189 String vmargs = getJvmArguments(true); 1190 if (vmargs != null) { 1191 addToList(w_platform, "jvmargs", vmargs, true); 1192 } 1193 1194 if (!"".equals(appletParams)) { 1195 addToList(w_app, "params", "{"+appletParams+"}", false); 1196 } 1197 1198 if ((deployParams.callbacks != null) && !deployParams.callbacks.isEmpty()) { 1199 for (JSCallback cb: deployParams.callbacks) { 1200 addToList(w_callback, cb.getName(), cb.getCmd(), false); 1201 } 1202 } 1203 1204 //prepare content of launchApp function 1205 out_launch_code.append(poff2).append("dtjava.launch("); 1206 out_launch_code.append(listToString(w_app, poff3)).append(",\n"); 1207 out_launch_code.append(listToString(w_platform, poff3)).append(",\n"); 1208 out_launch_code.append(listToString(w_callback, poff3)).append("\n"); 1209 out_launch_code.append(poff2).append(");\n"); 1210 1211 out.println("<script>"); 1212 out.println(poff + "function launchApplication(jnlpfile) {"); 1213 out.print(out_launch_code.toString()); 1214 out.println(poff2 + "return false;"); 1215 out.println(poff + "}"); 1216 out.println("</script>"); 1217 1218 if (templateStrings != null) { 1219 templateStrings.put(TemplatePlaceholders.LAUNCH_CODE, 1220 out_launch_code.toString()); 1221 } 1222 1223 //applet deployment 1224 String appId = deployParams.appId; //if null then it will be autogenerated 1225 String placeholder = deployParams.placeholder; 1226 if (placeholder == null) { //placeholder can not be null 1227 placeholder = "'javafx-app-placeholder'"; 1228 } 1229 1230 //prepare content of embedApp() 1231 List p_app = new ArrayList(); 1232 List p_platform = new ArrayList(); 1233 List p_callback = new ArrayList(); 1234 1235 if (appId != null) { 1236 addToList(p_app, "id", appId, true); 1237 } 1238 if (deployParams.isSwingApp) { 1239 addToList(p_app, "toolkit", "swing", true); 1240 } 1241 addToList(p_app, "url", jnlpfile_browser, true); 1242 addToList(p_app, "placeholder", placeholder, false); 1243 if (deployParams.embeddedWidth != null && deployParams.embeddedHeight != null) { 1244 addToList(p_app, "width", ""+deployParams.embeddedWidth, true); 1245 addToList(p_app, "height", ""+deployParams.embeddedHeight, true); 1246 } else { 1247 addToList(p_app, "width", ""+deployParams.width, false); 1248 addToList(p_app, "height", ""+deployParams.height, false); 1249 } 1250 if (jnlp_content_browser != null) { 1251 addToList(p_app, "jnlp_content", jnlp_content_browser, true); 1252 } 1253 1254 addToList(p_platform, "javafx", deployParams.fxPlatform, true); 1255 if (vmargs != null) { 1256 addToList(p_platform, "jvmargs", vmargs, true); 1257 } 1258 1259 if ((deployParams.callbacks != null) && !deployParams.callbacks.isEmpty()) { 1260 for (JSCallback cb: deployParams.callbacks) { 1261 addToList(p_callback, cb.getName(), cb.getCmd(), false); 1262 } 1263 } 1264 1265 if (!"".equals(appletParams)) { 1266 addToList(p_app, "params", "{"+appletParams+"}", false); 1267 } 1268 1269 if (swingMode) { 1270 //Splash will not work in SwingMode 1271 //Unless user overwrites onGetSplash handler (and that means he handles splash on his own) 1272 // we will reset splash function to be "none" 1273 boolean needOnGetSplashImpl = true; 1274 if (deployParams.callbacks != null) { 1275 for (JSCallback c: deployParams.callbacks) { 1276 if ("onGetSplash".equals(c.getName())) { 1277 needOnGetSplashImpl = false; 1278 } 1279 } 1280 } 1281 1282 if (needOnGetSplashImpl) { 1283 addToList(p_callback, "onGetSplash", "function() {}", false); 1284 } 1285 } 1286 1287 out_embed_dynamic.append("dtjava.embed(\n"); 1288 out_embed_dynamic.append(listToString(p_app, poff3)).append(",\n"); 1289 out_embed_dynamic.append(listToString(p_platform, poff3)).append(",\n"); 1290 out_embed_dynamic.append(listToString(p_callback, poff3)).append("\n"); 1291 1292 out_embed_dynamic.append(poff2).append(");\n"); 1293 1294 //now wrap content with function 1295 String embedFuncName = "javafxEmbed" + 1296 ((deployParams.appId != null) ? 1297 "_"+deployParams.appId : ""); 1298 out_embed_onload.append("\n<script>\n"); 1299 out_embed_onload.append(poff).append("function ").append(embedFuncName).append("() {\n"); 1300 out_embed_onload.append(poff2); 1301 out_embed_onload.append(out_embed_dynamic); 1302 out_embed_onload.append(poff).append("}\n"); 1303 1304 out_embed_onload.append(poff).append( 1305 "<!-- Embed FX application into web page once page is loaded -->\n"); 1306 out_embed_onload.append(poff).append("dtjava.addOnloadCallback(").append(embedFuncName).append( 1307 ");\n"); 1308 out_embed_onload.append("</script>\n"); 1309 1310 if (templateStrings != null) { 1311 templateStrings.put( 1312 TemplatePlaceholders.EMBED_CODE_ONLOAD, 1313 out_embed_onload.toString()); 1314 templateStrings.put( 1315 TemplatePlaceholders.EMBED_CODE_DYNAMIC, 1316 out_embed_dynamic.toString()); 1317 } 1318 1319 out.println(out_embed_onload.toString()); 1320 1321 out.println("</head><body>"); 1322 out.println("<h2>Test page for <b>"+deployParams.appName+"</b></h2>"); 1323 String launchString = "return launchApplication('" + jnlpfile_webstart + "');"; 1324 out.println(" <b>Webstart:</b> <a href='" + jnlpfile_webstart + 1325 "' onclick=\"" + launchString + "\">" 1326 + "click to launch this app as webstart</a><br><hr><br>"); 1327 out.println(""); 1328 out.println(" <!-- Applet will be inserted here -->"); 1329 //placeholder is wrapped with single quotes already 1330 out.println(" <div id="+placeholder+"></div>"); 1331 out.println("</body></html>"); 1332 } 1333 1334 private void save(String fname, byte[] content) throws IOException { 1335 File odir = deployParams.outdir; 1336 save(new File(odir, fname), content); 1337 } 1338 1339 private void save(File f, byte[] content) throws IOException { 1340 if (f.exists()) { 1341 f.delete(); 1342 } 1343 FileOutputStream fos = new FileOutputStream(f); 1344 fos.write(content); 1345 fos.close(); 1346 } 1347 1348 private static void copyFileToOutDir( 1349 InputStream is, File fout) throws PackagerException { 1350 1351 OutputStream out = null; 1352 final File outDir = fout.getParentFile(); 1353 try { 1354 if (!outDir.exists() && !outDir.mkdirs()) { 1355 throw new PackagerException("ERR_CreatingDirFailed", outDir.getPath()); 1356 } 1357 1358 out = new FileOutputStream(fout); 1359 byte[] buf = new byte[16384]; 1360 int len; 1361 while ((len = is.read(buf)) > 0) { 1362 out.write(buf, 0, len); 1363 } 1364 } catch (IOException ex) { 1365 throw new PackagerException(ex, "ERR_FileCopyFailed", outDir.getPath()); 1366 } finally { 1367 try { 1368 is.close(); 1369 } catch (IOException ex) { 1370 } 1371 1372 if (out != null) { 1373 try { 1374 out.close(); 1375 } catch (IOException ex) { 1376 } 1377 } 1378 } 1379 } 1380 1381 1382 private String getAppletParameters() { 1383 String result = ""; 1384 if (deployParams.htmlParams != null) { 1385 for (HtmlParam p: deployParams.htmlParams) { 1386 if (!result.isEmpty()) { 1387 result += ", "; 1388 } 1389 String escape = p.needEscape ? "\"" : ""; 1390 result += "\""+p.name+"\": " + escape + p.value + escape; 1391 } 1392 } 1393 return result; 1394 } 1395 1396 private void jar( 1397 Manifest manifest, List<PackagerResource> files, 1398 File importJarFile, JarOutputStream jar, Filter filter) 1399 throws IOException, PackagerException { 1400 try { 1401 jar.putNextEntry(new ZipEntry("META-INF/")); 1402 jar.closeEntry(); 1403 jar.putNextEntry(new ZipEntry(JarFile.MANIFEST_NAME)); 1404 manifest.write(jar); 1405 jar.closeEntry(); 1406 1407 alreadyAddedEntries.add("META-INF/"); 1408 if (importJarFile != null) { //updating jar file 1409 copyFromOtherJar(jar, importJarFile); 1410 } else { //normal situation 1411 for (PackagerResource pr : files) { 1412 jar(pr.getFile(), jar, filter, 1413 pr.getBaseDir().getAbsolutePath().length() + 1); 1414 } 1415 } 1416 } finally { 1417 jar.close(); 1418 alreadyAddedEntries.clear(); 1419 } 1420 } 1421 1422 private Set<String> alreadyAddedEntries = new HashSet<String>(); 1423 private void createParentEntries(String relativePath, JarOutputStream jar) throws IOException { 1424 String[] pathComponents = relativePath.split("/"); 1425 StringBuilder pathSB = new StringBuilder(); 1426 // iterating over directories only, the last component is the file 1427 // or will be created next time. 1428 for (int i = 0; i < pathComponents.length - 1; i++) { 1429 pathSB.append(pathComponents[i]).append("/"); 1430 if (!alreadyAddedEntries.contains(pathSB.toString())) { 1431 jar.putNextEntry(new ZipEntry(pathSB.toString())); 1432 jar.closeEntry(); 1433 } 1434 alreadyAddedEntries.add(pathSB.toString()); 1435 } 1436 } 1437 1438 //add everything but manifest from given jar file 1439 private void copyFromOtherJar(JarOutputStream jar, File inputFile) throws IOException { 1440 JarFile inJar = new JarFile(inputFile); 1441 1442 Enumeration<JarEntry> all = inJar.entries(); 1443 while (all.hasMoreElements()) { 1444 JarEntry je = all.nextElement(); 1445 1446 //skip manifest or root manifest dir entry (can not add duplicate) 1447 if ("META-INF/MANIFEST.MF".equals(je.getName().toUpperCase()) 1448 || "META-INF/".equals(je.getName().toUpperCase())) { 1449 continue; 1450 } 1451 1452 InputStream in = inJar.getInputStream(je); 1453 jar.putNextEntry(new JarEntry(je.getName())); 1454 1455 byte b[] = new byte[65000]; 1456 int i; 1457 try { 1458 while ((i = in.read(b)) > 0) { 1459 jar.write(b, 0, i); 1460 } 1461 } finally { 1462 in.close(); 1463 } 1464 1465 jar.closeEntry(); 1466 } 1467 } 1468 1469 private void jar(File f, JarOutputStream jar, Filter filter, int cut) 1470 throws IOException, PackagerException { 1471 if (!f.exists()) { 1472 throw new FileNotFoundException("Input folder does not exist [" 1473 +f.getAbsolutePath()+"]"); 1474 } 1475 1476 if (f.isDirectory()) { 1477 File[] children = f.listFiles(); 1478 if (children != null) { 1479 for (File innerFile : children) { 1480 jar(innerFile, jar, filter, cut); 1481 } 1482 } 1483 } else if (filter == Filter.ALL 1484 || (filter == Filter.CLASSES_ONLY && f.getName().endsWith(".class")) 1485 || (filter == Filter.RESOURCES && isResource(f.getAbsolutePath()))) { 1486 final String absPath = f.getAbsolutePath(); 1487 if (absPath.endsWith("META-INF\\MANIFEST.MF") 1488 || absPath.endsWith("META-INF/MANIFEST.MF")) { 1489 return; 1490 } 1491 createParentEntries(absPath.substring(cut).replace('\\', '/'), jar); 1492 if (createJarParams.css2bin && f.getName().endsWith(".css")) { 1493 // generate bss file into temporary directory 1494 int startOfExt = absPath.lastIndexOf(".") + 1; 1495 String bssFileName = absPath 1496 .substring(cut, startOfExt) 1497 .concat("bss"); 1498 1499 File bssFile = new File(bssTmpDir, bssFileName); 1500 bssFile.getParentFile().mkdirs(); 1501 1502 createBinaryCss(absPath, bssFile.getAbsolutePath()); 1503 jar.putNextEntry(new ZipEntry(bssFileName.replace('\\', '/'))); 1504 f = bssFile; 1505 } else { 1506 jar.putNextEntry(new ZipEntry(absPath.substring(cut).replace('\\', '/'))); 1507 } 1508 1509 byte b[] = new byte[65000]; 1510 int i; 1511 FileInputStream in = new FileInputStream(f); 1512 try { 1513 while ((i = in.read(b)) > 0) { 1514 jar.write(b, 0, i); 1515 } 1516 } finally { 1517 in.close(); 1518 } 1519 jar.closeEntry(); 1520 } 1521 } 1522 1523 1524 private void createBinaryCss(List<PackagerResource> cssResources, File outdir) 1525 throws PackagerException { 1526 for (PackagerResource cssRes: cssResources) { 1527 String relPath = cssRes.getRelativePath(); 1528 createBinaryCss(cssRes.getFile(), outdir, relPath); 1529 } 1530 } 1531 1532 private void createBinaryCss(File f, File outdir, String relPath) 1533 throws PackagerException { 1534 if (f.isDirectory()) { 1535 File[] children = f.listFiles(); 1536 if (children != null) { 1537 for (File innerFile : children) { 1538 createBinaryCss(innerFile, outdir, relPath + '/' + innerFile.getName()); 1539 } 1540 } 1541 } else if (f.getName().endsWith(".css")) { 1542 String cssFileName = f.getAbsolutePath(); 1543 String bssFileName = new File(outdir.getAbsolutePath(), 1544 replaceExtensionByBSS(relPath)) 1545 .getAbsolutePath(); 1546 createBinaryCss(cssFileName, bssFileName); 1547 } 1548 } 1549 1550 // Returns path to jfxrt.jar relatively to jar containing PackagerLib.class 1551 private String getJfxrtPath() throws PackagerException { 1552 String theClassFile = "PackagerLib.class"; 1553 Class theClass = PackagerLib.class; 1554 String classUrl = theClass.getResource(theClassFile).toString(); 1555 1556 if (!classUrl.startsWith("jar:file:") || classUrl.indexOf("!") == -1){ 1557 throw new PackagerException("ERR_CantFindRuntime"); 1558 } 1559 1560 // Strip everything after and including the "!" 1561 classUrl = classUrl.substring(0, classUrl.lastIndexOf("!")); 1562 // Strip everything after the last "/" or "\" to get rid of the jar filename 1563 int lastIndexOfSlash = Math.max(classUrl.lastIndexOf("/"), classUrl.lastIndexOf("\\")); 1564 String jfxrtPath = classUrl.substring(0, lastIndexOfSlash) 1565 + "/../rt/lib/ext/jfxrt.jar!/"; 1566 1567 return jfxrtPath; 1568 } 1569 1570 private Class loadClassFromRuntime(String className) throws PackagerException { 1571 try { 1572 ClassLoader cl = getClassLoader(); 1573 return cl.loadClass(className); 1574 } catch (ClassNotFoundException ex) { 1575 throw new PackagerException(ex, "ERR_CantFindRuntime"); 1576 } 1577 } 1578 1579 private void createBinaryCss(String cssFile, String binCssFile) throws PackagerException { 1580 String ifname = cssFile; 1581 String ofname = (binCssFile != null) 1582 ? binCssFile 1583 : replaceExtensionByBSS(ifname); 1584 1585 // create parent directories 1586 File of = new File(ofname); 1587 File parentFile = of.getParentFile(); 1588 if (parentFile != null) { 1589 parentFile.mkdirs(); 1590 } 1591 1592 // Using reflection because CSS parser is part of runtime 1593 // and we want to avoid dependency on jfxrt during build 1594 Class clazz; 1595 try { 1596 clazz = Class.forName("com.sun.javafx.css.parser.Css2Bin"); 1597 } catch (ClassNotFoundException e) { 1598 // class was not found with default class loader, trying to 1599 // locate it by loading from jfxrt.jar 1600 clazz = loadClassFromRuntime("com.sun.javafx.css.parser.Css2Bin"); 1601 } 1602 1603 try { 1604 Method m = clazz.getMethod("convertToBinary", new Class[]{String.class, String.class}); 1605 m.invoke(null, ifname, ofname); 1606 } catch (Exception ex) { 1607 Throwable causeEx = ex.getCause(); 1608 String cause = (causeEx != null) ? causeEx.getMessage() 1609 : bundle.getString("ERR_UnknownReason"); 1610 1611 throw new PackagerException(ex, "ERR_BSSConversionFailed", cssFile, cause); 1612 } 1613 } 1614 1615 private static String replaceExtensionByBSS(String cssName) { 1616 return cssName.substring(0, cssName.lastIndexOf(".") + 1).concat("bss"); 1617 } 1618 1619 1620 private boolean isResource(String name) { 1621 if (name.endsWith(".class")) { 1622 return false; 1623 } 1624 if (name.endsWith(".java")) { 1625 return false; 1626 } 1627 if (name.endsWith(".fx")) { 1628 return false; 1629 } 1630 if (name.endsWith(".cvsignore")) { 1631 return false; 1632 } 1633 if (name.endsWith(".hgignore")) { 1634 return false; 1635 } 1636 if (name.endsWith("vssver.scc")) { 1637 return false; 1638 } 1639 if (name.endsWith(".DS_Store")) { 1640 return false; 1641 } 1642 if (name.endsWith("~")) { 1643 return false; 1644 } 1645 name = name.replace('\\', '/'); 1646 if (name.indexOf("/CVS/") >= 0) { 1647 return false; 1648 } 1649 if (name.indexOf("/.svn/") >= 0) { 1650 return false; 1651 } 1652 if (name.indexOf("/.hg/") >= 0) { 1653 return false; 1654 } 1655 if (name.indexOf("/.#") >= 0) { 1656 return false; 1657 } 1658 if (name.indexOf("/._") >= 0) { 1659 return false; 1660 } 1661 if (name.endsWith("#") && name.indexOf("/#") >= 0) { 1662 return false; 1663 } 1664 if (name.endsWith("%") && name.indexOf("/%") >= 0) { 1665 return false; 1666 } 1667 if (name.endsWith("MANIFEST.MF")) { 1668 return false; 1669 } 1670 return true; 1671 } 1672 1673 private static String[] webFiles = { 1674 "javafx-loading-100x100.gif", 1675 dtFX, 1676 "javafx-loading-25x25.gif", 1677 "error.png", 1678 "upgrade_java.png", 1679 "javafx-chrome.png", 1680 "get_java.png", 1681 "upgrade_javafx.png", 1682 "get_javafx.png" 1683 }; 1684 1685 private static String prefixWebFiles = "/resources/web-files/"; 1686 1687 private boolean extractWebFiles() throws PackagerException { 1688 return doExtractWebFiles(webFiles); 1689 } 1690 1691 private boolean doExtractWebFiles(String lst[]) throws PackagerException { 1692 File f = new File(deployParams.outdir, webfilesDir); 1693 f.mkdirs(); 1694 1695 for (String s: lst) { 1696 InputStream is = 1697 PackagerLib.class.getResourceAsStream(prefixWebFiles+s); 1698 if (is == null) { 1699 System.err.println("Internal error. Missing resources [" + 1700 (prefixWebFiles+s) + "]"); 1701 return false; 1702 } else { 1703 copyFileToOutDir(is, new File(f, s)); 1704 } 1705 } 1706 return true; 1707 } 1708 1709 private static boolean deleteDirectory(File dir) { 1710 if (dir == null || !dir.exists()) { 1711 return false; 1712 } 1713 1714 if (dir.isDirectory()) { 1715 for (String file : dir.list()) { 1716 deleteDirectory(new File(dir, file)); 1717 } 1718 } 1719 return dir.delete(); 1720 } 1721 1722 private void processEmbeddedCertificates(PrintStream out) 1723 throws CertificateEncodingException, IOException { 1724 if (deployParams.embedCertificates) { 1725 Set<CertPath> certPaths = collectCertPaths(); 1726 String signed = isSignedJNLP ? " signedjnlp=\"true\">" : ">"; 1727 if (certPaths != null && !certPaths.isEmpty()) { 1728 out.println(" <jfx:details" + signed); 1729 for (CertPath cp : certPaths) { 1730 String base64 = Utils.getBase64Encoded(cp); 1731 out.println(" <jfx:certificate-path>" + base64 + 1732 "</jfx:certificate-path>"); 1733 } 1734 out.println(" </jfx:details>"); 1735 } 1736 } 1737 } 1738 1739 private Set<CertPath> collectCertPaths() throws IOException { 1740 Set<CertPath> result = new HashSet<CertPath>(); 1741 for (DeployResource resource: deployParams.resources) { 1742 final File srcFile = resource.getFile(); 1743 if (srcFile.exists() && srcFile.isFile() && 1744 srcFile.getName().toLowerCase().endsWith("jar")) { 1745 result.addAll(extractCertPaths(srcFile)); 1746 } 1747 } 1748 return result; 1749 } 1750 1751 private Set<CertPath> extractCertPaths(File jar) throws IOException { 1752 Set<CertPath> result = new HashSet<CertPath>(); 1753 JarFile jf = new JarFile(jar); 1754 1755 // need to fully read jar file to build up internal signer info map 1756 Utils.readAllFully(jf); 1757 1758 boolean blobSigned = false; 1759 Enumeration<JarEntry> entries = jf.entries(); 1760 while (entries.hasMoreElements()) { 1761 JarEntry je = entries.nextElement(); 1762 String entryName = je.getName(); 1763 1764 CodeSigner[] signers = null; 1765 if (entryName.equalsIgnoreCase(JarSignature.BLOB_SIGNATURE)) { 1766 byte[] raw = Utils.getBytes(jf.getInputStream(je)); 1767 try { 1768 JarSignature js = JarSignature.load(raw); 1769 blobSigned = true; 1770 signers = js.getCodeSigners(); 1771 } catch(Exception ex) { 1772 throw new IOException(ex); 1773 } 1774 } else { 1775 signers = je.getCodeSigners(); 1776 } 1777 result.addAll(extractCertPaths(signers)); 1778 1779 if (entryName.equalsIgnoreCase("JNLP-INF/APPLICATION.JNLP")) { 1780 isSignedJNLP = true; 1781 } 1782 1783 // if blob and also know signed JNLP, no need to continue 1784 if (blobSigned && isSignedJNLP) { 1785 break; 1786 } 1787 1788 } 1789 return result; 1790 } 1791 1792 private static Collection<CertPath> extractCertPaths(CodeSigner[] signers) { 1793 Collection<CertPath> result = new ArrayList<CertPath>(); 1794 if (signers != null) { 1795 for (CodeSigner cs : signers) { 1796 CertPath cp = cs.getSignerCertPath(); 1797 if (cp != null) { 1798 result.add(cp); 1799 } 1800 } 1801 } 1802 return result; 1803 } 1804 }