< prev index next >

src/jdk.incubator.jpackage/macosx/classes/jdk/incubator/jpackage/internal/MacAppImageBuilder.java

Print this page

        

@@ -21,22 +21,16 @@
  * 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 jdk.jpackage.internal;
+package jdk.incubator.jpackage.internal;
 
-import java.io.BufferedWriter;
 import java.io.File;
 import java.io.FileInputStream;
-import java.io.FileOutputStream;
-import java.io.FileWriter;
 import java.io.IOException;
 import java.io.InputStream;
-import java.io.OutputStream;
-import java.io.OutputStreamWriter;
-import java.io.UncheckedIOException;
 import java.io.Writer;
 import java.math.BigInteger;
 import java.nio.file.Files;
 import java.nio.file.Path;
 import java.nio.file.StandardCopyOption;

@@ -52,71 +46,75 @@
 import java.util.Optional;
 import java.util.ResourceBundle;
 import java.util.Set;
 import java.util.concurrent.atomic.AtomicReference;
 import java.util.function.Consumer;
-
-import static jdk.jpackage.internal.StandardBundlerParam.*;
-import static jdk.jpackage.internal.MacBaseInstallerBundler.*;
-import static jdk.jpackage.internal.MacAppBundler.*;
+import java.util.stream.Stream;
+import javax.xml.parsers.DocumentBuilder;
+import javax.xml.parsers.DocumentBuilderFactory;
+import javax.xml.xpath.XPath;
+import javax.xml.xpath.XPathConstants;
+import javax.xml.xpath.XPathFactory;
+
+import static jdk.incubator.jpackage.internal.StandardBundlerParam.*;
+import static jdk.incubator.jpackage.internal.MacBaseInstallerBundler.*;
+import static jdk.incubator.jpackage.internal.MacAppBundler.*;
+import static jdk.incubator.jpackage.internal.OverridableResource.createResource;
 
 public class MacAppImageBuilder extends AbstractAppImageBuilder {
 
     private static final ResourceBundle I18N = ResourceBundle.getBundle(
-            "jdk.jpackage.internal.resources.MacResources");
+            "jdk.incubator.jpackage.internal.resources.MacResources");
 
     private static final String LIBRARY_NAME = "libapplauncher.dylib";
-    private static final String TEMPLATE_BUNDLE_ICON = "GenericApp.icns";
+    private static final String TEMPLATE_BUNDLE_ICON = "java.icns";
     private static final String OS_TYPE_CODE = "APPL";
     private static final String TEMPLATE_INFO_PLIST_LITE =
             "Info-lite.plist.template";
     private static final String TEMPLATE_RUNTIME_INFO_PLIST =
             "Runtime-Info.plist.template";
 
     private final Path root;
     private final Path contentsDir;
-    private final Path javaDir;
+    private final Path appDir;
     private final Path javaModsDir;
     private final Path resourcesDir;
     private final Path macOSDir;
     private final Path runtimeDir;
     private final Path runtimeRoot;
     private final Path mdir;
 
-    private final Map<String, ? super Object> params;
-
     private static List<String> keyChains;
 
     public static final BundlerParamInfo<Boolean>
             MAC_CONFIGURE_LAUNCHER_IN_PLIST = new StandardBundlerParam<>(
                     "mac.configure-launcher-in-plist",
                     Boolean.class,
                     params -> Boolean.FALSE,
                     (s, p) -> Boolean.valueOf(s));
 
