1 /*
   2  * Copyright (c) 2015, 2016, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.  Oracle designates this
   8  * particular file as subject to the "Classpath" exception as provided
   9  * by Oracle in the LICENSE file that accompanied this code.
  10  *
  11  * This code is distributed in the hope that it will be useful, but WITHOUT
  12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  14  * version 2 for more details (a copy is included in the LICENSE file that
  15  * accompanied this code).
  16  *
  17  * You should have received a copy of the GNU General Public License version
  18  * 2 along with this work; if not, write to the Free Software Foundation,
  19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  20  *
  21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  22  * or visit www.oracle.com if you need additional information or have any
  23  * questions.
  24  */
  25 
  26 package jdk.packager.builders;
  27 
  28 import com.oracle.tools.packager.JLinkBundlerHelper;
  29 import com.oracle.tools.packager.RelativeFileSet;
  30 import jdk.tools.jlink.builder.*;
  31 import jdk.tools.jlink.plugin.Pool;
  32 
  33 import com.oracle.tools.packager.Log;
  34 
  35 import java.io.ByteArrayOutputStream;
  36 import java.io.File;
  37 import java.io.FileInputStream;
  38 import java.io.FileOutputStream;
  39 import java.io.IOException;
  40 import java.io.InputStream;
  41 import java.io.PrintStream;
  42 import java.nio.file.Files;
  43 import java.nio.file.Path;
  44 import java.text.MessageFormat;
  45 import java.util.Arrays;
  46 import java.util.List;
  47 import java.util.Map;
  48 import java.util.ResourceBundle;
  49 import java.util.Set;
  50 
  51 import static com.oracle.tools.packager.StandardBundlerParam.*;
  52 import static com.oracle.tools.packager.StandardBundlerParam.ARGUMENTS;
  53 import static com.oracle.tools.packager.StandardBundlerParam.USER_JVM_OPTIONS;
  54 
  55 /**
  56  *
  57  * Created by dferrin on 9/1/15.
  58  */
  59 public abstract class AbstractAppImageBuilder extends DefaultImageBuilder {
  60 
  61     private static final ResourceBundle I18N =
  62             ResourceBundle.getBundle(AbstractAppImageBuilder.class.getName());
  63 
  64     //do not use file separator -
  65     // we use it for classpath lookup and there / are not platform specific
  66     public final static String BUNDLER_PREFIX = "package/";
  67 
  68     public AbstractAppImageBuilder(Map<String, Object> properties, Path root) throws IOException {
  69         super(true, root);
  70     }
  71 
  72     abstract protected void prepareApplicationFiles(Pool files, Set<String> modules) throws IOException;
  73 
  74     abstract protected InputStream getResourceAsStream(String name);
  75 
  76     protected InputStream locateResource(String publicName, String category,
  77                                          String defaultName, File customFile,
  78                                          boolean verbose, File publicRoot) throws IOException {
  79         InputStream is = null;
  80         boolean customFromClasspath = false;
  81         boolean customFromFile = false;
  82         if (publicName != null) {
  83             if (publicRoot != null) {
  84                 File publicResource = new File(publicRoot, publicName);
  85                 if (publicResource.exists() && publicResource.isFile()) {
  86                     is = new FileInputStream(publicResource);
  87                 }
  88             } else {
  89                 is = getResourceAsStream(publicName);
  90             }
  91             customFromClasspath = (is != null);
  92         }
  93         if (is == null && customFile != null) {
  94             is = new FileInputStream(customFile);
  95             customFromFile = (is != null);
  96         }
  97         if (is == null && defaultName != null) {
  98             is = getResourceAsStream(defaultName);
  99         }
 100         String msg = null;
 101         if (customFromClasspath) {
 102             msg = MessageFormat.format(I18N.getString("message.using-custom-resource-from-classpath"), category == null ? "" : "[" + category + "] ", publicName);
 103         } else if (customFromFile) {
 104             msg = MessageFormat.format(I18N.getString("message.using-custom-resource-from-file"), category == null ? "" : "[" + category + "] ", customFile.getAbsoluteFile());
 105         } else if (is != null) {
 106             msg = MessageFormat.format(I18N.getString("message.using-default-resource-from-classpath"), category == null ? "" : "[" + category + "] ", publicName);
 107         } else {
 108             msg = MessageFormat.format(I18N.getString("message.using-default-resource"), category == null ? "" : "[" + category + "] ", publicName);
 109         }
 110         if (verbose) {
 111             Log.info(msg);
 112         }
 113         return is;
 114     }
 115 
 116 
 117     protected String preprocessTextResource(String publicName, String category,
 118                                             String defaultName, Map<String, String> pairs,
 119                                             boolean verbose, File publicRoot) throws IOException {
 120         InputStream inp = locateResource(publicName, category, defaultName, null, verbose, publicRoot);
 121         if (inp == null) {
 122             throw new RuntimeException("Module corrupt? No "+defaultName+" resource!");
 123         }
 124 
 125         try (InputStream is = inp) {
 126             //read fully into memory
 127             ByteArrayOutputStream baos = new ByteArrayOutputStream();
 128             byte[] buffer = new byte[1024];
 129             int length;
 130             while ((length = is.read(buffer)) != -1) {
 131                 baos.write(buffer, 0, length);
 132             }
 133 
 134             //substitute
 135             String result = new String(baos.toByteArray());
 136             for (Map.Entry<String, String> e : pairs.entrySet()) {
 137                 if (e.getValue() != null) {
 138                     result = result.replace(e.getKey(), e.getValue());
 139                 }
 140             }
 141             return result;
 142         }
 143     }
 144 
 145     public void writeCfgFile(Map<String, ? super Object> params, File cfgFileName, String runtimeLocation) throws IOException {
 146         cfgFileName.delete();
 147 
 148         boolean appCDEnabled = UNLOCK_COMMERCIAL_FEATURES.fetchFrom(params) && ENABLE_APP_CDS.fetchFrom(params);
 149         String appCDSCacheMode = APP_CDS_CACHE_MODE.fetchFrom(params);
 150 
 151         PrintStream out = new PrintStream(cfgFileName);
 152 
 153         out.println("[Application]");
 154         out.println("app.name=" + APP_NAME.fetchFrom(params));
 155         out.println("app.mainjar=" + MAIN_JAR.fetchFrom(params).getIncludedFiles().iterator().next());
 156         out.println("app.version=" + VERSION.fetchFrom(params));
 157         out.println("app.preferences.id=" + PREFERENCES_ID.fetchFrom(params));
 158         out.println("app.mainclass=" +
 159                 MAIN_CLASS.fetchFrom(params).replaceAll("\\.", "/"));
 160         out.println("app.classpath=" +
 161                 String.join(File.pathSeparator, CLASSPATH.fetchFrom(params).split("[ :;]")));
 162         out.println("app.modulepath=" +
 163                 String.join(File.pathSeparator, JLinkBundlerHelper.JDK_MODULE_PATH.fetchFrom(params)));
 164         out.println("app.runtime=" + runtimeLocation);
 165         out.println("app.identifier=" + IDENTIFIER.fetchFrom(params));
 166         if (appCDEnabled) {
 167             out.println("app.appcds.cache=" + appCDSCacheMode.split("\\+")[0]);
 168         }
 169 
 170 
 171         out.println();
 172         out.println("[JVMOptions]");
 173         List<String> jvmargs = JVM_OPTIONS.fetchFrom(params);
 174         for (String arg : jvmargs) {
 175             out.println(arg);
 176         }
 177         Map<String, String> jvmProps = JVM_PROPERTIES.fetchFrom(params);
 178         for (Map.Entry<String, String> property : jvmProps.entrySet()) {
 179             out.println("-D" + property.getKey() + "=" + property.getValue());
 180         }
 181         String preloader = PRELOADER_CLASS.fetchFrom(params);
 182         if (preloader != null) {
 183             out.println("-Djavafx.preloader="+preloader);
 184         }
 185 
 186 
 187         out.println();
 188         out.println("[JVMUserOptions]");
 189         Map<String, String> overridableJVMOptions = USER_JVM_OPTIONS.fetchFrom(params);
 190         for (Map.Entry<String, String> arg: overridableJVMOptions.entrySet()) {
 191             if (arg.getKey() == null || arg.getValue() == null) {
 192                 Log.info(I18N.getString("message.jvm-user-arg-is-null"));
 193             } else {
 194                 out.println(arg.getKey().replaceAll("([\\=])", "\\\\$1") + "=" + arg.getValue());
 195             }
 196         }
 197 
 198         if (appCDEnabled) {
 199             prepareAppCDS(params, out);
 200         }
 201 
 202         out.println();
 203         out.println("[ArgOptions]");
 204         List<String> args = ARGUMENTS.fetchFrom(params);
 205         for (String arg : args) {
 206             if (arg.endsWith("=") && (arg.indexOf("=") == arg.lastIndexOf("="))) {
 207                 out.print(arg.substring(0, arg.length() - 1));
 208                 out.println("\\=");
 209             } else {
 210                 out.println(arg);
 211             }
 212         }
 213 
 214 
 215         out.close();
 216     }
 217 
 218     protected abstract String getCacheLocation(Map<String, ? super Object> params);
 219 
 220     void prepareAppCDS(Map<String, ? super Object> params, PrintStream out) throws IOException {
 221         //TODO check 8u40 or later
 222 
 223         File tempDir = Files.createTempDirectory("javapackager").toFile();
 224         tempDir.deleteOnExit();
 225         File classList = new File(tempDir, APP_FS_NAME.fetchFrom(params)  + ".classlist");
 226 
 227         try (FileOutputStream fos = new FileOutputStream(classList);
 228              PrintStream ps = new PrintStream(fos)) {
 229             for (String className : APP_CDS_CLASS_ROOTS.fetchFrom(params)) {
 230                 String slashyName = className.replace(".", "/");
 231                 ps.println(slashyName);
 232             }
 233         }
 234         APP_RESOURCES_LIST.fetchFrom(params).add(new RelativeFileSet(classList.getParentFile(), Arrays.asList(classList)));
 235 
 236         out.println();
 237         out.println("[AppCDSJVMOptions]");
 238         out.println("-XX:+UnlockCommercialFeatures");
 239         out.print("-XX:SharedArchiveFile=");
 240         out.print(getCacheLocation(params));
 241         out.print(APP_FS_NAME.fetchFrom(params));
 242         out.println(".jpa");
 243         out.println("-Xshare:auto");
 244         out.println("-XX:+UseAppCDS");
 245         if (Log.isDebug()) {
 246             out.println("-verbose:class");
 247             out.println("-XX:+TraceClassPaths");
 248             out.println("-XX:+UnlockDiagnosticVMOptions");
 249         }
 250         out.println("");
 251 
 252         out.println("[AppCDSGenerateCacheJVMOptions]");
 253         out.println("-XX:+UnlockCommercialFeatures");
 254         out.println("-Xshare:dump");
 255         out.println("-XX:+UseAppCDS");
 256         out.print("-XX:SharedArchiveFile=");
 257         out.print(getCacheLocation(params));
 258         out.print(APP_FS_NAME.fetchFrom(params));
 259         out.println(".jpa");
 260         out.println("-XX:SharedClassListFile=$PACKAGEDIR/" + APP_FS_NAME.fetchFrom(params) + ".classlist");
 261         if (Log.isDebug()) {
 262             out.println("-XX:+UnlockDiagnosticVMOptions");
 263         }
 264     }
 265 
 266 
 267 }