/*
* Copyright to the original author or authors.
*
* Licensed 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.rioproject.cybernode;
import net.jini.config.Configuration;
import net.jini.config.EmptyConfiguration;
import net.jini.id.UuidFactory;
import org.rioproject.cybernode.service.Environment;
import org.rioproject.cybernode.service.ServiceBeanContainerImpl;
import org.rioproject.cybernode.service.ServiceBeanDelegateImpl;
import org.rioproject.deploy.ServiceBeanInstance;
import org.rioproject.deploy.ServiceBeanInstantiationException;
import org.rioproject.impl.opstring.OAR;
import org.rioproject.impl.opstring.OpStringLoader;
import org.rioproject.opstring.*;
import org.rioproject.resolver.Artifact;
import org.rioproject.resolver.ResolverHelper;
import org.rioproject.impl.client.LookupCachePool;
import org.rioproject.start.LogManagementHelper;
import org.rioproject.impl.system.ComputeResource;
import org.rioproject.url.artifact.ArtifactURLStreamHandlerFactory;
import org.rioproject.impl.util.StringUtil;
import java.io.File;
import java.net.MalformedURLException;
import java.net.URL;
import java.rmi.RMISecurityManager;
import java.security.*;
import java.util.*;
/**
* A simple container for instantiating service beans. This utility can be used
* to create a service bean either from a classname, or from an
* <tt>OperationalString</tt> document. If the latter is used, the service bean
* is created using the attributes contained within the <tt>OperationalString</tt>
* document.
*
* <p>The <tt>StaticCybernode</tt> returns the implementation (back-end) object
* for the service bean(s) that have been created. This allows direct
* manipulation of the implementation, allowing debugging and unit testing to occur
* easily.
*
* <p>Notes:<br>
* <ul>
* <li>This utility expects that all necessary classes are in the
* classpath of the JVM in order to activate the service bean(s).</li>
* <li>If associations have been declared (with setter properties) the
* injection of associated services is undefined at this time.</li>
* <li><tt>OperationalString</tt> attributes relating to provisioning and
* SLAs are undefined at this time
* <li>The StaticCybernode does not support the instantiation of forked services.</li>
* </ul>
*
* @author Dennis Reedy
*/
public class StaticCybernode {
private ServiceBeanContainerImpl instantiator;
//private final Map<Object, Object> serviceMap = new HashMap<Object, Object>();
private final Set<ActivatedService> serviceSet = new HashSet<ActivatedService>();
static {
Policy.setPolicy(
new Policy() {
public PermissionCollection getPermissions(CodeSource codesource) {
Permissions perms = new Permissions();
perms.add(new AllPermission());
return(perms);
}
public void refresh() {
}
});
System.setSecurityManager(new RMISecurityManager());
System.setProperty("StaticCybernode", "true");
}
public StaticCybernode() {
LogManagementHelper.setup();
try {
new URL("artifact:org.rioproject");
} catch (MalformedURLException e) {
URL.setURLStreamHandlerFactory(new ArtifactURLStreamHandlerFactory());
}
try {
Configuration config = EmptyConfiguration.INSTANCE;
instantiator = new ServiceBeanContainerImpl(config);
ComputeResource cr = new ComputeResource();
String provisionRoot = Environment.setupProvisionRoot(true, config);
cr.setPersistentProvisioningRoot(provisionRoot);
cr.boot();
instantiator.setComputeResource(cr);
} catch (Exception e) {
e.printStackTrace();
}
instantiator.setUuid(UuidFactory.generate());
LookupCachePool.getInstance().setServiceBeanContainer(instantiator);
}
/**
* Shutdown & terminate the StaticCybernode
*/
public void destroy() {
instantiator.terminateServices();
}
/**
* Activate a service bean.
*
* @param classname The service bean class to create.
* @return The service bean implementation, initialized by the
* <tt>Cybernode</tt>. The service bean will have been instantiated with
* an empty configuration.
*
* @throws org.rioproject.deploy.ServiceBeanInstantiationException If the service bean cannot be created
*/
public Object activate(String classname) throws ServiceBeanInstantiationException {
if(classname==null)
throw new IllegalArgumentException("classname must not be null");
ServiceBeanInstance instance =
instantiator.activate(makeServiceElement(classname),
null, // OperationalStringManager
null); // EventHandler (slas)
ServiceBeanDelegateImpl delegate =
(ServiceBeanDelegateImpl) instantiator.getServiceBeanDelegate(instance.getServiceBeanID());
Object impl = delegate.getImpl();
serviceSet.add(new ActivatedService(impl, delegate.getProxy(), delegate));
return impl;
}
/**
* Activate service beans defined in an <tt>OperationalString</tt> document,
* scoping the beans to be activated by providing the bean names.
*
* @param opstring The <tt>OperationalString</tt> document
* @param beans The bean names to create. If not provided all
* services in the opstring will be created
* @return A map of bean name keys and service implementation object values
*
* @throws Exception If the <tt>OperationalString</tt> document results in
* parsing errors or the bean(s) cannot be created.
*/
public Map<String, Object> activate(File opstring, String... beans) throws Exception {
if(opstring==null)
throw new IllegalArgumentException("opstring file must not be null");
Map<String, Object> map = new HashMap<String, Object>();
OpStringLoader opl = new OpStringLoader();
OperationalString[] opStrings = opl.parseOperationalString(opstring);
for(OperationalString ops : opStrings) {
if(beans!=null && beans.length>0)
map.putAll(activate(ops, beans));
else
map.putAll(activate(ops));
}
return map;
}
/**
* Activate service beans defined by an OAR artifact,
* scoping the beans to be activated by providing the bean names.
*
* @param artifact The <code>Artifact</code> to activate
* @param beans The bean names to create. If not provided all
* services in the opstring will be created
* @return A map of bean name keys and service implementation object values
*
* @throws Exception If the <tt>OperationalString</tt> document results in
* parsing errors or the bean(s) cannot be created.
*/
@SuppressWarnings("unused")
public Map<String, Object> activate(Artifact artifact, String... beans) throws Exception {
if(artifact==null)
throw new IllegalArgumentException("artifact must not be null");
URL opStringURL = ResolverHelper.getResolver().getLocation(artifact.getGAV(), "oar");
if(opStringURL==null)
throw new OperationalStringException("Artifact "+artifact+" not resolvable");
OAR oar = new OAR(new File(opStringURL.toURI()));
OperationalString[] opStrings = oar.loadOperationalStrings();
Map<String, Object> map = new HashMap<String, Object>();
for(OperationalString ops : opStrings) {
if(beans!=null && beans.length>0)
map.putAll(activate(ops, beans));
else
map.putAll(activate(ops));
}
return map;
}
/**
* Activate service beans defined in an {@link org.rioproject.opstring.OperationalString},
* scoping the beans to be activated by providing the bean names.
*
* @param opstring The <tt>OperationalString</tt> document
* @param beans The bean names to create. If not provided all
* services in the opstring will be created
* @return A map of bean name keys and service implementation object values
*
* @throws org.rioproject.deploy.ServiceBeanInstantiationException If the bean(s) cannot be created.
*/
public Map<String, Object> activate(OperationalString opstring, String... beans)
throws ServiceBeanInstantiationException {
if(opstring==null)
throw new IllegalArgumentException("opstring must not be null");
Map<String, Object> map = new HashMap<String, Object>();
for(ServiceElement elem : opstring.getServices()) {
for(String bean : beans) {
if(elem.getName().equals(bean)) {
map.put(elem.getName(), instantiateBean(elem));
}
}
}
for(OperationalString nested : opstring.getNestedOperationalStrings()) {
activate(nested, beans);
}
return map;
}
/**
* Activate all service beans defined in an
* {@link org.rioproject.opstring.OperationalString}
*
* @param opstring The <tt>OperationalString</tt> document
* @return A map of bean name keys and service implementation object values
*
* @throws org.rioproject.deploy.ServiceBeanInstantiationException If the bean(s) cannot be created.
*/
public Map<String, Object> activate(OperationalString opstring)
throws ServiceBeanInstantiationException {
Map<String, Object> map = new HashMap<String, Object>();
for(ServiceElement elem : opstring.getServices()) {
map.put(elem.getName(), instantiateBean(elem));
}
return map;
}
/**
* De-activate a service
*
* @param impl The service implementation, obtained through the
* <tt>activate</tt> method
*/
public void deactivate(Object impl) {
ActivatedService a = getActivatedService(impl);
if(a!=null) {
a.delegate.terminate();
a.proxy = null;
a.impl = null;
a.delegate = null;
synchronized (serviceSet) {
serviceSet.remove(a);
}
}
}
/**
* Get the proxy for a service implementation created by the
* <tt>StaticCybernode</tt>
*
* @param impl The service implementation, obtained through the
* <tt>activate</tt> method
* @return The proxy for the service, or null if the service implementation
* has not been created by the <tt>StaticCybernode</tt>
*
* @throws IllegalArgumentException if the impl parameter is null
*/
public Object getServiceProxy(Object impl) {
if(impl==null)
throw new IllegalArgumentException("impl must not be null");
ActivatedService a = getActivatedService(impl);
return a==null?null:a.proxy;
}
private ActivatedService getActivatedService(Object impl) {
ActivatedService activatedService = null;
synchronized (serviceSet) {
for(ActivatedService a : serviceSet) {
if(a.impl.equals(impl)) {
activatedService = a;
break;
}
}
}
return activatedService;
}
private Object instantiateBean(ServiceElement elem) throws ServiceBeanInstantiationException {
if(elem.forkService())
throw new ServiceBeanInstantiationException("The StaticCybernode does not " +
"support the instantiation of a " +
"service declared to be forked");
ServiceBeanInstance instance = instantiator.activate(elem,
null, // OperationalStringManager
null); // EventHandler (slas)
ServiceBeanDelegateImpl delegate = (ServiceBeanDelegateImpl) instantiator.getServiceBeanDelegate(instance.getServiceBeanID());
Object impl = delegate.getImpl();
serviceSet.add(new ActivatedService(impl, delegate.getProxy(), delegate));
return impl;
}
private static String[] parseBeans(String beans) {
return StringUtil.toArray(beans);
}
private ServiceElement makeServiceElement(String implClass) {
ServiceElement elem = new ServiceElement();
ClassBundle main = new ClassBundle(implClass);
elem.setComponentBundle(main);
ServiceBeanConfig sbc = new ServiceBeanConfig();
String name = implClass;
int ndx = implClass.lastIndexOf(".");
if(ndx>0)
name = implClass.substring(ndx+1);
sbc.setName(name);
elem.setServiceBeanConfig(sbc);
return elem;
}
private static class ActivatedService {
private Object impl;
private Object proxy;
private ServiceBeanDelegateImpl delegate;
private ActivatedService(Object impl, Object proxy, ServiceBeanDelegateImpl delegate) {
this.impl = impl;
this.proxy = proxy;
this.delegate = delegate;
}
}
/**
* The <tt>StaticCybernode</tt> can be invoked directly from the command
* line. The <tt>StaticCybernode</tt> expects that all necessary classes
* are in the classpath of the JVM in order to activate the service
* bean(s). This includes the Rio, River (Jini) and Groovy jars, as well as
* any specific application classes.
*
* <p>Invoking the <tt>StaticCybernode</tt> is done as follows:
* <pre>
* Usage:
* org.rioproject.cybernode.StaticCybernode service-class-name | opstring-file [bean-names]
* </pre>
* <p>The <tt>StaticCybernode</tt> takes either a
* <table style="text-align: left; width: 100%;" border="1" cellpadding="2"
* cellspacing="2">
* <tbody>
* <tr>
* <td style="vertical-align: top;"><b>Argument</b><br>
* </td>
* <td style="vertical-align: top;"><b>Description</b><br>
* </td>
* </tr>
* <tr>
* <td style="vertical-align: top;">service-class-name<br>
* </td>
* <td style="vertical-align: top;">The fully qualified class name
* of the service bean to instantiate. The service bean will be created
* with an <i>empty</i> configuration<br>
* </td>
* </tr>
* <tr>
* <td style="vertical-align: top;">opstring-file<br>
* </td>
* <td style="vertical-align: top;">The OperationalString document
* (either .xml or .groovy) declaring service beans and service bean
* attributes.<br>
* </td>
* </tr>
* <tr>
* <td style="vertical-align: top;">bean-names<br>
* </td>
* <td style="vertical-align: top;">Optional comma-separated list of
* beans to create within the provided opstring-file<br>
* </td>
* </tr>
* </tbody>
* </table>
*
* @param args Either a <tt>service-class-name</tt> or the location of an
* <tt>opstring-file</tt> with optional <tt>bean-names</tt>
*/
public static void main(String... args) {
if(args.length==0) {
StringBuilder sb = new StringBuilder();
sb.append("Usage: \n");
sb.append("\t")
.append(StaticCybernode.class.getName())
.append(" service-class-name | opstring-file [bean-names]\n");
System.out.println(sb.toString());
System.exit(1);
}
try {
StaticCybernode sbc = new StaticCybernode();
List<String> options = new ArrayList<String>(Arrays.asList(args));
String option = options.get(0);
if(option.endsWith(".xml") || option.endsWith(".groovy")) {
options.remove(option);
String[] beans = null;
if(!options.isEmpty())
beans = parseBeans(options.get(0));
sbc.activate(new File(args[0]), beans);
}
else {
sbc.activate(args[0]);
}
} catch (Exception e) {
e.printStackTrace();
}
}
}