-    public static final EnumeratedBundlerParam<String> MAC_CATEGORY =
-            new EnumeratedBundlerParam<>(
-                    Arguments.CLIOptions.MAC_APP_STORE_CATEGORY.getId(),
-                    String.class,
-                    params -> "Unknown",
-                    (s, p) -> s,
-                    MacAppBundler.getMacCategories(),
-                    false //strict - for MacStoreBundler this should be strict
-            );
-
     public static final BundlerParamInfo<String> MAC_CF_BUNDLE_NAME =
             new StandardBundlerParam<>(
-                    "mac.CFBundleName",
+                    Arguments.CLIOptions.MAC_BUNDLE_NAME.getId(),
                     String.class,
                     params -> null,
                     (s, p) -> s);
 
     public static final BundlerParamInfo<String> MAC_CF_BUNDLE_IDENTIFIER =
             new StandardBundlerParam<>(
                     Arguments.CLIOptions.MAC_BUNDLE_IDENTIFIER.getId(),
                     String.class,
-                    IDENTIFIER::fetchFrom,
+                    params -> {
+                        // Get identifier from app image if user provided
+                        // app image and did not provide the identifier via CLI.
+                        String identifier = extractBundleIdentifier(params);
+                        if (identifier != null) {
+                            return identifier;
+                        }
+
+                        return IDENTIFIER.fetchFrom(params);
+                    },
                     (s, p) -> s);
 
     public static final BundlerParamInfo<String> MAC_CF_BUNDLE_VERSION =
             new StandardBundlerParam<>(
                     "mac.CFBundleVersion",

@@ -129,17 +127,10 @@
                             return "100";
                         }
                     },
                     (s, p) -> s);
 
