blob: cae4b6728e5a0bccff8543587355eee4264862bb [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.apache.tuscany.sca.node.equinox.launcher;
import static org.osgi.framework.Constants.ACTIVATION_LAZY;
import static org.osgi.framework.Constants.BUNDLE_ACTIVATIONPOLICY;
import static org.osgi.framework.Constants.BUNDLE_CLASSPATH;
import static org.osgi.framework.Constants.BUNDLE_MANIFESTVERSION;
import static org.osgi.framework.Constants.BUNDLE_NAME;
import static org.osgi.framework.Constants.BUNDLE_SYMBOLICNAME;
import static org.osgi.framework.Constants.BUNDLE_VENDOR;
import static org.osgi.framework.Constants.BUNDLE_VERSION;
import static org.osgi.framework.Constants.DYNAMICIMPORT_PACKAGE;
import static org.osgi.framework.Constants.EXPORT_PACKAGE;
import static org.osgi.framework.Constants.REQUIRE_BUNDLE;
import static org.osgi.framework.Constants.RESOLUTION_DIRECTIVE;
import static org.osgi.framework.Constants.RESOLUTION_OPTIONAL;
import static org.osgi.framework.Constants.VISIBILITY_DIRECTIVE;
import static org.osgi.framework.Constants.VISIBILITY_REEXPORT;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.FilenameFilter;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.Reader;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.MalformedURLException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.net.URLClassLoader;
import java.security.AccessController;
import java.security.PrivilegedAction;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.StringTokenizer;
import java.util.jar.Attributes;
import java.util.jar.JarFile;
import java.util.jar.JarInputStream;
import java.util.jar.JarOutputStream;
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 java.util.zip.ZipInputStream;
import org.osgi.framework.Bundle;
import org.osgi.framework.BundleContext;
import org.osgi.framework.BundleException;
import org.osgi.framework.Constants;
/**
* Common functions and constants used by the admin components.
*
* @version $Rev$ $Date$
*/
final class NodeLauncherUtil {
private static final String NODE_API_BUNDLE = "org.apache.tuscany.sca.node.api";
private static final String BASE_BUNDLE = "org.apache.tuscany.sca.base";
private static final Logger logger = Logger.getLogger(NodeLauncherUtil.class.getName());
static final String META_INF_SERVICES = "META-INF.services;partial=true;mandatory:=partial";
static final String LAUNCHER_EQUINOX_LIBRARIES = "org.apache.tuscany.sca.node.launcher.equinox.libraries";
static final String GATEWAY_BUNDLE = "org.apache.tuscany.sca.gateway";
private static final String NODE_FACTORY = "org.apache.tuscany.sca.node.NodeFactory";
private static final String DOMAIN_MANAGER_LAUNCHER_BOOTSTRAP =
"org.apache.tuscany.sca.domain.manager.launcher.DomainManagerLauncherBootstrap";
private static final String NODE_IMPLEMENTATION_DAEMON_BOOTSTRAP =
"org.apache.tuscany.sca.implementation.node.launcher.NodeImplementationDaemonBootstrap";
private static final String TUSCANY_HOME = "TUSCANY_HOME";
private static final String TUSCANY_PATH = "TUSCANY_PATH";
/**
* Creates a new node.
*
* @param configurationURI
* @param compositeURI
* @param compositeContent
* @param contributions
* @param contributionClassLoader
* @param bundleContext
* @throws LauncherException
*/
static Object node(String configurationURI,
String compositeURI,
String compositeContent,
Contribution[] contributions,
BundleContext bundleContext) throws LauncherException {
try {
// Get the node runtime bundle.
Bundle bundle = null;
for (Bundle b : bundleContext.getBundles()) {
if (NODE_API_BUNDLE.equals(b.getSymbolicName())) {
bundle = b;
break;
}
if (b.getSymbolicName().contains(BASE_BUNDLE)) {
bundle = b;
break;
}
}
if (bundle == null) {
throw new IllegalStateException("Bundle " + NODE_API_BUNDLE + " is not installed");
}
// Use Java reflection to create the node as only the runtime class
// loader knows the runtime classes required by the node
Class<?> bootstrapClass = bundle.loadClass(NODE_FACTORY);
Object node = createNode(bootstrapClass, configurationURI, compositeURI, compositeContent, contributions);
// If the SCANodeFactory interface is available in the current classloader, create
// an SCANode proxy around the node we've just create
try {
Class<?> type = Class.forName(NODE_FACTORY);
type = type.getDeclaredClasses()[0];
return type.getMethod("createProxy", Class.class, Object.class).invoke(null, type, node);
} catch (ClassNotFoundException e) {
// Ignore
}
return node;
} catch (Exception e) {
NodeLauncher.logger.log(Level.SEVERE, "SCA Node could not be created", e);
throw new LauncherException(e);
}
}
private static Object createNode(Class<?> bootstrapClass,
String configurationURI,
String compositeURI,
String compositeContent,
Contribution[] contributions) throws NoSuchMethodException,
IllegalAccessException, InvocationTargetException, MalformedURLException {
Method newInstance = bootstrapClass.getMethod("newInstance");
Object nodeFactory = newInstance.invoke(null);
Object node;
if (configurationURI != null) {
// NodeFactory.createNode(URL)
Method create = bootstrapClass.getMethod("createNode", URL.class);
node = create.invoke(nodeFactory, new URL(configurationURI));
} else if (compositeContent != null) {
// NodeFactory.createNode(Reader, Stringp[], String[])
Method create = bootstrapClass.getMethod("createNode", Reader.class, String[].class, String[].class);
String[] uris = new String[contributions.length];
String[] locations = new String[contributions.length];
for (int i = 0; i < contributions.length; i++) {
uris[i] = contributions[i].getURI();
locations[i] = contributions[i].getLocation();
}
node = create.invoke(nodeFactory, compositeContent, uris, locations);
} else {
// NodeFactory.createNode(String, Stringp[], String[])
Method create = bootstrapClass.getMethod("createNode", String.class, String[].class, String[].class);
String[] uris = new String[contributions.length];
String[] locations = new String[contributions.length];
for (int i = 0; i < contributions.length; i++) {
uris[i] = contributions[i].getURI();
locations[i] = contributions[i].getLocation();
}
node = create.invoke(nodeFactory, compositeURI, uris, locations);
}
return node;
}
/**
* Creates a new node daemon.
*
* @throws LauncherException
*/
static Object nodeDaemon() throws LauncherException {
ClassLoader tccl = Thread.currentThread().getContextClassLoader();
try {
// Use Java reflection to create the node daemon as only the runtime class
// loader knows the runtime classes required by the node
String className = NODE_IMPLEMENTATION_DAEMON_BOOTSTRAP;
Class<?> bootstrapClass;
bootstrapClass = Class.forName(className, false, tccl);
Object bootstrap = bootstrapClass.getConstructor().newInstance();
Object nodeDaemon = bootstrapClass.getMethod("getNode").invoke(bootstrap);
return nodeDaemon;
} catch (Exception e) {
NodeDaemonLauncher.logger.log(Level.SEVERE, "SCA Node Daemon could not be created", e);
throw new LauncherException(e);
} finally {
Thread.currentThread().setContextClassLoader(tccl);
}
}
/**
* Creates a new domain manager.
*
* @throws LauncherException
*/
static Object domainManager(String rootDirectory) throws LauncherException {
ClassLoader tccl = Thread.currentThread().getContextClassLoader();
try {
// Use Java reflection to create the node daemon as only the runtime class
// loader knows the runtime classes required by the node
String className = DOMAIN_MANAGER_LAUNCHER_BOOTSTRAP;
Class<?> bootstrapClass;
bootstrapClass = Class.forName(className, false, tccl);
Constructor<?> constructor = bootstrapClass.getConstructor(String.class);
Object bootstrap = constructor.newInstance(rootDirectory);
Object domainManager = bootstrapClass.getMethod("getNode").invoke(bootstrap);
return domainManager;
} catch (Exception e) {
DomainManagerLauncher.logger.log(Level.SEVERE, "SCA Domain Manager could not be created", e);
throw new LauncherException(e);
} finally {
Thread.currentThread().setContextClassLoader(tccl);
}
}
/**
* starting with -, then some digits, then . or - or _, then some digits again
*
*/
// Mike Edwards 13/04/2009 - this original pattern allows for any number of repeated
// groups of digits, so that 1.2.3.4 is legal, for example. The problem with this is
// that OSGi only deals with a maximum of 3 groups of digits...
// private static Pattern pattern = Pattern.compile("-(\\d)+((\\.|-|_)(\\d)+)*");
//
// This updated version restricts the allowed patterns to a maximum of 3 groups of
// digits so that "1", "1.2" and "1.2.3" are allowed but not "1.2.3.4" etc
private static Pattern pattern = Pattern.compile("-(\\d)+((\\.|-|_)(\\d)+){0,2}");
/**
* Returns the version number to use for the given JAR file.
*
* @param jarFile
* @return
*/
static String jarVersion(URL jarFile) {
String name = jarFile.getFile();
int index = name.lastIndexOf('/');
if (index != -1) {
// Find the last segment
name = name.substring(index + 1);
}
index = name.lastIndexOf('.');
if (index != -1) {
// Trim the extension
name = name.substring(0, index);
}
Matcher matcher = pattern.matcher(name);
String version = "0.0.0";
if (matcher.find()) {
version = matcher.group();
// Remove the leading "-" character
version = version.substring(1);
// The Pattern above allows the version string to contain "-" and "_" as digit separators.
// OSGi only allows for "." as a separator thus any "-" and "_" characters in the version string must be replaced by "."
version = version.replace('-', '.');
version = version.replace('_', '.');
}
return version;
}
static String artifactId(URL jarFile) {
String name = jarFile.getFile();
int index = name.lastIndexOf('/');
if (index != -1) {
// Find the last segment
name = name.substring(index + 1);
}
index = name.lastIndexOf('.');
if (index != -1) {
// Trim the extension
name = name.substring(0, index);
}
Matcher matcher = pattern.matcher(name);
if (matcher.find()) {
return name.substring(0, matcher.start());
} else {
return name;
}
}
/**
* Add the packages found in the given JAR to a set.
*
* @param jarFile
* @param packages
* @throws IOException
*/
private static void addPackages(URL jarFile, Set<String> packages, String version) throws IOException {
if (version == null) {
version = ";version=" + jarVersion(jarFile);
} else {
version = ";version=" + version;
}
File file = file(jarFile);
if (file.isDirectory()) {
List<String> classFiles = listClassFiles(file);
for (String cls : classFiles) {
int index = cls.lastIndexOf('/');
if (index == -1) {
// Default package cannot be exported
continue;
}
String pkg = cls.substring(0, index);
pkg = pkg.replace('/', '.') + version;
// Export META-INF.services
if ("META-INF.services".equals(pkg)) {
packages.add(META_INF_SERVICES);
} else {
packages.add(pkg);
}
}
} else if (file.isFile()) {
ZipInputStream is = new ZipInputStream(new FileInputStream(file));
ZipEntry entry;
while ((entry = is.getNextEntry()) != null) {
String entryName = entry.getName();
// Export split packages for META-INF/services
if(entryName.startsWith("META-INF/services/")) {
packages.add("META-INF.services" + ";partial=true;mandatory:=partial");
}
if (!entry.isDirectory() && entryName != null
&& entryName.length() > 0
&& !entryName.startsWith(".")
&& entryName.endsWith(".class") // Exclude resources from Export-Package
&& entryName.lastIndexOf("/") > 0) {
String pkg = entryName.substring(0, entryName.lastIndexOf("/")).replace('/', '.') + version;
packages.add(pkg);
}
}
is.close();
}
}
private static List<String> listClassFiles(File directory) {
List<String> artifacts = new ArrayList<String>();
traverse(artifacts, directory, directory);
// Add META-INF/services to be exported
if (new File(directory, "META-INF/services").isDirectory()) {
artifacts.add("META-INF/services/");
}
return artifacts;
}
/**
* Recursively traverse a root directory
*
* @param fileList
* @param file
* @param root
* @throws IOException
*/
private static void traverse(List<String> fileList, File file, File root) {
if (file.isFile() && file.getName().endsWith(".class")) {
fileList.add(root.toURI().relativize(file.toURI()).toString());
} else if (file.isDirectory()) {
String uri = root.toURI().relativize(file.toURI()).toString();
if (uri.endsWith("/")) {
uri = uri.substring(0, uri.length() - 1);
}
fileList.add(uri);
File[] files = file.listFiles();
for (File f : files) {
if (!f.getName().startsWith(".")) {
traverse(fileList, f, root);
}
}
}
}
/**
* Finds the OSGi manifest file for a JAR file, where the manifest file is held in a META-INF directory
* alongside the JAR
* @param jarURL - The URL of the JAR file
* @return - a Manifest object corresponding to the manifest file, or NULL if there is no OSGi manifest
*/
static private Manifest findOSGiManifest(URL jarURL) {
try {
File jarFile = new File(jarURL.toURI());
File theManifestFile = new File(jarFile.getParent(), "META-INF/MANIFEST.MF");
if (theManifestFile.exists()) {
// Create manifest object by reading the manifest file
Manifest manifest = new Manifest(new FileInputStream(theManifestFile));
// Check that this manifest file has the necessary OSGi metadata
String bundleName = manifest.getMainAttributes().getValue(BUNDLE_SYMBOLICNAME);
if (bundleName != null) {
return manifest;
} // end if
} // end if
} catch (Exception e) {
// Could not read the manifest - continue
}
return null;
} // end findOSGiManifest
/**
* Generate a manifest from a list of third-party JAR files.
*
* @param jarFiles
* @param bundleSymbolicName The Bundle-SymbolicName
* @param bundleVersion The Bundle-Version
* @return
* @throws IllegalStateException
*/
static private Manifest thirdPartyLibraryBundleManifest(Collection<URL> jarFiles,
String bundleSymbolicName,
String bundleVersion) throws IllegalStateException {
try {
// List exported packages and bundle classpath entries
StringBuffer classpath = new StringBuffer();
StringBuffer exports = new StringBuffer();
StringBuffer imports = new StringBuffer();
Set<String> packages = new HashSet<String>();
for (URL jarFile : jarFiles) {
addPackages(jarFile, packages, bundleVersion);
classpath.append("\"external:");
classpath.append(file(jarFile).getPath().replace(File.separatorChar, '/'));
classpath.append("\",");
}
Set<String> importPackages = new HashSet<String>();
for (String pkg : packages) {
String importPackage = pkg;
int index = pkg.indexOf(';');
if (index != -1) {
importPackage = pkg.substring(0, index);
}
if (!importPackages.contains(importPackage)) {
// Exclude META-INF.services
if (!"META-INF.services".equals(importPackage)) {
imports.append(pkg);
imports.append(',');
}
importPackages.add(importPackage);
exports.append(pkg);
exports.append(',');
} else {
logger.warning("Duplicate package skipped: " + pkg);
}
}
// Create a manifest
Manifest manifest = new Manifest();
Attributes attributes = manifest.getMainAttributes();
attributes.putValue("Manifest-Version", "1.0");
attributes.putValue(BUNDLE_MANIFESTVERSION, "2");
if (bundleVersion == null) {
bundleVersion = "0.0.0";
}
attributes.putValue(Constants.BUNDLE_VERSION, bundleVersion);
if (bundleSymbolicName == null) {
bundleSymbolicName = LAUNCHER_EQUINOX_LIBRARIES;
}
attributes.putValue(BUNDLE_SYMBOLICNAME, bundleSymbolicName);
if (exports.length() > 0) {
attributes.putValue(EXPORT_PACKAGE, exports.substring(0, exports.length() - 1));
}
/*
if (imports.length() > 0) {
attributes.putValue(IMPORT_PACKAGE, imports.substring(0, imports.length() - 1));
}
*/
if (classpath.length() > 0) {
attributes.putValue(BUNDLE_CLASSPATH, classpath.substring(0, classpath.length() - 1));
}
// The system bundle has incomplete javax.transaction* packages exported
attributes.putValue(DYNAMICIMPORT_PACKAGE, "javax.transaction;version=\"1.1\",javax.transaction.xa;version=\"1.1\",*");
return manifest;
} catch (IOException e) {
throw new IllegalStateException(e);
}
}
/**
* Generates a library bundle from a list of third-party JARs.
*
* @param jarFiles
* @param bundleSymbolicName The Bundle-SymbolicName
* @param bundleVersion The Bundle-Version
* @return
* @throws IOException
*/
static InputStream thirdPartyLibraryBundle(Collection<URL> jarFiles, String bundleSymbolicName, String bundleVersion)
throws IOException {
ByteArrayOutputStream bos = new ByteArrayOutputStream();
Manifest mf = thirdPartyLibraryBundleManifest(jarFiles, bundleSymbolicName, bundleVersion);
JarOutputStream jos = new JarOutputStream(bos, mf);
jos.close();
return new ByteArrayInputStream(bos.toByteArray());
}
static InputStream thirdPartyLibraryBundle(Collection<URL> jarFiles, Manifest manifest) throws IOException {
ByteArrayOutputStream bos = new ByteArrayOutputStream();
StringBuffer classpath = new StringBuffer();
for (URL jarFile : jarFiles) {
classpath.append("\"external:");
classpath.append(file(jarFile).getPath().replace(File.separatorChar, '/'));
classpath.append("\",");
}
if (classpath.length() > 0) {
manifest.getMainAttributes().putValue(BUNDLE_CLASSPATH, classpath.substring(0, classpath.length() - 1));
}
JarOutputStream jos = new JarOutputStream(bos, manifest);
jos.close();
return new ByteArrayInputStream(bos.toByteArray());
}
/**
* Returns the location of this bundle.
*
* @return
* @throws IOException
*/
static String thisBundleLocation() throws IOException, URISyntaxException {
String resource = NodeLauncherUtil.class.getName().replace('.', '/') + ".class";
URL url = NodeLauncherUtil.class.getClassLoader().getResource(resource);
if (url == null) {
throw new FileNotFoundException(resource);
}
URI uri = toURI(url);
String scheme = uri.getScheme();
if (scheme.equals("jar")) {
String path = uri.toString().substring(4);
int i = path.indexOf("!/");
path = path.substring(0, i);
return path;
} else {
String path = uri.toString();
path = path.substring(0, path.length() - resource.length());
return path;
}
}
static URI toURI(URL url) {
File file = file(url);
if (file != null) {
return file.toURI();
} else {
return createURI(url.toString());
}
}
/**
* Escape the space in URL string
* @param uri
* @return
*/
static URI createURI(String uri) {
if (uri == null) {
return null;
}
if (uri.indexOf('%') != -1) {
// Avoid double-escaping
return URI.create(uri);
}
int index = uri.indexOf(':');
String scheme = null;
String ssp = uri;
if (index != -1) {
scheme = uri.substring(0, index);
ssp = uri.substring(index + 1);
}
try {
return new URI(scheme, ssp, null);
} catch (URISyntaxException e) {
throw new IllegalArgumentException(e);
}
}
/**
* Returns the location of this bundle.
*
* @param bundle
* @return
* @throws IOException
*/
static String thisBundleLocation(Bundle bundle) throws IOException, URISyntaxException, ClassNotFoundException {
String resource = NodeLauncherUtil.class.getName();
Class<?> clazz = bundle.loadClass(NodeLauncherUtil.class.getName());
URL url = clazz.getProtectionDomain().getCodeSource().getLocation();
if (url == null) {
throw new FileNotFoundException(resource);
}
URI uri = toURI(url);
String scheme = uri.getScheme();
if (scheme.equals("jar")) {
String path = uri.toString().substring(4);
int i = path.indexOf("!/");
path = path.substring(0, i);
return path;
} else {
String path = uri.toString();
//path = path.substring(0, path.length() - resource.length());
return path;
}
}
/**
* Install the given bundle.
*
* @param bundleContext
* @param location
* @throws BundleException
* @throws IOException
*/
static void fixupBundle(String location) throws BundleException, IOException {
File target = file(new URL(location));
if (!target.exists()) {
return;
}
location = target.toURI().toString();
// For development mode, copy the MANIFEST.MF file to the bundle location as it's
// initially outside of target/classes, at the root of the project.
if (location.endsWith("/target/classes/")) {
File targetManifest = new File(target, "META-INF/MANIFEST.MF");
File sourceManifest = new File(target.getParentFile().getParentFile(), "META-INF/MANIFEST.MF");
if (!sourceManifest.isFile()) {
return;
}
targetManifest.getParentFile().mkdirs();
OutputStream os = new FileOutputStream(targetManifest);
InputStream is = new FileInputStream(sourceManifest);
byte[] buf = new byte[2048];
for (;;) {
int l = is.read(buf);
if (l == -1) {
break;
}
os.write(buf, 0, l);
}
is.close();
os.close();
}
}
/**
* Returns a string representation of the given bundle.
*
* @param b
* @param verbose
* @return
*/
static String string(Bundle bundle, boolean verbose) {
StringBuffer sb = new StringBuffer();
sb.append(bundle.getBundleId()).append(" ").append(bundle.getSymbolicName());
int s = bundle.getState();
if ((s & Bundle.UNINSTALLED) != 0) {
sb.append(" UNINSTALLED");
}
if ((s & Bundle.INSTALLED) != 0) {
sb.append(" INSTALLED");
}
if ((s & Bundle.RESOLVED) != 0) {
sb.append(" RESOLVED");
}
if ((s & Bundle.STARTING) != 0) {
sb.append(" STARTING");
}
if ((s & Bundle.STOPPING) != 0) {
sb.append(" STOPPING");
}
if ((s & Bundle.ACTIVE) != 0) {
sb.append(" ACTIVE");
}
if (verbose) {
sb.append(" ").append(bundle.getLocation());
sb.append(" ").append(bundle.getHeaders());
}
return sb.toString();
}
/**
* Returns the name of a bundle, or null if the given file is not a bundle.
*
* @param file
* @return
* @throws IOException
*/
static String bundleName(File file) throws IOException {
if (!file.exists()) {
return null;
}
String bundleName = null;
if (file.isDirectory()) {
File mf = new File(file, "META-INF/MANIFEST.MF");
if (mf.isFile()) {
Manifest manifest = new Manifest(new FileInputStream(mf));
bundleName = manifest.getMainAttributes().getValue(BUNDLE_SYMBOLICNAME);
} else {
if (file.toURI().getPath().endsWith("/target/classes/")) {
// Development mode, MANIFEST.MF is outside the bundle location
mf = new File(file.getParentFile().getParentFile(), "META-INF/MANIFEST.MF");
if (mf.isFile()) {
Manifest manifest = new Manifest(new FileInputStream(mf));
bundleName = manifest.getMainAttributes().getValue(BUNDLE_SYMBOLICNAME);
}
}
}
} else {
JarFile jar = new JarFile(file, false);
Manifest manifest = jar.getManifest();
if (manifest != null) {
bundleName = manifest.getMainAttributes().getValue(BUNDLE_SYMBOLICNAME);
}
jar.close();
}
if (bundleName == null) {
return bundleName;
}
int sc = bundleName.indexOf(';');
if (sc != -1) {
bundleName = bundleName.substring(0, sc);
}
return bundleName;
}
/**
* Collect JAR files in the given directory.
*
* @param directory
* @param urls
* @param filter
* @throws MalformedURLException
*/
private static void collectClasspathEntries(File directory, Set<URL> urls, FilenameFilter filter, boolean recursive)
throws MalformedURLException {
File[] files = directory.listFiles(filter);
if (files != null) {
int count = 0;
for (File file : files) {
if (recursive && file.isDirectory()) {
collectClasspathEntries(file, urls, filter, recursive);
} else {
urls.add(file.toURI().toURL());
count++;
}
}
if (count != 0) {
logger.fine("Runtime classpath: " + count
+ " JAR"
+ (count > 1 ? "s" : "")
+ " from "
+ directory.toString());
}
}
}
/**
* Collect development .../ target/classes directories in the given directory.
*
* @param directory
* @param urls
* @param filter
* @throws MalformedURLException
*/
private static void collectTargetClassesClasspathEntries(File directory, Set<URL> urls, FilenameFilter filter)
throws MalformedURLException {
File[] files = directory.listFiles();
if (files != null) {
int count = 0;
for (File file : files) {
if (!file.isDirectory()) {
continue;
}
File target = new File(file, "target");
if (!target.isDirectory()) {
continue;
}
File classes = new File(target, "classes");
if (classes.isDirectory() && filter.accept(target, "classes")) {
urls.add(classes.toURI().toURL());
count++;
}
}
if (count != 0 && logger.isLoggable(Level.FINE)) {
logger.fine("Runtime classpath: " + count
+ " classes folder"
+ (count > 1 ? "s" : "")
+ " from "
+ directory.toString());
}
}
}
/**
* Collect JAR files under the given distribution directory.
*
* @param directory
* @param jarDirectoryURLs
* @param jarURLs
* @param filter
* @throws MalformedURLException
*/
private static void collectDistributionClasspathEntries(String directory,
Set<URL> jarDirectoryURLs,
Set<URL> jarURLs,
FilenameFilter filter) throws MalformedURLException {
File directoryFile = new File(directory);
URL directoryURL = directoryFile.toURI().toURL();
if (!jarDirectoryURLs.contains(directoryURL) && directoryFile.exists()) {
// Collect files under the given directory
jarDirectoryURLs.add(directoryURL);
collectClasspathEntries(directoryFile, jarURLs, filter, false);
// Collect files under <directory>/modules
File modulesDirectory = new File(directoryFile, "modules");
URL modulesDirectoryURL = modulesDirectory.toURI().toURL();
if (!jarDirectoryURLs.contains(modulesDirectoryURL) && modulesDirectory.exists()) {
jarDirectoryURLs.add(modulesDirectoryURL);
collectClasspathEntries(modulesDirectory, jarURLs, filter, true);
}
// Collect files under <directory>/lib
/* SL - this is commented out to prevent the jars in the lib dir being installed as
* OSGi bundles. There must have been a time (1.x?) when lib jars would have been
* installed but now the shaded jars live in lib and we don't want to install them
*
File libDirectory = new File(directoryFile, "lib");
URL libDirectoryURL = libDirectory.toURI().toURL();
if (!jarDirectoryURLs.contains(libDirectoryURL) && libDirectory.exists()) {
jarDirectoryURLs.add(libDirectoryURL);
collectClasspathEntries(libDirectory, jarURLs, filter, true);
}
*/
}
}
private static boolean isMavenTestMode() {
return getProperty("surefire.test.class.path") != null || getProperty("surefire.real.class.path") != null
|| getProperty("localRepository") != null;
}
/**
* Determine the Tuscany runtime classpath entries.
*
* @param useDistribution
* @param useAppClasspath
* @param useModulesDirectory
* @return
*/
static Set<URL> runtimeClasspathEntries(boolean useDistribution,
boolean useAppClasspath,
boolean useModulesDirectory) throws FileNotFoundException,
URISyntaxException, MalformedURLException {
// Build list of runtime JARs
Set<URL> jarDirectoryURLs = new HashSet<URL>();
Set<URL> jarURLs = new HashSet<URL>();
// Determine the path to the launcher class
URI uri;
try {
uri = codeLocation(NodeLauncherUtil.class);
} catch (Exception e) {
uri = URI.create("");
}
// If the launcher class is in a JAR, add all runtime JARs from directory containing
// that JAR (e.g. the Tuscany modules directory) as well as the ../modules and
// ../lib directories
if (uri.getPath().endsWith(".jar")) {
if (useDistribution) {
File file = new File(uri);
if (file.exists()) {
File jarDirectory = file.getParentFile();
if (jarDirectory != null && jarDirectory.exists()) {
// Collect JAR files from the directory containing the input JAR
// (e.g. the Tuscany modules directory)
URL jarDirectoryURL = jarDirectory.toURI().toURL();
jarDirectoryURLs.add(jarDirectoryURL);
collectClasspathEntries(jarDirectory, jarURLs, new StandAloneJARFileNameFilter(), true);
File homeDirectory = jarDirectory.getParentFile();
if (homeDirectory != null && homeDirectory.exists()) {
collectDistributionClasspathEntries(homeDirectory.getAbsolutePath(),
jarDirectoryURLs,
jarURLs,
new StandAloneJARFileNameFilter());
}
}
}
}
} else if (uri.getPath().endsWith("/target/classes/")) {
// Development mode, we're running off classes in a workspace
// and not from Maven surefire, collect all bundles in the workspace
if (useModulesDirectory) {
if (!isMavenTestMode()) {
File file = new File(uri);
if (file.exists()) {
File moduleDirectory = file.getParentFile().getParentFile();
if (moduleDirectory != null) {
File modulesDirectory = moduleDirectory.getParentFile();
if (modulesDirectory != null && modulesDirectory.exists()
&& modulesDirectory.getName().equals("modules")) {
collectDevelopmentClasspathEntries(modulesDirectory.getAbsolutePath(),
jarDirectoryURLs,
jarURLs,
new StandAloneDevelopmentClassesFileNameFilter());
// Added Mike Edwards, 09/04/2009
// Get hold of the Libraries that are used by the Tuscany modules
collectDevelopmentLibraryEntries(modulesDirectory, jarDirectoryURLs, jarURLs);
} // end if
}
}
}
}
}
// Look for a TUSCANY_HOME system property or environment variable
// Add all the JARs found under $TUSCANY_HOME, $TUSCANY_HOME/modules
// and $TUSCANY_HOME/lib
if (useDistribution) {
String home = getProperty(TUSCANY_HOME);
if (home != null && home.length() != 0) {
logger.info(TUSCANY_HOME + ": " + home);
collectDistributionClasspathEntries(home, jarDirectoryURLs, jarURLs, new StandAloneJARFileNameFilter());
}
// Look for a TUSCANY_PATH system property or environment variable
// Add all the JARs found under $TUSCANY_PATH, $TUSCANY_PATH/modules
// and $TUSCANY_PATH/lib
String ext = getProperty(TUSCANY_PATH);
if (ext != null && ext.length() != 0) {
logger.info(TUSCANY_PATH + ": " + ext);
String separator = getProperty("path.separator");
for (StringTokenizer tokens = new StringTokenizer(ext, separator); tokens.hasMoreTokens();) {
collectDistributionClasspathEntries(tokens.nextToken(),
jarDirectoryURLs,
jarURLs,
new StandAloneJARFileNameFilter());
}
}
}
// Add the classpath entries from the current classloader
if (useAppClasspath) {
collectClassLoaderClasspathEntries(jarURLs, NodeLauncherUtil.class.getClassLoader());
}
return jarURLs;
}
/**
* Returns the JAR files on the classpath used by the given classloader.
*
* @param classLoader
* @return
*/
static List<URL> jarFilesOnClasspath(ClassLoader classLoader) {
Set<URL> entries = new HashSet<URL>();
collectClassLoaderClasspathEntries(entries, classLoader);
return new ArrayList<URL>(entries);
}
private static String getProperty(final String prop) {
return AccessController.doPrivileged(new PrivilegedAction<String>() {
public String run() {
String value = System.getProperty(prop);
if (value == null || value.length() == 0) {
return System.getenv(prop);
} else {
return value;
}
}
});
}
/**
* Collect JARs on the classpath of a URLClassLoader.
*
* @param urls
* @param cl
*/
private static void collectClassLoaderClasspathEntries(Set<URL> urls, ClassLoader cl) {
if (cl == null) {
return;
}
int count = urls.size();
// Collect JARs from the URLClassLoader's classpath
if (cl instanceof URLClassLoader) {
URL[] jarURLs = ((URLClassLoader)cl).getURLs();
if (jarURLs != null) {
for (URL jarURL : jarURLs) {
urls.add(jarURL);
try {
urls.addAll(manifestClassPath(jarURL));
} catch (Exception e) {
logger.log(Level.SEVERE, e.getMessage(), e);
}
}
count = urls.size() - count;
if (count != 0 && logger.isLoggable(Level.FINE)) {
logger.fine("Runtime classpath: " + count
+ " JAR"
+ (count > 1 ? "s" : "")
+ " from application classpath.");
}
}
}
}
static Set<URL> manifestClassPath(URL jarFile) throws Exception {
Set<URL> urls = new HashSet<URL>();
if (jarFile != null) {
Manifest mf = null;
if ("file".equals(jarFile.getProtocol())) {
File f = file(jarFile);
if (f.isDirectory()) {
File mfFile = new File(f, "META-INF/MANIFEST.MF");
if (mfFile.isFile()) {
FileInputStream is = new FileInputStream(mfFile);
mf = new Manifest(is);
is.close();
}
} else if (f.isFile()) {
JarInputStream jar = new JarInputStream(jarFile.openStream());
mf = jar.getManifest();
jar.close();
}
}
if (mf != null) {
String cp = mf.getMainAttributes().getValue("Class-Path");
if (cp != null) {
StringTokenizer st = new StringTokenizer(cp);
while (st.hasMoreTokens()) {
URL entry = new URL(jarFile.toURI().toURL(), st.nextToken()).toURI().toURL();
urls.add(entry);
}
}
}
}
return urls;
}
/**
* A file name filter used to filter JAR files.
*/
private static class StandAloneJARFileNameFilter implements FilenameFilter {
public boolean accept(File dir, String name) {
name = name.toLowerCase();
if (new File(dir, name).isDirectory()) {
return true;
}
// Filter out the Tomcat and Webapp hosts
if (name.startsWith("tuscany-host-tomcat") || name.startsWith("tuscany-host-webapp")) {
//FIXME This is temporary
return false;
}
// Don't include the sources jar files
if (name.endsWith("-sources.jar")) {
return false;
}
// Include JAR and MAR files
if (name.endsWith(".jar")) {
return true;
}
if (name.endsWith(".mar")) {
return true;
}
return false;
}
}
/**
* A file name filter used to filter target/classes directories.
*/
private static class StandAloneDevelopmentClassesFileNameFilter implements FilenameFilter {
public boolean accept(File dir, String name) {
name = name.toLowerCase();
if (dir.getName().equals("target") && name.equals("classes")) {
// Filter out the Tomcat and Webapp hosts
String dirPath = dir.getAbsolutePath();
if (dirPath.endsWith("host-tomcat/target") || dirPath.endsWith("host-webapp/target")) {
//FIXME This is temporary
return false;
}
return true;
}
// Filter out the Tomcat and Webapp hosts
if (name.startsWith("tuscany-host-tomcat") || name.startsWith("tuscany-host-webapp")) {
//FIXME This is temporary
return false;
}
// Include JAR and MAR files
if (name.endsWith(".jar")) {
return true;
}
if (name.endsWith(".mar")) {
return true;
}
return false;
}
}
/**
* A file name filter used to filter JAR files.
*/
private static class WebAppJARFileNameFilter extends StandAloneJARFileNameFilter {
@Override
public boolean accept(File dir, String name) {
if (!super.accept(dir, name)) {
return false;
}
name = name.toLowerCase();
// Exclude servlet-api JARs
if (name.startsWith("servlet-api")) {
return false;
}
// Exclude the Tomcat and Jetty hosts
if (name.startsWith("tuscany-host-tomcat") || name.startsWith("tuscany-host-jetty")) {
//FIXME This is temporary
return false;
}
return true;
}
}
/**
* A file name filter used to filter the libraries in the \java\sca\distribution\all\target\modules
* directory
*/
private static class DistributionLibsFileNameFilter implements FilenameFilter {
public boolean accept(File dir, String name) {
name = name.toLowerCase();
// Include subdirectories
if (new File(dir, name).isDirectory()) {
return true;
}
// Filter out the Tuscany SCA jars - since the development versions of these are used
// from the \target\classes directories...
if (name.startsWith("tuscany")) {
// tuscany-sdo jars don't form part of the SCA modules...
if (name.startsWith("tuscany-sdo")) return true;
return false;
}
// Include JAR and MAR files
if (name.endsWith(".jar")) {
return true;
}
if (name.endsWith(".mar")) {
return true;
}
return false;
} // end accept
} // end DistributionLibsFileNameFilter
/**
* Returns the File object representing the given URL.
*
* @param url
* @return
*/
static File file(URL url) {
if (url == null || !url.getProtocol().equals("file")) {
return null;
} else {
String filename = url.getFile().replace('/', File.separatorChar);
int pos = 0;
while ((pos = filename.indexOf('%', pos)) >= 0) {
if (pos + 2 < filename.length()) {
String hexStr = filename.substring(pos + 1, pos + 3);
char ch = (char)Integer.parseInt(hexStr, 16);
filename = filename.substring(0, pos) + ch + filename.substring(pos + 3);
}
}
return new File(filename);
}
}
/**
* Returns the location of the classpath entry, JAR, WAR etc. containing the given class.
*
* @param clazz
* @return
*/
static private URI codeLocation(Class<?> clazz) {
URL url = clazz.getProtectionDomain().getCodeSource().getLocation();
return toURI(url);
}
/**
* Collect JAR files under the given distribution directory.
*
* @param directory
* @param jarDirectoryURLs
* @param jarURLs
* @param filter
* @throws MalformedURLException
*/
private static void collectDevelopmentClasspathEntries(String directory,
Set<URL> jarDirectoryURLs,
Set<URL> jarURLs,
FilenameFilter filter) throws MalformedURLException {
File directoryFile = new File(directory);
URL directoryURL = directoryFile.toURI().toURL();
if (!jarDirectoryURLs.contains(directoryURL) && directoryFile.exists()) {
// Collect files under the given directory
jarDirectoryURLs.add(directoryURL);
collectTargetClassesClasspathEntries(directoryFile, jarURLs, filter);
}
} // end collectDevelopmentClasspathEntries
/**
* Collect the dependent Library JAR files for the development use of Tuscany
* It is assumed that these live in the \java\sca\distribution\all\target\modules
* directory, where the development modules live in \java\sca\modules, but that
* same directory also contains prebuilt versions of the Tuscany JARs, which must be
* filtered out so as not to clash with the development versions of the code
*
* @param directory - the \java\sca\modules directory
* @param jarDirectoryURLs
* @param jarURLs
* @throws MalformedURLException
*/
private static void collectDevelopmentLibraryEntries(File modulesDirectory,
Set<URL> jarDirectoryURLs,
Set<URL> jarURLs) throws MalformedURLException {
// Get the \java\sca directory
File rootDirectory = modulesDirectory.getParentFile();
// Get the \java\sca\distribution\all\target\modules
String sep = File.separator;
File libsDirectory = new File(rootDirectory, "distribution" + sep + "all" + sep + "target" + sep + "modules");
URL libsURL = libsDirectory.toURI().toURL();
if (!jarDirectoryURLs.contains(libsURL) && libsDirectory.exists()) {
// Collect files under the libs module directory
jarDirectoryURLs.add(libsURL);
collectClasspathEntries(libsDirectory, jarURLs, new DistributionLibsFileNameFilter(), true);
} // end if
} // end collectDevelopmentLibraryEntries
/**
* Generate a gateway bundle that aggregate other bundles to handle split packages
* @param bundleSymbolicNames
* @throws FileNotFoundException
* @throws IOException
*/
static InputStream generateGatewayBundle(Collection<String> bundleSymbolicNames, String bundleVersion, boolean reexport)
throws IOException {
Manifest manifest = new Manifest();
Attributes attrs = manifest.getMainAttributes();
StringBuffer requireBundle = new StringBuffer();
for (String name : new HashSet<String>(bundleSymbolicNames)) {
requireBundle.append(name).append(";").append(RESOLUTION_DIRECTIVE).append(":=")
.append(RESOLUTION_OPTIONAL);
if (reexport) {
requireBundle.append(";").append(VISIBILITY_DIRECTIVE).append(":=").append(VISIBILITY_REEXPORT);
}
requireBundle.append(",");
}
int len = requireBundle.length();
if (len > 0 && requireBundle.charAt(len - 1) == ',') {
requireBundle.deleteCharAt(len - 1);
attrs.putValue(REQUIRE_BUNDLE, requireBundle.toString());
attrs.putValue("Manifest-Version", "1.0");
attrs.putValue("Implementation-Vendor", "The Apache Software Foundation");
attrs.putValue("Implementation-Vendor-Id", "org.apache");
if (bundleVersion != null) {
attrs.putValue(BUNDLE_VERSION, bundleVersion);
}
attrs.putValue(BUNDLE_MANIFESTVERSION, "2");
attrs.putValue(BUNDLE_SYMBOLICNAME, GATEWAY_BUNDLE);
attrs.putValue(BUNDLE_NAME, "Apache Tuscany SCA Gateway Bundle");
attrs.putValue(BUNDLE_VENDOR, "The Apache Software Foundation");
attrs.putValue(EXPORT_PACKAGE, "META-INF.services");
attrs.putValue(DYNAMICIMPORT_PACKAGE, "*");
attrs.putValue(BUNDLE_ACTIVATIONPOLICY, ACTIVATION_LAZY);
ByteArrayOutputStream bos = new ByteArrayOutputStream();
JarOutputStream jos = new JarOutputStream(bos, manifest);
jos.close();
return new ByteArrayInputStream(bos.toByteArray());
} else {
return null;
}
}
}