/*
* Copyright 2008 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.impl.container;
import com.sun.jini.config.Config;
import com.sun.jini.start.AggregatePolicyProvider;
import com.sun.jini.start.LoaderSplitPolicyProvider;
import net.jini.admin.Administrable;
import net.jini.config.Configuration;
import net.jini.id.ReferentUuid;
import net.jini.id.Uuid;
import net.jini.io.MarshalledInstance;
import net.jini.security.BasicProxyPreparer;
import net.jini.security.ProxyPreparer;
import net.jini.security.policy.DynamicPolicyProvider;
import net.jini.security.policy.PolicyFileProvider;
import org.rioproject.RioVersion;
import org.rioproject.admin.ServiceBeanControl;
import org.rioproject.config.Constants;
import org.rioproject.deploy.ServiceBeanInstantiationException;
import org.rioproject.impl.servicebean.DefaultServiceBeanFactory;
import org.rioproject.impl.servicebean.DefaultServiceBeanManager;
import org.rioproject.impl.servicebean.ServiceBeanActivation;
import org.rioproject.impl.servicebean.ServiceElementUtil;
import org.rioproject.impl.system.ComputeResource;
import org.rioproject.impl.system.capability.PlatformCapabilityLoader;
import org.rioproject.loader.ClassAnnotator;
import org.rioproject.loader.CommonClassLoader;
import org.rioproject.loader.ServiceClassLoader;
import org.rioproject.log.LoggerConfig;
import org.rioproject.opstring.ClassBundle;
import org.rioproject.opstring.ServiceElement;
import org.rioproject.resolver.RemoteRepository;
import org.rioproject.resolver.Resolver;
import org.rioproject.resolver.ResolverException;
import org.rioproject.resolver.ResolverHelper;
import org.rioproject.rmi.ResolvingLoader;
import org.rioproject.servicebean.ServiceBeanContext;
import org.rioproject.servicebean.ServiceBeanContextFactory;
import org.rioproject.servicebean.ServiceBeanFactory;
import org.rioproject.servicebean.ServiceBeanManager;
import org.rioproject.system.capability.PlatformCapability;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.net.MalformedURLException;
import java.net.URL;
import java.security.Policy;
import java.util.*;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.atomic.AtomicInteger;
/**
* The ServiceBeanLoader will load and create a service.
*
* @author Dennis Reedy
*/
public class ServiceBeanLoader {
/** Component name for loading configuration artifacts specifically related
* to service creation */
private static final String CONFIG_COMPONENT = "service.load";
/** A Logger */
private static Logger logger = LoggerFactory.getLogger(ServiceBeanLoader.class.getName());
private final static AggregatePolicyProvider globalPolicy;
private final static Policy initialGlobalPolicy;
static {
initialGlobalPolicy = Policy.getPolicy();
globalPolicy = new AggregatePolicyProvider(initialGlobalPolicy);
Policy.setPolicy(globalPolicy);
}
private static final Map<String, AtomicInteger> counterTable =
Collections.synchronizedMap(new HashMap<String, AtomicInteger>());
private static final List<ProvisionedResources> provisionedResources =
Collections.synchronizedList(new ArrayList<ProvisionedResources>());
private final static ExecutorService service = Executors.newCachedThreadPool();
/**
* Clean up resources
* <br>
* <li>
* Remove ServiceClassLoader from global policy to prevent leaking
* ServiceClassLoader instances
* <li>Remove any downloaded jars
* </ul>
*
* @param result The ServiceBeanLoaderResult object to unload
* @param elem The ServiceElement to use as a reference
*/
public static void unload(final ServiceBeanLoaderResult result, final ServiceElement elem) {
unload(result.getImpl().getClass().getClassLoader(), elem);
}
/**
* Clean up resources
* <br>
* <li>
* Remove ServiceClassLoader from global policy to prevent leaking
* ServiceClassLoader instances
* <li>Remove any downloaded jars
* </ul>
*
* @param loader The ClassLoader to unload
* @param elem The ServiceElement to use as a reference
*/
public static void unload(final ClassLoader loader, final ServiceElement elem) {
if(globalPolicy!=null)
globalPolicy.setPolicy(loader, null);
checkAndMaybeCleanProvisionedResources(elem);
service.submit(new Runnable() {
public void run() {
ResolvingLoader.release(loader);
}
});
}
@Override
protected void finalize() throws Throwable {
service.shutdownNow();
super.finalize();
}
/*
* Check and maybe remove provisioned resources collection
*/
private static void checkAndMaybeCleanProvisionedResources(final ServiceElement elem) {
List<ProvisionedResources> toRemove = new ArrayList<ProvisionedResources>();
ProvisionedResources[] copy = provisionedResources.toArray(new ProvisionedResources[provisionedResources.size()]);
for(ProvisionedResources pr : copy) {
if(elem.getComponentBundle()!=null &&
elem.getComponentBundle().getArtifact()!=null)
if(pr.getArtifact()!=null &&
pr.getArtifact().equals(elem.getComponentBundle().getArtifact())) {
toRemove.add(pr);
}
}
for(ProvisionedResources pr : toRemove) {
AtomicInteger count = counterTable.get(pr.getArtifact());
if(count!=null) {
int using = count.decrementAndGet();
if(logger.isTraceEnabled()) {
logger.trace("Number of [{}] artifacts still active={}", pr.getArtifact(), using);
}
if(using==0) {
provisionedResources.remove(pr);
} else {
counterTable.put(pr.getArtifact(), count);
}
}
}
}
/**
* The load method is invoked to load and instantiate a ServiceBean.
*
* @param sElem The ServiceElement
* @param serviceID Uuid for the service
* @param jsbManager The ServiceBeanManager
* @param container The ServiceBeanContainer
*
* @return A ServiceBeanLoaderResult object with attributes to access the instantiated
* service
*
* @throws org.rioproject.deploy.ServiceBeanInstantiationException If errors occur while creating the
* service bean
*/
public static ServiceBeanLoaderResult load(final ServiceElement sElem,
final Uuid serviceID,
final ServiceBeanManager jsbManager,
final ServiceBeanContainer container) throws ServiceBeanInstantiationException {
ServiceBeanFactory.Created created = null;
MarshalledInstance marshalledProxy = null;
ServiceBeanContext context;
CommonClassLoader commonCL = CommonClassLoader.getInstance();
ComputeResource computeResource = container.getComputeResource();
/*
* Provision service jars
*/
URL[] exports = new URL[0];
URL[] implJARs = new URL[0];
if(System.getProperty("StaticCybernode")==null) {
try {
Resolver resolver = ResolverHelper.getResolver();
boolean install = computeResource.getPersistentProvisioning();
Map<String, ProvisionedResources> serviceResources = provisionService(sElem, resolver, install);
ProvisionedResources dlPR = serviceResources.get("dl");
ProvisionedResources implPR = serviceResources.get("impl");
if(dlPR.getJars().length==0 && dlPR.getArtifact()!=null) {
String convertedArtifact = dlPR.getArtifact().replaceAll(":", "/");
String[] artifactParts = convertedArtifact.split(" ");
List<URL> exportURLList = new ArrayList<URL>();
for(String artifactPart : artifactParts) {
// TODO: if the repositories is default maven central, still need to add?
exportURLList.add(new URL("artifact:"+artifactPart+dlPR.getRepositories()));
}
exports = exportURLList.toArray(new URL[exportURLList.size()]);
} else {
exports = dlPR.getJars();
}
implJARs = implPR.getJars();
} catch(Exception e) {
throw new ServiceBeanInstantiationException("Unable to provision JARs for " +
"service "+ ServiceLogUtil.logName(sElem), e);
}
}
final Thread currentThread = Thread.currentThread();
ClassLoader currentClassLoader = currentThread.getContextClassLoader();
Uuid serviceIDToUse = serviceID;
try {
ClassBundle jsbBundle = sElem.getComponentBundle();
List<URL> urlList = new ArrayList<URL>();
/*
URL[] implJARs;
if(jsbBundle!=null && jsbBundle.getCodebase()!=null)
implJARs = jsbBundle.getJARs();
else
implJARs = new URL[0];
*/
urlList.addAll(Arrays.asList(implJARs));
/* Get matched PlatformCapability jars to load */
PlatformCapability[] pCaps = computeResource.getPlatformCapabilities();
PlatformCapability[] matched = ServiceElementUtil.getMatchedPlatformCapabilities(sElem, pCaps);
for (PlatformCapability pCap : matched) {
URL[] urls = PlatformCapabilityLoader.getLoadableClassPath(pCap);
for(URL url : urls) {
if(!urlList.contains(url))
urlList.add(url);
}
}
URL[] classpath = urlList.toArray(new URL[urlList.size()]);
Properties metaData = new Properties();
metaData.setProperty("opStringName", sElem.getOperationalStringName());
metaData.setProperty("serviceName", sElem.getName());
ServiceClassLoader jsbCL = new ServiceClassLoader(ServiceClassLoader.getURIs(classpath),
new ClassAnnotator(exports),
commonCL,
metaData);
/*
ServiceClassLoader jsbCL =
new ServiceClassLoader(classpath, annotator, commonCL);
*/
currentThread.setContextClassLoader(jsbCL);
if(logger.isDebugEnabled()) {
StringBuilder buffer = new StringBuilder();
if(implJARs.length==0) {
buffer.append("<empty>");
} else {
for(int i=0; i<implJARs.length; i++) {
if(i>0)
buffer.append(", ");
buffer.append(implJARs[i].toExternalForm());
}
}
String className = (jsbBundle==null?"<not defined>": jsbBundle.getClassName());
if(logger.isDebugEnabled()) {
logger.debug("Create ServiceClassLoader for {}, classpath {}, codebase {}",
className, buffer.toString(), jsbCL.getClassAnnotation());
}
}
/* Get the servicePolicyFile from the environment. If the
* property has not been set use the policy set for the VM */
String servicePolicyFile = System.getProperty("rio.service.security.policy",
System.getProperty("java.security.policy"));
if(logger.isTraceEnabled()) {
logger.trace("{} Service security policy file {}",
ServiceLogUtil.logName(sElem), servicePolicyFile);
}
if(servicePolicyFile!=null) {
if(logger.isTraceEnabled()) {
logger.trace("Set {} service security to LoaderSplitPolicyProvider", ServiceLogUtil.logName(sElem));
}
DynamicPolicyProvider service_policy = new DynamicPolicyProvider(new PolicyFileProvider(servicePolicyFile));
LoaderSplitPolicyProvider splitServicePolicy = new LoaderSplitPolicyProvider(jsbCL,
service_policy,
new DynamicPolicyProvider(initialGlobalPolicy));
globalPolicy.setPolicy(jsbCL, splitServicePolicy);
}
/* Reload the shared configuration using the service's classloader */
//String[] configFiles = container.getSharedConfigurationFiles().toArray(new String[sharedConfigurationFiles.size()]);
Configuration sharedConfiguration = container.getSharedConfiguration();
/* Get the ServiceBeanContextFactory */
ServiceBeanContextFactory serviceBeanContextFactory =
(ServiceBeanContextFactory)Config.getNonNullEntry(sharedConfiguration,
CONFIG_COMPONENT,
"serviceBeanContextFactory",
ServiceBeanContextFactory.class,
new ServiceContextFactory());
/* Create the ServiceBeanContext */
context = serviceBeanContextFactory.create(sElem,
jsbManager,
computeResource,
sharedConfiguration);
/* Add a temporary startup value, used to check when issuing
* lifecycle notification (RIO-141) */
Map<String, Object> configParms = context.getServiceBeanConfig().getConfigurationParameters();
configParms.put(Constants.STARTING, true);
context.getServiceBeanConfig().setConfigurationParameters(configParms);
/*
* Initialize any configured Logger instances. If there are any
* exceptions loading the configurations, log the appropriate
* message and continue
*/
LoggerConfig[] loggerConfigs = context.getServiceBeanConfig().getLoggerConfigs();
if(loggerConfigs != null) {
for (LoggerConfig loggerConfig : loggerConfigs) {
try {
loggerConfig.getLogger();
} catch (Throwable t) {
logger.warn("Loading LoggerConfig", t);
}
}
}
/* Get the ServiceBeanFactory */
ServiceBeanFactory serviceBeanFactory =
(ServiceBeanFactory)Config.getNonNullEntry(context.getConfiguration(),
CONFIG_COMPONENT,
"serviceBeanFactory",
ServiceBeanFactory.class,
new DefaultServiceBeanFactory());
if(logger.isTraceEnabled()) {
logger.trace("service = {}, serviceBeanFactory = {}",
ServiceLogUtil.logName(sElem), serviceBeanFactory);
}
created = serviceBeanFactory.create(context);
logger.trace("Created ServiceBeanFactory.Created {}", created);
Object impl = created.getImpl();
logger.trace("Obtained implementation {}", impl);
if(context.getServiceElement().getComponentBundle()==null) {
String compName = impl.getClass().getName();
if(compName.indexOf(".")>0) {
int index = compName.lastIndexOf(".");
compName = compName.substring(0, index);
}
context.getServiceBeanConfig().addInitParameter(ServiceBeanActivation.BOOT_CONFIG_COMPONENT, compName);
}
/* Get the ProxyPreparer */
if(logger.isTraceEnabled()) {
logger.trace("Get the ProxyPreparer for {}", ServiceLogUtil.logName(sElem));
}
ProxyPreparer servicePreparer = (ProxyPreparer)Config.getNonNullEntry(context.getConfiguration(),
CONFIG_COMPONENT,
"servicePreparer",
ProxyPreparer.class,
new BasicProxyPreparer());
if(logger.isTraceEnabled()) {
logger.trace("Getting the proxy");
}
Object proxy = created.getProxy();
if(logger.isTraceEnabled()) {
logger.trace("Obtained the proxy %s", proxy);
}
if(proxy != null) {
proxy = servicePreparer.prepareProxy(proxy);
}
if(logger.isTraceEnabled()) {
logger.trace("Proxy {}, prepared? {}", proxy, (proxy==null?"not prepared, returned proxy was null": "yes"));
}
/*
* Set the MarshalledInstance into the ServiceBeanManager
*/
if(logger.isTraceEnabled()) {
logger.trace("Set the MarshalledInstance into the ServiceBeanManager");
}
marshalledProxy = new MarshalledInstance(proxy);
((DefaultServiceBeanManager)context.getServiceBeanManager()).setMarshalledInstance(marshalledProxy);
/*
* The service may have created it's own serviceID
*/
if(proxy instanceof ReferentUuid) {
serviceIDToUse = ((ReferentUuid)proxy).getReferentUuid();
if(logger.isDebugEnabled()) {
logger.debug("Service has provided Uuid: {}", serviceIDToUse);
}
((DefaultServiceBeanManager)context.getServiceBeanManager()).setServiceID(serviceIDToUse);
}
/*
* If the proxy is Administrable and an instanceof
* ServiceBeanControl, set the ServiceBeanControl in the
* DefaultAssociationManagement object
*/
if(proxy instanceof Administrable) {
Object adminObject = ((Administrable)proxy).getAdmin();
if(adminObject instanceof ServiceBeanControl) {
context.getAssociationManagement().setServiceBeanControl((ServiceBeanControl)adminObject);
}
}
if(logger.isTraceEnabled()) {
logger.trace("Proxy = {}", proxy);
}
} catch(Exception e) {
ServiceBeanInstantiationException sbe;
if(logger.isTraceEnabled()) {
logger.trace("Loading ServiceBean", e);
}
if(e instanceof ServiceBeanInstantiationException)
sbe = (ServiceBeanInstantiationException)e;
else
sbe = new ServiceBeanInstantiationException(e.getClass().getName()+ ": "+ e.getLocalizedMessage(), e);
throw sbe;
} finally {
currentThread.setContextClassLoader(currentClassLoader);
}
return(new ServiceBeanLoaderResult(context, created.getImpl(), created.getBeanAdapter(), marshalledProxy, serviceIDToUse));
}
static synchronized Map<String, ProvisionedResources> provisionService(final ServiceElement elem,
final Resolver resolver,
final boolean supportsInstallation)
throws MalformedURLException, ServiceBeanInstantiationException {
Map<String, ProvisionedResources> map = new HashMap<String, ProvisionedResources>();
URL[] implJARs = null;
String implArtifact = (elem.getComponentBundle()!=null?
elem.getComponentBundle().getArtifact(): null);
ProvisionedResources implPR = getProvisionedResources(implArtifact);
if(implPR==null) {
implPR = new ProvisionedResources(implArtifact);
if(elem.getComponentBundle()!=null) {
if(System.getProperty("StaticCybernode")==null) {
if(elem.getComponentBundle().getArtifact()!=null && resolver!=null) {
if(!supportsInstallation)
throw new ServiceBeanInstantiationException("Service ["+elem.getName()+"] " +
"cannot be instantiated, the Cybernode " +
"does not support persistent provisioning, " +
"and the service requires artifact resolution " +
"for "+implArtifact);
URL[] jars;
try {
StringBuilder builder = new StringBuilder();
for(RemoteRepository repository: elem.getRemoteRepositories()) {
if(builder.length()>0){
builder.append(", ");
}
builder.append(repository.getUrl());
}
if(logger.isInfoEnabled()) {
logger.info("Resolve {} using these repositories [{}]",
elem.getComponentBundle().getArtifact(), builder.toString());
}
jars = ResolverHelper.resolve(elem.getComponentBundle().getArtifact(),
resolver,
elem.getRemoteRepositories());
} catch (ResolverException e) {
Throwable thrown = e;
if(e.getCause()!=null)
thrown = e.getCause();
throw new ServiceBeanInstantiationException("Could not resolve implementation artifact", thrown);
}
implJARs = jars;
} else {
/* RIO-228 */
if(System.getProperty("StaticCybernode")!=null)
implJARs = new URL[0];
else
implJARs = elem.getComponentBundle().getJARs();
}
}
} else {
implJARs = new URL[0];
}
if(implJARs!=null)
implPR.setJars(implJARs);
}
String exportArtifact = null;
for(ClassBundle cb : elem.getExportBundles()) {
if(cb.getArtifact()!=null) {
exportArtifact = cb.getArtifact();
break;
}
}
ProvisionedResources dlPR = getProvisionedResources(exportArtifact);
if(dlPR==null) {
if(implArtifact!=null && exportArtifact==null) {
exportArtifact = "org.rioproject:rio-api:"+ RioVersion.VERSION;
}
dlPR = new ProvisionedResources(exportArtifact);
dlPR.setJars(elem.getExportURLs());
for(RemoteRepository repository: elem.getRemoteRepositories()) {
StringBuilder repositoryString = new StringBuilder();
repositoryString.append(repository.getUrl());
if(repository.getId()!=null) {
repositoryString.append("@");
repositoryString.append(repository.getId());
}
dlPR.addRepositoryUrl(repositoryString.toString());
}
}
/*
* If we are instantiating a service that does not use artifact deployment,
* then we must check the dlPR jars for requisite jar inclusion
*/
if(exportArtifact==null && implArtifact==null) {
String localCodebase = System.getProperty(Constants.CODESERVER);
if(localCodebase!=null) {
if(!localCodebase.endsWith("/"))
localCodebase = localCodebase+"/";
String[] requisiteExports = new String[]{"rio-api-"+RioVersion.VERSION+".jar", "jsk-dl-2.2.2.jar"};
for(String export : requisiteExports) {
boolean found = false;
for(URL u : dlPR.getJars()) {
if(u.toExternalForm().endsWith(export)) {
found = true;
break;
}
}
if(!found) {
/* We check if the impl has an artifact, not the dl. The
* dl may not declare an artifact. This accounts for the
* case where a service is just implemented as a pojo with
* no custom remote methods, and is just exported using Rio
* infrastructure support
* (through the org.rioproject.service.Service
* interface).
*
* If there is no declared artifact, we sail through since
* the ServiceElement would have been constructed with the
* default platform export jars as part of it's creation.
*/
if(implPR.getArtifact()!=null) {
dlPR.addJar(new URL(localCodebase+export));
}
}
}
}
}
if(logger.isDebugEnabled()) {
StringBuilder sb = new StringBuilder();
sb.append("Service ").append(ServiceLogUtil.logName(elem)).append(" ");
if(implArtifact!=null)
sb.append("impl artifact: [").append(implPR.getArtifact()).append("] ");
sb.append("impl jars: ");
sb.append(implPR.getJarsAsString());
sb.append(", ");
if(dlPR.getArtifact()!=null)
sb.append("export artifact: [").append(dlPR.getArtifact()).append("] ");
sb.append("export jars: ");
sb.append(dlPR.getJarsAsString());
logger.debug(sb.toString());
}
map.put("impl", implPR);
map.put("dl", dlPR);
addProvisionedResources(map);
registerArtifactCounts(map);
return map;
}
private static ProvisionedResources getProvisionedResources(final String artifact) {
ProvisionedResources pr = null;
if(artifact!=null && !artifact.contains("SNAPSHOT")) {
for(ProvisionedResources alreadyProvisioned : provisionedResources) {
if(alreadyProvisioned.getArtifact()!=null &&
alreadyProvisioned.getArtifact().equals(artifact)) {
pr = alreadyProvisioned;
break;
}
}
}
return pr;
}
private static void addProvisionedResources(final Map<String, ProvisionedResources> map) {
for(Map.Entry<String, ProvisionedResources> entry : map.entrySet()) {
ProvisionedResources pr = entry.getValue();
if(pr.getArtifact()!=null && !pr.getArtifact().contains("SNAPSHOT")) {
if(!provisionedResources.contains(pr))
provisionedResources.add(pr);
}
}
}
private static void registerArtifactCounts(final Map<String, ProvisionedResources> map) {
for(Map.Entry<String, ProvisionedResources> entry : map.entrySet()) {
ProvisionedResources pr = entry.getValue();
if(pr.getArtifact()!=null && !pr.getArtifact().contains("SNAPSHOT")) {
AtomicInteger count = counterTable.get(pr.getArtifact());
if(count==null)
count = new AtomicInteger(1);
else
count.incrementAndGet();
counterTable.put(pr.getArtifact(), count);
if(logger.isTraceEnabled()) {
logger.trace("Counter for [{}] is now {}", pr.getArtifact(), count.get());
}
}
}
}
private static class ProvisionedResources {
Set<URL> jars = new HashSet<URL>();
String artifact;
StringBuilder repositories = new StringBuilder();
private ProvisionedResources(final String artifact) {
this.artifact = artifact;
}
void setJars(final URL[] jars) {
this.jars.addAll(Arrays.asList(jars));
}
void addJar(final URL jar) {
jars.add(jar);
}
URL[] getJars() {
return jars.toArray(new URL[jars.size()]);
}
String getArtifact() {
return artifact;
}
String getJarsAsString() {
return jars.isEmpty() ? "<>" : jars.toString();
}
void addRepositoryUrl(String u) {
repositories.append(";").append(u);
}
String getRepositories() {
return repositories.toString();
}
@Override
public String toString() {
final StringBuilder sb = new StringBuilder();
sb.append("jars=").append(
jars.isEmpty() ? "<>" : jars.toString());
sb.append('}');
return sb.toString();
}
}
}