-    public static final BundlerParamInfo<String> DEFAULT_ICNS_ICON =
-            new StandardBundlerParam<>(
-            ".mac.default.icns",
-            String.class,
-            params -> TEMPLATE_BUNDLE_ICON,
-            (s, p) -> s);
-
     public static final BundlerParamInfo<File> ICON_ICNS =
             new StandardBundlerParam<>(
             "icon.icns",
             File.class,
             params -> {

@@ -161,80 +152,37 @@
             // valueOf(null) is false, we actually do want null in some cases
             (s, p) -> (s == null || "null".equalsIgnoreCase(s)) ?
                     null : Boolean.valueOf(s)
         );
 
-    public MacAppImageBuilder(Map<String, Object> config, Path imageOutDir)
+    public MacAppImageBuilder(Map<String, Object> params, Path imageOutDir)
             throws IOException {
-        super(config, imageOutDir.resolve(APP_NAME.fetchFrom(config)
+        super(params, imageOutDir.resolve(APP_NAME.fetchFrom(params)
                 + ".app/Contents/runtime/Contents/Home"));
 
         Objects.requireNonNull(imageOutDir);
 
-        this.params = config;
         this.root = imageOutDir.resolve(APP_NAME.fetchFrom(params) + ".app");
         this.contentsDir = root.resolve("Contents");
-        this.javaDir = contentsDir.resolve("Java");
-        this.javaModsDir = javaDir.resolve("mods");
+        this.appDir = contentsDir.resolve("app");
+        this.javaModsDir = appDir.resolve("mods");
         this.resourcesDir = contentsDir.resolve("Resources");
         this.macOSDir = contentsDir.resolve("MacOS");
         this.runtimeDir = contentsDir.resolve("runtime");
         this.runtimeRoot = runtimeDir.resolve("Contents/Home");
         this.mdir = runtimeRoot.resolve("lib");
-        Files.createDirectories(javaDir);
+        Files.createDirectories(appDir);
         Files.createDirectories(resourcesDir);
         Files.createDirectories(macOSDir);
         Files.createDirectories(runtimeDir);
     }
 
-    public MacAppImageBuilder(Map<String, Object> config, String jreName,
-            Path imageOutDir) throws IOException {
-        super(null, imageOutDir.resolve(jreName + "/Contents/Home"));
-
-        Objects.requireNonNull(imageOutDir);
-
-        this.params = config;
-        this.root = imageOutDir.resolve(jreName );
-        this.contentsDir = root.resolve("Contents");
-        this.javaDir = null;
-        this.javaModsDir = null;
-        this.resourcesDir = null;
-        this.macOSDir = null;
-        this.runtimeDir = this.root;
-        this.runtimeRoot = runtimeDir.resolve("Contents/Home");
-        this.mdir = runtimeRoot.resolve("lib");
-
-        Files.createDirectories(runtimeDir);
-    }
-
     private void writeEntry(InputStream in, Path dstFile) throws IOException {
         Files.createDirectories(dstFile.getParent());
         Files.copy(in, dstFile);
     }
 
-    // chmod ugo+x file
-    private void setExecutable(Path file) {
-        try {
-            Set<PosixFilePermission> perms =
-                    Files.getPosixFilePermissions(file);
-            perms.add(PosixFilePermission.OWNER_EXECUTE);
-            perms.add(PosixFilePermission.GROUP_EXECUTE);
-            perms.add(PosixFilePermission.OTHERS_EXECUTE);
-            Files.setPosixFilePermissions(file, perms);
-        } catch (IOException ioe) {
-            throw new UncheckedIOException(ioe);
-        }
-    }
-
-    private static void createUtf8File(File file, String content)
-        throws IOException {
-        try (OutputStream fout = new FileOutputStream(file);
-             Writer output = new OutputStreamWriter(fout, "UTF-8")) {
-            output.write(content);
-        }
-    }
-
     public static boolean validCFBundleVersion(String v) {
         // CFBundleVersion (String - iOS, OS X) specifies the build version
         // number of the bundle, which identifies an iteration (released or
         // unreleased) of the bundle. The build version number should be a
         // string comprised of three non-negative, period-separated integers

@@ -286,20 +234,21 @@
         return true;
     }
 
     @Override
     public Path getAppDir() {
-        return javaDir;
+        return appDir;
     }
 
     @Override
     public Path getAppModsDir() {
         return javaModsDir;
     }
 
     @Override
-    public void prepareApplicationFiles() throws IOException {
+    public void prepareApplicationFiles(Map<String, ? super Object> params)
+            throws IOException {
         Map<String, ? super Object> originalParams = new HashMap<>(params);
         // Generate PkgInfo
         File pkgInfoFile = new File(contentsDir.toFile(), "PkgInfo");
         pkgInfoFile.createNewFile();
         writePkgInfo(pkgInfoFile);

@@ -315,11 +264,11 @@
             writeEntry(is_lib, macOSDir.resolve(LIBRARY_NAME));
         }
         executable.toFile().setExecutable(true, false);
         // generate main app launcher config file
         File cfg = new File(root.toFile(), getLauncherCfgName(params));
-        writeCfgFile(params, cfg, "$APPDIR/runtime");
+        writeCfgFile(params, cfg);
 
         // create additional app launcher(s) and config file(s)
         List<Map<String, ? super Object>> entryPoints =
                 StandardBundlerParam.ADD_LAUNCHERS.fetchFrom(params);
         for (Map<String, ? super Object> entryPoint : entryPoints) {

@@ -333,29 +282,23 @@
             }
             addExecutable.toFile().setExecutable(true, false);
 
             // add config file for add launcher
             cfg = new File(root.toFile(), getLauncherCfgName(tmp));
-            writeCfgFile(tmp, cfg, "$APPDIR/runtime");
+            writeCfgFile(tmp, cfg);
         }
 
         // Copy class path entries to Java folder
-        copyClassPathEntries(javaDir);
+        copyClassPathEntries(appDir, params);
 
         /*********** Take care of "config" files *******/
-        File icon = ICON_ICNS.fetchFrom(params);
 
-        InputStream in = locateResource(
-                APP_NAME.fetchFrom(params) + ".icns",
-                "icon",
-                DEFAULT_ICNS_ICON.fetchFrom(params),
-                icon,
-                VERBOSE.fetchFrom(params),
-                RESOURCE_DIR.fetchFrom(params));
-        Files.copy(in,
-                resourcesDir.resolve(APP_NAME.fetchFrom(params) + ".icns"),
-                StandardCopyOption.REPLACE_EXISTING);
+        createResource(TEMPLATE_BUNDLE_ICON, params)
+                .setCategory("icon")
+                .setExternal(ICON_ICNS.fetchFrom(params))
+                .saveToFile(resourcesDir.resolve(APP_NAME.fetchFrom(params)
+                        + ".icns"));
 
         // copy file association icons
         for (Map<String, ?
                 super Object> fa : FILE_ASSOCIATIONS.fetchFrom(params)) {
             File f = FA_ICON.fetchFrom(fa);

@@ -365,27 +308,35 @@
                 }
 
             }
         }
 
-        copyRuntimeFiles();
-        sign();
+        copyRuntimeFiles(params);
+        sign(params);
     }
 
     @Override
