blob: 0aa01a477d5d403f921a85ca1725b9ea7ccaa572 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.tuscany.sca.node;
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);
}