/*
* 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.rmi;
import org.rioproject.resolver.Resolver;
import org.rioproject.resolver.ResolverException;
import org.rioproject.resolver.ResolverHelper;
import org.rioproject.url.artifact.ArtifactURLConfiguration;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.File;
import java.lang.reflect.Field;
import java.net.MalformedURLException;
import java.rmi.server.RMIClassLoader;
import java.rmi.server.RMIClassLoaderSpi;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
/**
* An <code>RMIClassLoader</code> provider that supports the resolving of artifacts based on the
* {@link org.rioproject.url.artifact} protocol.
*
* <p>The <code>ResolvingLoader</code> uses a {@link org.rioproject.resolver.Resolver} to adapt codebases that
* have <code>artifact:</code> URLs.</p>
*
* <p>For each <code>artifact:</code> URL, the artifact is resolved (along with it's dependencies and
* transitive dependencies), and installed locally. The installed artifact location(s) are then passed to the
* default <code>RMIClassLoader</code> provider instance, where a class loader is created.</p>
*
* @author Dennis Reedy
*/
@SuppressWarnings("PMD.AvoidThrowingRawExceptionTypes")
public class ResolvingLoader extends RMIClassLoaderSpi {
/**
* A table of artifacts to derived codebases. This improves performance by resolving the classpath once per
* artifact.
*/
private final Map<String, String> artifactToCodebase = new ConcurrentHashMap<String, String>();
private static final Resolver resolver;
private static final Logger logger = LoggerFactory.getLogger(ResolvingLoader.class.getName());
static {
try {
resolver = ResolverHelper.getResolver();
} catch (ResolverException e) {
throw new RuntimeException(e);
}
}
private static final RMIClassLoaderSpi loader = RMIClassLoader.getDefaultProviderInstance();
@Override
public Class<?> loadClass(final String codebase,
final String name,
final ClassLoader defaultLoader) throws MalformedURLException, ClassNotFoundException {
if(logger.isTraceEnabled()) {
logger.trace("Load class {}, with codebase {}, defaultLoader {}",
name, codebase, defaultLoader==null?"NULL":defaultLoader.getClass().getName());
}
String resolvedCodebase = resolveCodebase(codebase);
Class<?> cl = loader.loadClass(resolvedCodebase, name, defaultLoader);
if(logger.isDebugEnabled()) {
logger.debug("Class {} loaded by {}", name, cl.getClassLoader());
}
return cl;
}
@Override
public Class<?> loadProxyClass(final String codebase,
final String[] interfaces,
final ClassLoader defaultLoader) throws MalformedURLException, ClassNotFoundException {
if(logger.isTraceEnabled()) {
logger.trace("Load proxy classes {}, with codebase {}, defaultLoader {}",
Arrays.toString(interfaces), codebase, defaultLoader==null?"NULL":defaultLoader.getClass().getName());
}
String resolvedCodebase = resolveCodebase(codebase);
Class<?> proxyClass = loader.loadProxyClass(resolvedCodebase, interfaces, defaultLoader);
if(logger.isDebugEnabled()) {
logger.debug("Proxy classes {} loaded by {}", Arrays.toString(interfaces), proxyClass.getClassLoader());
}
return proxyClass;
}
@Override
public ClassLoader getClassLoader(String codebase) throws MalformedURLException {
String resolvedCodebase = resolveCodebase(codebase);
ClassLoader classLoader = loader.getClassLoader(resolvedCodebase);
if(logger.isTraceEnabled()) {
logger.trace("ClassLoader for codebase {}, resolved as {} is {}", codebase, resolvedCodebase, classLoader);
}
return classLoader;
}
@Override
public String getClassAnnotation(final Class<?> aClass) {
String loaderAnnotation = loader.getClassAnnotation(aClass);
String artifact = null;
if(loaderAnnotation!=null) {
for(Map.Entry<String, String> entry : artifactToCodebase.entrySet()) {
if(entry.getValue().equals(loaderAnnotation)) {
artifact = entry.getKey();
break;
}
}
}
String annotation = artifact==null?loaderAnnotation:artifact;
if(logger.isDebugEnabled())
logger.debug("Annotation for {} is {}", aClass.getName(), annotation);
return annotation;
}
public static void release(final ClassLoader serviceLoader) {
try {
Field loaderTable = sun.rmi.server.LoaderHandler.class.getDeclaredField("loaderTable");
loaderTable.setAccessible(true);
HashMap loaderTableMap = (HashMap)loaderTable.get(null);
findAndRemove(serviceLoader, loaderTableMap);
} catch (NoSuchFieldException e) {
logger.warn("Failure accessing the loaderTable field", e);
} catch (IllegalAccessException e) {
logger.warn("Failure accessing the loaderTable field", e);
}
}
private String resolveCodebase(final String codebase) {
String adaptedCodebase;
if(codebase!=null && codebase.startsWith("artifact:")) {
adaptedCodebase = artifactToCodebase.get(codebase);
if(adaptedCodebase==null) {
try {
logger.debug("Resolve {} ", codebase);
StringBuilder builder = new StringBuilder();
String path = codebase.substring(codebase.indexOf(":")+1);
ArtifactURLConfiguration artifactURLConfiguration = new ArtifactURLConfiguration(path);
String[] cp = resolver.getClassPathFor(artifactURLConfiguration.getArtifact(),
artifactURLConfiguration.getRepositories());
for(String s : cp) {
if(builder.length()>0)
builder.append(" ");
builder.append(new File(s).toURI().toURL().toExternalForm());
}
adaptedCodebase = builder.toString();
artifactToCodebase.put(codebase, adaptedCodebase);
} catch (ResolverException e) {
adaptedCodebase = codebase;
logger.warn("Unable to resolve {}", codebase);
} catch (MalformedURLException e) {
adaptedCodebase = codebase;
logger.warn("The codebase {} is malformed", codebase, e);
}
}
} else {
adaptedCodebase = codebase;
}
return adaptedCodebase;
}
private synchronized static void findAndRemove(ClassLoader loader, Map loaderTable) {
for(Object o : loaderTable.entrySet()) {
Map.Entry entry = (Map.Entry) o;
Object key = entry.getKey();
try {
Field parentField = key.getClass().getDeclaredField("parent");
parentField.setAccessible(true);
ClassLoader toCheck = (ClassLoader) parentField.get(key);
if (isDescendantOf(toCheck, loader)) {
parentField.set(key, null);
}
} catch (NoSuchFieldException e) {
logger.warn("Failure accessing the parent field", e);
} catch (IllegalAccessException e) {
logger.warn("Failure accessing the parent field", e);
}
}
}
private static boolean isDescendantOf(ClassLoader toCheck, ClassLoader loader) {
if(toCheck==null)
return false;
if(toCheck.equals(loader))
return true;
boolean descendantOf = false;
ClassLoader parent = toCheck.getParent();
while(parent!=null) {
if(parent.equals(loader)) {
descendantOf = true;
break;
}
parent = parent.getParent();
}
return descendantOf;
}
}