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