org.drools.wiring.api.classloader.ProjectClassLoader Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of drools-wiring-api Show documentation
Show all versions of drools-wiring-api Show documentation
This module defines an API for wiring classloaders and other services used in Drools.
/*
* Copyright 2015 Red Hat, Inc. and/or its affiliates.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
*
* 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.drools.wiring.api.classloader;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.lang.annotation.Annotation;
import java.net.URL;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Stream;
import org.drools.wiring.api.ComponentsFactory;
import org.drools.wiring.api.ResourceProvider;
import org.drools.wiring.api.util.ClassUtils;
import org.kie.internal.utils.KieTypeResolver;
import org.kie.memorycompiler.StoreClassLoader;
import org.kie.memorycompiler.WritableClassLoader;
import static org.drools.util.ClassUtils.findParentClassLoader;
import static org.drools.util.Config.getConfig;
public abstract class ProjectClassLoader extends ClassLoader implements KieTypeResolver, StoreClassLoader, WritableClassLoader {
static final boolean CACHE_NON_EXISTING_CLASSES = true;
private static boolean enableStoreFirst = Boolean.valueOf(getConfig("drools.projectClassLoader.enableStoreFirst", "true"));
static {
registerAsParallelCapable();
}
private Map store;
private Map definedTypes;
private final Set nonExistingClasses = ConcurrentHashMap.newKeySet();
private ClassLoader droolsClassLoader;
private InternalTypesClassLoader typesClassLoader;
private final Map> loadedClasses = new ConcurrentHashMap<>();
protected Set generatedClassNames = new HashSet<>();
private ResourceProvider resourceProvider;
protected ProjectClassLoader( ClassLoader parent, ResourceProvider resourceProvider) {
super(parent);
this.resourceProvider = resourceProvider;
}
public static ClassLoader getClassLoader(ClassLoader classLoader, Class< ? > cls) {
ProjectClassLoader projectClassLoader = createProjectClassLoader(classLoader);
if (cls != null) {
projectClassLoader.setDroolsClassLoader(cls.getClassLoader());
}
return projectClassLoader;
}
public ClassLoader getTypesClassLoader() {
return typesClassLoader instanceof ClassLoader ? (( ClassLoader ) typesClassLoader) : this;
}
public static ProjectClassLoader createProjectClassLoader() {
return ComponentsFactory.createProjectClassLoader(findParentClassLoader(ProjectClassLoader.class), null);
}
public static ProjectClassLoader createProjectClassLoader(ClassLoader parent) {
return createProjectClassLoader(parent, (ResourceProvider)null);
}
public static ProjectClassLoader createProjectClassLoader(ClassLoader parent, ResourceProvider resourceProvider) {
if (parent == null) {
return ComponentsFactory.createProjectClassLoader(findParentClassLoader(ProjectClassLoader.class), resourceProvider);
}
return parent instanceof ProjectClassLoader ? (ProjectClassLoader)parent : ComponentsFactory.createProjectClassLoader(parent, resourceProvider);
}
public static ProjectClassLoader createProjectClassLoader(ClassLoader parent, Map store) {
ProjectClassLoader projectClassLoader = createProjectClassLoader(parent);
projectClassLoader.store = store;
return projectClassLoader;
}
public abstract boolean isDynamic();
@Override
protected Class> loadClass(String name, boolean resolve) throws ClassNotFoundException {
Class> cls = loadedClasses.get(name);
if (cls != null) {
return cls;
}
if (isStoreFirst(name)) {
Class> clazz = findLoadedClass(name); // skip parent classloader
if (clazz != null) {
return clazz;
}
if (typesClassLoader != null) {
clazz = typesClassLoader.findLoadedClassWithoutParent(name);
if (clazz != null) {
return clazz;
}
}
// if generated class, go straight to defineType
cls = tryDefineType(name, null);
} else {
try {
cls = internalLoadClass(name, resolve);
} catch (ClassNotFoundException e2) {
// for stored classes which are not in generatedClassNames
cls = loadType(name, resolve);
}
}
loadedClasses.put(name, cls);
return cls;
}
protected boolean isStoreFirst(String name) {
return false;
}
// This method has to be public because is also used by the android ClassLoader
public Class> internalLoadClass(String name, boolean resolve) throws ClassNotFoundException {
if (CACHE_NON_EXISTING_CLASSES && nonExistingClasses.contains(name)) {
throw getClassNotFoundExceptionWithName(name);
}
if (droolsClassLoader != null) {
try {
return Class.forName(name, resolve, droolsClassLoader);
} catch (ClassNotFoundException e) { }
}
try {
return super.loadClass(name, resolve);
} catch (ClassNotFoundException e) {
try {
return Class.forName(name, resolve, getParent());
} catch (ClassNotFoundException e1) {
if (CACHE_NON_EXISTING_CLASSES) {
nonExistingClasses.add(name);
}
throw e1;
}
}
}
private Class> loadType(String name, boolean resolve) throws ClassNotFoundException {
ClassNotFoundException cnfe = null;
if (typesClassLoader != null) {
try {
return typesClassLoader.loadType(name, resolve);
} catch (ClassNotFoundException e) {
cnfe = e;
}
}
return tryDefineType(name, cnfe);
}
public Class> tryDefineType(String name, ClassNotFoundException cnfe) throws ClassNotFoundException {
byte[] bytecode = getBytecode( ClassUtils.convertClassToResourcePath(name));
if (bytecode == null) {
if (CACHE_NON_EXISTING_CLASSES) {
nonExistingClasses.add(name);
}
throw cnfe != null ? cnfe : getClassNotFoundExceptionWithName(name);
}
return defineType(name, bytecode);
}
private synchronized Class> defineType(String name, byte[] bytecode) {
if (definedTypes == null) {
definedTypes = new HashMap<>();
} else {
ClassBytecode existingClass = definedTypes.get(name);
if (existingClass != null && Arrays.equals(bytecode, existingClass.bytes)) {
return existingClass.clazz;
}
}
if (typesClassLoader == null) {
typesClassLoader = makeClassLoader();
}
Class> clazz = typesClassLoader.defineClass(name, bytecode);
definedTypes.put(name, new ClassBytecode(clazz, bytecode));
loadedClasses.put(name, clazz);
return clazz;
}
@Override
public Class> writeClass( String name, byte[] bytecode ) {
return defineClass( name, bytecode, 0, bytecode.length );
}
public Class> defineClass(String name, byte[] bytecode) {
return defineClass(name, ClassUtils.convertClassToResourcePath(name), bytecode);
}
public synchronized Class> defineClass(String name, String resourceName, byte[] bytecode) {
storeClass(name, resourceName, bytecode);
return defineType(name, bytecode);
}
public synchronized void undefineClass(String name) {
String resourceName = ClassUtils.convertClassToResourcePath(name);
if (store.remove(resourceName) != null) {
if (CACHE_NON_EXISTING_CLASSES) {
nonExistingClasses.add(name);
}
typesClassLoader = null;
}
}
public void storeClass(String name, byte[] bytecode) {
storeClass(name, ClassUtils.convertClassToResourcePath(name), bytecode);
}
public void storeClasses(Map classesMap) {
for ( Map.Entry entry : classesMap.entrySet() ) {
if ( entry.getValue() != null ) {
String resourceName = entry.getKey();
String className = ClassUtils.convertResourceToClassName( resourceName );
storeClass( className, resourceName, entry.getValue() );
}
}
}
public void storeClass(String name, String resourceName, byte[] bytecode) {
if (store == null) {
store = new HashMap<>();
}
store.put(resourceName, bytecode);
if (CACHE_NON_EXISTING_CLASSES) {
nonExistingClasses.remove(name);
}
}
public boolean isClassInUse(String className, Class extends Annotation> annotationClazz) {
Class> clazz = loadedClasses.get(className);
boolean clazzFound = clazz != null;
if (annotationClazz != null) {
return clazzFound && !clazz.isAnnotationPresent(annotationClazz);
} else {
return clazzFound;
}
}
public boolean isClassInUse(String className) {
return loadedClasses.containsKey(className);
}
@Override
public InputStream getResourceAsStream(String name) {
byte[] bytecode = getBytecode(name);
if (bytecode != null) {
return new ByteArrayInputStream( bytecode );
}
if (resourceProvider != null) {
try {
InputStream is = resourceProvider.getResourceAsStream(name);
if (is != null) {
return is;
}
} catch (IOException e) { }
}
return super.getResourceAsStream(name);
}
@Override
public URL getResource(String name) {
if (droolsClassLoader != null) {
URL resource = droolsClassLoader.getResource(name);
if (resource != null) {
return resource;
}
}
if (resourceProvider != null) {
URL resource = resourceProvider.getResource(name);
if (resource != null) {
return resource;
}
}
return super.getResource(name);
}
@Override
public Enumeration getResources(String name) throws IOException {
Enumeration resources = super.getResources(name);
if (resourceProvider != null) {
URL providedResource = resourceProvider.getResource(name);
if (resources != null) {
return new ResourcesEnum(providedResource, resources);
}
}
return resources;
}
public Set getGeneratedClassNames() {
return generatedClassNames;
}
public void setGeneratedClassNames(Set generatedClassNames) {
this.generatedClassNames = generatedClassNames;
}
public static boolean isEnableStoreFirst() {
return enableStoreFirst;
}
// test purpose
static void setEnableStoreFirst(boolean enableStoreFirst) {
ProjectClassLoader.enableStoreFirst = enableStoreFirst;
}
private static class ResourcesEnum implements Enumeration {
private URL providedResource;
private final Enumeration resources;
private ResourcesEnum(URL providedResource, Enumeration resources) {
this.providedResource = providedResource;
this.resources = resources;
}
@Override
public boolean hasMoreElements() {
return providedResource != null || resources.hasMoreElements();
}
@Override
public URL nextElement() {
if (providedResource != null) {
URL result = providedResource;
providedResource = null;
return result;
}
return resources.nextElement();
}
}
public byte[] getBytecode(String resourceName) {
return store == null ? null : store.get(resourceName);
}
@Override
public Map getStore() {
return store;
}
public void clearStore() {
if (store != null) {
store.clear();
}
}
public void setDroolsClassLoader(ClassLoader droolsClassLoader) {
if (getParent() != droolsClassLoader && isModularClassLoader(droolsClassLoader)) {
this.droolsClassLoader = droolsClassLoader;
if (CACHE_NON_EXISTING_CLASSES) {
nonExistingClasses.clear();
}
}
}
private static boolean isModularClassLoader(ClassLoader cl) {
return isOsgiClassLoader(cl) || isJbossModuleClassLoader(cl);
}
private static boolean isJbossModuleClassLoader(ClassLoader cl) {
return "org.jboss.modules".equals( cl.getClass().getPackage().getName() );
}
private static boolean isOsgiClassLoader(ClassLoader cl) {
for (Class> clc = cl.getClass(); clc != null && !clc.equals(ClassLoader.class); clc = clc.getSuperclass()) {
if (Stream.of(clc.getInterfaces()).map(Class::getSimpleName).anyMatch(name -> name.equals("BundleReference"))) {
return true;
}
}
return false;
}
// WARNING: This is and should be used just for testing purposes.
// If not, dragons will come to the Earth, eat all cookies and
// hijack all kittens and puppies.
public void setInternalClassLoader(InternalTypesClassLoader classLoader) {
typesClassLoader = classLoader;
}
public void setResourceProvider(ResourceProvider resourceProvider) {
this.resourceProvider = resourceProvider;
}
public void initFrom(ProjectClassLoader other) {
if (other.store != null) {
if (store == null) {
store = new HashMap<>();
}
store.putAll(other.store);
}
nonExistingClasses.addAll(other.nonExistingClasses);
}
public abstract InternalTypesClassLoader makeClassLoader();
public interface InternalTypesClassLoader extends KieTypeResolver {
Class> defineClass( String name, byte[] bytecode );
Class> loadType( String name, boolean resolve ) throws ClassNotFoundException;
default Class> findLoadedClassWithoutParent(String name) {
throw new UnsupportedOperationException();
}
}
public synchronized List reinitTypes() {
typesClassLoader = null;
nonExistingClasses.clear();
loadedClasses.clear();
if (definedTypes != null) {
List removedTypes = new ArrayList<>(definedTypes.keySet());
definedTypes.clear();
return removedTypes;
}
return Collections.emptyList();
}
private static class ClassBytecode {
private final Class> clazz;
private final byte[] bytes;
private ClassBytecode(Class> clazz, byte[] bytes) {
this.clazz = clazz;
this.bytes = bytes;
}
}
private ClassNotFoundException getClassNotFoundExceptionWithName(String name) {
if (CACHE_NON_EXISTING_CLASSES) {
DummyClassNotFoundException.INSTANCE.name = name;
return DummyClassNotFoundException.INSTANCE;
}
return new ClassNotFoundException(name);
}
public static class DummyClassNotFoundException extends ClassNotFoundException {
private static final DummyClassNotFoundException INSTANCE = CACHE_NON_EXISTING_CLASSES ? new DummyClassNotFoundException() : null;
private String name;
@Override
public String getMessage() {
return name + "\n(Note: This is just a cached Exception for performance reasons, the stack trace is not correct and also the name of the class may be wrong in multithreaded situations. Disable non existing classes cache to see the actual one.)";
}
}
}