/* * Copyright (c) 2012, 2017, 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.oracle.tools.packager.jnlp; import com.oracle.tools.packager.AbstractBundler; import com.oracle.tools.packager.BundlerParamInfo; import com.oracle.tools.packager.ConfigException; import com.oracle.tools.packager.Log; import com.oracle.tools.packager.RelativeFileSet; import com.oracle.tools.packager.StandardBundlerParam; import com.oracle.tools.packager.UnsupportedPlatformException; import com.sun.javafx.tools.packager.PackagerException; import com.sun.javafx.tools.resource.PackagerResource; import com.sun.javafx.tools.packager.TemplatePlaceholders; import javax.xml.stream.XMLOutputFactory; import javax.xml.stream.XMLStreamException; import javax.xml.stream.XMLStreamWriter; import javax.xml.transform.OutputKeys; import javax.xml.transform.Transformer; import javax.xml.transform.TransformerException; import javax.xml.transform.TransformerFactory; import javax.xml.transform.stream.StreamResult; import javax.xml.transform.stream.StreamSource; import java.io.ByteArrayOutputStream; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.OutputStream; import java.io.PrintStream; import java.io.StringReader; import java.io.StringWriter; import java.security.cert.CertificateEncodingException; import java.text.MessageFormat; import java.util.ArrayList; import java.util.Arrays; import java.util.Base64; import java.util.Collection; import java.util.EnumMap; import java.util.HashMap; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.ResourceBundle; import java.util.regex.Matcher; import java.util.regex.Pattern; import static com.oracle.tools.packager.StandardBundlerParam.*; import jdk.packager.internal.legacy.JLinkBundlerHelper; public class JNLPBundler extends AbstractBundler { private static final ResourceBundle I18N = ResourceBundle.getBundle(JNLPBundler.class.getName()); 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 = "https://java.com/js/dtjava.js"; private static final String JFX_NS_URI = "http://javafx.com"; public static final StandardBundlerParam OUT_FILE = new StandardBundlerParam<>( I18N.getString("param.out-file.name"), I18N.getString("param.out-file.description"), "jnlp.outfile", String.class, null, null); public static final StandardBundlerParam SWING_APP = new StandardBundlerParam<>( I18N.getString("param.swing-app.name"), I18N.getString("param.swing-app.description"), "jnlp.swingApp", Boolean.class, p -> Boolean.FALSE, (s, p) -> Boolean.parseBoolean(s)); public static final StandardBundlerParam INCLUDE_DT = new StandardBundlerParam<>( I18N.getString("param.include-deployment-toolkit.name"), I18N.getString("param.include-deployment-toolkit.description"), "jnlp.includeDT", Boolean.class, p -> Boolean.FALSE, (s, p) -> Boolean.parseBoolean(s)); public static final StandardBundlerParam EMBED_JNLP = new StandardBundlerParam<>( I18N.getString("param.embed-jnlp.name"), I18N.getString("param.embed-jnlp.description"), "jnlp.embedJnlp", Boolean.class, p -> Boolean.FALSE, (s, p) -> Boolean.parseBoolean(s)); public static final StandardBundlerParam EXTENSION = new StandardBundlerParam<>( I18N.getString("param.extension.name"), I18N.getString("param.extension.description"), "jnlp.extension", Boolean.class, p -> Boolean.FALSE, (s, p) -> Boolean.parseBoolean(s)); @SuppressWarnings("unchecked") public static final StandardBundlerParam> TEMPLATES = new StandardBundlerParam<>( I18N.getString("param.templates.name"), I18N.getString("param.templates.description"), "jnlp.templates", (Class>) (Object) Map.class, p -> new LinkedHashMap<>(), null); public static final StandardBundlerParam CODEBASE = new StandardBundlerParam<>( I18N.getString("param.codebase.name"), I18N.getString("param.codebase.description"), "jnlp.codebase", String.class, p -> null, null); public static final StandardBundlerParam PLACEHOLDER = new StandardBundlerParam<>( I18N.getString("param.placeholder.name"), I18N.getString("param.placeholder.description"), "jnlp.placeholder", String.class, p -> "javafx-app-placeholder", (s, p) -> { if (!s.startsWith("'")) { s = "'" + s; } if (!s.endsWith("'")) { s = s + "'"; } return s; }); public static final StandardBundlerParam OFFLINE_ALLOWED = new StandardBundlerParam<>( I18N.getString("param.offline-allowed.name"), I18N.getString("param.offline-allowed.description"), "jnlp.offlineAllowed", Boolean.class, p -> true, (s, p) -> Boolean.valueOf(s)); public static final StandardBundlerParam ALL_PERMISSIONS = new StandardBundlerParam<>( I18N.getString("param.all-permissions.name"), I18N.getString("param.all-permissions.description"), "jnlp.allPermisions", Boolean.class, p -> false, (s, p) -> Boolean.valueOf(s)); public static final StandardBundlerParam WIDTH = new StandardBundlerParam<>( I18N.getString("param.width.name"), I18N.getString("param.width.description"), "jnlp.width", Integer.class, p -> 0, (s, p) -> Integer.parseInt(s)); public static final StandardBundlerParam HEIGHT = new StandardBundlerParam<>( I18N.getString("param.height.name"), I18N.getString("param.height.description"), "jnlp.height", Integer.class, p -> 0, (s, p) -> Integer.parseInt(s)); public static final StandardBundlerParam EMBEDDED_WIDTH = new StandardBundlerParam<>( I18N.getString("param.embedded-width.name"), I18N.getString("param.embedded-width.description"), "jnlp.embeddedWidth", String.class, p -> Integer.toString(WIDTH.fetchFrom(p)), (s, p) -> s); public static final StandardBundlerParam EMBEDDED_HEIGHT = new StandardBundlerParam<>( I18N.getString("param.embedded-height.name"), I18N.getString("param.embedded-height.description"), "jnlp.embeddedHeight", String.class, p -> Integer.toString(HEIGHT.fetchFrom(p)), (s, p) -> s); public static final StandardBundlerParam FALLBACK_APP = new StandardBundlerParam<>( I18N.getString("param.fallback-app.name"), I18N.getString("param.fallback-app.description"), "jnlp.fallbackApp", String.class, p -> null, (s, p) -> s); public static final StandardBundlerParam UPDATE_MODE = new StandardBundlerParam<>( I18N.getString("param.update-mode.name"), I18N.getString("param.update-mode.description"), "jnlp.updateMode", String.class, p -> "background", (s, p) -> s); public static final StandardBundlerParam FX_PLATFORM = new StandardBundlerParam<>( I18N.getString("param.fx-platform.name"), I18N.getString("param.fx-platform.description"), "jnlp.fxPlatform", String.class, p -> "1.8+", (s, p) -> s); public static final StandardBundlerParam JRE_PLATFORM = new StandardBundlerParam<>( I18N.getString("param.jre-platform.name"), I18N.getString("param.jre-platform.description"), "jnlp.jrePlatform", String.class, p -> "1.8+", (s, p) -> s); @SuppressWarnings("unchecked") public static final StandardBundlerParam>> ICONS = new StandardBundlerParam<>( I18N.getString("param.icons.name"), I18N.getString("param.icons.description"), "jnlp.icons", (Class>>) (Object) List.class, params -> new ArrayList<>(1), null ); @SuppressWarnings("unchecked") public static final StandardBundlerParam> APP_PARAMS = new StandardBundlerParam<>( I18N.getString("param.params.name"), I18N.getString("param.params.description"), "jnlp.params", (Class>) (Object) Map.class, params -> new HashMap<>(), null ); @SuppressWarnings("unchecked") public static final StandardBundlerParam> ESCAPED_APPLET_PARAMS = new StandardBundlerParam<>( I18N.getString("param.escaped-applet-params.name"), I18N.getString("param.escaped-applet-params.description"), "jnlp.escapedAppletParams", (Class>) (Object) Map.class, params -> new HashMap<>(), null ); @SuppressWarnings("unchecked") public static final StandardBundlerParam> APPLET_PARAMS = new StandardBundlerParam<>( I18N.getString("param.applet-params.name"), I18N.getString("param.applet-params.description"), "jnlp.appletParams", (Class>) (Object) Map.class, params -> new HashMap<>(), null ); @SuppressWarnings("unchecked") public static final StandardBundlerParam> JS_CALLBACKS = new StandardBundlerParam<>( I18N.getString("param.js-callbacks.name"), I18N.getString("param.js-callbacks.description"), "jnlp.jsCallbacks", (Class>) (Object) Map.class, params -> new HashMap<>(), null ); public static final StandardBundlerParam INSTALL_HINT = new StandardBundlerParam<>( I18N.getString("param.menu-install-hint.name"), I18N.getString("param.menu-install-hint.description"), "jnlp.install", Boolean.class, params -> null, // valueOf(null) is false, and we actually do want null in some cases (s, p) -> (s == null || "null".equalsIgnoreCase(s))? null : Boolean.valueOf(s) ); public static final StandardBundlerParam ICONS_HREF = new StandardBundlerParam<>( I18N.getString("param.icons-href.name"), I18N.getString("param.icons-href.description"), "jnlp.icons.href", String.class, null, null ); public static final StandardBundlerParam ICONS_KIND = new StandardBundlerParam<>( I18N.getString("param.icons-kind.name"), I18N.getString("param.icons-kind.description"), "jnlp.icons.kind", String.class, params -> null, null ); public static final StandardBundlerParam ICONS_WIDTH = new StandardBundlerParam<>( I18N.getString("param.icons-width.name"), I18N.getString("param.icons-width.description"), "jnlp.icons.width", String.class, params -> null, null ); public static final StandardBundlerParam ICONS_HEIGHT = new StandardBundlerParam<>( I18N.getString("param.icons-height.name"), I18N.getString("param.icons-height.description"), "jnlp.icons.height", String.class, params -> null, null ); public static final StandardBundlerParam ICONS_DEPTH = new StandardBundlerParam<>( I18N.getString("param.icons-depth.name"), I18N.getString("param.icons-depth.description"), "jnlp.icons.depth", String.class, params -> null, null ); private enum Mode {FX, APPLET, SwingAPP} @Override public String getName() { return I18N.getString("bundler.name"); } @Override public String getDescription() { return I18N.getString("bundler.description"); } @Override public String getID() { return "jnlp"; } @Override public String getBundleType() { return "JNLP"; } @Override public Collection> getBundleParameters() { return Arrays.asList( ALL_PERMISSIONS, APPLET_PARAMS, APP_NAME, APP_PARAMS, APP_RESOURCES_LIST, ARGUMENTS, CODEBASE, DESCRIPTION, EMBED_JNLP, EMBEDDED_HEIGHT, EMBEDDED_WIDTH, ESCAPED_APPLET_PARAMS, EXTENSION, // FALLBACK_APP, // FX_PLATFORM, HEIGHT, ICONS, IDENTIFIER, INCLUDE_DT, INSTALL_HINT, JRE_PLATFORM, JS_CALLBACKS, JVM_OPTIONS, JVM_PROPERTIES, MAIN_CLASS, MENU_HINT, OFFLINE_ALLOWED, OUT_FILE, PRELOADER_CLASS, PLACEHOLDER, SHORTCUT_HINT, SWING_APP, TEMPLATES, TITLE, UPDATE_MODE, VENDOR, WIDTH ); } @Override public boolean validate(Map params) throws UnsupportedPlatformException, ConfigException { if (OUT_FILE.fetchFrom(params) == null) { throw new ConfigException( I18N.getString("error.no-outfile"), I18N.getString("error.no-outfile.advice")); } if (APP_RESOURCES_LIST.fetchFrom(params) == null) { throw new ConfigException( I18N.getString("error.no-app-resources"), I18N.getString("error.no-app-resources.advice")); } if (!EXTENSION.fetchFrom(params)) { StandardBundlerParam.validateMainClassInfoFromAppResources(params); } return true; } private String readTextFile(File in) throws PackagerException { StringBuilder sb = new StringBuilder(); try (InputStreamReader isr = new InputStreamReader(new FileInputStream(in))) { char[] buf = new char[16384]; int len; while ((len = isr.read(buf)) > 0) { sb.append(buf, sb.length(), len); } } catch (IOException ex) { throw new PackagerException(ex, "ERR_FileReadFailed", in.getAbsolutePath()); } return sb.toString(); } private String processTemplate(Map params, 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 templateId to be not null, e.g. DT.SCRIPT.CODE */ || idPart.equals(IDENTIFIER.fetchFrom(params)))) { 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(); } @Override public File execute(Map params, File outputParentDir) { Map templates = TEMPLATES.fetchFrom(params); boolean templateOn = !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 outfile = OUT_FILE.fetchFrom(params); boolean isSwingApp = SWING_APP.fetchFrom(params); String jnlp_filename_webstart = outfile + ".jnlp"; String jnlp_filename_browser = isSwingApp ? (outfile + "_browser.jnlp") : jnlp_filename_webstart; String html_filename = outfile + ".html"; //create out dir outputParentDir.mkdirs(); boolean includeDT = INCLUDE_DT.fetchFrom(params); if (includeDT && !extractWebFiles(outputParentDir)) { throw new PackagerException("ERR_NoEmbeddedDT"); } ByteArrayOutputStream jnlp_bos_webstart = new ByteArrayOutputStream(); ByteArrayOutputStream jnlp_bos_browser = new ByteArrayOutputStream(); //for swing case we need to generate 2 JNLP files if (isSwingApp) { PrintStream jnlp_ps = new PrintStream(jnlp_bos_webstart); generateJNLP(params, jnlp_ps, jnlp_filename_webstart, Mode.SwingAPP); jnlp_ps.close(); //save JNLP save(outputParentDir, jnlp_filename_webstart, jnlp_bos_webstart.toByteArray()); jnlp_ps = new PrintStream(jnlp_bos_browser); generateJNLP(params, jnlp_ps, jnlp_filename_browser, Mode.APPLET); jnlp_ps.close(); //save JNLP save(outputParentDir, jnlp_filename_browser, jnlp_bos_browser.toByteArray()); } else { PrintStream jnlp_ps = new PrintStream(jnlp_bos_browser); generateJNLP(params, jnlp_ps, jnlp_filename_browser, Mode.FX); jnlp_ps.close(); //save JNLP save(outputParentDir, 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 boolean isExtension = EXTENSION.fetchFrom(params); if (!isExtension) { // even though the html is unused if templateOn, // the templateStrings is updated as a side effect. ByteArrayOutputStream html_bos = new ByteArrayOutputStream(); PrintStream html_ps = new PrintStream(html_bos); generateHTML(params, html_ps, jnlp_bos_browser.toByteArray(), jnlp_filename_browser, jnlp_bos_webstart.toByteArray(), jnlp_filename_webstart, templateStrings, isSwingApp); html_ps.close(); //process template file if (templateOn) { for (Map.Entry t: TEMPLATES.fetchFrom(params).entrySet()) { File out = t.getValue(); if (out == null) { System.out.println( "Perform inplace substitution for " + t.getKey().getAbsolutePath()); out = t.getKey(); } save(out, processTemplate(params, readTextFile(t.getKey()), templateStrings).getBytes()); } } else { //save HTML save(outputParentDir, html_filename, html_bos.toByteArray()); } } //copy jar files for (RelativeFileSet rfs : APP_RESOURCES_LIST.fetchFrom(params)) { copyFiles(rfs, outputParentDir); } return outputParentDir; } catch (Exception ex) { Log.info("JNLP failed : " + ex.getMessage()); ex.printStackTrace(); Log.debug(ex); return null; } } private static void copyFiles(RelativeFileSet resources, File outdir) throws IOException, PackagerException { File rootDir = resources.getBaseDirectory(); for (String s : resources.getIncludedFiles()) { final File srcFile = new File(rootDir, s); if (srcFile.exists() && srcFile.isFile()) { //skip file copying if jar is in the same location final File destFile = new File(outdir, s); if (!srcFile.getCanonicalFile().equals(destFile.getCanonicalFile())) { copyFileToOutDir(new FileInputStream(srcFile), destFile); } else { Log.verbose(MessageFormat.format(I18N.getString("error.jar-no-self-copy"), s)); } } } } //return null if args are default private String getJvmArguments(Map params, boolean includeProperties) { List jvmargs = JVM_OPTIONS.fetchFrom(params); Map properties = JVM_PROPERTIES.fetchFrom(params); StringBuilder sb = new StringBuilder(); for (String v : jvmargs) { sb.append(v); //may need to escape if parameter has spaces sb.append(" "); } if (includeProperties) { for (Map.Entry entry : properties.entrySet()) { sb.append("-D"); sb.append(entry.getKey()); sb.append("="); sb.append(entry.getValue()); //may need to escape if value has spaces sb.append(" "); } } if (sb.length() > 0) { return sb.toString(); } return null; } private void generateJNLP(Map params, PrintStream out, String jnlp_filename, Mode m) throws IOException, CertificateEncodingException { String codebase = CODEBASE.fetchFrom(params); String title = TITLE.fetchFrom(params); String vendor = VENDOR.fetchFrom(params); String description = DESCRIPTION.fetchFrom(params); try { XMLOutputFactory xmlOutputFactory = XMLOutputFactory.newFactory(); ByteArrayOutputStream baos = new ByteArrayOutputStream(); XMLStreamWriter xout = xmlOutputFactory.createXMLStreamWriter(baos, "utf-8"); xout.writeStartDocument("utf-8", "1.0"); xout.writeCharacters("\n"); xout.writeStartElement("jnlp"); xout.writeAttribute("spec", "1.0"); xout.writeNamespace("jfx", "http://javafx.com"); if (codebase != null) { xout.writeAttribute("codebase", codebase); } xout.writeAttribute("href", jnlp_filename); xout.writeStartElement("information"); xout.writeStartElement("title"); if (title != null) { xout.writeCharacters(title); } else { xout.writeCData("Sample JavaFX Application"); } xout.writeEndElement(); xout.writeStartElement("vendor"); if (vendor != null) { xout.writeCharacters(vendor); } else { xout.writeCharacters("Unknown vendor"); } xout.writeEndElement(); xout.writeStartElement("description"); if (description != null) { xout.writeCharacters(description); } else { xout.writeCharacters("Sample JavaFX 2.0 application."); } xout.writeEndElement(); for (Map iconInfo : ICONS.fetchFrom(params)) { String href = ICONS_HREF.fetchFrom(iconInfo); String kind = ICONS_KIND.fetchFrom(iconInfo); String width = ICONS_WIDTH.fetchFrom(iconInfo); String height = ICONS_HEIGHT.fetchFrom(iconInfo); String depth = ICONS_DEPTH.fetchFrom(iconInfo); xout.writeStartElement("icon"); xout.writeAttribute("href", href); if (kind != null) xout.writeAttribute("kind", kind); if (width != null) xout.writeAttribute("width", width); if (height != null) xout.writeAttribute("height", height); if (depth != null) xout.writeAttribute("depth", depth); xout.writeEndElement(); } boolean offlineAllowed = OFFLINE_ALLOWED.fetchFrom(params); boolean isExtension = EXTENSION.fetchFrom(params); if (offlineAllowed && !isExtension) { xout.writeEmptyElement("offline-allowed"); } Boolean needShortcut = SHORTCUT_HINT.fetchFrom(params); Boolean needMenu = MENU_HINT.fetchFrom(params); Boolean needInstall = INSTALL_HINT.fetchFrom(params); if ((needShortcut != null && Boolean.TRUE.equals(needShortcut)) || (needMenu != null && Boolean.TRUE.equals(needMenu)) || (needInstall != null && Boolean.TRUE.equals(needInstall))) { xout.writeStartElement("shortcut"); if (Boolean.TRUE.equals(needInstall)) { xout.writeAttribute("installed", needInstall.toString()); } if (Boolean.TRUE.equals(needShortcut)) { xout.writeEmptyElement("desktop"); } if (Boolean.TRUE.equals(needMenu)) { xout.writeEmptyElement("menu"); } xout.writeEndElement(); } xout.writeEndElement(); // information boolean allPermissions = ALL_PERMISSIONS.fetchFrom(params); if (allPermissions) { xout.writeStartElement("security"); xout.writeEmptyElement("all-permissions"); xout.writeEndElement(); } String updateMode = UPDATE_MODE.fetchFrom(params); if (updateMode != null) { xout.writeStartElement("update"); xout.writeAttribute("check", UPDATE_MODE.fetchFrom(params)); xout.writeEndElement(); // update } boolean needToCloseResourceTag = false; //jre is available for all platforms if (!isExtension) { xout.writeStartElement("resources"); needToCloseResourceTag = true; xout.writeStartElement("j2se"); xout.writeAttribute("version", JRE_PLATFORM.fetchFrom(params)); String vmargs = getJvmArguments(params, false); if (vmargs != null) { xout.writeAttribute("java-vm-args", vmargs); } xout.writeAttribute("href", "http://java.sun.com/products/autodl/j2se"); xout.writeEndElement(); //j2se for (Map.Entry entry : JVM_PROPERTIES.fetchFrom(params).entrySet()) { xout.writeStartElement("property"); xout.writeAttribute("name", entry.getKey()); xout.writeAttribute("value", entry.getValue()); xout.writeEndElement(); //property } } 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) { for (RelativeFileSet rfs : APP_RESOURCES_LIST.fetchFrom(params)) { //if not same OS or arch then open new resources element if (!needToCloseResourceTag || ((currentOS == null && rfs.getOs() != null) || currentOS != null && !currentOS.equals(rfs.getOs())) || ((currentArch == null && rfs.getArch() != null) || currentArch != null && !currentArch.equals(rfs.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) { xout.writeEndElement(); } needToCloseResourceTag = true; currentOS = rfs.getOs(); currentArch = rfs.getArch(); xout.writeStartElement("resources"); if (currentOS != null) xout.writeAttribute("os", currentOS); if (currentArch != null) xout.writeAttribute("arch", currentArch); } for (String relativePath : rfs.getIncludedFiles()) { final File srcFile = new File(rfs.getBaseDirectory(), relativePath); if (srcFile.exists() && srcFile.isFile()) { RelativeFileSet.Type type = rfs.getType(); if (type == RelativeFileSet.Type.UNKNOWN) { if (relativePath.endsWith(".jar")) { type = RelativeFileSet.Type.jar; } else if (relativePath.endsWith(".jnlp")) { type = RelativeFileSet.Type.jnlp; } else if (relativePath.endsWith(".dll")) { type = RelativeFileSet.Type.nativelib; } else if (relativePath.endsWith(".so")) { type = RelativeFileSet.Type.nativelib; } else if (relativePath.endsWith(".dylib")) { type = RelativeFileSet.Type.nativelib; } } switch (type) { case jar: xout.writeStartElement("jar"); xout.writeAttribute("href", relativePath); xout.writeAttribute("size", Long.toString(srcFile.length())); if (rfs.getMode() != null) { xout.writeAttribute("download", rfs.getMode()); } xout.writeEndElement(); //jar break; case jnlp: xout.writeStartElement("extension"); xout.writeAttribute("href", relativePath); xout.writeEndElement(); //extension break; case nativelib: xout.writeStartElement("nativelib"); xout.writeAttribute("href", relativePath); xout.writeEndElement(); //nativelib break; } } } } if (needToCloseResourceTag) { xout.writeEndElement(); } if (!isExtension) { Integer width = WIDTH.fetchFrom(params); Integer height = HEIGHT.fetchFrom(params); if (width == null) { width = 0; } if (height == null) { height = 0; } String applicationClass = MAIN_CLASS.fetchFrom(params); String preloader = PRELOADER_CLASS.fetchFrom(params); Map appParams = APP_PARAMS.fetchFrom(params); List arguments = ARGUMENTS.fetchFrom(params); String appName = APP_NAME.fetchFrom(params); if (m == Mode.APPLET) { xout.writeStartElement("applet-desc"); xout.writeAttribute("width", Integer.toString(width)); xout.writeAttribute("height", Integer.toString(height)); xout.writeAttribute("main-class", applicationClass); xout.writeAttribute("name", appName); xout.writeStartElement("param"); xout.writeAttribute("name", "requiredFXVersion"); xout.writeAttribute("value", FX_PLATFORM.fetchFrom(params)); xout.writeEndElement(); // param for (Map.Entry appParamEntry : appParams.entrySet()) { xout.writeStartElement("param"); xout.writeAttribute("name", appParamEntry.getKey()); if (appParamEntry.getValue() != null) { xout.writeAttribute("value", appParamEntry.getValue()); } xout.writeEndElement(); // param } xout.writeEndElement(); // applet-desc } else if (m == Mode.SwingAPP) { xout.writeStartElement("application-desc"); xout.writeAttribute("main-class", applicationClass); xout.writeAttribute("name", appName); for (String a : arguments) { xout.writeStartElement("argument"); xout.writeCharacters(a); xout.writeEndElement(); // argument } xout.writeEndElement(); } else { //JavaFX application //embed fallback application String fallbackApp = FALLBACK_APP.fetchFrom(params); if (fallbackApp != null) { xout.writeStartElement("applet-desc"); xout.writeAttribute("width", Integer.toString(width)); xout.writeAttribute("height", Integer.toString(height)); xout.writeAttribute("main-class", fallbackApp); xout.writeAttribute("name", appName); xout.writeStartElement("param"); xout.writeAttribute("name", "requiredFXVersion"); xout.writeAttribute("value", FX_PLATFORM.fetchFrom(params)); xout.writeEndElement(); // param xout.writeEndElement(); // applet-desc } xout.writeStartElement("jfx", "javafx-desc", JFX_NS_URI); xout.writeAttribute("width", Integer.toString(width)); xout.writeAttribute("height", Integer.toString(height)); xout.writeAttribute("main-class", applicationClass); xout.writeAttribute("name", appName); if (preloader != null) { xout.writeAttribute("preloader-class", preloader); } if (appParams != null) { for (Map.Entry appParamEntry : appParams.entrySet()) { xout.writeStartElement("param"); xout.writeAttribute("name", appParamEntry.getKey()); if (appParamEntry.getValue() != null) { xout.writeAttribute("value", appParamEntry.getValue()); } xout.writeEndElement(); // param } } if (arguments != null) { for (String a : arguments) { xout.writeStartElement("argument"); xout.writeCharacters(a); xout.writeEndElement(); // argument } } xout.writeEndElement(); //javafx-desc } } xout.writeEndElement(); // jnlp // now pretty print String s = baos.toString(); out.println(xmlPrettyPrint(s)); } catch (XMLStreamException | TransformerException e) { e.printStackTrace(); } } private String xmlPrettyPrint(String s) throws TransformerException { // System.out.println(s); TransformerFactory factory = TransformerFactory.newInstance(); Transformer transformer = factory.newTransformer(); transformer.setOutputProperty(OutputKeys.INDENT, "yes"); transformer.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "4"); StringWriter formattedStringWriter = new StringWriter(); transformer.transform(new StreamSource(new StringReader(s)), new StreamResult(formattedStringWriter)); return formattedStringWriter.toString(); } private void addToList(List l, String name, String value, boolean isString) { if (isString) { l.add(name + " : '" + value.replaceAll("(['\"\\\\])", "\\\\$1") + "'"); } else { l.add(name + " : " + value); } } 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[]) { return Base64.getEncoder().encodeToString(inp); } private void generateHTML(Map params, PrintStream theOut, 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; try { XMLOutputFactory xmlOutputFactory = XMLOutputFactory.newFactory(); ByteArrayOutputStream baos = new ByteArrayOutputStream(); XMLStreamWriter xout = xmlOutputFactory.createXMLStreamWriter(baos, "utf-8"); String appletParams = getAppletParameters(params); String jnlp_content_browser = null; String jnlp_content_webstart = null; boolean embedJNLP = EMBED_JNLP.fetchFrom(params); boolean includeDT = INCLUDE_DT.fetchFrom(params); if (embedJNLP) { jnlp_content_browser = encodeAsBase64(jnlp_bytes_browser); jnlp_content_webstart = encodeAsBase64(jnlp_bytes_webstart); } xout.writeStartElement("html"); xout.writeStartElement("head"); String dtURL = includeDT ? EMBEDDED_DT : PUBLIC_DT; if (templateStrings != null) { templateStrings.put(TemplatePlaceholders.SCRIPT_URL, dtURL); ByteArrayOutputStream baos2 = new ByteArrayOutputStream(); XMLStreamWriter xo2 = xmlOutputFactory.createXMLStreamWriter(baos2, "utf-8"); xo2.writeStartElement("SCRIPT"); xo2.writeAttribute("src", dtURL); xo2.writeEndElement(); xo2.close(); templateStrings.put(TemplatePlaceholders.SCRIPT_CODE, baos2.toString()); } xout.writeStartElement("SCRIPT"); xout.writeAttribute("src", dtURL); xout.writeEndElement(); 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", FX_PLATFORM.fetchFrom(params), true); String vmargs = getJvmArguments(params, true); if (vmargs != null) { addToList(w_platform, "jvmargs", vmargs, true); } if (!"".equals(appletParams)) { addToList(w_app, "params", "{" + appletParams + "}", false); } for (Map.Entry callbackEntry : JS_CALLBACKS.fetchFrom(params).entrySet()) { addToList(w_callback, callbackEntry.getKey(), callbackEntry.getValue(), false); } //prepare content of launchApp function StringBuilder out_launch_code = new StringBuilder(); 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"); xout.writeStartElement("script"); xout.writeCharacters("\n" + poff + "function launchApplication(jnlpfile) {\n"); xout.writeCharacters(out_launch_code.toString()); xout.writeCharacters(poff2 + "return false;\n"); xout.writeCharacters(poff + "}\n"); xout.writeEndElement(); if (templateStrings != null) { templateStrings.put(TemplatePlaceholders.LAUNCH_CODE, out_launch_code.toString()); } //applet deployment String appId = IDENTIFIER.fetchFrom(params); String placeholder = PLACEHOLDER.fetchFrom(params); //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); } boolean isSwingApp = SWING_APP.fetchFrom(params); if (isSwingApp) { addToList(p_app, "toolkit", "swing", true); } addToList(p_app, "url", jnlpfile_browser, true); addToList(p_app, "placeholder", placeholder, true); addToList(p_app, "width", EMBEDDED_WIDTH.fetchFrom(params), true); addToList(p_app, "height", EMBEDDED_HEIGHT.fetchFrom(params), true); if (jnlp_content_browser != null) { addToList(p_app, "jnlp_content", jnlp_content_browser, true); } addToList(p_platform, "javafx", FX_PLATFORM.fetchFrom(params), true); if (vmargs != null) { addToList(p_platform, "jvmargs", vmargs, true); } for (Map.Entry callbackEntry : JS_CALLBACKS.fetchFrom(params).entrySet()) { addToList(w_callback, callbackEntry.getKey(), callbackEntry.getValue(), 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; for (String callback : JS_CALLBACKS.fetchFrom(params).keySet()) { if ("onGetSplash".equals(callback)) { needOnGetSplashImpl = false; } } if (needOnGetSplashImpl) { addToList(p_callback, "onGetSplash", "function() {}", false); } } StringBuilder out_embed_dynamic = new StringBuilder(); 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" + IDENTIFIER.fetchFrom(params); ByteArrayOutputStream baos_embed_onload = new ByteArrayOutputStream(); XMLStreamWriter xo_embed_onload = xmlOutputFactory.createXMLStreamWriter(baos_embed_onload, "utf-8"); writeEmbeddedDynamic(out_embed_dynamic, embedFuncName, xo_embed_onload); xo_embed_onload.close(); String out_embed_onload = xmlPrettyPrint(baos_embed_onload.toString()); if (templateStrings != null) { templateStrings.put( TemplatePlaceholders.EMBED_CODE_ONLOAD, out_embed_onload); templateStrings.put( TemplatePlaceholders.EMBED_CODE_DYNAMIC, out_embed_dynamic.toString()); } writeEmbeddedDynamic(out_embed_dynamic, embedFuncName, xout); xout.writeEndElement(); //head xout.writeStartElement("body"); xout.writeStartElement("h2"); xout.writeCharacters("Test page for "); xout.writeStartElement("b"); xout.writeCharacters(APP_NAME.fetchFrom(params)); xout.writeEndElement(); // b xout.writeEndElement(); // h2 xout.writeStartElement("b"); xout.writeCharacters("Webstart:"); xout.writeEndElement(); xout.writeStartElement("a"); xout.writeAttribute("href", jnlpfile_webstart); xout.writeAttribute("onclick", "return launchApplication('" + jnlpfile_webstart + "');"); xout.writeCharacters("click to launch this app as webstart"); xout.writeEndElement(); // a xout.writeEmptyElement("br"); xout.writeEmptyElement("hr"); xout.writeEmptyElement("br"); xout.writeCharacters("\n"); xout.writeComment(" Applet will be inserted here "); xout.writeStartElement("div"); xout.writeAttribute("id", placeholder); xout.writeEndElement(); //div xout.writeEndElement(); // body xout.writeEndElement(); // html xout.close(); theOut.print(xmlPrettyPrint(baos.toString())); } catch (XMLStreamException | TransformerException e) { e.printStackTrace(); } } private void writeEmbeddedDynamic(StringBuilder out_embed_dynamic, String embedFuncName, XMLStreamWriter xo_embed_onload) throws XMLStreamException { xo_embed_onload.writeStartElement("script"); xo_embed_onload.writeCharacters("\n function "); xo_embed_onload.writeCharacters(embedFuncName); xo_embed_onload.writeCharacters("() {\n "); xo_embed_onload.writeCharacters(out_embed_dynamic.toString()); xo_embed_onload.writeCharacters(" }\n "); xo_embed_onload.writeComment( " Embed FX application into web page once page is loaded "); xo_embed_onload.writeCharacters("\n dtjava.addOnloadCallback("); xo_embed_onload.writeCharacters(embedFuncName); xo_embed_onload.writeCharacters(");\n"); xo_embed_onload.writeEndElement(); } private void save(File outdir, String fname, byte[] content) throws IOException { save(new File(outdir, 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 isa, File fout) throws PackagerException { final File outDir = fout.getParentFile(); if (!outDir.exists() && !outDir.mkdirs()) { throw new PackagerException("ERR_CreatingDirFailed", outDir.getPath()); } try (InputStream is = isa; OutputStream 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()); } } private String getAppletParameters(Map params) { StringBuilder result = new StringBuilder(); boolean addComma = false; for (Map.Entry entry : ESCAPED_APPLET_PARAMS.fetchFrom(params).entrySet()) { if (addComma) { result.append(", "); } addComma = true; result.append("'") .append(quoteEscape(entry.getKey())) .append("' : '") .append(quoteEscape(entry.getValue())) .append("'"); } for (Map.Entry entry : APPLET_PARAMS.fetchFrom(params).entrySet()) { if (addComma) { result.append(", "); } addComma = true; result.append("'") .append(quoteEscape(entry.getKey())) .append("' : ") .append(entry.getValue()); } return result.toString(); } String quoteEscape(String s) { return s.replaceAll("(['\"\\\\])", "\\\\$1"); } 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 = "dtoolkit/resources/web-files/"; private boolean extractWebFiles(File outDir) throws PackagerException { return doExtractWebFiles(webFiles, outDir, webfilesDir); } private boolean doExtractWebFiles(String lst[], File outDir, String webFilesDir) throws PackagerException { File f = new File(outDir, webFilesDir); f.mkdirs(); for (String s: lst) { InputStream is = PackagerResource.class.getResourceAsStream(prefixWebFiles+s); if (is == null) { System.err.println("Internal error. Missing resources [" + (prefixWebFiles+s) + "]"); return false; } else { copyFileToOutDir(is, new File(f, s)); } } return true; } }