/*
* 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.start;
import com.sun.jini.config.Config;
import com.sun.jini.start.*;
import net.jini.config.Configuration;
import net.jini.export.ProxyAccessor;
import net.jini.security.BasicProxyPreparer;
import net.jini.security.ProxyPreparer;
import net.jini.security.policy.PolicyFileProvider;
import org.rioproject.config.PlatformCapabilityConfig;
import org.rioproject.config.PlatformLoader;
import org.rioproject.loader.ClassAnnotator;
import org.rioproject.loader.CommonClassLoader;
import org.rioproject.loader.ServiceClassLoader;
import org.rioproject.resolver.Artifact;
import org.rioproject.resolver.ResolverHelper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.File;
import java.io.IOException;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.net.URL;
import java.security.Policy;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.StringTokenizer;
import java.util.jar.Attributes;
import java.util.jar.JarFile;
import java.util.jar.Manifest;
/**
* The RioServiceDescriptor class is a utility that conforms to the Apache River
* {@code ServiceStarter} framework, and will start a service using the
* {@link org.rioproject.loader.CommonClassLoader} as an in-process service.
* Clients construct this object with the details of the
* service to be launched, then call <code>create</code> to launch the service in
* invoking object's VM.
* <P>
* This class provides separation of the import codebase (where the server
* classes are loaded from) from the export codebase (where clients should load
* classes from for proxies, etc.). This functionality allows multiple
* service objects to be placed in the same VM, with each object maintaining a
* distinct codebase and policy.
* <P>
* Services need to implement the following constructor:
* <blockquote>
*
* <pre>
* <impl>(String[] args, LifeCycle lc)
* </pre>
*
* </blockquote>
*
* where,
* <UL>
* <LI>args - are the service configuration arguments
* <LI>lc - is the hosting environment's {@link LifeCycle} reference.
* </UL>
*
* @author Dennis Reedy
*/
public class RioServiceDescriptor implements ServiceDescriptor {
static String COMPONENT = RioServiceDescriptor.class.getPackage().getName();
static Logger logger = LoggerFactory.getLogger(COMPONENT);
/**
* The parameter types for the constructor.
*/
private static final Class[] actTypes = {String[].class, LifeCycle.class};
private final String codebase;
private final String policy;
private final String classpath;
private final String implClassName;
private final String[] serverConfigArgs;
private final LifeCycle lifeCycle;
private static LifeCycle NoOpLifeCycle = new LifeCycle() { // default, no-op
// object
public boolean unregister(Object impl) {
return false;
}
};
private static AggregatePolicyProvider globalPolicy = null;
private static Policy initialGlobalPolicy = null;
/**
* Object returned by
* {@link RioServiceDescriptor#create(net.jini.config.Configuration)
* RioServiceDescriptor.create()}
* method that returns the proxy and implementation references
* for the created service.
*/
public static class Created {
/** The reference to the proxy of the created service */
public final Object proxy;
/** The reference to the implementation of the created service */
public final Object impl;
/** Constructs an instance of this class.
* @param impl reference to the implementation of the created service
* @param proxy reference to the proxy of the created service
*/
public Created(final Object impl, final Object proxy) {
this.proxy = proxy;
this.impl = impl;
}
}
/**
* Create a RioServiceDescriptor, assigning given parameters to their associated,
* internal fields.
*
* @param codebase location where clients can download required
* service-related classes (for example, stubs, proxies, etc.). Codebase
* components must be separated by spaces in which each component is in
* <code>URL</code> format.
* @param policy server policy filename or URL
* @param classpath location where server implementation classes can be
* found. Classpath components must be separated by path separators.
* @param implClassName name of server implementation class
* @param serverConfigArgs service configuration arguments
* @param lifeCycle <code>LifeCycle</code> reference for hosting
* environment
*/
public RioServiceDescriptor(final String codebase,
final String policy,
final String classpath,
final String implClassName,
// Optional Args
final LifeCycle lifeCycle,
final String... serverConfigArgs) {
if(codebase == null || policy == null || classpath == null || implClassName == null)
throw new IllegalArgumentException("Codebase, policy, classpath, and implementation cannot be null");
this.codebase = codebase;
this.policy = policy;
//this.classpath = setClasspath(classpath);
this.classpath = classpath;
this.implClassName = implClassName;
this.serverConfigArgs = serverConfigArgs;
this.lifeCycle = (lifeCycle == null) ? NoOpLifeCycle : lifeCycle;
}
/**
* Create a RioServiceDescriptor. Equivalent to calling the other overloaded
* constructor with <code>null</code> for the <code>LifeCycle</code>
* reference.
*
* @param codebase location where clients can download required
* service-related classes (for example, stubs, proxies, etc.). Codebase
* components must be separated by spaces in which each component is in
* <code>URL</code> format.
* @param policy server policy filename or URL
* @param classpath location where server implementation classes can be
* found. Classpath components must be separated by path separators.
* @param implClassName name of server implementation class
* @param serverConfigArgs service configuration arguments
*/
public RioServiceDescriptor(final String codebase,
final String policy,
final String classpath,
final String implClassName,
// Optional Args
final String... serverConfigArgs) {
this(codebase, policy, classpath, implClassName, null, serverConfigArgs);
}
/**
* Codebase accessor method.
*
* @return The codebase string associated with this service descriptor.
*/
public String getCodebase() {
return codebase;
}
/**
* Policy accessor method.
*
* @return The policy string associated with this service descriptor.
*/
public String getPolicy() {
return policy;
}
/**
* <code>LifeCycle</code> accessor method.
*
* @return The <code>LifeCycle</code> object associated with
* this service descriptor.
*/
@SuppressWarnings("unused")
public LifeCycle getLifeCycle() {
return lifeCycle;
}
/**
* LifCycle accessor method.
*
* @return The classpath string associated with this service descriptor.
*/
public String getClasspath() {
return classpath;
}
/**
* Implementation class accessor method.
*
* @return The implementation class string associated with this service
* descriptor.
*/
public String getImplClassName() {
return implClassName;
}
/**
* Service configuration arguments accessor method.
*
* @return The service configuration arguments associated with this service
* descriptor.
*/
public String[] getServerConfigArgs() {
return (serverConfigArgs != null)? serverConfigArgs.clone(): null;
}
synchronized void ensureSecurityManager() {
if (System.getSecurityManager() == null) {
System.setSecurityManager(new SecurityManager());
}
}
/**
* @see com.sun.jini.start.ServiceDescriptor#create
*/
public Object create(final Configuration config) throws Exception {
ensureSecurityManager();
Object proxy = null;
/* Warn user of inaccessible codebase(s) */
if(getCodebase().startsWith("http"))
HTTPDStatus.httpdWarning(getCodebase());
/* Set common JARs to the CommonClassLoader */
String defaultDir;
String rioHome = System.getProperty("rio.home");
List<URL> urlList = new ArrayList<URL>();
if(rioHome==null) {
logger.warn("rio.home not defined, no default platformDir");
} else {
defaultDir = rioHome+ File.separator+"config"+File.separator+"platform";
PlatformLoader platformLoader = new PlatformLoader();
PlatformCapabilityConfig[] caps = platformLoader.getDefaultPlatform(rioHome);
for (PlatformCapabilityConfig cap : caps) {
URL[] urls = cap.getClasspathURLs();
urlList.addAll(Arrays.asList(urls));
}
String platformDir = (String)config.getEntry(COMPONENT, "platformDir", String.class, defaultDir);
if(logger.isDebugEnabled())
logger.debug("Platform directory set as {}", platformDir);
caps = platformLoader.parsePlatform(platformDir);
for (PlatformCapabilityConfig cap : caps) {
if (cap.getCommon()) {
URL[] urls = cap.getClasspathURLs();
urlList.addAll(Arrays.asList(urls));
}
}
}
URL[] commonJARs = urlList.toArray(new URL[urlList.size()]);
/*
if(commonJARs.length==0)
throw new RuntimeException("No commonJARs have been defined");
*/
CommonClassLoader commonCL = CommonClassLoader.getInstance();
if(logger.isDebugEnabled())
logger.debug("Created {}", commonCL.getClass().getName());
if(logger.isTraceEnabled()) {
StringBuilder buffer = new StringBuilder();
for(int i=0; i<commonJARs.length; i++) {
if(i>0)
buffer.append("\n");
buffer.append(commonJARs[i].toExternalForm());
}
logger.trace("commonJARs=\n{}", buffer.toString());
}
commonCL.addCommonJARs(commonJARs);
final Thread currentThread = Thread.currentThread();
ClassLoader currentClassLoader = currentThread.getContextClassLoader();
ClassAnnotator annotator = new ClassAnnotator(ClassLoaderUtil.getCodebaseURLs(getCodebase()));
String serviceClassPath;
if(Artifact.isArtifact(getClasspath())) {
String[] classPath = ResolverHelper.getResolver().getClassPathFor(getClasspath());
StringBuilder classPathBuilder = new StringBuilder();
for(String jar:classPath) {
if(classPathBuilder.length()>0)
classPathBuilder.append(File.pathSeparator);
classPathBuilder.append(jar);
}
serviceClassPath = classPathBuilder.toString();
} else {
serviceClassPath = getClasspath();
}
ServiceClassLoader serviceCL =
new ServiceClassLoader(ServiceClassLoader.getURIs(ClassLoaderUtil.getClasspathURLs(serviceClassPath)),
annotator,
commonCL);
if(logger.isDebugEnabled())
logger.debug("Created {}", serviceCL);
currentThread.setContextClassLoader(serviceCL);
if(logger.isTraceEnabled())
logger.trace("{}", ClassLoaderUtil.getContextClassLoaderTree());
/* Get the ProxyPreparer */
ProxyPreparer servicePreparer = (ProxyPreparer)Config.getNonNullEntry(config,
COMPONENT,
"servicePreparer",
ProxyPreparer.class,
new BasicProxyPreparer());
synchronized(RioServiceDescriptor.class) {
/* supplant global policy 1st time through */
if(globalPolicy == null) {
//initialGlobalPolicy = Policy.getPolicy();
initialGlobalPolicy = new PolicyFileProvider(getPolicy());
globalPolicy = new AggregatePolicyProvider(initialGlobalPolicy);
Policy.setPolicy(globalPolicy);
if(logger.isTraceEnabled())
logger.trace("Global policy set: {}", globalPolicy.toString());
}
/*DynamicPolicyProvider service_policy = new DynamicPolicyProvider(new PolicyFileProvider(getPolicy()));
LoaderSplitPolicyProvider splitServicePolicy =
new LoaderSplitPolicyProvider(serviceCL, service_policy, new DynamicPolicyProvider(initialGlobalPolicy));
globalPolicy.setPolicy(serviceCL, splitServicePolicy);*/
}
Object impl;
try {
Class<?> implClass;
implClass = Class.forName(getImplClassName(), false, serviceCL);
if(logger.isTraceEnabled())
logger.trace("Attempting to get implementation constructor");
Constructor constructor = implClass.getDeclaredConstructor(actTypes);
if(logger.isTraceEnabled())
logger.trace("Obtained implementation constructor: {}", constructor.toString());
constructor.setAccessible(true);
impl = constructor.newInstance(getServerConfigArgs(), lifeCycle);
if(logger.isTraceEnabled())
logger.trace("Obtained implementation instance: {}", impl.toString());
if(impl instanceof ServiceProxyAccessor) {
proxy = ((ServiceProxyAccessor)impl).getServiceProxy();
} else if(impl instanceof ProxyAccessor) {
proxy = ((ProxyAccessor)impl).getProxy();
} else {
proxy = null; // just for insurance
}
if(proxy != null) {
proxy = servicePreparer.prepareProxy(proxy);
}
if(logger.isTraceEnabled())
logger.trace("Proxy: {}", proxy==null?"<NULL>":proxy.toString());
currentThread.setContextClassLoader(currentClassLoader);
} catch(InvocationTargetException e) {
Throwable t = e.getCause()==null? e.getTargetException(): e.getCause();
if(t!=null && t instanceof Exception)
throw (Exception)t;
throw e;
} finally {
currentThread.setContextClassLoader(currentClassLoader);
}
return(new Created(impl, proxy));
}
/*
* Iterate through the classpath, for each jar see if there is a ClassPath
* manifest setting. If there is, append the settings to the classpath
*/
private String setClasspath(final String cp) {
if(logger.isDebugEnabled())
logger.debug("Create classpath from [{}]", cp);
StringBuilder buff = new StringBuilder();
for(String s : toArray(cp, "," + File.pathSeparator)) {
if(buff.length()>0 && !buff.toString().endsWith(File.pathSeparator))
buff.append(File.pathSeparator);
buff.append(s);
File f = new File(s);
try {
String jarPath = f.getParentFile().getCanonicalPath();
if(!jarPath.endsWith(File.separator)) {
jarPath = jarPath+File.separator;
}
if(logger.isDebugEnabled())
logger.debug("Creating jar file path from [{}]", f.getCanonicalPath());
JarFile jar = new JarFile(f);
Manifest man = jar.getManifest();
if (man == null) {
buff.append(File.pathSeparator);
continue;
}
Attributes attributes = man.getMainAttributes();
if (attributes == null) {
buff.append(File.pathSeparator);
continue;
}
String values = (String)attributes.get(new Attributes.Name("Class-Path"));
if(values!=null) {
for(String v : toArray(values, " ," + File.pathSeparator)) {
buff.append(File.pathSeparator);
String name = jarPath+v;
File add = new File(name);
buff.append(add.getCanonicalPath());
}
} else {
buff.append(File.pathSeparator);
}
} catch (IOException e) {
logger.warn("While trying to create classpath", e);
}
}
if(logger.isDebugEnabled())
logger.debug("Classpath created [{}]", buff.toString());
return buff.toString();
}
private String[] toArray(final String arg, final String delim) {
StringTokenizer tok = new StringTokenizer(arg, delim);
String[] array = new String[tok.countTokens()];
int i=0;
while(tok.hasMoreTokens()) {
array[i] = tok.nextToken();
i++;
}
return array;
}
public String toString() {
StringBuilder toStringBuilder = new StringBuilder();
toStringBuilder.append("RioServiceDescriptor codebase: ").append(codebase).append(", ");
toStringBuilder.append("policy: ").append(policy).append(", ");
toStringBuilder.append("classpath: ").append(classpath).append(", ");
toStringBuilder.append("implClassName: ").append(implClassName).append(", ");
toStringBuilder.append("serverConfigArgs: ").append((serverConfigArgs == null ? null :Arrays.asList(serverConfigArgs))).append(", ");
toStringBuilder.append("lifeCycle: ").append(lifeCycle);
return toStringBuilder.toString();
}
}