/* * 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; } } }