com.sun.javafx.tools.packager.PackagerLib Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of openjfx-78-backport Show documentation
Show all versions of openjfx-78-backport Show documentation
This is a backport of OpenJFX 8 to run on Java 7.
The newest version!
/*
* Copyright (c) 2012, 2013, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package com.sun.javafx.tools.packager;
import com.sun.javafx.tools.ant.Utils;
import com.sun.javafx.tools.packager.DeployParams.Icon;
import com.sun.javafx.tools.packager.JarSignature.InputStreamSource;
import com.sun.javafx.tools.packager.bundlers.BundleParams;
import com.sun.javafx.tools.packager.bundlers.Bundler;
import com.sun.javafx.tools.resource.DeployResource;
import com.sun.javafx.tools.resource.PackagerResource;
import java.io.BufferedReader;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.PrintStream;
import java.io.Writer;
import java.lang.reflect.Method;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;
import java.security.CodeSigner;
import java.security.InvalidKeyException;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.SignatureException;
import java.security.UnrecoverableKeyException;
import java.security.cert.CertPath;
import java.security.cert.Certificate;
import java.security.cert.CertificateEncodingException;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.Collection;
import java.util.EnumMap;
import java.util.Enumeration;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.ResourceBundle;
import java.util.Set;
import java.util.jar.Attributes;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
import java.util.jar.JarOutputStream;
import java.util.jar.Manifest;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;
import sun.misc.BASE64Encoder;
public class PackagerLib {
public static final String JAVAFX_VERSION = "2.2";
private static final ResourceBundle bundle =
ResourceBundle.getBundle("com/sun/javafx/tools/packager/Bundle");
private static final String dtFX = "dtjava.js";
private static final String webfilesDir = "web-files";
//Note: leading "." is important for IE8
private static final String EMBEDDED_DT = "./"+webfilesDir+"/"+dtFX;
private static final String PUBLIC_DT = "http://java.com/js/dtjava.js";
private CreateJarParams createJarParams;
private DeployParams deployParams;
private CreateBSSParams createBssParams;
private File bssTmpDir;
private boolean isSignedJNLP;
private enum Filter {ALL, CLASSES_ONLY, RESOURCES};
private static String[] launcherFiles = {
"com/javafx/main/Main.class",
"com/javafx/main/Main$1.class",
"com/javafx/main/Main$2.class",
"com/javafx/main/NoJavaFXFallback.class"
};
private static String prefix_in_antjar = "/resources/classes/";
private ClassLoader classLoader;
private ClassLoader getClassLoader() throws PackagerException {
if (classLoader == null) {
try {
URL[] urls = {new URL(getJfxrtPath())};
classLoader = URLClassLoader.newInstance(urls);
} catch (MalformedURLException ex) {
throw new PackagerException(ex, "ERR_CantFindRuntime");
}
}
return classLoader;
}
public static final String MANIFEST_JAVAFX_MAIN ="JavaFX-Application-Class";
// if set of input resources consist of SINGLE element and
// this element is jar file then we expect this to be request to
// "update" jar file
// Input jar file MUST be executable jar file
//
// Check if we are in "special case" scenario
private File jarFileToUpdate(CreateJarParams params) {
if (params.resources.size() == 1) {
PackagerResource p = params.resources.get(0);
File f = p.getFile();
if (!f.isFile() || !f.getAbsolutePath().toLowerCase().endsWith(".jar")) {
return null;
}
JarFile jf = null;
try {
jf = new JarFile(f);
Manifest m = jf.getManifest(); //try to read manifest to validate it is jar
return f;
} catch (Exception e) {
//treat any excepion as "not a special case" scenario
Log.verbose(e);
} finally {
if (jf != null) {
try {
jf.close();
} catch (IOException ex) {
}
}
}
}
return null;
}
public void packageAsJar(CreateJarParams createJarParams) throws PackagerException {
if (createJarParams == null) {
throw new IllegalArgumentException("Parameters must not be null");
}
if (createJarParams.outfile == null) {
throw new IllegalArgumentException("Output file is not specified");
}
this.createJarParams = createJarParams;
//Special case: could be request for "update jar file"
File jarToUpdate = jarFileToUpdate(createJarParams);
Manifest m = null;
if (jarToUpdate != null) {
JarFile jf = null;
try {
//extract data we want to preserve
Log.info("Updating jar file: "+jarToUpdate.getAbsolutePath());
jf = new JarFile(jarToUpdate);
m = jf.getManifest();
if (m != null) {
Attributes attrs = m.getMainAttributes();
if (createJarParams.applicationClass == null) {
createJarParams.applicationClass =
attrs.getValue(Attributes.Name.MAIN_CLASS);
}
if (createJarParams.classpath == null) {
createJarParams.classpath =
attrs.getValue(Attributes.Name.CLASS_PATH);
}
}
} catch (IOException ex) {
throw new PackagerException(
ex, "ERR_FileReadFailed", jarToUpdate.getAbsolutePath());
} finally {
if (jf != null) {
try {
jf.close();
} catch (IOException ex) {
}
}
}
}
if (createJarParams.applicationClass == null) {
throw new IllegalArgumentException(
"Main application class is not specified");
}
//NOTE: This should be a save-to-temp file, then rename operation
File applicationJar = new File(createJarParams.outdir,
createJarParams.outfile.endsWith(".jar")
? createJarParams.outfile
: createJarParams.outfile + ".jar");
if (jarToUpdate != null &&
applicationJar.getAbsoluteFile().equals(jarToUpdate.getAbsoluteFile())) {
try {
File newInputJar = File.createTempFile("tempcopy", ".jar");
newInputJar.delete();
jarToUpdate.renameTo(newInputJar);
jarToUpdate = newInputJar;
} catch (IOException ioe) {
throw new PackagerException(
ioe, "ERR_FileCopyFailed", jarToUpdate.getAbsolutePath());
}
}
File parentFile = applicationJar.getParentFile();
if (parentFile != null) {
parentFile.mkdirs();
}
if (m == null) {
m = new Manifest();
}
Attributes attr = m.getMainAttributes();
attr.put(Attributes.Name.MANIFEST_VERSION, "1.0");
attr.put(new Attributes.Name("Created-By"), "JavaFX Packager");
if (createJarParams.manifestAttrs != null) {
for (Entry e: createJarParams.manifestAttrs.entrySet()) {
attr.put(new Attributes.Name(e.getKey()), e.getValue());
}
}
if (createJarParams.embedLauncher) {
//Reset Classpath (make sense if we are updating jar)
attr.remove(Attributes.Name.CLASS_PATH);
attr.put(Attributes.Name.MAIN_CLASS, "com/javafx/main/Main");
attr.put(new Attributes.Name(MANIFEST_JAVAFX_MAIN), createJarParams.applicationClass);
attr.put(new Attributes.Name("JavaFX-Version"), createJarParams.fxVersion);
if (createJarParams.preloader != null) {
attr.put(new Attributes.Name("JavaFX-Preloader-Class"), createJarParams.preloader);
}
if (createJarParams.classpath != null) {
// Allow comma or semicolon as delimeter (turn them into spaces)
String cp = createJarParams.classpath;
cp = cp.replace(';', ' ').replace(',', ' ');
attr.put(new Attributes.Name("JavaFX-Class-Path"), cp);
}
//Default fallback class uses "private" interface to
// get additional error paramters. Do not add it to manifest
// explicitly to avoid treating it as custom
if (createJarParams.fallbackClass != null &&
!createJarParams.defaultFallbackApp.equals(
createJarParams.fallbackClass)) {
attr.put(new Attributes.Name("JavaFX-Fallback-Class"),
createJarParams.fallbackClass);
}
if (createJarParams.arguments != null) {
int idx = 1;
for (String arg: createJarParams.arguments) {
attr.put(new Attributes.Name("JavaFX-Argument-"+idx),
encodeAsBase64(arg.getBytes()));
idx++;
}
}
if (createJarParams.params != null) {
int idx = 1;
for (Param p : createJarParams.params) {
if (p.name != null) { //otherwise it is something weird and we skip it
attr.put(new Attributes.Name("JavaFX-Parameter-Name-" + idx),
encodeAsBase64(p.name.getBytes()));
if (p.value != null) { //legal, means not value specified
attr.put(new Attributes.Name("JavaFX-Parameter-Value-" + idx),
encodeAsBase64(p.value.getBytes()));
}
idx++;
}
}
}
} else {
attr.put(Attributes.Name.MAIN_CLASS, createJarParams.applicationClass);
}
if (createJarParams.css2bin) {
try {
bssTmpDir = File.createTempFile("bssfiles", "");
} catch (IOException ex) {
throw new PackagerException(ex, "ERR_CreatingTempFileFailed");
}
bssTmpDir.delete();
}
if (applicationJar.exists() && !applicationJar.delete()) {
throw new PackagerException(
"ERR_CantDeleteFile", createJarParams.outfile);
}
try {
jar(m, createJarParams.resources, jarToUpdate,
new JarOutputStream(new FileOutputStream(applicationJar)),
Filter.ALL);
} catch (IOException ex) {
throw new PackagerException(
ex, "ERR_CreatingJarFailed", createJarParams.outfile);
}
// cleanup
deleteDirectory(bssTmpDir);
this.createJarParams = null;
}
private String readTextFile(File in) throws PackagerException {
StringBuilder sb = new StringBuilder();
InputStreamReader isr = null;
try {
char[] buf = new char[16384];
int len;
isr = new InputStreamReader(new FileInputStream(in));
while ((len = isr.read(buf)) > 0) {
sb.append(buf, sb.length(), len);
}
} catch (IOException ex) {
throw new PackagerException(ex, "ERR_FileReadFailed",
in.getAbsolutePath());
} finally {
if (isr != null) {
try {
isr.close();
} catch (IOException ex) {
}
}
}
return sb.toString();
}
private String processTemplate(String inpText,
Map templateStrings) {
//Core pattern matches
// #DT.SCRIPT#
// #DT.EMBED.CODE.ONLOAD#
// #DT.EMBED.CODE.ONLOAD(App2)#
String corePattern = "(#[\\w\\.\\(\\)]+#)";
//This will match
// "/*", "//" or "")) ||
(match.startsWith("//")) ||
(match.startsWith("/*") && match.endsWith(" */"));
//try to find if we have match
String coreReplacement = null;
//map with rules have no template ids
//int p = coreMatch.indexOf("\\(");
//strip leading/trailing #, then split of id part
String parts[] = coreMatch.substring(1, coreMatch.length()-1).split("[\\(\\)]");
String rulePart = parts[0];
String idPart = (parts.length == 1) ?
//strip trailing ')'
null : parts[1];
if (templateStrings.containsKey(
TemplatePlaceholders.fromString(rulePart))
&& (idPart == null /* it is ok for templeteId to be not null, e.g. DT.SCRIPT.CODE */
|| idPart.equals(deployParams.appId))) {
coreReplacement = templateStrings.get(
TemplatePlaceholders.fromString(rulePart));
}
if (coreReplacement != null) {
if (inComment || coreMatch.length() == match.length()) {
m.appendReplacement(result, coreReplacement);
} else { // pattern matched something that is not comment
// Very unlikely but lets play it safe
int pp = match.indexOf(coreMatch);
String v = match.substring(0, pp) +
coreReplacement +
match.substring(pp + coreMatch.length());
m.appendReplacement(result, v);
}
}
}
m.appendTail(result);
return result.toString();
}
private static enum Mode {FX, APPLET, SwingAPP};
public void generateDeploymentPackages(DeployParams deployParams) throws PackagerException {
if (deployParams == null) {
throw new IllegalArgumentException("Parameters must not be null.");
}
this.deployParams = deployParams;
boolean templateOn = !deployParams.templates.isEmpty();
Map templateStrings = null;
if (templateOn) {
templateStrings =
new EnumMap(TemplatePlaceholders.class);
}
try {
//In case of FX app we will have one JNLP and one HTML
//In case of Swing with FX we will have 2 JNLP files and one HTML
String jnlp_filename_webstart = deployParams.outfile + ".jnlp";
String jnlp_filename_browser
= deployParams.isSwingApp ?
(deployParams.outfile + "_browser.jnlp") : jnlp_filename_webstart;
String html_filename = deployParams.outfile + ".html";
//create out dir
File odir = deployParams.outdir;
odir.mkdirs();
if (deployParams.includeDT) {
extractWebFiles();
}
ByteArrayOutputStream jnlp_bos_webstart = new ByteArrayOutputStream();
ByteArrayOutputStream jnlp_bos_browser = new ByteArrayOutputStream();
//for swing case we need to generate 2 JNLP files
if (deployParams.isSwingApp) {
PrintStream jnlp_ps = new PrintStream(jnlp_bos_webstart);
generateJNLP(jnlp_ps, jnlp_filename_webstart, Mode.SwingAPP);
jnlp_ps.close();
//save JNLP
save(jnlp_filename_webstart, jnlp_bos_webstart.toByteArray());
jnlp_ps = new PrintStream(jnlp_bos_browser);
generateJNLP(jnlp_ps, jnlp_filename_browser, Mode.APPLET);
jnlp_ps.close();
//save JNLP
save(jnlp_filename_browser, jnlp_bos_browser.toByteArray());
} else {
PrintStream jnlp_ps = new PrintStream(jnlp_bos_browser);
generateJNLP(jnlp_ps, jnlp_filename_browser, Mode.FX);
jnlp_ps.close();
//save JNLP
save(jnlp_filename_browser, jnlp_bos_browser.toByteArray());
jnlp_bos_webstart = jnlp_bos_browser;
}
//we do not need html if this is component and not main app
if (!deployParams.isExtension) {
ByteArrayOutputStream html_bos =
new ByteArrayOutputStream();
PrintStream html_ps = new PrintStream(html_bos);
generateHTML(html_ps,
jnlp_bos_browser.toByteArray(), jnlp_filename_browser,
jnlp_bos_webstart.toByteArray(), jnlp_filename_webstart,
templateStrings, deployParams.isSwingApp);
html_ps.close();
//process template file
if (templateOn) {
for (DeployParams.Template t: deployParams.templates) {
File out = t.out;
if (out == null) {
System.out.println(
"Perform inplace substitution for " +
t.in.getAbsolutePath());
out = t.in;
}
save(out, processTemplate(
readTextFile(t.in), templateStrings).getBytes());
}
} else {
//save HTML
save(html_filename, html_bos.toByteArray());
}
}
//copy jar files
for (DeployResource resource: deployParams.resources) {
copyFiles(resource, deployParams.outdir);
}
} catch (Exception ex) {
throw new PackagerException(ex, "ERR_DeployFailed");
}
BundleParams bp = deployParams.getBundleParams();
if (bp != null) {
generateNativeBundles(deployParams.outdir, bp, deployParams.verbose);
}
this.deployParams = null;
}
private void generateNativeBundles(File outdir, BundleParams bp, boolean verbose) {
outdir = new File(outdir, "bundles");
if (bp.getRuntime() != null) {
Log.info("Using base JDK at: "
+ bp.getRuntime().getBaseDirectory().getAbsolutePath());
if (Log.isDebug()) {
bp.getRuntime().dump();
}
} else {
Log.info("No base JDK. Package will use system JRE.");
}
List bundlers = Bundler.get(bp, Log.isDebug() || verbose);
for (Bundler b: bundlers) {
b.bundle(bp, outdir);
}
}
private static void copyFiles(DeployResource resource, File outdir) throws IOException, PackagerException {
if (resource.getFile().isDirectory()) {
final File baseDir = resource.getBaseDir();
for (File file: resource.getFile().listFiles()) {
copyFiles(new DeployResource(baseDir, file), outdir);
}
} else {
final File srcFile = resource.getFile();
if (srcFile.exists() && srcFile.isFile()) {
//skip file copying if jar is in the same location
final File destFile =
new File(outdir, resource.getRelativePath());
if (!srcFile.getCanonicalFile().equals(
destFile.getCanonicalFile())) {
copyFileToOutDir(new FileInputStream(srcFile),
destFile);
} else {
Log.verbose("Skip jar copy to itself: " +
resource.getRelativePath());
}
}
}
}
public void generateBSS(CreateBSSParams params) throws PackagerException {
if (params == null) {
throw new IllegalArgumentException("Parameters must not be null.");
}
this.createBssParams = params;
createBinaryCss(createBssParams.resources, createBssParams.outdir);
this.createBssParams = null;
}
public void signJar(SignJarParams params) throws PackagerException {
try {
JarSignature signature = retrieveSignature(params);
for (PackagerResource pr: params.resources) {
signFile(pr, signature, params.outdir, params.verbose);
}
} catch (Exception ex) {
Log.verbose(ex);
throw new PackagerException("ERR_SignFailed", ex);
}
}
private JarSignature retrieveSignature(SignJarParams params) throws KeyStoreException,
NoSuchAlgorithmException, UnrecoverableKeyException, IOException,
CertificateException, InvalidKeyException {
if (params.keyPass == null) {
params.keyPass = params.storePass;
}
if (params.keyStore == null) {
throw new IOException("No keystore specified");
}
if (params.storePass == null) {
throw new IOException("No store password specified");
}
if (params.storeType == null) {
throw new IOException("No store type is specified");
}
KeyStore store = KeyStore.getInstance(params.storeType);
store.load(new FileInputStream(params.keyStore), params.storePass.toCharArray());
Certificate[] chain = store.getCertificateChain(params.alias);
X509Certificate certChain[] = new X509Certificate[chain.length];
for (int i=0; i argsList = new ArrayList();
for (Object a : args) {
if (a instanceof List) {
argsList.addAll((List)a);
} else if (a instanceof String) {
argsList.add((String)a);
}
}
final Process p = Runtime.getRuntime().exec(argsList.toArray(new String[argsList.size()]));
final BufferedReader in = new BufferedReader(new InputStreamReader(p.getInputStream()));
Thread t = new Thread(new Runnable() {
@Override
public void run() {
try {
String line;
while ((line = in.readLine()) != null) {
System.out.println(line);
}
} catch (IOException ioe) {
Log.verbose(ioe);
}
}
});
t.setDaemon(true);
t.start();
final BufferedReader err = new BufferedReader(new InputStreamReader(p.getErrorStream()));
t = new Thread(new Runnable() {
@Override
public void run() {
try {
String line;
while ((line = err.readLine()) != null) {
System.err.println(line);
}
} catch (IOException ioe) {
Log.verbose(ioe);
}
}
});
t.setDaemon(true);
t.start();
return p.waitFor();
}
private static void scanAndCopy(PackagerResource dir, Writer out, File outdir) throws PackagerException {
if (!dir.getFile().exists()) {
throw new PackagerException("ERR_MissingDirectory", dir.getFile().getName());
}
if ((dir.getFile().listFiles() == null) || (dir.getFile().listFiles().length == 0)) {
throw new PackagerException("ERR_EmptySourceDirectory", dir.getFile().getName());
}
try {
for (File f : dir.getFile().listFiles()) {
if (f.isDirectory()) {
scanAndCopy(new PackagerResource(dir.getBaseDir(), f), out, outdir);
} else if (f.getName().endsWith(".java")) {
out.write('\'' + f.getAbsolutePath().replace('\\', '/') + "\'\n");
} else {
copyFileToOutDir(new FileInputStream(f),
new File(outdir.getPath() + File.separator
+ dir.getRelativePath() + File.separator
+ f.getName()));
}
}
} catch (IOException ex) {
throw new PackagerException("ERR_FileCopyFailed", dir.getFile().getName());
}
}
//return null if args are default
private String getJvmArguments(boolean includeProperties) {
StringBuilder sb = new StringBuilder();
for(String v: deployParams.jvmargs) {
sb.append(v); //may need to escape if parameter has spaces
sb.append(" ");
}
if (includeProperties) {
for(String k: deployParams.properties.keySet()) {
sb.append("-D");
sb.append(k);
sb.append("=");
sb.append(deployParams.properties.get(k)); //may need to escape if value has spaces
sb.append(" ");
}
}
if (sb.length() > 0) {
return sb.toString();
}
return null;
}
private void generateJNLP(PrintStream out, String jnlp_filename, Mode m)
throws IOException, CertificateEncodingException
{
out.println("");
//have to use "old" spec version or old javaws will fail
// with "unknown" version exception ...
out.println("");
out.println(" ");
out.println(" " +
((deployParams.title != null)
? deployParams.title : "Sample JavaFX Application") +
" ");
out.println(" " +
((deployParams.vendor != null)
? deployParams.vendor : "Unknown vendor") +
" ");
out.println(" " +
((deployParams.description != null)
? deployParams.description : "Sample JavaFX 2.0 application.") +
" ");
for (Iterator it = deployParams.icons.iterator(); it.hasNext();) {
DeployParams.Icon i = it.next();
if (i.mode == DeployParams.RunMode.WEBSTART ||
i.mode == DeployParams.RunMode.ALL) {
out.println(" ");
}
}
if (deployParams.offlineAllowed && !deployParams.isExtension) {
out.println(" ");
}
out.println(" ");
if (!deployParams.isExtension) {
//FX is platfrom specific (soon will be available for Mac and Linux too)
out.println(" ");
out.println(" ");
out.println(" ");
}
boolean needToCloseResourceTag = false;
//jre is available for all platforms
if (!deployParams.isExtension) {
out.println(" ");
needToCloseResourceTag = true;
String vmargs = getJvmArguments(false);
vmargs = (vmargs == null) ? "" : " java-vm-args=\""+vmargs+"\" ";
out.println(" ");
for (String k : deployParams.properties.keySet()) {
out.println(" ");
}
}
String currentOS = null, currentArch = null;
//NOTE: This should sort the list by os+arch; it will reduce the number of resource tags
String pendingPrint = null;
for (DeployResource resource: deployParams.resources) {
//if not same OS or arch then open new resources element
if (!needToCloseResourceTag ||
((currentOS == null && resource.getOs() != null) ||
currentOS != null && !currentOS.equals(resource.getOs())) ||
((currentArch == null && resource.getArch() != null) ||
currentArch != null && !currentArch.equals(resource.getArch()))) {
//we do not print right a way as it may be empty block
// Not all resources make sense for JNLP (e.g. data or license)
if (needToCloseResourceTag) {
pendingPrint = " \n";
} else {
pendingPrint = "";
}
currentOS = resource.getOs();
currentArch = resource.getArch();
pendingPrint += " \n";
}
final File srcFile = resource.getFile();
if (srcFile.exists() && srcFile.isFile()) {
final String relativePath = resource.getRelativePath();
DeployResource.Type type = resource.getType();
switch (type) {
case jar:
if (pendingPrint != null) {
out.print(pendingPrint);
pendingPrint = null;
needToCloseResourceTag = true;
}
out.print(" ");
break;
case jnlp:
if (pendingPrint != null) {
out.print(pendingPrint);
pendingPrint = null;
needToCloseResourceTag = true;
}
out.println(" ");
break;
case nativelib:
if (pendingPrint != null) {
out.print(pendingPrint);
needToCloseResourceTag = true;
pendingPrint = null;
}
out.println(" ");
break;
}
}
}
if (needToCloseResourceTag) {
out.println(" ");
}
if (deployParams.allPermissions) {
out.println("");
out.println(" ");
processEmbeddedCertificates(out);
out.println(" ");
}
if (deployParams.needShortcut) {
out.println(" ");
// //TODO: Add support for a more sophisticated shortcut tag.
// // install no shortcuts, and do not consider "installed"
// // install no shortcuts, but consider "installed"
// // install desktop shortcut, but do not consider the app "installed"
// // install menu shortcut, and consider app "installed"
}
if (!deployParams.isExtension) {
if (m == Mode.APPLET) {
out.print(" ");
if (deployParams.params != null) {
for (Param p : deployParams.params) {
out.println(" ");
}
}
out.println(" ");
} else if (m == Mode.SwingAPP) {
out.print(" ");
if (deployParams.arguments != null) {
for (String a : deployParams.arguments) {
out.println(" " + a + " ");
}
}
out.println(" ");
} else { //JavaFX application
//embed fallback application
if (deployParams.fallbackApp != null) {
out.print(" ");
out.println(" ");
out.println(" ");
}
//javafx application descriptor
out.print(" ");
} else {
out.println(">");
if (deployParams.params != null) {
for (Param p : deployParams.params) {
out.println(" ");
}
}
if (deployParams.arguments != null) {
for (String a : deployParams.arguments) {
out.println(" " + a + " ");
}
}
out.println(" ");
}
}
} else {
out.println(" ");
}
out.println(" ");
out.println(" ");
}
private void addToList(List l, String name, String value, boolean isString) {
String s = isString ? "'" : "";
String v = name +" : " + s + value + s;
l.add(v);
}
private String listToString(List lst, String offset) {
StringBuilder b = new StringBuilder();
if (lst == null || lst.isEmpty()) {
return offset + "{}";
}
b.append(offset).append("{\n");
boolean first = true;
for (String s : lst) {
if (!first) {
b.append(",\n");
}
first = false;
b.append(offset).append(" ");
b.append(s);
}
b.append("\n");
b.append(offset).append("}");
return b.toString();
}
private String encodeAsBase64(byte inp[]) {
BASE64Encoder encoder = new BASE64Encoder();
return encoder.encode(inp);
}
private void generateHTML(PrintStream out,
byte[] jnlp_bytes_browser, String jnlpfile_browser,
byte[] jnlp_bytes_webstart, String jnlpfile_webstart,
Map templateStrings,
boolean swingMode) {
String poff = " ";
String poff2 = poff + poff;
String poff3 = poff2 + poff;
StringBuilder out_embed_dynamic = new StringBuilder();
StringBuilder out_embed_onload = new StringBuilder();
StringBuilder out_launch_code = new StringBuilder();
String appletParams = getAppletParameters();
String jnlp_content_browser = null;
String jnlp_content_webstart = null;
if (deployParams.embedJNLP) {
jnlp_content_browser =
encodeAsBase64(jnlp_bytes_browser).replaceAll("\\r|\\n", "");
jnlp_content_webstart =
encodeAsBase64(jnlp_bytes_webstart).replaceAll("\\r|\\n", "");
}
out.println("");
String dtURL = deployParams.includeDT ? EMBEDDED_DT : PUBLIC_DT;
String includeDtString = "";
if (templateStrings != null) {
templateStrings.put(TemplatePlaceholders.SCRIPT_URL, dtURL);
templateStrings.put(TemplatePlaceholders.SCRIPT_CODE, includeDtString);
}
out.println(" " + includeDtString);
String webstartError = "System is not setup to launch JavaFX applications. " +
"Make sure that you have a recent Java runtime, then install JavaFX Runtime 2.0 "+
"and check that JavaFX is enabled in the Java Control Panel.";
List w_app = new ArrayList();
List w_platform = new ArrayList();
List w_callback = new ArrayList();
addToList(w_app, "url", jnlpfile_webstart, true);
if (jnlp_content_webstart != null) {
addToList(w_app, "jnlp_content", jnlp_content_webstart, true);
}
addToList(w_platform, "javafx", deployParams.fxPlatform, true);
String vmargs = getJvmArguments(true);
if (vmargs != null) {
addToList(w_platform, "jvmargs", vmargs, true);
}
if (!"".equals(appletParams)) {
addToList(w_app, "params", "{"+appletParams+"}", false);
}
if ((deployParams.callbacks != null) && !deployParams.callbacks.isEmpty()) {
for (JSCallback cb: deployParams.callbacks) {
addToList(w_callback, cb.getName(), cb.getCmd(), false);
}
}
//prepare content of launchApp function
out_launch_code.append(poff2).append("dtjava.launch(");
out_launch_code.append(listToString(w_app, poff3)).append(",\n");
out_launch_code.append(listToString(w_platform, poff3)).append(",\n");
out_launch_code.append(listToString(w_callback, poff3)).append("\n");
out_launch_code.append(poff2).append(");\n");
out.println("");
if (templateStrings != null) {
templateStrings.put(TemplatePlaceholders.LAUNCH_CODE,
out_launch_code.toString());
}
//applet deployment
String appId = deployParams.appId; //if null then it will be autogenerated
String placeholder = deployParams.placeholder;
if (placeholder == null) { //placeholder can not be null
placeholder = "'javafx-app-placeholder'";
}
//prepare content of embedApp()
List p_app = new ArrayList();
List p_platform = new ArrayList();
List p_callback = new ArrayList();
if (appId != null) {
addToList(p_app, "id", appId, true);
}
if (deployParams.isSwingApp) {
addToList(p_app, "toolkit", "swing", true);
}
addToList(p_app, "url", jnlpfile_browser, true);
addToList(p_app, "placeholder", placeholder, false);
if (deployParams.embeddedWidth != null && deployParams.embeddedHeight != null) {
addToList(p_app, "width", ""+deployParams.embeddedWidth, true);
addToList(p_app, "height", ""+deployParams.embeddedHeight, true);
} else {
addToList(p_app, "width", ""+deployParams.width, false);
addToList(p_app, "height", ""+deployParams.height, false);
}
if (jnlp_content_browser != null) {
addToList(p_app, "jnlp_content", jnlp_content_browser, true);
}
addToList(p_platform, "javafx", deployParams.fxPlatform, true);
if (vmargs != null) {
addToList(p_platform, "jvmargs", vmargs, true);
}
if ((deployParams.callbacks != null) && !deployParams.callbacks.isEmpty()) {
for (JSCallback cb: deployParams.callbacks) {
addToList(p_callback, cb.getName(), cb.getCmd(), false);
}
}
if (!"".equals(appletParams)) {
addToList(p_app, "params", "{"+appletParams+"}", false);
}
if (swingMode) {
//Splash will not work in SwingMode
//Unless user overwrites onGetSplash handler (and that means he handles splash on his own)
// we will reset splash function to be "none"
boolean needOnGetSplashImpl = true;
if (deployParams.callbacks != null) {
for (JSCallback c: deployParams.callbacks) {
if ("onGetSplash".equals(c.getName())) {
needOnGetSplashImpl = false;
}
}
}
if (needOnGetSplashImpl) {
addToList(p_callback, "onGetSplash", "function() {}", false);
}
}
out_embed_dynamic.append("dtjava.embed(\n");
out_embed_dynamic.append(listToString(p_app, poff3)).append(",\n");
out_embed_dynamic.append(listToString(p_platform, poff3)).append(",\n");
out_embed_dynamic.append(listToString(p_callback, poff3)).append("\n");
out_embed_dynamic.append(poff2).append(");\n");
//now wrap content with function
String embedFuncName = "javafxEmbed" +
((deployParams.appId != null) ?
"_"+deployParams.appId : "");
out_embed_onload.append("\n\n");
if (templateStrings != null) {
templateStrings.put(
TemplatePlaceholders.EMBED_CODE_ONLOAD,
out_embed_onload.toString());
templateStrings.put(
TemplatePlaceholders.EMBED_CODE_DYNAMIC,
out_embed_dynamic.toString());
}
out.println(out_embed_onload.toString());
out.println("");
out.println("Test page for "+deployParams.appName+"
");
String launchString = "return launchApplication('" + jnlpfile_webstart + "');";
out.println(" Webstart: "
+ "click to launch this app as webstart
");
out.println("");
out.println(" ");
//placeholder is wrapped with single quotes already
out.println(" ");
out.println("");
}
private void save(String fname, byte[] content) throws IOException {
File odir = deployParams.outdir;
save(new File(odir, fname), content);
}
private void save(File f, byte[] content) throws IOException {
if (f.exists()) {
f.delete();
}
FileOutputStream fos = new FileOutputStream(f);
fos.write(content);
fos.close();
}
private static void copyFileToOutDir(
InputStream is, File fout) throws PackagerException {
OutputStream out = null;
final File outDir = fout.getParentFile();
try {
if (!outDir.exists() && !outDir.mkdirs()) {
throw new PackagerException("ERR_CreatingDirFailed", outDir.getPath());
}
out = new FileOutputStream(fout);
byte[] buf = new byte[16384];
int len;
while ((len = is.read(buf)) > 0) {
out.write(buf, 0, len);
}
} catch (IOException ex) {
throw new PackagerException(ex, "ERR_FileCopyFailed", outDir.getPath());
} finally {
try {
is.close();
} catch (IOException ex) {
}
if (out != null) {
try {
out.close();
} catch (IOException ex) {
}
}
}
}
private String getAppletParameters() {
String result = "";
if (deployParams.htmlParams != null) {
for (HtmlParam p: deployParams.htmlParams) {
if (!result.isEmpty()) {
result += ", ";
}
String escape = p.needEscape ? "\"" : "";
result += "\""+p.name+"\": " + escape + p.value + escape;
}
}
return result;
}
private void jar(
Manifest manifest, List files,
File importJarFile, JarOutputStream jar, Filter filter)
throws IOException, PackagerException {
try {
jar.putNextEntry(new ZipEntry("META-INF/"));
jar.closeEntry();
jar.putNextEntry(new ZipEntry(JarFile.MANIFEST_NAME));
manifest.write(jar);
jar.closeEntry();
alreadyAddedEntries.add("META-INF/");
if (importJarFile != null) { //updating jar file
copyFromOtherJar(jar, importJarFile);
} else { //normal situation
for (PackagerResource pr : files) {
jar(pr.getFile(), jar, filter,
pr.getBaseDir().getAbsolutePath().length() + 1);
}
}
if (createJarParams.embedLauncher) {
addEmbeddedLauncher(jar);
}
} finally {
jar.close();
alreadyAddedEntries.clear();
}
}
private Set alreadyAddedEntries = new HashSet();
private void createParentEntries(String relativePath, JarOutputStream jar) throws IOException {
String[] pathComponents = relativePath.split("/");
StringBuilder pathSB = new StringBuilder();
// iterating over directories only, the last component is the file
// or will be created next time.
for (int i = 0; i < pathComponents.length - 1; i++) {
pathSB.append(pathComponents[i]).append("/");
if (!alreadyAddedEntries.contains(pathSB.toString())) {
jar.putNextEntry(new ZipEntry(pathSB.toString()));
jar.closeEntry();
}
alreadyAddedEntries.add(pathSB.toString());
}
}
private void addEmbeddedLauncher(JarOutputStream jar) throws IOException {
for (String cls : launcherFiles) {
String nm = prefix_in_antjar + cls;
InputStream in = PackagerLib.class.getResourceAsStream(nm);
if (in == null) {
System.err.println(
"Internal error. Missing embedded resource [" + cls + "]");
}
jar.putNextEntry(new JarEntry(cls));
byte b[] = new byte[65000];
int i;
try {
while ((i = in.read(b)) > 0) {
jar.write(b, 0, i);
}
} finally {
in.close();
}
jar.closeEntry();
}
}
//add everything but manifest from given jar file
private void copyFromOtherJar(JarOutputStream jar, File inputFile) throws IOException {
JarFile inJar = new JarFile(inputFile);
Enumeration all = inJar.entries();
while (all.hasMoreElements()) {
JarEntry je = all.nextElement();
//skip manifest or root manifest dir entry (can not add duplicate)
if ("META-INF/MANIFEST.MF".equals(je.getName().toUpperCase())
|| "META-INF/".equals(je.getName().toUpperCase())) {
continue;
}
InputStream in = inJar.getInputStream(je);
jar.putNextEntry(new JarEntry(je.getName()));
byte b[] = new byte[65000];
int i;
try {
while ((i = in.read(b)) > 0) {
jar.write(b, 0, i);
}
} finally {
in.close();
}
jar.closeEntry();
}
}
private void jar(File f, JarOutputStream jar, Filter filter, int cut)
throws IOException, PackagerException {
if (!f.exists()) {
throw new FileNotFoundException("Input folder does not exist ["
+f.getAbsolutePath()+"]");
}
if (f.isDirectory()) {
for (File innerFile : f.listFiles()) {
jar(innerFile, jar, filter, cut);
}
} else if (filter == Filter.ALL
|| (filter == Filter.CLASSES_ONLY && f.getName().endsWith(".class"))
|| (filter == Filter.RESOURCES && isResource(f.getAbsolutePath()))) {
final String absPath = f.getAbsolutePath();
if (absPath.endsWith("META-INF\\MANIFEST.MF")
|| absPath.endsWith("META-INF/MANIFEST.MF")) {
return;
}
createParentEntries(absPath.substring(cut).replace('\\', '/'), jar);
if (createJarParams.css2bin && f.getName().endsWith(".css")) {
// generate bss file into temporary directory
int startOfExt = absPath.lastIndexOf(".") + 1;
String bssFileName = absPath
.substring(cut, startOfExt)
.concat("bss");
File bssFile = new File(bssTmpDir, bssFileName);
bssFile.getParentFile().mkdirs();
createBinaryCss(absPath, bssFile.getAbsolutePath());
jar.putNextEntry(new ZipEntry(bssFileName.replace('\\', '/')));
f = bssFile;
} else {
jar.putNextEntry(new ZipEntry(absPath.substring(cut).replace('\\', '/')));
}
byte b[] = new byte[65000];
int i;
FileInputStream in = new FileInputStream(f);
try {
while ((i = in.read(b)) > 0) {
jar.write(b, 0, i);
}
} finally {
in.close();
}
jar.closeEntry();
}
}
private void createBinaryCss(List cssResources, File outdir)
throws PackagerException {
for (PackagerResource cssRes: cssResources) {
String relPath = cssRes.getRelativePath();
createBinaryCss(cssRes.getFile(), outdir, relPath);
}
}
private void createBinaryCss(File f, File outdir, String relPath)
throws PackagerException {
if (f.isDirectory()) {
for (File innerFile : f.listFiles()) {
createBinaryCss(innerFile, outdir, relPath + '/' + innerFile.getName());
}
} else if (f.getName().endsWith(".css")) {
String cssFileName = f.getAbsolutePath();
String bssFileName = new File(outdir.getAbsolutePath(),
replaceExtensionByBSS(relPath))
.getAbsolutePath();
createBinaryCss(cssFileName, bssFileName);
}
}
// Returns path to jfxrt.jar relatively to jar containing PackagerLib.class
private String getJfxrtPath() throws PackagerException {
String theClassFile = "PackagerLib.class";
Class theClass = PackagerLib.class;
String classUrl = theClass.getResource(theClassFile).toString();
if (!classUrl.startsWith("jar:file:") || classUrl.indexOf("!") == -1){
throw new PackagerException("ERR_CantFindRuntime");
}
// Strip everything after and including the "!"
classUrl = classUrl.substring(0, classUrl.lastIndexOf("!"));
// Strip everything after the last "/" or "\" to get rid of the jar filename
int lastIndexOfSlash = Math.max(classUrl.lastIndexOf("/"), classUrl.lastIndexOf("\\"));
String jfxrtPath = classUrl.substring(0, lastIndexOfSlash)
+ "/../rt/lib/ext/jfxrt.jar!/";
return jfxrtPath;
}
private Class loadClassFromRuntime(String className) throws PackagerException {
try {
ClassLoader cl = getClassLoader();
return cl.loadClass(className);
} catch (ClassNotFoundException ex) {
throw new PackagerException(ex, "ERR_CantFindRuntime");
}
}
private void createBinaryCss(String cssFile, String binCssFile) throws PackagerException {
String ifname = cssFile;
String ofname = (binCssFile != null)
? binCssFile
: replaceExtensionByBSS(ifname);
// create parent directories
File of = new File(ofname);
File parentFile = of.getParentFile();
if (parentFile != null) {
parentFile.mkdirs();
}
// Using reflection because CSS parser is part of runtime
// and we want to avoid dependency on jfxrt during build
Class clazz;
try {
clazz = Class.forName("com.sun.javafx.css.parser.Css2Bin");
} catch (ClassNotFoundException e) {
// class was not found with default class loader, trying to
// locate it by loading from jfxrt.jar
clazz = loadClassFromRuntime("com.sun.javafx.css.parser.Css2Bin");
}
try {
Method m = clazz.getMethod("convertToBinary", new Class[]{String.class, String.class});
m.invoke(null, ifname, ofname);
} catch (Exception ex) {
Throwable causeEx = ex.getCause();
String cause = (causeEx != null) ? causeEx.getMessage()
: bundle.getString("ERR_UnknownReason");
throw new PackagerException(ex, "ERR_BSSConversionFailed", cssFile, cause);
}
}
private static String replaceExtensionByBSS(String cssName) {
return cssName.substring(0, cssName.lastIndexOf(".") + 1).concat("bss");
}
private boolean isResource(String name) {
if (name.endsWith(".class")) {
return false;
}
if (name.endsWith(".java")) {
return false;
}
if (name.endsWith(".fx")) {
return false;
}
if (name.endsWith(".cvsignore")) {
return false;
}
if (name.endsWith(".hgignore")) {
return false;
}
if (name.endsWith("vssver.scc")) {
return false;
}
if (name.endsWith(".DS_Store")) {
return false;
}
if (name.endsWith("~")) {
return false;
}
name = name.replace('\\', '/');
if (name.indexOf("/CVS/") >= 0) {
return false;
}
if (name.indexOf("/.svn/") >= 0) {
return false;
}
if (name.indexOf("/.hg/") >= 0) {
return false;
}
if (name.indexOf("/.#") >= 0) {
return false;
}
if (name.indexOf("/._") >= 0) {
return false;
}
if (name.endsWith("#") && name.indexOf("/#") >= 0) {
return false;
}
if (name.endsWith("%") && name.indexOf("/%") >= 0) {
return false;
}
if (name.endsWith("MANIFEST.MF")) {
return false;
}
return true;
}
private static String[] webFiles = {
"javafx-loading-100x100.gif",
dtFX,
"javafx-loading-25x25.gif",
"error.png",
"upgrade_java.png",
"javafx-chrome.png",
"get_java.png",
"upgrade_javafx.png",
"get_javafx.png"
};
private static String prefixWebFiles = "/resources/web-files/";
private void extractWebFiles() throws PackagerException {
doExtractWebFiles(webFiles);
}
private void doExtractWebFiles(String lst[]) throws PackagerException {
File f = new File(deployParams.outdir, webfilesDir);
f.mkdirs();
for (String s: lst) {
InputStream is =
PackagerLib.class.getResourceAsStream(prefixWebFiles+s);
if (is == null) {
System.err.println("Internal error. Missing resources [" +
(prefixWebFiles+s) + "]");
} else {
copyFileToOutDir(is, new File(f, s));
}
}
}
private static boolean deleteDirectory(File dir) {
if (dir == null || !dir.exists()) {
return false;
}
if (dir.isDirectory()) {
for (String file : dir.list()) {
deleteDirectory(new File(dir, file));
}
}
return dir.delete();
}
private void processEmbeddedCertificates(PrintStream out)
throws CertificateEncodingException, IOException {
if (deployParams.embedCertificates) {
Set certPaths = collectCertPaths();
String signed = isSignedJNLP ? " signedjnlp=\"true\">" : ">";
if (certPaths != null && !certPaths.isEmpty()) {
out.println(" " + base64 +
"");
}
out.println(" ");
}
}
}
private Set collectCertPaths() throws IOException {
Set result = new HashSet();
for (DeployResource resource: deployParams.resources) {
final File srcFile = resource.getFile();
if (srcFile.exists() && srcFile.isFile() &&
srcFile.getName().toLowerCase().endsWith("jar")) {
result.addAll(extractCertPaths(srcFile));
}
}
return result;
}
private Set extractCertPaths(File jar) throws IOException {
Set result = new HashSet();
JarFile jf = new JarFile(jar);
// need to fully read jar file to build up internal signer info map
Utils.readAllFully(jf);
boolean blobSigned = false;
Enumeration entries = jf.entries();
while (entries.hasMoreElements()) {
JarEntry je = entries.nextElement();
String entryName = je.getName();
CodeSigner[] signers = null;
if (entryName.equalsIgnoreCase(JarSignature.BLOB_SIGNATURE)) {
byte[] raw = Utils.getBytes(jf.getInputStream(je));
try {
JarSignature js = JarSignature.load(raw);
blobSigned = true;
signers = js.getCodeSigners();
} catch(Exception ex) {
throw new IOException(ex);
}
} else {
signers = je.getCodeSigners();
}
result.addAll(extractCertPaths(signers));
if (entryName.equalsIgnoreCase("JNLP-INF/APPLICATION.JNLP")) {
isSignedJNLP = true;
}
// if blob and also know signed JNLP, no need to continue
if (blobSigned && isSignedJNLP) {
break;
}
}
return result;
}
private static Collection extractCertPaths(CodeSigner[] signers) {
Collection result = new ArrayList();
if (signers != null) {
for (CodeSigner cs : signers) {
CertPath cp = cs.getSignerCertPath();
if (cp != null) {
result.add(cp);
}
}
}
return result;
}
}