blob: 7129fe598f8f3dc804d916431f5022f431ceec51 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.netbeans.nbbuild;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.io.Reader;
import java.io.StringWriter;
import java.io.Writer;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URLDecoder;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import java.util.StringTokenizer;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
import java.util.jar.Manifest;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.zip.ZipEntry;
import javax.xml.parsers.ParserConfigurationException;
import org.apache.tools.ant.BuildException;
import org.apache.tools.ant.DirectoryScanner;
import org.apache.tools.ant.Project;
import org.apache.tools.ant.Task;
import org.apache.tools.ant.taskdefs.Copy;
import org.apache.tools.ant.taskdefs.Delete;
import org.apache.tools.ant.taskdefs.ManifestException;
import org.apache.tools.ant.taskdefs.SignJar;
import org.apache.tools.ant.taskdefs.Zip;
import org.apache.tools.ant.types.FileSet;
import org.apache.tools.ant.types.Resource;
import org.apache.tools.ant.types.ResourceCollection;
import org.apache.tools.ant.types.ZipFileSet;
import org.apache.tools.ant.types.resources.FileResource;
import org.apache.tools.ant.util.FileUtils;
import org.apache.tools.zip.ZipOutputStream;
import org.xml.sax.SAXException;
/** Generates JNLP files for signed versions of the module JAR files.
*
* @author Jaroslav Tulach, Jesse Glick
*/
public class MakeJNLP extends Task {
/** the files to work on */
private ResourceCollection files;
private SignJar signTask;
private static final String MANIFEST = "META-INF/MANIFEST.MF"; //NOI18N
private static final String UTF_8 = "UTF-8"; //NOI18N
private static final String ATTR_CODEBASE = "Codebase"; //NOI18N
private static final String ATTR_PERMISSIONS = "Permissions"; //NOI18N
private static final String ATTR_APPLICATION_NAME = "Application-Name"; //NOI18N
public FileSet createModules()
throws BuildException {
FileSet fs = new FileSet();
fs.setProject(getProject());
addConfigured(fs);
return fs;
}
public void addConfigured(ResourceCollection rc) throws BuildException {
if (files != null) throw new BuildException("modules can be specified just once");
files = rc;
}
private SignJar getSignTask() {
if (signTask == null) {
signTask = (SignJar)getProject().createTask("signjar");
}
return signTask;
}
private File targetFile;
public void setDir(File t) {
targetFile = t;
}
public void setAlias(String a) {
getSignTask().setAlias(a);
}
public void setStorePass(String p) {
getSignTask().setStorepass(p);
}
public void setKeystore(String k) {
getSignTask().setKeystore(k);
}
public void setStoreType(String t) {
getSignTask().setStoretype(t);
}
private String appName;
public void setAppName(String appName) {
this.appName = appName;
}
private final String manifestPermissions = "all-permissions";
private final String manifestCodebase = "*";
private String codebase = "$$codebase";
public void setCodebase(String s) {
this.codebase = s;
}
private boolean verify;
public void setVerify(boolean v) {
this.verify = v;
}
private String verifyExcludes;
/** Comma separated list of allowed excluded names of files during verify
* phase.
*/
public void setVerifyExcludes(String s) {
this.verifyExcludes = s;
}
private String permissions = "<all-permissions/>";
/**
* XML fragment pasted into the security part of the .jnlp file.
* Should default to "&lt;all-permissions/&gt;"
*/
public void setPermissions(String s) {
permissions = s;
}
private FileSet indirectJars;
/**
* Other JARs which should be copied into the destination directory and referred to as resources,
* even though they are not listed as Class-Path extensions of the module and would not normally
* be in its effective classpath. The basedir of the fileset should be a cluster root; for each
* such JAR, a file META-INF/clusterpath/$relpath will be inserted in the JAR, where $relpath is the
* relative path within the cluster. This permits the JAR to be located at runtime in a flat classpath,
* using ClassLoader.getResource.
*/
public void addIndirectJars(FileSet fs) {
indirectJars = fs;
}
private FileSet indirectFiles;
/**
* Other non-JAR files which should be made available to InstalledFileLocator.
* The basedir of the fileset should be a cluster root; each
* such file will be packed into a ZIP entry META-INF/files/$relpath
* where the JAR will be available at runtime in a flat classpath,
* using ClassLoader.getResource.
*/
public void addIndirectFiles(FileSet fs) {
indirectFiles = fs;
}
private boolean signJars = true;
/**
* Whether the final jars should be signed or not. Defaults to true
* (if not supplied).
*/
public void setSignJars(boolean s) {
this.signJars = s;
}
private boolean processJarVersions = false;
/**
* Whether to add versions and sizes of jars into jnlp files. Defaults to false
* (if not supplied).
* @param b
*/
public void setProcessJarVersions(boolean b) {
this.processJarVersions = b;
}
private Map<String, String> jarVersions;
/**
* Explicit definition of jar file versions (for jars without versions specified
* in manifest file)
* @param jarVersions
*/
public void setJarVersions(Map<String, String> jarVersions) {
this.jarVersions = jarVersions;
}
private Set<String> nativeLibraries;
public void setNativeLibraries(Set<String> libs) {
this.nativeLibraries = libs;
}
private Set<File> jarDirectories;
private String includelocales;
public void setIncludelocales(String includelocales) {
this.includelocales = includelocales;
}
/**
* Signs or copies the given files according to the signJars variable value.
*/
private void signOrCopy(File from, File to) {
if (!from.exists() && from.getParentFile().getName().equals("locale")) {
// skip missing locale files, probably the best fix for #103301
log("Localization file " + from + " is referenced, but cannot be found. Skipping.", Project.MSG_WARN);
return;
}
if (signJars) {
getSignTask().setJar(from);
if (to != null) {
// #125970: might be .../modules/locale/something_ja.jar
to.getParentFile().mkdirs();
}
getSignTask().setSignedjar(to);
getSignTask().setDigestAlg("SHA1");
getSignTask().execute();
} else if (to != null) {
Copy copy = (Copy)getProject().createTask("copy");
copy.setFile(from);
copy.setTofile(to);
copy.execute();
}
if (processJarVersions) {
if (jarDirectories == null) {
jarDirectories = new HashSet<>();
}
jarDirectories.add(new File(to.getParent()));
}
}
@Override
public void execute() throws BuildException {
if (targetFile == null) throw new BuildException("Output dir must be provided");
if (files == null) throw new BuildException("modules must be provided");
try {
generateFiles();
if (processJarVersions && jarDirectories!=null && jarDirectories.size() > 0) {
generateVersionXMLFiles();
}
} catch (IOException ex) {
throw new BuildException(ex);
}
}
private void generateFiles() throws IOException, BuildException {
Set<String> declaredLocales = new HashSet<>();
boolean useAllLocales = false;
if(includelocales == null || "*".equals(includelocales)) {
useAllLocales = true;
} else if ("".equals(includelocales)) {
useAllLocales = false;
} else {
StringTokenizer tokenizer = new StringTokenizer(includelocales, ",");
while (tokenizer.hasMoreElements()) {
declaredLocales.add(tokenizer.nextToken());
}
}
Set<String> indirectFilePaths = new HashSet<>();
File tmpFile = null;
for (FileSet fs : new FileSet[] {indirectJars, indirectFiles}) {
if (fs != null) {
DirectoryScanner scan = fs.getDirectoryScanner(getProject());
for (String f : scan.getIncludedFiles()) {
indirectFilePaths.add(f.replace(File.separatorChar, '/'));
}
}
}
for (Iterator<Resource> fileIt = files.iterator(); fileIt.hasNext();) {
FileResource fr = (FileResource) fileIt.next();
File jar = fr.getFile();
if (!jar.canRead()) {
throw new BuildException("Cannot read file: " + jar);
}
try (JarFile theJar = new JarFile(jar)) {
String codenamebase = JarWithModuleAttributes.extractCodeName(theJar.getManifest().getMainAttributes());
if (codenamebase == null) {
throw new BuildException("Not a NetBeans Module: " + jar);
}
if (
codenamebase.equals("org.objectweb.asm.all") &&
jar.getParentFile().getName().equals("core") &&
jar.getParentFile().getParentFile().getName().startsWith("platform")
) {
continue;
}
{
int slash = codenamebase.indexOf('/');
if (slash >= 0) {
codenamebase = codenamebase.substring(0, slash);
}
}
String dashcnb = codenamebase.replace('.', '-');
String title;
String oneline;
String shrt;
String osDep = null;
{
String bundle = theJar.getManifest().getMainAttributes().getValue("OpenIDE-Module-Localizing-Bundle");
Properties prop = new Properties();
if (bundle != null) {
ZipEntry en = theJar.getEntry(bundle);
if (en == null) {
throw new BuildException("Cannot find entry: " + bundle + " in file: " + jar);
} try (InputStream is = theJar.getInputStream(en)) {
prop.load(is);
}
}
title = prop.getProperty("OpenIDE-Module-Name", codenamebase);
oneline = prop.getProperty("OpenIDE-Module-Short-Description", title);
shrt = prop.getProperty("OpenIDE-Module-Long-Description", oneline);
}
{
String osMan = theJar.getManifest().getMainAttributes().getValue("OpenIDE-Module-Requires");
if (osMan != null) {
if (osMan.indexOf("org.openide.modules.os.MacOSX") >= 0) { // NOI18N
osDep = "Mac OS X"; // NOI18N
} else if (osMan.indexOf("org.openide.modules.os.Linux") >= 0) { // NOI18N
osDep = "Linux"; // NOI18N
} else if (osMan.indexOf("org.openide.modules.os.Solaris") >= 0) { // NOI18N
osDep = "Solaris"; // NOI18N
} else if (osMan.indexOf("org.openide.modules.os.Windows") >= 0) { // NOI18N
osDep = "Windows"; // NOI18N
}
}
} Map<String,List<File>> localizedFiles = verifyExtensions(jar, theJar.getManifest(), dashcnb, codenamebase, verify, indirectFilePaths);
new File(targetFile, dashcnb).mkdir();
File signed = new File(new File(targetFile, dashcnb), jar.getName());
File jnlp = new File(targetFile, dashcnb + ".jnlp");
StringWriter writeJNLP = new StringWriter();
writeJNLP.write("<?xml version='1.0' encoding='UTF-8'?>\n");
writeJNLP.write("<!DOCTYPE jnlp PUBLIC \"-//Sun Microsystems, Inc//DTD JNLP Descriptor 6.0//EN\" \"http://java.sun.com/dtd/JNLP-6.0.dtd\">\n");
writeJNLP.write("<jnlp spec='1.0+' codebase='" + codebase + "'>\n");
writeJNLP.write(" <information>\n");
writeJNLP.write(" <title>" + XMLUtil.toElementContent(title) + "</title>\n");
writeJNLP.write(" <vendor>NetBeans</vendor>\n");
writeJNLP.write(" <description kind='one-line'>" + XMLUtil.toElementContent(oneline) + "</description>\n");
writeJNLP.write(" <description kind='short'>" + XMLUtil.toElementContent(shrt) + "</description>\n");
writeJNLP.write(" </information>\n");
writeJNLP.write(permissions +"\n");
if (osDep == null) {
writeJNLP.write(" <resources>\n");
} else {
writeJNLP.write(" <resources os='" + osDep + "'>\n");
} writeJNLP.write(constructJarHref(jar, dashcnb));
processExtensions(jar, theJar.getManifest(), writeJNLP, dashcnb, codebase);
processIndirectJars(writeJNLP, dashcnb);
processIndirectFiles(writeJNLP, dashcnb);
writeJNLP.write(" </resources>\n");
if (useAllLocales || !declaredLocales.isEmpty()){
// write down locales
for (Map.Entry<String,List<File>> e : localizedFiles.entrySet()) {
String locale = e.getKey();
if (!declaredLocales.isEmpty() && !declaredLocales.contains(locale)) {
continue;
}
List<File> allFiles = e.getValue();
writeJNLP.write(" <resources locale='" + locale + "'>\n");
for (File n : allFiles) {
log("generating locale " + locale + " for " + n, Project.MSG_VERBOSE);
String name = n.getName();
String clusterRootPrefix = jar.getParent() + File.separatorChar;
String absname = n.getAbsolutePath();
if (absname.startsWith(clusterRootPrefix)) {
name = absname.substring(clusterRootPrefix.length()).replace(File.separatorChar, '-');
}
File t = new File(new File(targetFile, dashcnb), name);
File localeTmpFile = null;
if (n.exists() && isSigned(n) == null) {
try {
localeTmpFile = extendLibraryManifest(getProject(), n, t, manifestCodebase, manifestPermissions, appName, jnlp);
} catch (IOException | ManifestException ex) {
getProject().log(
"Failed to extend libraries manifests: " + ex.getMessage(), //NOI18N
Project.MSG_WARN);
}
} else {
getProject().log(
String.format(
"Not adding security attributes into locale library: %s the library is already signed.",
safeRelativePath(getProject().getBaseDir(),t)),
Project.MSG_WARN);
}
if (localeTmpFile != null) {
signOrCopy(localeTmpFile, t);
deleteTmpFile(localeTmpFile);
}
else {
signOrCopy(n, t);
}
writeJNLP.write(constructJarHref(n, dashcnb, name));
}
writeJNLP.write(" </resources>\n");
}
} writeJNLP.write(" <component-desc/>\n");
writeJNLP.write("</jnlp>\n");
writeJNLP.close();
FileWriter w = new FileWriter(jnlp);
w.write(writeJNLP.toString());
w.close();
if (jar.exists() && isSigned(jar) == null) {
try {
tmpFile = extendLibraryManifest(getProject(), jar, signed, manifestCodebase, manifestPermissions, appName, jnlp);
} catch (IOException | ManifestException ex) {
getProject().log(
"Failed to extend libraries manifests: " + ex.getMessage(), //NOI18N
Project.MSG_WARN);
}
} else {
getProject().log(
String.format(
"Not adding security attributes into library: %s the library is already signed.",
safeRelativePath(getProject().getBaseDir(),signed)),
Project.MSG_WARN);
} if (tmpFile != null) {
signOrCopy(tmpFile, signed);
deleteTmpFile(tmpFile);
} else {
signOrCopy(jar, signed);
}
}
}
}
private File extendLibraryManifest(
final Project prj,
final File sourceJar,
final File signedJar,
final String codebase,
final String permissions,
final String appName,
final File jnlp) throws IOException, ManifestException {
org.apache.tools.ant.taskdefs.Manifest manifest = null;
Copy cp = new Copy();
File tmpFile = new File(String.format("%s.tmp", signedJar.getAbsolutePath()));
cp.setFile(sourceJar);
cp.setTofile(tmpFile);
cp.execute();
boolean success = false;
try {
final Map<String,String> extendedAttrs = new HashMap<>();
final org.apache.tools.zip.ZipFile zf = new org.apache.tools.zip.ZipFile(sourceJar);
try {
final org.apache.tools.zip.ZipEntry manifestEntry = zf.getEntry(MANIFEST);
if (manifestEntry != null) {
final Reader in = new InputStreamReader(zf.getInputStream(manifestEntry), Charset.forName(UTF_8)); //NOI18N
try {
manifest = new org.apache.tools.ant.taskdefs.Manifest(in);
} finally {
in.close();
}
} else {
manifest = new org.apache.tools.ant.taskdefs.Manifest();
}
final org.apache.tools.ant.taskdefs.Manifest.Section mainSection = manifest.getMainSection();
String attr = mainSection.getAttributeValue(ATTR_CODEBASE);
if (attr == null) {
mainSection.addAttributeAndCheck(new org.apache.tools.ant.taskdefs.Manifest.Attribute(
ATTR_CODEBASE,
codebase));
extendedAttrs.put(ATTR_CODEBASE, codebase);
}
attr = mainSection.getAttributeValue(ATTR_PERMISSIONS);
if (attr == null) {
mainSection.addAttributeAndCheck(new org.apache.tools.ant.taskdefs.Manifest.Attribute(
ATTR_PERMISSIONS,
permissions));
extendedAttrs.put(ATTR_PERMISSIONS, permissions);
}
attr = mainSection.getAttributeValue(ATTR_APPLICATION_NAME);
if (attr == null) {
mainSection.addAttributeAndCheck(new org.apache.tools.ant.taskdefs.Manifest.Attribute(
ATTR_APPLICATION_NAME,
appName));
extendedAttrs.put(ATTR_APPLICATION_NAME, appName);
}
if (!extendedAttrs.isEmpty()) {
final Enumeration<? extends org.apache.tools.zip.ZipEntry> zent = zf.getEntries();
final ZipOutputStream out = new ZipOutputStream(tmpFile);
try {
if (jnlp != null) {
org.apache.tools.zip.ZipEntry jnlpEntry = new org.apache.tools.zip.ZipEntry("JNLP-INF/" + jnlp.getName());
out.putNextEntry(jnlpEntry);
FileInputStream fis = new FileInputStream(jnlp.getAbsolutePath());
try {
copy(fis, out);
} finally {
fis.close();
}
}
while (zent.hasMoreElements()) {
final org.apache.tools.zip.ZipEntry entry = zent.nextElement();
final InputStream in = zf.getInputStream(entry);
try {
out.putNextEntry(entry);
if (MANIFEST.equals(entry.getName())) {
final PrintWriter manifestOut = new PrintWriter(new OutputStreamWriter(out, Charset.forName(UTF_8)));
manifest.write(manifestOut);
manifestOut.flush();
} else {
copy(in,out);
}
} finally {
in.close();
}
}
} finally {
out.close();
}
success = true;
final StringBuilder message = new StringBuilder("Updating library "). //NOI18N
append(safeRelativePath(prj.getBaseDir(), tmpFile)).
append(" manifest"); //NOI18N
for (Map.Entry<String,String> e : extendedAttrs.entrySet()) {
message.append(String.format(" %s: %s,", e.getKey(), e.getValue()));
}
message.deleteCharAt(message.length()-1);
prj.log(message.toString(), Project.MSG_VERBOSE);
}
} finally {
zf.close();
}
} finally {
if (!success) {
final Delete rm = new Delete();
rm.setFile(tmpFile);
rm.setQuiet(true);
rm.execute();
tmpFile = null;
}
}
return tmpFile;
}
private static void deleteTmpFile(File tmpFile) {
final Delete del = new Delete();
del.setFile(tmpFile);
del.execute();
}
private static void copy(final InputStream in, final OutputStream out) throws IOException {
final byte[] BUFFER = new byte[4096];
int len;
for (;;) {
len = in.read(BUFFER);
if (len == -1) {
return;
}
out.write(BUFFER, 0, len);
}
}
private static String safeRelativePath(File from, File to) {
try {
return FileUtils.getRelativePath(from, to);
} catch (Exception ex) {
return to.getAbsolutePath();
}
}
private Map<String,List<File>> verifyExtensions(File f, Manifest mf, String dashcnb, String codebasename, boolean verify, Set<String> indirectFilePaths) throws IOException, BuildException {
Map<String,List<File>> localizedFiles = new HashMap<>();
File clusterRoot = f.getParentFile();
String moduleDirPrefix = "";
File updateTracking;
log("Verifying extensions for: " + codebasename + ", cluster root: " + clusterRoot + ", verify: " + verify, Project.MSG_DEBUG);
for(;;) {
updateTracking = new File(clusterRoot, "update_tracking");
if (updateTracking.isDirectory()) {
break;
}
moduleDirPrefix = clusterRoot.getName() + "/" + moduleDirPrefix;
clusterRoot = clusterRoot.getParentFile();
if (clusterRoot == null || !clusterRoot.exists()) {
if (!verify) {
return localizedFiles;
}
throw new BuildException("Cannot find update_tracking directory for module " + f);
}
}
File ut = new File(updateTracking, dashcnb + ".xml");
if (!ut.exists()) {
throw new BuildException("The file " + ut + " for module " + codebasename + " cannot be found");
}
Map<String,String> fileToOwningModule = new HashMap<>();
try {
ModuleSelector.readUpdateTracking(getProject(), ut.toString(), fileToOwningModule);
} catch (IOException | ParserConfigurationException | SAXException ex) {
throw new BuildException(ex);
}
log("project files: " + fileToOwningModule, Project.MSG_DEBUG);
String name = relative(f, clusterRoot);
log(" removing: " + name, Project.MSG_DEBUG);
removeWithLocales(fileToOwningModule, name, clusterRoot, localizedFiles);
name = "config/Modules/" + dashcnb + ".xml";
log(" removing: " + name, Project.MSG_DEBUG);
removeWithLocales(fileToOwningModule, name, clusterRoot, localizedFiles);
name = "config/ModuleAutoDeps/" + dashcnb + ".xml";
log(" removing: " + name, Project.MSG_DEBUG);
removeWithLocales(fileToOwningModule, name, clusterRoot, localizedFiles);
name = "update_tracking/" + dashcnb + ".xml";
log(" removing: " + name, Project.MSG_DEBUG);
removeWithLocales(fileToOwningModule, name, clusterRoot, localizedFiles);
String path = mf.getMainAttributes().getValue("Class-Path");
if (path != null) {
StringTokenizer tok = new StringTokenizer(path, ", ");
while(tok.hasMoreElements()) {
String s = tok.nextToken();
File e = new File(f.getParentFile(), s);
String r = relative(e, clusterRoot);
removeWithLocales(fileToOwningModule, r, clusterRoot, localizedFiles);
}
}
fileToOwningModule.remove("ant/nblib/" + dashcnb + ".jar");
fileToOwningModule.remove("VERSION.txt"); // cluster release information
log(" removing: " + indirectFilePaths, Project.MSG_DEBUG);
fileToOwningModule.keySet().removeAll(indirectFilePaths);
if (verifyExcludes != null) {
StringTokenizer tok = new StringTokenizer(verifyExcludes, ", ");
while(tok.hasMoreElements()) {
removeWithLocales(fileToOwningModule, tok.nextToken(), clusterRoot, localizedFiles);
}
}
if (verify) {
if (!fileToOwningModule.isEmpty()) {
throw new BuildException(
"Cannot build JNLP for module " + f + " as these files are in " +
"module's NBM, but are not referenced from any path (see harness/README for properties you can define to fix):\n" + fileToOwningModule.keySet()
);
}
}
return localizedFiles;
}
private static void removeWithLocales(Map<String,String> removeFrom, String removeWhat, File clusterRoot, Map<String,List<File>> recordLocales) {
if (removeFrom.remove(removeWhat) != null && removeWhat.endsWith(".jar")) {
int basedir = removeWhat.lastIndexOf('/');
String base = basedir == -1 ? "" : removeWhat.substring(0, basedir);
String name = removeWhat.substring(basedir + 1, removeWhat.length() - 4);
Pattern p = Pattern.compile(base + "/locale/" + name + "(|_[a-zA-Z0-9_]+)\\.jar");
Iterator<String> it = removeFrom.keySet().iterator();
while (it.hasNext()) {
String s = it.next();
Matcher m = p.matcher(s);
if (m.matches()) {
String locale = m.group(1).substring(1);
List<File> l = recordLocales.get(locale);
if (l == null) {
l = new ArrayList<>();
recordLocales.put(locale, l);
}
l.add(new File(clusterRoot, s.replace('/', File.separatorChar)));
it.remove();
}
}
}
}
private void processExtensions(File f, Manifest mf, Writer fileWriter, String dashcnb, String codebase) throws IOException, BuildException {
File nblibJar = new File(new File(new File(f.getParentFile().getParentFile(), "ant"), "nblib"), dashcnb + ".jar");
if (nblibJar.isFile()) {
File ext = new File(new File(targetFile, dashcnb), "ant-nblib-" + nblibJar.getName());
fileWriter.write(constructJarHref(ext, dashcnb));
signOrCopy(nblibJar, ext);
}
String path = mf.getMainAttributes().getValue("Class-Path");
if (path == null) {
return;
}
StringTokenizer tok = new StringTokenizer(path, ", ");
while(tok.hasMoreElements()) {
String s = URLDecoder.decode(tok.nextToken(), "UTF-8");
if (s.contains("${java.home}")) {
continue;
}
File e = new File(f.getParentFile(), s);
if (!e.canRead()) {
throw new BuildException("Cannot read extension " + e + " referenced from " + f);
}
String n = e.getName();
if (n.endsWith(".jar")) {
n = n.substring(0, n.length() - 4);
}
File ext = new File(new File(targetFile, dashcnb), s.replace("../", "").replace('/', '-'));
if (e.exists() && isSigned(e) != null) {
getProject().log(
String.format(
"Not adding security attributes into library: %s the library is already signed.",
safeRelativePath(getProject().getBaseDir(),ext)),
Project.MSG_WARN);
Copy copy = (Copy)getProject().createTask("copy");
copy.setFile(e);
copy.setTofile(ext);
copy.execute();
String extJnlpName = dashcnb + '-' + ext.getName().replaceFirst("\\.jar$", "") + ".jnlp";
File jnlp = new File(targetFile, extJnlpName);
try (FileWriter writeJNLP = new FileWriter(jnlp)) {
writeJNLP.write("<?xml version='1.0' encoding='UTF-8'?>\n");
writeJNLP.write("<!DOCTYPE jnlp PUBLIC \"-//Sun Microsystems, Inc//DTD JNLP Descriptor 6.0//EN\" \"http://java.sun.com/dtd/JNLP-6.0.dtd\">\n");
writeJNLP.write("<jnlp spec='1.0+' codebase='" + codebase + "'>\n");
writeJNLP.write(" <information>\n");
writeJNLP.write(" <title>" + n + "</title>\n");
writeJNLP.write(" <vendor>NetBeans</vendor>\n");
writeJNLP.write(" </information>\n");
writeJNLP.write(permissions +"\n");
writeJNLP.write(" <resources>\n");
writeJNLP.write(constructJarHref(ext, dashcnb));
writeJNLP.write(" </resources>\n");
writeJNLP.write(" <component-desc/>\n");
writeJNLP.write("</jnlp>\n");
}
fileWriter.write(" <extension name='" + e.getName().replaceFirst("\\.jar$", "") + "' href='" + extJnlpName + "'/>\n");
} else {
File tmpFile = null;
try {
tmpFile = extendLibraryManifest(getProject(), e, ext, manifestCodebase, manifestPermissions, appName, null);
} catch (IOException | ManifestException ex) {
getProject().log(
"Failed to extend libraries manifests: " + ex.getMessage(), //NOI18N
Project.MSG_WARN);
}
if (tmpFile != null) {
signOrCopy(tmpFile, ext);
deleteTmpFile(tmpFile);
} else {
signOrCopy(e, ext);
}
fileWriter.write(constructJarHref(ext, dashcnb));
}
}
}
private void processIndirectJars(Writer fileWriter, String dashcnb) throws IOException, BuildException {
if (indirectJars == null) {
return;
}
DirectoryScanner scan = indirectJars.getDirectoryScanner(getProject());
for (String f : scan.getIncludedFiles()) {
File jar = new File(scan.getBasedir(), f);
String rel = f.replace(File.separatorChar, '/');
String sig;
try {
sig = isSigned(jar);
} catch (IOException x) {
throw new BuildException("Cannot check signature on " + jar, x, getLocation());
}
// javaws will reject .zip files even with signatures.
String rel2 = rel.endsWith(".jar") ? rel : rel.replaceFirst("(\\.zip)?$", ".jar");
File ext = new File(new File(targetFile, dashcnb), rel2.replace('/', '-').replaceFirst("^modules-", ""));
Zip jartask = (Zip) getProject().createTask("jar");
jartask.setDestFile(ext);
ZipFileSet zfs = new ZipFileSet();
zfs.setSrc(jar);
if (sig != null) {
// Need to cancel original signature since we are adding one entry to the JAR.
zfs.setExcludes("META-INF/" + sig + ".*");
}
jartask.addZipfileset(zfs);
zfs = new ZipFileSet();
File blank = File.createTempFile("empty", "");
blank.deleteOnExit();
zfs.setFile(blank);
zfs.setFullpath("META-INF/clusterpath/" + rel);
jartask.addZipfileset(zfs);
jartask.execute();
blank.delete();
fileWriter.write(constructJarHref(ext, dashcnb));
signOrCopy(ext, null);
}
}
private void processIndirectFiles(Writer fileWriter, String dashcnb) throws IOException, BuildException {
if (indirectFiles == null) {
return;
}
DirectoryScanner scan = indirectFiles.getDirectoryScanner(getProject());
Map<String,File> entries = new LinkedHashMap<>();
for (String f : scan.getIncludedFiles()) {
entries.put(f.replace(File.separatorChar, '/'), new File(scan.getBasedir(), f));
}
if (entries.isEmpty()) {
return;
}
File ext = new File(new File(targetFile, dashcnb), "extra-files.jar");
Zip jartask = (Zip) getProject().createTask("jar");
jartask.setDestFile(ext);
for (Map.Entry<String,File> entry : entries.entrySet()) {
ZipFileSet zfs = new ZipFileSet();
zfs.setFile(entry.getValue());
zfs.setFullpath("META-INF/files/" + entry.getKey());
jartask.addZipfileset(zfs);
}
jartask.execute();
fileWriter.write(constructJarHref(ext, dashcnb));
signOrCopy(ext, null);
}
private String relative(File file, File root) {
String sfile = file.toString().replace(File.separatorChar, '/');
String sroot = (root.toString() + File.separator).replace(File.separatorChar, '/');
if (sfile.startsWith(sroot)) {
try {
String result = new URI(null, sfile.substring(sroot.length()), null).normalize().getPath();
return result;
} catch (URISyntaxException x) {
throw new BuildException(x, getLocation()); // or just ignore?
}
}
return sfile;
}
/** return alias if signed, or null if not */
private static String isSigned(File f) throws IOException {
try (JarFile jar = new JarFile(f)) {
Enumeration<JarEntry> en = jar.entries();
while (en.hasMoreElements()) {
Matcher m = SF.matcher(en.nextElement().getName());
if (m.matches()) {
return m.group(1);
}
}
return null;
}
}
private static final Pattern SF = Pattern.compile("META-INF/(.+)\\.SF");
/**
* returns version of jar file depending on manifest.mf or explicitly specified
* @param jar
* @return
* @throws IOException
*/
private String getJarVersion(JarFile jar) throws IOException {
String version = jar.getManifest().getMainAttributes().getValue("OpenIDE-Module-Specification-Version");
if (version == null) {
version = jar.getManifest().getMainAttributes().getValue("Specification-Version");
}
if (version == null && jarVersions != null) {
version = jarVersions.get(jar.getName());
}
return version;
}
/**
* Constructs jar or nativelib tag for jars
* @param f
* @param dashcnb
* @return
* @throws IOException
*/
private String constructJarHref(File f, String dashcnb) throws IOException {
return constructJarHref(f, dashcnb, f.getName());
}
/**
* Constructs jar or nativelib tag for jars using custom name
* @param f
* @param dashcnb
* @return
* @throws IOException
*/
private String constructJarHref(File f, String dashcnb, String name) throws IOException {
String tag = "jar";
if (nativeLibraries != null && nativeLibraries.contains(name)) {
tag = "nativelib";
}
if (processJarVersions) {
if (!f.exists()) {
throw new BuildException("JAR file " + f + " does not exist, cannot extract required versioning info.");
}
JarFile extJar = new JarFile(f);
String version = getJarVersion(extJar);
if (version != null) {
return " <" + tag + " href='" + dashcnb + '/' + name + "' version='" + version + "' size='" + f.length() + "'/>\n";
}
}
return " <" + tag + " href='" + dashcnb + '/' + name + "'/>\n";
}
private void generateVersionXMLFiles() throws IOException {
FileSet fs = new FileSet();
fs.setIncludes("**/*.jar");
for (File directory : jarDirectories) {
fs.setDir(directory);
DirectoryScanner scan = fs.getDirectoryScanner(getProject());
StringWriter writeVersionXML = new StringWriter();
writeVersionXML.append("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n");
writeVersionXML.append("<jnlp-versions>\n");
for (String jarName : scan.getIncludedFiles()) {
File jar = new File(scan.getBasedir(), jarName);
JarFile jarFile = new JarFile(jar);
String version = getJarVersion(jarFile);
if (version != null) {
writeVersionXML.append(" <resource>\n <pattern>\n <name>");
writeVersionXML.append(jar.getName());
writeVersionXML.append("</name>\n <version-id>");
writeVersionXML.append(version);
writeVersionXML.append("</version-id>\n </pattern>\n <file>");
writeVersionXML.append(jar.getName());
writeVersionXML.append("</file>\n </resource>\n");
} else {
writeVersionXML.append(" <!-- No version found for ");
writeVersionXML.append(jar.getName());
writeVersionXML.append(" -->\n");
}
}
writeVersionXML.append("</jnlp-versions>\n");
writeVersionXML.close();
File versionXML = new File(directory, "version.xml");
FileWriter w = new FileWriter(versionXML);
w.write(writeVersionXML.toString());
w.close();
}
}
}