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 
  29 import jdk.packager.internal.JLinkBundlerHelper;
  30 import com.oracle.tools.packager.RelativeFileSet;
  31 
  32 import com.oracle.tools.packager.Log;
  33 import com.oracle.tools.packager.StandardBundlerParam;
  34 import jdk.packager.internal.Module;
  35 
  36 import java.io.ByteArrayOutputStream;
  37 import java.io.File;
  38 import java.io.FileInputStream;
  39 import java.io.FileOutputStream;
  40 import java.io.IOException;
  41 import java.io.InputStream;
  42 import java.io.PrintStream;
  43 import java.nio.file.Files;
  44 import java.nio.file.Path;
  45 import java.text.MessageFormat;
  46 import java.util.Arrays;
  47 import java.util.List;
  48 import java.util.Map;
  49 import java.util.ResourceBundle;
  50 import java.util.Set;
  51 
  52 import static com.oracle.tools.packager.StandardBundlerParam.*;
  53 import static com.oracle.tools.packager.StandardBundlerParam.ARGUMENTS;
  54 import static com.oracle.tools.packager.StandardBundlerParam.USER_JVM_OPTIONS;
  55 import java.util.ArrayList;
  56 
  57 
  58 public abstract class AbstractAppImageBuilder {
  59 
  60     private static final ResourceBundle I18N =
  61             ResourceBundle.getBundle(AbstractAppImageBuilder.class.getName());
  62 
  63     //do not use file separator -
  64     // we use it for classpath lookup and there / are not platform specific
  65     public final static String BUNDLER_PREFIX = "package/";
  66 
  67     private Map<String, Object> properties;
  68     private Path root;
  69     protected List<String> excludeFileList = new ArrayList<>();
  70 
  71     public AbstractAppImageBuilder(Map<String, Object> properties, Path root) throws IOException {
  72         this.properties = properties;
  73         this.root = root;
  74         excludeFileList.add(".*\\.diz");
  75     }
  76 
  77     public abstract InputStream getResourceAsStream(String name);
  78     public abstract void prepareApplicationFiles() throws IOException;
  79 
  80     public Map<String, Object> getProperties() {
  81         return this.properties;
  82     }
  83 
  84     public Path getRoot() {
  85         return this.root;
  86     }
  87 
  88     public String getExcludeFileList() {
  89         String result = "";
  90 
  91         for (String item : excludeFileList) {
  92             if (!result.isEmpty()) {
  93                 result += ",";
  94             }
  95 
  96             result += item;
  97         }
  98 
  99         return result;
 100     }
 101 
 102     protected InputStream locateResource(String publicName, String category,
 103                                          String defaultName, File customFile,
 104                                          boolean verbose, File publicRoot) throws IOException {
 105         InputStream is = null;
 106         boolean customFromClasspath = false;
 107         boolean customFromFile = false;
 108         if (publicName != null) {
 109             if (publicRoot != null) {
 110                 File publicResource = new File(publicRoot, publicName);
 111                 if (publicResource.exists() && publicResource.isFile()) {
 112                     is = new FileInputStream(publicResource);
 113                 }
 114             } else {
 115                 is = getResourceAsStream(publicName);
 116             }
 117             customFromClasspath = (is != null);
 118         }
 119         if (is == null && customFile != null) {
 120             is = new FileInputStream(customFile);
 121             customFromFile = (is != null);
 122         }
 123         if (is == null && defaultName != null) {
 124             is = getResourceAsStream(defaultName);
 125         }
 126         String msg = null;
 127         if (customFromClasspath) {
 128             msg = MessageFormat.format(I18N.getString("message.using-custom-resource-from-classpath"), category == null ? "" : "[" + category + "] ", publicName);
 129         } else if (customFromFile) {
 130             msg = MessageFormat.format(I18N.getString("message.using-custom-resource-from-file"), category == null ? "" : "[" + category + "] ", customFile.getAbsoluteFile());
 131         } else if (is != null) {
 132             msg = MessageFormat.format(I18N.getString("message.using-default-resource-from-classpath"), category == null ? "" : "[" + category + "] ", publicName);
 133         } else {
 134             msg = MessageFormat.format(I18N.getString("message.using-default-resource"), category == null ? "" : "[" + category + "] ", publicName);
 135         }
 136         if (verbose) {
 137             Log.info(msg);
 138         }
 139         return is;
 140     }
 141 
 142 
 143     protected String preprocessTextResource(String publicName, String category,
 144                                             String defaultName, Map<String, String> pairs,
 145                                             boolean verbose, File publicRoot) throws IOException {
 146         InputStream inp = locateResource(publicName, category, defaultName, null, verbose, publicRoot);
 147         if (inp == null) {
 148             throw new RuntimeException("Module corrupt? No "+defaultName+" resource!");
 149         }
 150 
 151         try (InputStream is = inp) {
 152             //read fully into memory
 153             ByteArrayOutputStream baos = new ByteArrayOutputStream();
 154             byte[] buffer = new byte[1024];
 155             int length;
 156             while ((length = is.read(buffer)) != -1) {
 157                 baos.write(buffer, 0, length);
 158             }
 159 
 160             //substitute
 161             String result = new String(baos.toByteArray());
 162             for (Map.Entry<String, String> e : pairs.entrySet()) {
 163                 if (e.getValue() != null) {
 164                     result = result.replace(e.getKey(), e.getValue());
 165                 }
 166             }
 167             return result;
 168         }
 169     }
 170 
 171     public void writeCfgFile(Map<String, ? super Object> params, File cfgFileName, String runtimeLocation) throws IOException {
 172         cfgFileName.delete();
 173 
 174         boolean appCDEnabled = UNLOCK_COMMERCIAL_FEATURES.fetchFrom(params) && ENABLE_APP_CDS.fetchFrom(params);
 175         String appCDSCacheMode = APP_CDS_CACHE_MODE.fetchFrom(params);
 176         File mainJar = JLinkBundlerHelper.getMainJar(params);
 177         Module.ModuleType mainJarType = Module.ModuleType.Unknown;
 178 
 179         if (mainJar != null) {
 180             mainJarType = new Module(mainJar).getModuleType();
 181         }
 182 
 183         String mainModule = StandardBundlerParam.MODULE.fetchFrom(params);
 184 
 185         PrintStream out = new PrintStream(cfgFileName);
 186 
 187         out.println("[Application]");
 188         out.println("app.name=" + APP_NAME.fetchFrom(params));
 189         out.println("app.version=" + VERSION.fetchFrom(params));
 190         out.println("app.preferences.id=" + PREFERENCES_ID.fetchFrom(params));
 191         out.println("app.runtime=" + runtimeLocation);
 192         out.println("app.identifier=" + IDENTIFIER.fetchFrom(params));
 193         out.println("app.classpath=" + String.join(File.pathSeparator, CLASSPATH.fetchFrom(params).split("[ :;]")));
 194 
 195         // The main app is required to be a jar, modular or unnamed.
 196         if (mainJarType == Module.ModuleType.Unknown || mainJarType == Module.ModuleType.ModularJar) {
 197             if (mainModule != null) {
 198                 out.println("app.mainmodule=" + mainModule); // TODO get app class from main module mainifest.
 199             }
 200         }
 201         else {
 202             String mainClass = JLinkBundlerHelper.getMainClass(params);
 203 
 204             if (mainJar != null && mainClass != null) {
 205                 // If the app is contained in an unnamed jar then launch it the
 206                 // legacy way and the main class string must be of the format com/foo/Main
 207                 out.println("app.mainclass=" + mainClass.replaceAll("\\.", "/"));
 208                 out.println("app.mainjar=" + mainJar.toPath().getFileName().toString());
 209             }
 210         }
 211 
 212         String version = JLinkBundlerHelper.getJDKVersion(params);
 213 
 214         if (!version.isEmpty()) {
 215             out.println("app.java.version=" + version);
 216         }
 217 
 218         out.println("packager.java.version=" + System.getProperty("java.version"));
 219 
 220         Integer port = JLinkBundlerHelper.DEBUG.fetchFrom(params);
 221 
 222         if (port != null) {
 223             out.println("app.debug=-agentlib:jdwp=transport=dt_socket,server=y,suspend=y,address=localhost:" + port);
 224         }
 225 
 226         if (appCDEnabled) {
 227             out.println("app.appcds.cache=" + appCDSCacheMode.split("\\+")[0]);
 228         }
 229 
 230 
 231         out.println();
 232         out.println("[JVMOptions]");
 233         List<String> jvmargs = JVM_OPTIONS.fetchFrom(params);
 234         for (String arg : jvmargs) {
 235             out.println(arg);
 236         }
 237         Map<String, String> jvmProps = JVM_PROPERTIES.fetchFrom(params);
 238         for (Map.Entry<String, String> property : jvmProps.entrySet()) {
 239             out.println("-D" + property.getKey() + "=" + property.getValue());
 240         }
 241         String preloader = PRELOADER_CLASS.fetchFrom(params);
 242         if (preloader != null) {
 243             out.println("-Djavafx.preloader="+preloader);
 244         }
 245 
 246 
 247         out.println();
 248         out.println("[JVMUserOptions]");
 249         Map<String, String> overridableJVMOptions = USER_JVM_OPTIONS.fetchFrom(params);
 250         for (Map.Entry<String, String> arg: overridableJVMOptions.entrySet()) {
 251             if (arg.getKey() == null || arg.getValue() == null) {
 252                 Log.info(I18N.getString("message.jvm-user-arg-is-null"));
 253             } else {
 254                 out.println(arg.getKey().replaceAll("([\\=])", "\\\\$1") + "=" + arg.getValue());
 255             }
 256         }
 257 
 258         if (appCDEnabled) {
 259             prepareAppCDS(params, out);
 260         }
 261 
 262         out.println();
 263         out.println("[ArgOptions]");
 264         List<String> args = ARGUMENTS.fetchFrom(params);
 265         for (String arg : args) {
 266             if (arg.endsWith("=") && (arg.indexOf("=") == arg.lastIndexOf("="))) {
 267                 out.print(arg.substring(0, arg.length() - 1));
 268                 out.println("\\=");
 269             } else {
 270                 out.println(arg);
 271             }
 272         }
 273 
 274 
 275         out.close();
 276     }
 277 
 278     protected abstract String getCacheLocation(Map<String, ? super Object> params);
 279 
 280     void prepareAppCDS(Map<String, ? super Object> params, PrintStream out) throws IOException {
 281         File tempDir = Files.createTempDirectory("javapackager").toFile();
 282         tempDir.deleteOnExit();
 283         File classList = new File(tempDir, APP_FS_NAME.fetchFrom(params)  + ".classlist");
 284 
 285         try (FileOutputStream fos = new FileOutputStream(classList);
 286              PrintStream ps = new PrintStream(fos)) {
 287             for (String className : APP_CDS_CLASS_ROOTS.fetchFrom(params)) {
 288                 String slashyName = className.replace(".", "/");
 289                 ps.println(slashyName);
 290             }
 291         }
 292         APP_RESOURCES_LIST.fetchFrom(params).add(new RelativeFileSet(classList.getParentFile(), Arrays.asList(classList)));
 293 
 294         out.println();
 295         out.println("[AppCDSJVMOptions]");
 296         out.println("-XX:+UnlockCommercialFeatures");
 297         out.print("-XX:SharedArchiveFile=");
 298         out.print(getCacheLocation(params));
 299         out.print(APP_FS_NAME.fetchFrom(params));
 300         out.println(".jpa");
 301         out.println("-Xshare:auto");
 302         out.println("-XX:+UseAppCDS");
 303         if (Log.isDebug()) {
 304             out.println("-verbose:class");
 305             out.println("-XX:+TraceClassPaths");
 306             out.println("-XX:+UnlockDiagnosticVMOptions");
 307         }
 308         out.println("");
 309 
 310         out.println("[AppCDSGenerateCacheJVMOptions]");
 311         out.println("-XX:+UnlockCommercialFeatures");
 312         out.println("-Xshare:dump");
 313         out.println("-XX:+UseAppCDS");
 314         out.print("-XX:SharedArchiveFile=");
 315         out.print(getCacheLocation(params));
 316         out.print(APP_FS_NAME.fetchFrom(params));
 317         out.println(".jpa");
 318         out.println("-XX:SharedClassListFile=$PACKAGEDIR/" + APP_FS_NAME.fetchFrom(params) + ".classlist");
 319         if (Log.isDebug()) {
 320             out.println("-XX:+UnlockDiagnosticVMOptions");
 321         }
 322     }
 323 }