From bfc5133237c096260597a01f186188e9331cc34b Mon Sep 17 00:00:00 2001 From: rfeng Date: Fri, 15 May 2009 22:37:25 +0000 Subject: Update the node launcher to use the NodeFactory git-svn-id: http://svn.us.apache.org/repos/asf/tuscany@775369 13f79535-47bb-0310-9956-ffa450edef68 --- .../NodeImplementationLauncherBootstrap.java | 4 +- .../node/webapp/NodeWebAppServletHost.java | 42 ++-- .../org/apache/tuscany/sca/node/NodeFactory.java | 51 ++++- .../impl/BindingConfigurationImpl.java | 4 +- .../impl/ContributionConfigurationImpl.java | 2 +- .../impl/DeploymentCompositeImpl.java | 2 +- .../configuration/impl/NodeConfigurationImpl.java | 4 +- .../node/equinox/launcher/NodeLauncherUtil.java | 249 +++++++++++---------- .../sca/node/launcher/NodeLauncherUtil.java | 229 ++++++++++--------- 9 files changed, 334 insertions(+), 253 deletions(-) diff --git a/java/sca/modules/implementation-node-runtime/src/main/java/org/apache/tuscany/sca/implementation/node/launcher/NodeImplementationLauncherBootstrap.java b/java/sca/modules/implementation-node-runtime/src/main/java/org/apache/tuscany/sca/implementation/node/launcher/NodeImplementationLauncherBootstrap.java index a26f63f9d0..9a9a4d8ddf 100644 --- a/java/sca/modules/implementation-node-runtime/src/main/java/org/apache/tuscany/sca/implementation/node/launcher/NodeImplementationLauncherBootstrap.java +++ b/java/sca/modules/implementation-node-runtime/src/main/java/org/apache/tuscany/sca/implementation/node/launcher/NodeImplementationLauncherBootstrap.java @@ -19,6 +19,8 @@ package org.apache.tuscany.sca.implementation.node.launcher; +import java.net.URL; + import org.apache.tuscany.sca.node.Client; import org.apache.tuscany.sca.node.Contribution; import org.apache.tuscany.sca.node.Node; @@ -101,7 +103,7 @@ public class NodeImplementationLauncherBootstrap { */ public NodeImplementationLauncherBootstrap(String configurationURI) throws Exception { NodeFactory nodeFactory = NodeFactory.newInstance(); - node = new NodeFacade(nodeFactory.createNode(configurationURI)); + node = new NodeFacade(nodeFactory.createNode(new URL(configurationURI))); } /** diff --git a/java/sca/modules/implementation-node-runtime/src/main/java/org/apache/tuscany/sca/implementation/node/webapp/NodeWebAppServletHost.java b/java/sca/modules/implementation-node-runtime/src/main/java/org/apache/tuscany/sca/implementation/node/webapp/NodeWebAppServletHost.java index 5bf4f188f4..b6d2f3d643 100644 --- a/java/sca/modules/implementation-node-runtime/src/main/java/org/apache/tuscany/sca/implementation/node/webapp/NodeWebAppServletHost.java +++ b/java/sca/modules/implementation-node-runtime/src/main/java/org/apache/tuscany/sca/implementation/node/webapp/NodeWebAppServletHost.java @@ -6,15 +6,15 @@ * 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. + * under the License. */ package org.apache.tuscany.sca.implementation.node.webapp; @@ -53,7 +53,7 @@ import org.apache.tuscany.sca.node.NodeFactory; /** * ServletHost implementation for use in a Webapp Node environment. - * + * * @version $Rev$ $Date$ */ public class NodeWebAppServletHost implements ServletHost, Filter { @@ -63,7 +63,7 @@ public class NodeWebAppServletHost implements ServletHost, Filter { private Map servlets = new HashMap(); private Node node; - + private String contextPath = "/"; private int defaultPort = 8080; @@ -75,7 +75,7 @@ public class NodeWebAppServletHost implements ServletHost, Filter { /** * Returns the Servlet host for the current Web app. - * + * * @return */ public static NodeWebAppServletHost servletHost() { @@ -84,12 +84,12 @@ public class NodeWebAppServletHost implements ServletHost, Filter { /** * Initialize the Servlet host. - * + * * @param filterConfig * @throws ServletException */ public void init(final FilterConfig filterConfig) throws ServletException { - + // Create a Servlet config wrapping the given filter config ServletConfig servletConfig = servletConfig(filterConfig); @@ -102,27 +102,31 @@ public class NodeWebAppServletHost implements ServletHost, Filter { // Derive the node name from the Webapp context path String nodeName = contextPath; if (nodeName.startsWith("/")) { - nodeName = nodeName.substring(1); + nodeName = nodeName.substring(1); } if (nodeName.endsWith("/")) { - nodeName = nodeName.substring(0, nodeName.length() - 1); + nodeName = nodeName.substring(0, nodeName.length() - 1); } - + // Determine the node configuration URI String nodeConfiguration = NodeImplementationLauncherUtil.nodeConfigurationURI(nodeName); - + // Create the SCA node NodeFactory nodeFactory = NodeFactory.newInstance(); - node = nodeFactory.createNode(nodeConfiguration); - + try { + node = nodeFactory.createNode(new URL(nodeConfiguration)); + } catch (MalformedURLException e) { + throw new ServletException(e); + } + // Register the Servlet host ServletHostExtensionPoint servletHosts = servletHosts(node); servletHosts.getServletHosts().clear(); servletHosts.addServletHost(servletHost); - // Save the node in the Servlet context + // Save the node in the Servlet context servletContext.setAttribute(Client.class.getName(), node); - + // Start the node node.start(); @@ -259,7 +263,7 @@ public class NodeWebAppServletHost implements ServletHost, Filter { /** * Destroy the Servlet host. - * + * * @throws ServletException */ public void destroy() { @@ -347,7 +351,7 @@ public class NodeWebAppServletHost implements ServletHost, Filter { /** * Returns the Servlet host extension point used by the given node. - * + * * @return */ private static ServletHostExtensionPoint servletHosts(Node node) { @@ -365,7 +369,7 @@ public class NodeWebAppServletHost implements ServletHost, Filter { /** * Returns a Servlet config wrapping a filter config. - * + * * @param filterConfig * @return */ diff --git a/java/sca/modules/node-api/src/main/java/org/apache/tuscany/sca/node/NodeFactory.java b/java/sca/modules/node-api/src/main/java/org/apache/tuscany/sca/node/NodeFactory.java index 485c159287..781fd2688c 100644 --- a/java/sca/modules/node-api/src/main/java/org/apache/tuscany/sca/node/NodeFactory.java +++ b/java/sca/modules/node-api/src/main/java/org/apache/tuscany/sca/node/NodeFactory.java @@ -30,6 +30,7 @@ import java.net.URISyntaxException; import java.net.URL; import java.net.URLConnection; import java.util.ArrayList; +import java.util.Arrays; import java.util.List; import org.apache.tuscany.sca.node.configuration.DefaultNodeConfigurationFactory; @@ -279,7 +280,11 @@ public abstract class NodeFactory extends DefaultNodeConfigurationFactory { throw new ServiceRuntimeException("No SCA contribution is provided or discovered"); } // Try to find contributions on the classpath by the composite URI - contributions = getContributions(getContributionLocations(null, deploymentCompositeURI)); + List locations = getContributionLocations(null, deploymentCompositeURI); + if (locations.isEmpty()) { + throw new ServiceRuntimeException("No SCA contributions are found on the classpath"); + } + contributions = getContributions(locations); } NodeConfiguration configuration = createConfiguration(contributions); if (deploymentCompositeURI != null && configuration.getContributions().size() > 0) { @@ -288,6 +293,29 @@ public abstract class NodeFactory extends DefaultNodeConfigurationFactory { return createNode(configuration); } + /** + * The following methods are used by the node launcher + */ + public final Node createNode(String deploymentCompositeURI, String[] uris, String locations[]) { + return createNode(deploymentCompositeURI, getContributions(Arrays.asList(uris), Arrays.asList(locations))); + } + + public final Node createNode(String deploymentCompositeURI, String locations[]) { + return createNode(deploymentCompositeURI, getContributions(Arrays.asList(locations))); + } + + public final Node createNode(Reader deploymentCompositeContent, String[] uris, String locations[]) { + return createNode(deploymentCompositeContent, getContributions(Arrays.asList(uris), Arrays.asList(locations))); + } + + public final Node createNode(String compositeURI, ClassLoader classLoader) { + List locations = ContributionLocationHelper.getContributionLocations(classLoader, compositeURI); + return createNode(compositeURI, locations.toArray(new String[locations.size()])); + } + /** + * ------------------- end of methods ----------------- + */ + /** * Create a new SCA node using the list of SCA contributions * @param contributions @@ -371,14 +399,14 @@ public abstract class NodeFactory extends DefaultNodeConfigurationFactory { List locations = new ArrayList(); locations.addAll(getContributionLocations(null, SCA_CONTRIBUTION_META)); locations.addAll(getContributionLocations(null, SCA_CONTRIBUTION_GENERATED_META)); + if (locations.isEmpty()) { + throw new ServiceRuntimeException("No SCA contributions are found on the classpath"); + } Contribution[] contributions = getContributions(locations); return createNode(contributions); } private Contribution[] getContributions(List locations) { - if (locations.isEmpty()) { - throw new ServiceRuntimeException("No SCA contributions are found on the classpath"); - } Contribution[] contributions = new Contribution[locations.size()]; int index = 0; for (String location : locations) { @@ -387,10 +415,21 @@ public abstract class NodeFactory extends DefaultNodeConfigurationFactory { return contributions; } + private Contribution[] getContributions(List uris, List locations) { + if (uris.size() != locations.size()) { + throw new IllegalArgumentException("The number of URIs does not match the number of locations"); + } + Contribution[] contributions = new Contribution[locations.size()]; + for (int i = 0, n = locations.size(); i < n; i++) { + contributions[i] = new Contribution(uris.get(i), locations.get(i)); + } + return contributions; + } + /** * Create a new SCA node based on the configuration - * @param configuration - * @return + * @param configuration The configuration of a node + * @return The SCA node */ public abstract Node createNode(NodeConfiguration configuration); diff --git a/java/sca/modules/node-api/src/main/java/org/apache/tuscany/sca/node/configuration/impl/BindingConfigurationImpl.java b/java/sca/modules/node-api/src/main/java/org/apache/tuscany/sca/node/configuration/impl/BindingConfigurationImpl.java index dd38f97161..8fba4ee6a3 100644 --- a/java/sca/modules/node-api/src/main/java/org/apache/tuscany/sca/node/configuration/impl/BindingConfigurationImpl.java +++ b/java/sca/modules/node-api/src/main/java/org/apache/tuscany/sca/node/configuration/impl/BindingConfigurationImpl.java @@ -27,9 +27,9 @@ import javax.xml.namespace.QName; import org.apache.tuscany.sca.node.configuration.BindingConfiguration; /** - * + * Default implementation of BindingConfiguration */ -class BindingConfigurationImpl implements BindingConfiguration { +public class BindingConfigurationImpl implements BindingConfiguration { private QName type; private List baseURIs = new ArrayList(); diff --git a/java/sca/modules/node-api/src/main/java/org/apache/tuscany/sca/node/configuration/impl/ContributionConfigurationImpl.java b/java/sca/modules/node-api/src/main/java/org/apache/tuscany/sca/node/configuration/impl/ContributionConfigurationImpl.java index b863ede5f6..b08a4e6cff 100644 --- a/java/sca/modules/node-api/src/main/java/org/apache/tuscany/sca/node/configuration/impl/ContributionConfigurationImpl.java +++ b/java/sca/modules/node-api/src/main/java/org/apache/tuscany/sca/node/configuration/impl/ContributionConfigurationImpl.java @@ -35,7 +35,7 @@ import org.apache.tuscany.sca.node.configuration.DeploymentComposite; /** * Configuration for an SCA contribution used by the SCA node */ -class ContributionConfigurationImpl implements ContributionConfiguration { +public class ContributionConfigurationImpl implements ContributionConfiguration { private List deploymentComposites = new ArrayList(); private String uri; private String location; diff --git a/java/sca/modules/node-api/src/main/java/org/apache/tuscany/sca/node/configuration/impl/DeploymentCompositeImpl.java b/java/sca/modules/node-api/src/main/java/org/apache/tuscany/sca/node/configuration/impl/DeploymentCompositeImpl.java index 4edcf75bba..e94f195931 100644 --- a/java/sca/modules/node-api/src/main/java/org/apache/tuscany/sca/node/configuration/impl/DeploymentCompositeImpl.java +++ b/java/sca/modules/node-api/src/main/java/org/apache/tuscany/sca/node/configuration/impl/DeploymentCompositeImpl.java @@ -24,7 +24,7 @@ import org.apache.tuscany.sca.node.configuration.DeploymentComposite; /** * Configuration for a deployment composite */ -class DeploymentCompositeImpl implements DeploymentComposite { +public class DeploymentCompositeImpl implements DeploymentComposite { private String location; private String content; private String contributionURI; diff --git a/java/sca/modules/node-api/src/main/java/org/apache/tuscany/sca/node/configuration/impl/NodeConfigurationImpl.java b/java/sca/modules/node-api/src/main/java/org/apache/tuscany/sca/node/configuration/impl/NodeConfigurationImpl.java index acbf3d5246..e9bd791f96 100644 --- a/java/sca/modules/node-api/src/main/java/org/apache/tuscany/sca/node/configuration/impl/NodeConfigurationImpl.java +++ b/java/sca/modules/node-api/src/main/java/org/apache/tuscany/sca/node/configuration/impl/NodeConfigurationImpl.java @@ -33,9 +33,9 @@ import org.apache.tuscany.sca.node.configuration.ContributionConfiguration; import org.apache.tuscany.sca.node.configuration.NodeConfiguration; /** - * + * Default implementation of NodeConfiguration */ -class NodeConfigurationImpl implements NodeConfiguration { +public class NodeConfigurationImpl implements NodeConfiguration { private String uri; private String domainURI; private List contributions = new ArrayList(); diff --git a/java/sca/modules/node-launcher-equinox/src/main/java/org/apache/tuscany/sca/node/equinox/launcher/NodeLauncherUtil.java b/java/sca/modules/node-launcher-equinox/src/main/java/org/apache/tuscany/sca/node/equinox/launcher/NodeLauncherUtil.java index d6aa838d41..d51fd0a278 100644 --- a/java/sca/modules/node-launcher-equinox/src/main/java/org/apache/tuscany/sca/node/equinox/launcher/NodeLauncherUtil.java +++ b/java/sca/modules/node-launcher-equinox/src/main/java/org/apache/tuscany/sca/node/equinox/launcher/NodeLauncherUtil.java @@ -6,15 +6,15 @@ * 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. + * under the License. */ package org.apache.tuscany.sca.node.equinox.launcher; @@ -36,7 +36,10 @@ 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; @@ -48,7 +51,6 @@ import java.util.ArrayList; import java.util.Collection; import java.util.HashSet; import java.util.List; -import java.util.Map; import java.util.Set; import java.util.StringTokenizer; import java.util.jar.Attributes; @@ -74,6 +76,8 @@ import org.osgi.framework.Constants; * @version $Rev$ $Date$ */ final class NodeLauncherUtil { + private static final String NODE_API_BUNDLE = "org.apache.tuscany.sca.node.api"; + private static final Logger logger = Logger.getLogger(NodeLauncherUtil.class.getName()); static final String LAUNCHER_EQUINOX_LIBRARIES = "org.apache.tuscany.sca.node.launcher.equinox.libraries"; @@ -86,8 +90,6 @@ final class NodeLauncherUtil { private static final String NODE_IMPLEMENTATION_DAEMON_BOOTSTRAP = "org.apache.tuscany.sca.implementation.node.launcher.NodeImplementationDaemonBootstrap"; - private static final String NODE_IMPLEMENTATION_LAUNCHER_BOOTSTRAP = - "org.apache.tuscany.sca.implementation.node.launcher.NodeImplementationLauncherBootstrap"; private static final String TUSCANY_HOME = "TUSCANY_HOME"; private static final String TUSCANY_PATH = "TUSCANY_PATH"; @@ -113,57 +115,20 @@ final class NodeLauncherUtil { // Get the node runtime bundle. Bundle bundle = null; for (Bundle b : bundleContext.getBundles()) { - if ("org.apache.tuscany.sca.implementation.node.runtime".equals(b.getSymbolicName())) { + if (NODE_API_BUNDLE.equals(b.getSymbolicName())) { bundle = b; break; } } if (bundle == null) { - throw new IllegalStateException( - "Bundle org.apache.tuscany.sca.implementation.node.runtime is not installed"); + 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_IMPLEMENTATION_LAUNCHER_BOOTSTRAP); - - Object bootstrap; - if (configurationURI != null) { - - // Construct the node with a configuration URI - bootstrap = bootstrapClass.getConstructor(String.class).newInstance(configurationURI); - - } else if (compositeContent != null) { - - // Construct the node with a composite URI, the composite content and - // the URIs and locations of a list of contributions - Constructor constructor = - bootstrapClass.getConstructor(String.class, 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(); - } - bootstrap = constructor.newInstance(compositeURI, compositeContent, uris, locations); + Class bootstrapClass = bundle.loadClass(NODE_FACTORY); - } else { - - // Construct the node with a composite URI and the URIs and - // locations of a list of contributions - Constructor constructor = - bootstrapClass.getConstructor(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(); - } - bootstrap = constructor.newInstance(compositeURI, uris, locations); - } - - // Get the node instance - Object node = bootstrapClass.getMethod("getNode").invoke(bootstrap); + 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 @@ -182,9 +147,52 @@ final class NodeLauncherUtil { } } + 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 { @@ -211,7 +219,7 @@ final class NodeLauncherUtil { /** * Creates a new domain manager. - * + * * @throws LauncherException */ static Object domainManager(String rootDirectory) throws LauncherException { @@ -239,20 +247,20 @@ final class NodeLauncherUtil { /** * 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 + // 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 */ @@ -275,7 +283,7 @@ final class NodeLauncherUtil { version = matcher.group(); // Remove the leading "-" character version = version.substring(1); - // The Pattern above allows the version string to contain "-" and "_" as digit separators. + // 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('_', '.'); @@ -306,7 +314,7 @@ final class NodeLauncherUtil { /** * Add the packages found in the given JAR to a set. - * + * * @param jarFile * @param packages * @throws IOException @@ -356,7 +364,7 @@ final class NodeLauncherUtil { /** * Recursively traverse a root directory - * + * * @param fileList * @param file * @param root @@ -380,35 +388,35 @@ final class NodeLauncherUtil { } } } - + /** * Finds the OSGi manifest file for a JAR file, where the manifest file is held in a META-INF directory - * alongside the JAR + * 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; + 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 @@ -419,23 +427,23 @@ final class NodeLauncherUtil { String bundleSymbolicName, String bundleVersion) throws IllegalStateException { try { - // Added by Mike Edwards, 12/04/2009 - to handle the third party JAR files in the distribution that - // have separate OSGi manifest files provided alongside them - // In some cases a single JAR file is already accompanied by a MANIFEST.MF file, sitting in - // a META-INF directory alongside the JAR - //if( jarFiles.size() == 1 ){ - // URL theJar = jarFiles.iterator().next(); - // Manifest theManifest = findOSGiManifest( theJar ); - // if( theManifest != null ) return theManifest; - //} // end if - // End of addition - + // Added by Mike Edwards, 12/04/2009 - to handle the third party JAR files in the distribution that + // have separate OSGi manifest files provided alongside them + // In some cases a single JAR file is already accompanied by a MANIFEST.MF file, sitting in + // a META-INF directory alongside the JAR + //if( jarFiles.size() == 1 ){ + // URL theJar = jarFiles.iterator().next(); + // Manifest theManifest = findOSGiManifest( theJar ); + // if( theManifest != null ) return theManifest; + //} // end if + // End of addition + // List exported packages and bundle classpath entries StringBuffer classpath = new StringBuffer(); StringBuffer exports = new StringBuffer(); StringBuffer imports = new StringBuffer(); Set packages = new HashSet(); - + for (URL jarFile : jarFiles) { addPackages(jarFile, packages, bundleVersion); classpath.append("\"external:"); @@ -495,16 +503,15 @@ final class NodeLauncherUtil { /** * 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 jarFiles, - String bundleSymbolicName, - String bundleVersion) throws IOException { + static InputStream thirdPartyLibraryBundle(Collection jarFiles, String bundleSymbolicName, String bundleVersion) + throws IOException { ByteArrayOutputStream bos = new ByteArrayOutputStream(); Manifest mf = thirdPartyLibraryBundleManifest(jarFiles, bundleSymbolicName, bundleVersion); JarOutputStream jos = new JarOutputStream(bos, mf); @@ -514,7 +521,7 @@ final class NodeLauncherUtil { /** * Returns the location of this bundle. - * + * * @return * @throws IOException */ @@ -554,7 +561,7 @@ final class NodeLauncherUtil { /** * Returns the location of this bundle. - * + * * @param bundle * @return * @throws IOException @@ -583,7 +590,7 @@ final class NodeLauncherUtil { /** * Install the given bundle. - * + * * @param bundleContext * @param location * @throws BundleException @@ -616,7 +623,7 @@ final class NodeLauncherUtil { /** * Returns a string representation of the given bundle. - * + * * @param b * @param verbose * @return @@ -653,7 +660,7 @@ final class NodeLauncherUtil { /** * Returns the name of a bundle, or null if the given file is not a bundle. - * + * * @param file * @return * @throws IOException @@ -698,7 +705,7 @@ final class NodeLauncherUtil { /** * Collect JAR files in the given directory. - * + * * @param directory * @param urls * @param filter @@ -729,7 +736,7 @@ final class NodeLauncherUtil { /** * Collect development .../ target/classes directories in the given directory. - * + * * @param directory * @param urls * @param filter @@ -766,7 +773,7 @@ final class NodeLauncherUtil { /** * Collect JAR files under the given distribution directory. - * + * * @param directory * @param jarDirectoryURLs * @param jarURLs @@ -875,7 +882,7 @@ final class NodeLauncherUtil { new StandAloneDevelopmentClassesFileNameFilter()); // Added Mike Edwards, 09/04/2009 // Get hold of the Libraries that are used by the Tuscany modules - collectDevelopmentLibraryEntries( modulesDirectory, jarDirectoryURLs, jarURLs ); + collectDevelopmentLibraryEntries(modulesDirectory, jarDirectoryURLs, jarURLs); } // end if } } @@ -920,7 +927,7 @@ final class NodeLauncherUtil { /** * Returns the JAR files on the classpath used by the given classloader. - * + * * @param classLoader * @return */ @@ -945,7 +952,7 @@ final class NodeLauncherUtil { /** * Collect JARs on the classpath of a URLClassLoader. - * + * * @param urls * @param cl */ @@ -968,7 +975,7 @@ final class NodeLauncherUtil { } } count = urls.size() - count; - + if (count != 0) { logger.info("Runtime classpath: " + count + " JAR" @@ -1010,7 +1017,7 @@ final class NodeLauncherUtil { } } return urls; - } + } /** * A file name filter used to filter JAR files. @@ -1093,7 +1100,7 @@ final class NodeLauncherUtil { return false; } - // Exclude the Tomcat and Jetty hosts + // Exclude the Tomcat and Jetty hosts if (name.startsWith("tuscany-host-tomcat") || name.startsWith("tuscany-host-jetty")) { //FIXME This is temporary return false; @@ -1113,22 +1120,30 @@ final class NodeLauncherUtil { name = name.toLowerCase(); // Include subdirectories - if (new File(dir, name).isDirectory()) { return true; } + if (new File(dir, name).isDirectory()) { + return true; + } // Filter out the Tuscany jars - since the development versions of these are used // from the \target\classes directories... - if (name.startsWith("tuscany")) { return false; } + if (name.startsWith("tuscany")) { + return false; + } // Include JAR and MAR files - if (name.endsWith(".jar")) { return true; } - if (name.endsWith(".mar")) { return true; } + 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 */ @@ -1151,7 +1166,7 @@ final class NodeLauncherUtil { /** * Returns the location of the classpath entry, JAR, WAR etc. containing the given class. - * + * * @param clazz * @return */ @@ -1162,7 +1177,7 @@ final class NodeLauncherUtil { /** * Collect JAR files under the given distribution directory. - * + * * @param directory * @param jarDirectoryURLs * @param jarURLs @@ -1183,14 +1198,14 @@ final class NodeLauncherUtil { } } // 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 + * 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 @@ -1200,8 +1215,8 @@ final class NodeLauncherUtil { Set jarDirectoryURLs, Set jarURLs) throws MalformedURLException { // Get the \java\sca directory - File rootDirectory = modulesDirectory.getParentFile(); - // Get the \java\sca\distribution\all\target\modules + 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(); diff --git a/java/sca/modules/node-launcher/src/main/java/org/apache/tuscany/sca/node/launcher/NodeLauncherUtil.java b/java/sca/modules/node-launcher/src/main/java/org/apache/tuscany/sca/node/launcher/NodeLauncherUtil.java index 0b641c1b84..33cb50ca97 100644 --- a/java/sca/modules/node-launcher/src/main/java/org/apache/tuscany/sca/node/launcher/NodeLauncherUtil.java +++ b/java/sca/modules/node-launcher/src/main/java/org/apache/tuscany/sca/node/launcher/NodeLauncherUtil.java @@ -6,15 +6,15 @@ * 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. + * under the License. */ package org.apache.tuscany.sca.node.launcher; @@ -23,7 +23,10 @@ import java.io.File; import java.io.FileNotFoundException; import java.io.FilenameFilter; import java.io.IOException; +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; @@ -47,10 +50,10 @@ import java.util.logging.Logger; * @version $Rev$ $Date$ */ final class NodeLauncherUtil { - private static final String NODE_IMPLEMENTATION_LAUNCHER_BOOTSTRAP = "org.apache.tuscany.sca.implementation.node.launcher.NodeImplementationLauncherBootstrap"; + private static final String NODE_FACTORY = "org.apache.tuscany.sca.node.NodeFactory"; private static final Logger logger = Logger.getLogger(NodeLauncherUtil.class.getName()); - + private static final String TUSCANY_HOME = "TUSCANY_HOME"; private static final String TUSCANY_PATH = "TUSCANY_PATH"; @@ -58,40 +61,40 @@ final class NodeLauncherUtil { /** * Returns a ClassLoader for the Tuscany runtime JARs for use in a standalone * J2SE environment. - * + * * @param parentClassLoader - * + * * @return */ static ClassLoader standAloneRuntimeClassLoader(ClassLoader parentClassLoader) throws FileNotFoundException, URISyntaxException, MalformedURLException { return runtimeClassLoader(parentClassLoader, new StandAloneJARFileNameFilter()); } - + /** * Returns a ClassLoader for the Tuscany runtime JARs for use in a Webapp * environment. - * + * * @param parentClassLoader - * + * * @return */ static ClassLoader webAppRuntimeClassLoader(ClassLoader parentClassLoader) throws FileNotFoundException, URISyntaxException, MalformedURLException { return runtimeClassLoader(parentClassLoader, new WebAppJARFileNameFilter()); } - + /** * Returns a ClassLoader for the Tuscany runtime JARs. - * + * * @param parentClassLoader * @param filter - * + * * @return */ private static ClassLoader runtimeClassLoader(ClassLoader parentClassLoader, FilenameFilter filter) throws FileNotFoundException, URISyntaxException, MalformedURLException { // First try to see if the runtime classes are already on the classpath // If yes, skip the discovery to avoid duplicate jars try { - Class.forName(NODE_IMPLEMENTATION_LAUNCHER_BOOTSTRAP, false, parentClassLoader); + Class.forName(NODE_FACTORY, false, parentClassLoader); return parentClassLoader; } catch (ClassNotFoundException e) { // Ignore; @@ -99,15 +102,15 @@ final class NodeLauncherUtil { // Build list of runtime JARs Set jarDirectoryURLs = new HashSet(); Set jarURLs = new HashSet(); - + // First determine the path to the launcher class - String resource = NodeLauncherUtil.class.getName().replace('.', '/') + ".class"; + String resource = NodeLauncherUtil.class.getName().replace('.', '/') + ".class"; URL url = NodeLauncherUtil.class.getClassLoader().getResource(resource); if (url == null) { throw new FileNotFoundException(resource); } URI uri = url.toURI(); - + // 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 @@ -119,7 +122,7 @@ final class NodeLauncherUtil { path = path.substring(0, i); uri = URI.create(path); } - + File file = new File(uri); if (file.exists()) { File jarDirectory = file.getParentFile(); @@ -131,7 +134,7 @@ final class NodeLauncherUtil { } } } - + // 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 @@ -143,7 +146,7 @@ final class NodeLauncherUtil { logger.fine(TUSCANY_HOME + ": " + home); collectJARFiles(home, jarDirectoryURLs, jarURLs, filter); } - + // 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 @@ -158,19 +161,19 @@ final class NodeLauncherUtil { collectJARFiles(tokens.nextToken(), jarDirectoryURLs, jarURLs, filter); } } - + // Return the runtime class loader if (!jarURLs.isEmpty()) { // Remove the URLs which are already in the parent classloader if (parentClassLoader instanceof URLClassLoader) { URLClassLoader cl = (URLClassLoader)parentClassLoader; jarURLs.removeAll(Arrays.asList(cl.getURLs())); - } - + } + // Return a ClassLoader configured with the runtime JARs ClassLoader classLoader = new RuntimeClassLoader(jarURLs.toArray(new URL[jarURLs.size()]), parentClassLoader); return classLoader; - + } else { return null; } @@ -178,7 +181,7 @@ final class NodeLauncherUtil { /** * Collect JAR files under the given directory. - * + * * @param directory * @param jarDirectoryURLs * @param jarURLs @@ -188,24 +191,24 @@ final class NodeLauncherUtil { private static void collectJARFiles(String directory, Set jarDirectoryURLs, Collection jarURLs, FilenameFilter filter) throws MalformedURLException { File directoryFile = new File(directory); - URL directoryURL = directoryFile.toURI().toURL(); + URL directoryURL = directoryFile.toURI().toURL(); if (!jarDirectoryURLs.contains(directoryURL) && directoryFile.exists()) { - + // Collect files under $TUSCANY_HOME jarDirectoryURLs.add(directoryURL); collectJARFiles(directoryFile, jarURLs, filter, false); - + // Collect files under $TUSCANY_HOME/modules File modulesDirectory = new File(directoryFile, "modules"); - URL modulesDirectoryURL = modulesDirectory.toURI().toURL(); + URL modulesDirectoryURL = modulesDirectory.toURI().toURL(); if (!jarDirectoryURLs.contains(modulesDirectoryURL) && modulesDirectory.exists()) { jarDirectoryURLs.add(modulesDirectoryURL); collectJARFiles(modulesDirectory, jarURLs, filter, true); - } + } // Collect files under $TUSCANY_HOME/lib File libDirectory = new File(directoryFile, "lib"); - URL libDirectoryURL = libDirectory.toURI().toURL(); + URL libDirectoryURL = libDirectory.toURI().toURL(); if (!jarDirectoryURLs.contains(libDirectoryURL) && libDirectory.exists()) { jarDirectoryURLs.add(libDirectoryURL); collectJARFiles(libDirectory, jarURLs, filter, true); @@ -218,7 +221,7 @@ final class NodeLauncherUtil { * @param directory * @param urls * @param filter - * @param recursive + * @param recursive * @throws MalformedURLException */ private static void collectJARFiles(File directory, Collection urls, FilenameFilter filter, boolean recursive) throws MalformedURLException { @@ -243,13 +246,13 @@ final class NodeLauncherUtil { * A file name filter used to filter JAR files. */ private static class StandAloneJARFileNameFilter implements FilenameFilter { - + public boolean accept(File dir, String name) { if(new File(dir, name).isDirectory()) { return true; } - name = name.toLowerCase(); - + name = name.toLowerCase(); + // Exclude tuscany-sca-all and tuscany-sca-manifest as they duplicate // code in the individual runtime module JARs if (name.startsWith("tuscany-sca-all")) { @@ -258,18 +261,18 @@ final class NodeLauncherUtil { if (name.startsWith("tuscany-sca-manifest")) { return false; } - + if ("features".equals(dir.getName()) && name.startsWith("equinox-manifest")) { return false; } - + // 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; @@ -280,7 +283,7 @@ final class NodeLauncherUtil { return false; } } - + /** * A file name filter used to filter JAR files. */ @@ -291,27 +294,27 @@ final class NodeLauncherUtil { if (!super.accept(dir, name)) { return false; } - name = name.toLowerCase(); - + name = name.toLowerCase(); + // Exclude servlet-api JARs if (name.startsWith("servlet-api")) { return false; } - - // Exclude the Tomcat and Jetty hosts + + // 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; } } - - + + /** * Creates a new node. - * + * * @param compositeURI * @param contributions * @throws LauncherException @@ -319,66 +322,34 @@ final class NodeLauncherUtil { static Object node(String configurationURI, String compositeURI, String compositeContent, Contribution[] contributions, ClassLoader contributionClassLoader) throws LauncherException { ClassLoader tccl = Thread.currentThread().getContextClassLoader(); try { - + // Set up runtime ClassLoader ClassLoader runtimeClassLoader = runtimeClassLoader(Thread.currentThread().getContextClassLoader(), new StandAloneJARFileNameFilter()); if (runtimeClassLoader != null) { Thread.currentThread().setContextClassLoader(runtimeClassLoader); } - + // Use Java reflection to create the node as only the runtime class // loader knows the runtime classes required by the node - String className = NODE_IMPLEMENTATION_LAUNCHER_BOOTSTRAP; + String className = NODE_FACTORY; Class bootstrapClass; if (runtimeClassLoader != null) { bootstrapClass = Class.forName(className, true, runtimeClassLoader); } else { bootstrapClass = Class.forName(className); } - - Object bootstrap; - if (configurationURI != null) { - - // Construct the node with a configuration URI - bootstrap = bootstrapClass.getConstructor(String.class).newInstance(configurationURI); - - } else if (contributionClassLoader != null) { - - // Construct the node with a compositeURI and a classloader - Constructor constructor = bootstrapClass.getConstructor(String.class, ClassLoader.class); - bootstrap = constructor.newInstance(compositeURI, contributionClassLoader); - - } else if (compositeContent != null) { - - // Construct the node with a composite URI, the composite content and - // the URIs and locations of a list of contributions - Constructor constructor = bootstrapClass.getConstructor(String.class, 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(); - } - bootstrap = constructor.newInstance(compositeURI, compositeContent, uris, locations); - - } else { - - // Construct the node with a composite URI and the URIs and - // locations of a list of contributions - Constructor constructor = bootstrapClass.getConstructor(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(); - } - bootstrap = constructor.newInstance(compositeURI, uris, locations); - } - - Object node = bootstrapClass.getMethod("getNode").invoke(bootstrap); + + Object node = + createNode(bootstrapClass, + configurationURI, + compositeURI, + compositeContent, + contributions, + contributionClassLoader); + return node; - + } catch (Exception e) { NodeLauncher.logger.log(Level.SEVERE, "SCA Node could not be created", e); throw new LauncherException(e); @@ -387,9 +358,59 @@ final class NodeLauncherUtil { } } + private static Object createNode(Class bootstrapClass, + String configurationURI, + String compositeURI, + String compositeContent, + Contribution[] contributions, + ClassLoader contributionClassLoader) 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 (contributionClassLoader != null) { + + // NodeFactory.createNode(String, ClassLoader) + Method create = bootstrapClass.getMethod("createNode", String.class, ClassLoader.class); + node = create.invoke(nodeFactory, compositeURI, contributionClassLoader); + + } 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 { @@ -401,7 +422,7 @@ final class NodeLauncherUtil { if (runtimeClassLoader != null) { Thread.currentThread().setContextClassLoader(runtimeClassLoader); } - + // Use Java reflection to create the node daemon as only the runtime class // loader knows the runtime classes required by the node String className = "org.apache.tuscany.sca.implementation.node.launcher.NodeImplementationDaemonBootstrap"; @@ -412,10 +433,10 @@ final class NodeLauncherUtil { bootstrapClass = Class.forName(className); } 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); @@ -426,7 +447,7 @@ final class NodeLauncherUtil { /** * Creates a new domain manager. - * + * * @throws LauncherException */ static Object domainManager(String rootDirectory) throws LauncherException { @@ -438,7 +459,7 @@ final class NodeLauncherUtil { if (runtimeClassLoader != null) { Thread.currentThread().setContextClassLoader(runtimeClassLoader); } - + // Use Java reflection to create the node daemon as only the runtime class // loader knows the runtime classes required by the node String className = "org.apache.tuscany.sca.domain.manager.launcher.DomainManagerLauncherBootstrap"; @@ -450,10 +471,10 @@ final class NodeLauncherUtil { } 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); @@ -468,7 +489,7 @@ final class NodeLauncherUtil { private static class RuntimeClassLoader extends URLClassLoader { private static final ClassLoader systemClassLoader = ClassLoader.getSystemClassLoader(); private ClassLoader parent; - + /** * Constructs a new class loader. * @param urls @@ -492,7 +513,7 @@ final class NodeLauncherUtil { public Enumeration findResources(String name) throws IOException { Enumeration resources = super.findResources(name); Enumeration parentResources = parent.getResources(name); - List allResources = new ArrayList(); + List allResources = new ArrayList(); for (; resources.hasMoreElements(); ) { allResources.add(resources.nextElement()); } @@ -526,7 +547,7 @@ final class NodeLauncherUtil { } } } catch (ClassNotFoundException e) { - + // The class was not found by the parent class loader, try // to load it using our RuntimeClassloader cl = super.findClass(name); @@ -535,5 +556,5 @@ final class NodeLauncherUtil { return cl; } } - + } -- cgit v1.2.3