| /* |
| * 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; |
| |
| import static org.apache.tuscany.sca.node.ContributionLocationHelper.getContributionLocations; |
| |
| import java.io.File; |
| import java.io.FileInputStream; |
| import java.io.IOException; |
| import java.io.InputStream; |
| import java.io.Reader; |
| import java.lang.reflect.InvocationTargetException; |
| import java.net.MalformedURLException; |
| import java.net.URI; |
| import java.net.URISyntaxException; |
| import java.net.URL; |
| import java.net.URLConnection; |
| import java.util.ArrayList; |
| import java.util.Arrays; |
| import java.util.HashMap; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Properties; |
| |
| import org.apache.tuscany.sca.core.ExtensionPointRegistry; |
| import org.apache.tuscany.sca.node.configuration.DefaultNodeConfigurationFactory; |
| import org.apache.tuscany.sca.node.configuration.NodeConfiguration; |
| import org.apache.tuscany.sca.node.configuration.NodeConfigurationFactory; |
| import org.apache.tuscany.sca.node.extensibility.NodeExtension; |
| import org.oasisopen.sca.ServiceReference; |
| import org.oasisopen.sca.ServiceRuntimeException; |
| |
| /** |
| * A factory for SCA processing nodes. An SCA processing node can be loaded |
| * with an SCA composite and the SCA contributions required by the composite. |
| * |
| * @version $Rev$ $Date$ |
| * @tuscany.spi.extension.asclient |
| */ |
| public abstract class NodeFactory extends DefaultNodeConfigurationFactory { |
| /** |
| * Default location of contribution metadata in an SCA contribution. |
| */ |
| private static final String SCA_CONTRIBUTION_META = "META-INF/sca-contribution.xml"; |
| |
| /** |
| * Default location of a generated contribution metadata in an SCA contribution. |
| */ |
| private static final String SCA_CONTRIBUTION_GENERATED_META = "META-INF/sca-contribution-generated.xml"; |
| |
| protected static NodeFactory instance; |
| protected static Class<?> factoryImplClass; |
| |
| protected static List<NodeFactory> factories = new ArrayList<NodeFactory>(); |
| |
| protected static void setNodeFactory(NodeFactory factory) { |
| instance = factory; |
| } |
| |
| public static class NodeProxy implements Node { |
| private Object node; |
| |
| private NodeProxy(Object node) { |
| super(); |
| this.node = node; |
| } |
| |
| public static <T> T createProxy(Class<T> type, Object node) { |
| try { |
| return type.getDeclaredConstructor(Object.class).newInstance(node); |
| } catch (Exception e) { |
| throw new IllegalArgumentException(e); |
| } |
| } |
| |
| public <B, R extends ServiceReference<B>> R cast(B target) throws IllegalArgumentException { |
| try { |
| return (R)node.getClass().getMethod("cast", Object.class).invoke(node, target); |
| } catch (Throwable e) { |
| handleException(e); |
| return null; |
| } |
| } |
| |
| public <B> B getService(Class<B> businessInterface, String serviceName) { |
| try { |
| return (B)node.getClass().getMethod("getService", Class.class, String.class).invoke(node, businessInterface, serviceName); |
| } catch (Throwable e) { |
| handleException(e); |
| return null; |
| } |
| } |
| |
| public <B> ServiceReference<B> getServiceReference(Class<B> businessInterface, String serviceName) { |
| try { |
| return (ServiceReference<B>)node.getClass().getMethod("getServiceReference", Class.class, String.class).invoke(node, businessInterface, serviceName); |
| } catch (Throwable e) { |
| handleException(e); |
| return null; |
| } |
| } |
| |
| public Node start() { |
| try { |
| return new NodeProxy(node.getClass().getMethod("start").invoke(node)); |
| } catch (Throwable e) { |
| handleException(e); |
| return null; |
| } |
| } |
| |
| public void stop() { |
| try { |
| node.getClass().getMethod("stop").invoke(node); |
| } catch (Throwable e) { |
| handleException(e); |
| } |
| } |
| |
| public void destroy() { |
| try { |
| node.getClass().getMethod("destroy").invoke(node); |
| } catch (Throwable e) { |
| handleException(e); |
| } |
| } |
| |
| private static void handleException(Throwable ex) { |
| if (ex instanceof InvocationTargetException) { |
| ex = ((InvocationTargetException)ex).getTargetException(); |
| } |
| if (ex instanceof RuntimeException) { |
| throw (RuntimeException)ex; |
| } |
| if (ex instanceof Error) { |
| throw (Error)ex; |
| } else { |
| throw new RuntimeException(ex); |
| } |
| } |
| |
| @Override |
| public String getEndpointAddress(String serviceBindingName) { |
| try { |
| return (String)node.getClass().getMethod("getEndpointAddress", String.class) |
| .invoke(node, serviceBindingName); |
| } catch (Throwable e) { |
| handleException(e); |
| return null; |
| } |
| } |
| |
| } |
| |
| /** |
| * Returns the SCA node factory instance. |
| * |
| * @return the SCA node factory |
| */ |
| public synchronized static NodeFactory getInstance() { |
| if (instance == null) { |
| instance = newInstance(); |
| } |
| return instance; |
| } |
| |
| /** |
| * Returns a new SCA node factory instance. |
| * |
| * @return a new SCA node factory |
| */ |
| public static NodeFactory newInstance() { |
| NodeFactory nodeFactory = null; |
| try { |
| Class<?> factoryClass = getFactoryImplClass(); |
| nodeFactory = (NodeFactory)factoryClass.newInstance(); |
| |
| } catch (Exception e) { |
| throw new ServiceRuntimeException(e); |
| } |
| factories.add(nodeFactory); |
| return nodeFactory; |
| } |
| |
| public static NodeFactory newInstance(Map<String, Map<String, String>> attributes) { |
| NodeFactory nodeFactory = null; |
| try { |
| Class<?> factoryClass = getFactoryImplClass(); |
| nodeFactory = (NodeFactory)factoryClass.newInstance(); |
| nodeFactory.configure(attributes); |
| } catch (Exception e) { |
| throw new ServiceRuntimeException(e); |
| } |
| factories.add(nodeFactory); |
| return nodeFactory; |
| } |
| |
| protected Properties properties; |
| |
| protected NodeFactory() { |
| this.properties = new Properties(); |
| } |
| |
| public static NodeFactory newInstance(Properties configProperties) { |
| NodeFactory nodeFactory = null; |
| try { |
| Class<?> factoryClass = getFactoryImplClass(); |
| nodeFactory = (NodeFactory)factoryClass.newInstance(); |
| nodeFactory.properties = configProperties; |
| nodeFactory.configure(new HashMap<String, Map<String,String>>()); |
| } catch (Exception e) { |
| throw new ServiceRuntimeException(e); |
| } |
| factories.add(nodeFactory); |
| return nodeFactory; |
| } |
| |
| public static NodeFactory newInstance(String configURI) { |
| Properties properties; |
| if (configURI == null || configURI.length() < 1) { |
| return newInstance(); |
| } else if (configURI.startsWith("properties:")) { |
| try { |
| properties = loadProperties(configURI.substring("properties:".length())); |
| } catch (IOException e) { |
| throw new ServiceRuntimeException(e); |
| } |
| } else if (configURI.startsWith("uri:")) { |
| properties = parseConfigURI(configURI.substring("uri:".length())); |
| } else { |
| properties = new Properties(); |
| properties.setProperty("defaultDomainName", configURI); |
| } |
| return newInstance(properties); |
| } |
| |
| /** |
| * Parse the config string into a Properties object. |
| * The config URI has the following format: |
| * uri:<domainName>?name=value&... |
| */ |
| private static Properties parseConfigURI(String configURI) { |
| Properties properties = new Properties(); |
| int qm = configURI.indexOf('?'); |
| if (qm < 0) { |
| properties.setProperty("defaultDomainName", configURI); |
| } else { |
| if (qm == 0) { |
| properties.setProperty("defaultDomainName", "default"); |
| } else { |
| properties.setProperty("defaultDomainName", configURI.substring(0, qm)); |
| } |
| if (configURI.length() > qm+1) { |
| Map<String, String> params = new HashMap<String, String>(); |
| for (String param : configURI.substring(qm+1).split("&")) { |
| String[] px = param.split("="); |
| if (px.length == 2) { |
| params.put(px[0], px[1]); |
| } else { |
| params.put(px[0], ""); |
| } |
| } |
| for (String name : params.keySet()) { |
| properties.setProperty(name, params.get(name)); |
| } |
| } |
| } |
| return properties; |
| } |
| |
| /** |
| * load the properties from external URL or a relative file |
| * properties:<url to properties file> |
| */ |
| private static Properties loadProperties(String propsURL) throws IOException { |
| |
| Properties properties = new Properties(); |
| File file = new File(propsURL); |
| |
| InputStream inputStream = null; |
| if (file.exists()) { |
| inputStream = new FileInputStream(file); |
| } else { |
| URL url = null; |
| try { |
| url = new URL(propsURL); |
| } catch (MalformedURLException e) { |
| inputStream = Thread.currentThread().getContextClassLoader().getResourceAsStream(propsURL); |
| if (inputStream == null) { |
| throw new IOException("File does not exist: " + propsURL + ", could not be found on the classpath and is not a valid URL: " + e); |
| } |
| } |
| if (inputStream == null && url != null) { |
| inputStream = url.openStream(); |
| } |
| } |
| if (inputStream != null) { |
| properties.load(inputStream); |
| inputStream.close(); |
| } |
| |
| return properties; |
| } |
| |
| /** |
| * Configure the NodeFactory instance |
| * @param attributes |
| */ |
| public void configure(Map<String, Map<String, String>> attributes) { |
| } |
| |
| private synchronized static Class<?> getFactoryImplClass() throws Exception { |
| if (factoryImplClass == null) { |
| // Use reflection APIs to call ServiceDiscovery to avoid hard dependency to tuscany-extensibility |
| try { |
| Class<?> discoveryClass = Class.forName("org.apache.tuscany.sca.extensibility.ServiceDiscovery"); |
| Object instance = discoveryClass.getMethod("getInstance").invoke(null); |
| Object factoryDeclaration = |
| discoveryClass.getMethod("getServiceDeclaration", Class.class).invoke(instance, NodeFactory.class); |
| if (factoryDeclaration != null) { |
| factoryImplClass = |
| (Class<?>)factoryDeclaration.getClass().getMethod("loadClass").invoke(factoryDeclaration); |
| } |
| } catch (ClassNotFoundException e) { |
| // Ignore |
| } |
| |
| if (factoryImplClass == null) { |
| // Fail back to default impl |
| String className = "org.apache.tuscany.sca.node.impl.NodeFactoryImpl"; |
| |
| factoryImplClass = Class.forName(className); |
| } |
| } |
| return factoryImplClass; |
| } |
| |
| /** |
| * Open a URL connection without cache |
| * @param url |
| * @return |
| * @throws IOException |
| */ |
| private static InputStream openStream(URL url) throws IOException { |
| InputStream is = null; |
| URLConnection connection = url.openConnection(); |
| connection.setUseCaches(false); |
| is = connection.getInputStream(); |
| return is; |
| } |
| |
| /** |
| * Escape the space in URL string |
| * @param uri |
| * @return |
| */ |
| private static URI createURI(String 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); |
| } |
| } |
| |
| /** |
| * Creates a new SCA node from the configuration URL |
| * |
| * @param configurationURL the URL of the node configuration which is the XML document |
| * that contains the URI of the composite and a collection of URLs for the contributions |
| * |
| * @return a new SCA node. |
| */ |
| public Node createNode(URL configurationURL) { |
| try { |
| InputStream is = openStream(configurationURL); |
| NodeConfiguration configuration = loadConfiguration(is, configurationURL); |
| return createNode(configuration); |
| } catch (IOException e) { |
| throw new ServiceRuntimeException(e); |
| } |
| } |
| |
| /** |
| * Creates a new SCA node from the XML configuration of the node |
| * @param is The input stream that the XML configuration can be read. The stream will be closed |
| * after this call. |
| * @return a new SCA node |
| */ |
| public Node createNode(InputStream is) { |
| NodeConfiguration configuration = loadConfiguration(is, null); |
| return createNode(configuration); |
| } |
| |
| /** |
| * Creates a new SCA node. |
| * |
| * @param deploymentCompositeURI the URI of the deployment composite. If the URI is relative, it should |
| * be resolved against the first contribution. Otherwise, the absolute URI is used to load the XML |
| * description of the composite. The deployment composite will be attached to the first contribution. |
| * |
| * @param contributions the URI of the contributions that provides the composites and related |
| * artifacts. If the list is empty, then we will use the thread context classloader to discover |
| * the contribution on the classpath |
| * |
| * @return a new SCA node. |
| */ |
| public Node createNode(String deploymentCompositeURI, Contribution... contributions) { |
| if (contributions == null || contributions.length == 0) { |
| if (deploymentCompositeURI == null || deploymentCompositeURI.indexOf(':') != -1) { |
| throw new ServiceRuntimeException("No SCA contribution is provided or discovered"); |
| } |
| // Try to find contributions on the classpath by the composite URI |
| List<String> 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) { |
| configuration.getContributions().get(0).addDeploymentComposite(createURI(deploymentCompositeURI)); |
| } |
| return createNode(configuration); |
| } |
| |
| public final Node createNode(URI domainRegistryURI, String... locations) { |
| return createNode(domainRegistryURI, null, locations); |
| } |
| |
| public final Node createNode(URI domainRegistryURI, String deploymentCompositeURI, String[] locations) { |
| Contribution[] contributions = getContributions(Arrays.asList(locations)); |
| NodeConfiguration configuration = createConfiguration(contributions); |
| if (deploymentCompositeURI != null && configuration.getContributions().size() > 0) { |
| configuration.getContributions().get(0).addDeploymentComposite(createURI(deploymentCompositeURI)); |
| } |
| configuration.setDomainRegistryURI(domainRegistryURI.toString()); |
| configuration.setDomainURI(getDomainURI(domainRegistryURI)); |
| return createNode(configuration); |
| } |
| |
| /** |
| * TODO: cleanup node use of registry uri, domain uri, and domain name |
| * so that its consistent across the code base |
| */ |
| public static String getDomainURI(URI configURI) { |
| String s = configURI.getHost(); |
| if (s == null) { |
| s = configURI.getSchemeSpecificPart(); |
| if (s != null) { |
| if (s.indexOf('?') > -1) { |
| s = s.substring(0, s.indexOf('?')); |
| } |
| } |
| } |
| return s; |
| } |
| |
| /** |
| * 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<String> 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 |
| * @return |
| */ |
| public Node createNode(Contribution... contributions) { |
| NodeConfiguration configuration = createConfiguration(contributions); |
| return createNode(configuration); |
| } |
| |
| |
| /** |
| * Creates a new SCA node. |
| * |
| * @param compositeContent the XML content of the deployment composite |
| * @param contributions the URI of the contributions that provides the composites and related artifacts |
| * @return a new SCA node. |
| */ |
| public Node createNode(InputStream compositeContent, Contribution... contributions) { |
| NodeConfiguration configuration = createConfiguration(contributions); |
| if (compositeContent != null && configuration.getContributions().size() > 0) { |
| configuration.getContributions().get(0).addDeploymentComposite(compositeContent); |
| } |
| return createNode(configuration); |
| } |
| |
| /** |
| * Creates a new SCA node. |
| * |
| * @param compositeContent the XML content of the deployment composite |
| * @param contributions the URI of the contributions that provides the composites and related artifacts |
| * @return a new SCA node. |
| */ |
| public Node createNode(Reader compositeContent, Contribution... contributions) { |
| NodeConfiguration configuration = createConfiguration(contributions); |
| if (compositeContent != null && configuration.getContributions().size() > 0) { |
| configuration.getContributions().get(0).addDeploymentComposite(compositeContent); |
| } |
| return createNode(configuration); |
| } |
| |
| /** |
| * Creates a new SCA node using defaults for the contribution location and deployable composites. |
| * By default, it uses the Thread context classloader to find META-INF/sca-contribution.xml or |
| * META-INF/sca-contribution-generated.xml on the classpath. The locations that contain such resources |
| * are taken as the SCA contributions. |
| * |
| * @return a new SCA node. |
| */ |
| public Node createNode() { |
| List<String> locations = new ArrayList<String>(); |
| 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 NodeConfiguration createConfiguration(Contribution... contributions) { |
| NodeConfigurationFactory factory = this; |
| NodeConfiguration configuration = factory.createNodeConfiguration(); |
| if (properties.getProperty("defaultDomainName") != null) { |
| configuration.setDomainURI(properties.getProperty("defaultDomainName")); |
| } |
| // Make sure a unique node URI is created for the same node factory |
| configuration.setURI(generateNodeURI()); |
| if (contributions != null) { |
| for (Contribution c : contributions) { |
| configuration.addContribution(c.getURI(), c.getLocation()); |
| } |
| } |
| return configuration; |
| } |
| |
| private static int count = 0; |
| |
| protected synchronized String generateNodeURI() { |
| return Node.DEFAULT_NODE_URI + (count++); |
| } |
| |
| private Contribution[] getContributions(List<String> locations) { |
| Contribution[] contributions = new Contribution[locations.size()]; |
| int index = 0; |
| for (String location : locations) { |
| contributions[index++] = new Contribution(location, location); |
| } |
| return contributions; |
| } |
| |
| private Contribution[] getContributions(List<String> uris, List<String> 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; |
| } |
| |
| public void destroy() { |
| count = 0; |
| instance = null; |
| factories.remove(this); |
| } |
| |
| public static List<NodeFactory> getNodeFactories() { |
| return factories; |
| } |
| |
| /** |
| * Create a new SCA node based on the configuration |
| * @param configuration The configuration of a node |
| * @return The SCA node |
| */ |
| public abstract Node createNode(NodeConfiguration configuration); |
| |
| /** |
| * Create an SCA node from a list of pre-built o.a.t.sca.contribution.Contribution objects. |
| * Pass java.lang.Objects for now as this class doesn't have direct dependencies on |
| * o.a.t.sca.contribution.Contribution. |
| */ |
| public abstract Node createNode(List<?> contributions); |
| |
| /** |
| * Create the node configuration from the XML document |
| * @param configuration The input stream of the XML document |
| * @return The node configuration |
| */ |
| public abstract NodeConfiguration loadConfiguration(InputStream xml, URL base); |
| |
| /** |
| * Get the ExtensionPointRegistry |
| * @return |
| */ |
| public abstract ExtensionPointRegistry getExtensionPointRegistry(); |
| |
| /** |
| * Initialize the factory |
| */ |
| public abstract void init(); |
| |
| /** |
| * Set auto destroy flag when all nodes are stopped |
| * @param b |
| */ |
| public abstract void setAutoDestroy(boolean b); |
| |
| /** |
| * Create and load the node for the purpose of metadata introspection |
| * @param configuration |
| * @return The extended Node |
| */ |
| public abstract NodeExtension loadNode(NodeConfiguration configuration); |
| } |