-    public void prepareJreFiles() throws IOException {
-        copyRuntimeFiles();
-        sign();
+    public void prepareJreFiles(Map<String, ? super Object> params)
+            throws IOException {
+        copyRuntimeFiles(params);
+        sign(params);
     }
 
-    private void copyRuntimeFiles() throws IOException {
+    @Override
+    File getRuntimeImageDir(File runtimeImageTop) {
+        File home = new File(runtimeImageTop, "Contents/Home");
+        return (home.exists() ? home : runtimeImageTop);
+    }
+
+    private void copyRuntimeFiles(Map<String, ? super Object> params)
+            throws IOException {
         // Generate Info.plist
-        writeInfoPlist(contentsDir.resolve("Info.plist").toFile());
+        writeInfoPlist(contentsDir.resolve("Info.plist").toFile(), params);
 
         // generate java runtime info.plist
         writeRuntimeInfoPlist(
-                runtimeDir.resolve("Contents/Info.plist").toFile());
+                runtimeDir.resolve("Contents/Info.plist").toFile(), params);
 
         // copy library
         Path runtimeMacOSDir = Files.createDirectories(
                 runtimeDir.resolve("Contents/MacOS"));
 

@@ -396,11 +347,11 @@
         }
 
         Files.copy(jli, runtimeMacOSDir.resolve("libjli.dylib"));
     }
 
-    private void sign() throws IOException {
+    private void sign(Map<String, ? super Object> params) throws IOException {
         if (Optional.ofNullable(
                 SIGN_BUNDLE.fetchFrom(params)).orElse(Boolean.TRUE)) {
             try {
                 addNewKeychain(params);
             } catch (InterruptedException e) {

@@ -422,15 +373,17 @@
         } else {
             return MAIN_CLASS.fetchFrom(params);
         }
     }
 
-    public static String getLauncherCfgName(Map<String, ? super Object> p) {
-        return "Contents/Java/" + APP_NAME.fetchFrom(p) + ".cfg";
+    public static String getLauncherCfgName(
+            Map<String, ? super Object> params) {
+        return "Contents/app/" + APP_NAME.fetchFrom(params) + ".cfg";
     }
 
-    private void copyClassPathEntries(Path javaDirectory) throws IOException {
+    private void copyClassPathEntries(Path javaDirectory,
+            Map<String, ? super Object> params) throws IOException {
         List<RelativeFileSet> resourcesList =
                 APP_RESOURCES_LIST.fetchFrom(params);
         if (resourcesList == null) {
             throw new RuntimeException(
                     I18N.getString("message.null-classpath"));

@@ -462,11 +415,12 @@
             }
             return nm;
         }
     }
 
-    private void writeRuntimeInfoPlist(File file) throws IOException {
+    private void writeRuntimeInfoPlist(File file,
+            Map<String, ? super Object> params) throws IOException {
         Map<String, String> data = new HashMap<>();
         String identifier = StandardBundlerParam.isRuntimeInstaller(params) ?
                 MAC_CF_BUNDLE_IDENTIFIER.fetchFrom(params) :
                 "com.oracle.java." + MAC_CF_BUNDLE_IDENTIFIER.fetchFrom(params);
         data.put("CF_BUNDLE_IDENTIFIER", identifier);

@@ -474,21 +428,19 @@
                 getBundleName(params): "Java Runtime Image";
         data.put("CF_BUNDLE_NAME", name);
         data.put("CF_BUNDLE_VERSION", VERSION.fetchFrom(params));
         data.put("CF_BUNDLE_SHORT_VERSION_STRING", VERSION.fetchFrom(params));
 
-        Writer w = new BufferedWriter(new FileWriter(file));
-        w.write(preprocessTextResource("Runtime-Info.plist",
-                I18N.getString("resource.runtime-info-plist"),
-                TEMPLATE_RUNTIME_INFO_PLIST,
-                data,
-                VERBOSE.fetchFrom(params),
-                RESOURCE_DIR.fetchFrom(params)));
-        w.close();
+        createResource(TEMPLATE_RUNTIME_INFO_PLIST, params)
+                .setPublicName("Runtime-Info.plist")
+                .setCategory(I18N.getString("resource.runtime-info-plist"))
+                .setSubstitutionData(data)
+                .saveToFile(file);
     }
 
-    private void writeInfoPlist(File file) throws IOException {
+    private void writeInfoPlist(File file, Map<String, ? super Object> params)
+            throws IOException {
         Log.verbose(MessageFormat.format(I18N.getString(
                 "message.preparing-info-plist"), file.getAbsolutePath()));
 
         //prepare config for exe
         //Note: do not need CFBundleDisplayName if we don't support localization

@@ -500,18 +452,16 @@
                 getBundleName(params));
         data.put("DEPLOY_BUNDLE_COPYRIGHT",
                 COPYRIGHT.fetchFrom(params) != null ?
                 COPYRIGHT.fetchFrom(params) : "Unknown");
         data.put("DEPLOY_LAUNCHER_NAME", getLauncherName(params));
-        data.put("DEPLOY_JAVA_RUNTIME_NAME", "$APPDIR/runtime");
         data.put("DEPLOY_BUNDLE_SHORT_VERSION",
                 VERSION.fetchFrom(params) != null ?
                 VERSION.fetchFrom(params) : "1.0.0");
         data.put("DEPLOY_BUNDLE_CFBUNDLE_VERSION",
                 MAC_CF_BUNDLE_VERSION.fetchFrom(params) != null ?
                 MAC_CF_BUNDLE_VERSION.fetchFrom(params) : "100");
-        data.put("DEPLOY_BUNDLE_CATEGORY", MAC_CATEGORY.fetchFrom(params));
 
         boolean hasMainJar = MAIN_JAR.fetchFrom(params) != null;
         boolean hasMainModule =
                 StandardBundlerParam.MODULE.fetchFrom(params) != null;
 

@@ -550,18 +500,12 @@
 
         newline = "";
 
         data.put("DEPLOY_LAUNCHER_CLASS", MAIN_CLASS.fetchFrom(params));
 
-        StringBuilder macroedPath = new StringBuilder();
-        for (String s : CLASSPATH.fetchFrom(params).split("[ ;:]+")) {
-            macroedPath.append(s);
-            macroedPath.append(":");
-        }
-        macroedPath.deleteCharAt(macroedPath.length() - 1);
-
-        data.put("DEPLOY_APP_CLASSPATH", macroedPath.toString());
+        data.put("DEPLOY_APP_CLASSPATH",
+                  getCfgClassPath(CLASSPATH.fetchFrom(params)));
 
         StringBuilder bundleDocumentTypes = new StringBuilder();
         StringBuilder exportedTypes = new StringBuilder();
         for (Map<String, ? super Object>
                 fileAssociation : FILE_ASSOCIATIONS.fetchFrom(params)) {

@@ -576,11 +520,11 @@
             List<String> mimeTypes = FA_CONTENT_TYPE.fetchFrom(fileAssociation);
             String itemContentType = MAC_CF_BUNDLE_IDENTIFIER.fetchFrom(params)
                     + "." + ((extensions == null || extensions.isEmpty())
                     ? "mime" : extensions.get(0));
             String description = FA_DESCRIPTION.fetchFrom(fileAssociation);
-            File icon = FA_ICON.fetchFrom(fileAssociation); //TODO FA_ICON_ICNS
+            File icon = FA_ICON.fetchFrom(fileAssociation);
 
             bundleDocumentTypes.append("    <dict>\n")
                     .append("      <key>LSItemContentTypes</key>\n")
                     .append("      <array>\n")
                     .append("        <string>")

@@ -687,27 +631,22 @@
         } else {
             associationData = "";
         }
         data.put("DEPLOY_FILE_ASSOCIATIONS", associationData);
 
-
-        Writer w = new BufferedWriter(new FileWriter(file));
-        w.write(preprocessTextResource(
-                // getConfig_InfoPlist(params).getName(),
-                "Info.plist",
-                I18N.getString("resource.app-info-plist"),
-                TEMPLATE_INFO_PLIST_LITE,
-                data, VERBOSE.fetchFrom(params),
-                RESOURCE_DIR.fetchFrom(params)));
-        w.close();
+        createResource(TEMPLATE_INFO_PLIST_LITE, params)
+                .setCategory(I18N.getString("resource.app-info-plist"))
+                .setSubstitutionData(data)
+                .setPublicName("Info.plist")
+                .saveToFile(file);
     }
 
     private void writePkgInfo(File file) throws IOException {
         //hardcoded as it does not seem we need to change it ever
         String signature = "????";
 
-        try (Writer out = new BufferedWriter(new FileWriter(file))) {
+        try (Writer out = Files.newBufferedWriter(file.toPath())) {
             out.write(OS_TYPE_CODE + signature);
             out.flush();
         }
     }
 

@@ -759,11 +698,11 @@
 
         args.addAll(keyChains);
         args.add(keyChain);
 
         ProcessBuilder  pb = new ProcessBuilder(args);
-        IOUtils.exec(pb, false);
+        IOUtils.exec(pb);
     }
 
     public static void restoreKeychainList(Map<String, ? super Object> params)
             throws IOException{
         if (Platform.getMajorVersion() < 10 ||

@@ -783,11 +722,11 @@
         args.add("-s");
 
         args.addAll(keyChains);
 
         ProcessBuilder  pb = new ProcessBuilder(args);
-        IOUtils.exec(pb, false);
+        IOUtils.exec(pb);
     }
 
     public static void signAppBundle(
             Map<String, ? super Object> params, Path appLocation,
             String signingIdentity, String identifierPrefix,

@@ -796,42 +735,44 @@
         AtomicReference<IOException> toThrow = new AtomicReference<>();
         String appExecutable = "/Contents/MacOS/" + APP_NAME.fetchFrom(params);
         String keyChain = SIGNING_KEYCHAIN.fetchFrom(params);
 
         // sign all dylibs and jars
-        Files.walk(appLocation)
-                // fix permissions
-                .peek(path -> {
+        try (Stream<Path> stream = Files.walk(appLocation)) {
+            stream.peek(path -> { // fix permissions
                     try {
                         Set<PosixFilePermission> pfp =
                             Files.getPosixFilePermissions(path);
                         if (!pfp.contains(PosixFilePermission.OWNER_WRITE)) {
                             pfp = EnumSet.copyOf(pfp);
                             pfp.add(PosixFilePermission.OWNER_WRITE);
                             Files.setPosixFilePermissions(path, pfp);
                         }
                     } catch (IOException e) {
-                        Log.debug(e);
+                    Log.verbose(e);
                     }
-                })
-                .filter(p -> Files.isRegularFile(p) &&
-                        !(p.toString().contains("/Contents/MacOS/libjli.dylib")
-                        || p.toString().contains(
-                                "/Contents/MacOS/JavaAppletPlugin")
-                        || p.toString().endsWith(appExecutable))
-                ).forEach(p -> {
+            }).filter(p -> Files.isRegularFile(p)
+                      && !(p.toString().contains("/Contents/MacOS/libjli.dylib")
+                      || p.toString().endsWith(appExecutable)
+                      || p.toString().contains("/Contents/runtime")
+                      || p.toString().contains("/Contents/Frameworks"))).forEach(p -> {
             //noinspection ThrowableResultOfMethodCallIgnored
             if (toThrow.get() != null) return;
 
             // If p is a symlink then skip the signing process.
             if (Files.isSymbolicLink(p)) {
                 if (VERBOSE.fetchFrom(params)) {
                     Log.verbose(MessageFormat.format(I18N.getString(
                             "message.ignoring.symlink"), p.toString()));
                 }
+                } else {
+                    if (p.toString().endsWith(LIBRARY_NAME)) {
+                        if (isFileSigned(p)) {
+                            return;
             }
-            else {
+                    }
+
                 List<String> args = new ArrayList<>();
                 args.addAll(Arrays.asList("codesign",
                         "-s", signingIdentity, // sign with this key
                         "--prefix", identifierPrefix,
                                 // use the identifier as a prefix

@@ -858,19 +799,19 @@
                             Files.getPosixFilePermissions(p);
                     File f = p.toFile();
                     f.setWritable(true, true);
 
                     ProcessBuilder pb = new ProcessBuilder(args);
-                    IOUtils.exec(pb, false);
+                        IOUtils.exec(pb);
 
                     Files.setPosixFilePermissions(p, oldPermissions);
                 } catch (IOException ioe) {
                     toThrow.set(ioe);
                 }
             }
         });
-
+        }
         IOException ioe = toThrow.get();
         if (ioe != null) {
             throw ioe;
         }
 

@@ -890,11 +831,11 @@
                     args.add("--keychain");
                     args.add(keyChain);
                 }
                 args.add(path.toString());
                 ProcessBuilder pb = new ProcessBuilder(args);
-                IOUtils.exec(pb, false);
+                IOUtils.exec(pb);
 
                 args = new ArrayList<>();
                 args.addAll(Arrays.asList("codesign",
                         "-s", signingIdentity, // sign with this key
                         "--prefix", identifierPrefix,

@@ -905,20 +846,19 @@
                     args.add(keyChain);
                 }
                 args.add(path.toString()
                         + "/Contents/_CodeSignature/CodeResources");
                 pb = new ProcessBuilder(args);
-                IOUtils.exec(pb, false);
+                IOUtils.exec(pb);
             } catch (IOException e) {
                 toThrow.set(e);
             }
         };
 
         Path javaPath = appLocation.resolve("Contents/runtime");
         if (Files.isDirectory(javaPath)) {
-            Files.list(javaPath)
-                    .forEach(signIdentifiedByPList);
+            signIdentifiedByPList.accept(javaPath);
 
             ioe = toThrow.get();
             if (ioe != null) {
                 throw ioe;
             }

@@ -949,9 +889,57 @@
         }
         args.add(appLocation.toString());
 
         ProcessBuilder pb =
                 new ProcessBuilder(args.toArray(new String[args.size()]));
-        IOUtils.exec(pb, false);
+        IOUtils.exec(pb);
+    }
+
+    private static boolean isFileSigned(Path file) {
+        ProcessBuilder pb =
+                new ProcessBuilder("codesign", "--verify", file.toString());
+
+        try {
+            IOUtils.exec(pb);
+        } catch (IOException ex) {
+            return false;
+        }
+
+        return true;
+    }
+
+    private static String extractBundleIdentifier(Map<String, Object> params) {
+        if (PREDEFINED_APP_IMAGE.fetchFrom(params) == null) {
+            return null;
+        }
+
+        try {
+            File infoPList = new File(PREDEFINED_APP_IMAGE.fetchFrom(params) +
+                                      File.separator + "Contents" +
+                                      File.separator + "Info.plist");
+
+            DocumentBuilderFactory dbf
+                    = DocumentBuilderFactory.newDefaultInstance();
+            dbf.setFeature("http://apache.org/xml/features/" +
+                           "nonvalidating/load-external-dtd", false);
+            DocumentBuilder b = dbf.newDocumentBuilder();
+            org.w3c.dom.Document doc = b.parse(new FileInputStream(
+                    infoPList.getAbsolutePath()));
+
+            XPath xPath = XPathFactory.newInstance().newXPath();
+            // Query for the value of <string> element preceding <key>
+            // element with value equal to CFBundleIdentifier
+            String v = (String) xPath.evaluate(
+                    "//string[preceding-sibling::key = \"CFBundleIdentifier\"][1]",
+                    doc, XPathConstants.STRING);
+
+            if (v != null && !v.isEmpty()) {
+                return v;
+            }
+        } catch (Exception ex) {
+            Log.verbose(ex);
+        }
+
+        return null;
     }
 
 }
< prev index next >