Parent Last Classloader to solve Java Class path hell? - java

I have a project which uses two versions of bouncyCastle jars bcprov-jdk15 and bcprov-jdk16. The jvm loads the older version but there is a feature I wrote which needs the newer version to run. I tried to solve this classpath hell by using a custom class loader. After some googling and with the help of some previous Stackoverflow answers[1] [2] and this blog, I wrote the following Parent Last Class loader to load the classes from the newer jar before delegating to the parent class loader.
public class ParentLastClassLoader extends ClassLoader {
private String jarFile; //Path to the jar file
private Hashtable classes = new Hashtable(); //used to cache already defined classes
public ParentLastClassLoader(ClassLoader parent, String path)
{
super(parent);
this.jarFile = path;
}
#Override
public Class<?> findClass(String name) throws ClassNotFoundException
{
System.out.println("Trying to find");
throw new ClassNotFoundException();
}
#Override
protected synchronized Class<?> loadClass(String className, boolean resolve) throws ClassNotFoundException
{
System.out.println("Trying to load");
try
{
System.out.println("Loading class in Child : " + className);
byte classByte[];
Class result = null;
//checks in cached classes
result = (Class) classes.get(className);
if (result != null) {
return result;
}
try {
JarFile jar = new JarFile(jarFile);
JarEntry entry = jar.getJarEntry(className + ".class");
InputStream is = jar.getInputStream(entry);
ByteArrayOutputStream byteStream = new ByteArrayOutputStream();
int nextValue = is.read();
while (-1 != nextValue) {
byteStream.write(nextValue);
nextValue = is.read();
}
classByte = byteStream.toByteArray();
result = defineClass(className, classByte, 0, classByte.length, null);
classes.put(className, result);
return result;
} catch (Exception e) {
throw new ClassNotFoundException(className + "Not found", e);
}
}
catch( ClassNotFoundException e ){
System.out.println("Delegating to parent : " + className);
// didn't find it, try the parent
return super.loadClass(className, resolve);
}
}
}
I loaded the main class in the feature with this class loader but the BouncyCaslte classes used in the feature are not loaded by my custom classloader.
ClassLoader loader = new ParentLastClassLoader(Thread.currentThread().getContextClassLoader(), pathToJar);
Class myClass = loader.loadClass("MainClassOfTheFeature");
Method mainMethod = myClass.getMethod("MainMethod");
mainMethod.invoke(myClass.getConstructor().newInstance());
Jvm still uses the classes it loaded from the older version. How can I make the JVM to load the classes from my class loader when running the feature and use the already loaded older classes in the older jar when the feature is not running?
Edit:
The problem remains even after setting the custom classloader as the Thread context classloader in the MainMethod of the feature Main class.
Thread.currentThread().setContextClassLoader(this.getClass().getClassLoader());

I managed to solve this problem. Modified the code of the ParentLastClassLoader to get an array of all the Jarfile paths which are needed by the feature. So when a class is loaded, all the jarfiles needed by the feature will be searched for the .class files. If a class file cannot be found, it will be delegated to the parent.
private class ParentLastClassLoader extends ClassLoader {
private String[] jarFiles; //Paths to the jar files
private Hashtable classes = new Hashtable(); //used to cache already defined classes
public ParentLastClassLoader(ClassLoader parent, String[] paths)
{
super(parent);
this.jarFiles = paths;
}
#Override
public Class<?> findClass(String name) throws ClassNotFoundException
{
System.out.println("Trying to find");
throw new ClassNotFoundException();
}
#Override
protected synchronized Class<?> loadClass(String className, boolean resolve) throws ClassNotFoundException
{
System.out.println("Trying to load");
try
{
System.out.println("Loading class in Child : " + className);
byte classByte[];
Class result = null;
//checks in cached classes
result = (Class) classes.get(className);
if (result != null) {
return result;
}
for(String jarFile: jarFiles){
try {
JarFile jar = new JarFile(jarFile);
JarEntry entry = jar.getJarEntry(className.replace(".","/") + ".class");
InputStream is = jar.getInputStream(entry);
ByteArrayOutputStream byteStream = new ByteArrayOutputStream();
int nextValue = is.read();
while (-1 != nextValue) {
byteStream.write(nextValue);
nextValue = is.read();
}
classByte = byteStream.toByteArray();
result = defineClass(className, classByte, 0, classByte.length, null);
classes.put(className, result);
} catch (Exception e) {
continue;
}
}
result = (Class) classes.get(className);
if (result != null) {
return result;
}
else{
throw new ClassNotFoundException("Not found "+ className);
}
}
catch( ClassNotFoundException e ){
System.out.println("Delegating to parent : " + className);
// didn't find it, try the parent
return super.loadClass(className, resolve);
}
}
}
The ParentLastClassLoader is instantiated as follows.
ClassLoader loader = new ParentLastClassLoader(Thread.currentThread().getContextClassLoader(), paths);
Once the ParentLastClassLoader is instantiated, the MainClassOfTheFeature will be loaded and its MainMethod will be invoked.

Ok, you created your own classloader and then loaded a class using it. The question is - how does the thread classloader will know about that?
So, you must load the class using some classloader, and then set this classloader as thread context classloader.

Related

.jar conflict causes java.lang.RuntimeException: Error starting org.neo4j.kernel.EmbeddedGraphDatabase exception

Because of different class versions in different .jar files I got this exception:
java.lang.RuntimeException: Error starting org.neo4j.kernel.EmbeddedGraphDatabase, E:\neo4j
at org.neo4j.kernel.InternalAbstractGraphDatabase.run(InternalAbstractGraphDatabase.java:333)
at org.neo4j.kernel.EmbeddedGraphDatabase.(EmbeddedGraphDatabase.java:63)
at org.neo4j.graphdb.factory.GraphDatabaseFactory$1.newDatabase(GraphDatabaseFactory.java:92)
at org.neo4j.graphdb.factory.GraphDatabaseBuilder.newGraphDatabase(GraphDatabaseBuilder.java:198)
at org.neo4j.graphdb.factory.GraphDatabaseFactory.newEmbeddedDatabase(GraphDatabaseFactory.java:69)
at neo4j_lucene.conflict_solver.ConfilctSolver.createDb(ConfilctSolver.java:55)
at neo4j_lucene.conflict_solver.ConfilctSolver.main(ConfilctSolver.java:35)
despite I'm using ClassLoader for solving this problem, but again I get same exception. Here is my code:
try {
CustomClassLoader ccl = new CustomClassLoader();
Object object;
Class clas;
clas = ccl
.loadClass("org.neo4j.graphdb.factory.GraphDatabaseFactory");
object = clas.newInstance();
graphDb = ((GraphDatabaseFactory) object)
.newEmbeddedDatabase(DB_PATH);
} catch (Exception e) {
e.printStackTrace();
}
Custom class loader code:
public class CustomClassLoader extends ClassLoader {
private String jarFile = "C:/Users/RaufA/Desktop/test.jar"; // Path
// to
// the
// jar
// file
private Hashtable classes = new Hashtable(); // used to cache already
// defined classes
public CustomClassLoader() {
super(CustomClassLoader.class.getClassLoader()); // calls the parent
// class
// loader's
// constructor
}
public Class loadClass(String className) throws ClassNotFoundException {
return findClass(className);
}
public Class findClass(String className) {
byte classByte[];
Class result = null;
result = (Class) classes.get(className); // checks in cached classes
if (result != null) {
return result;
}
try {
return findSystemClass(className);
} catch (Exception e) {
}
try {
JarFile jar = new JarFile(jarFile);
JarEntry entry = jar.getJarEntry(className + ".class");
System.out.println(className+".class");
InputStream is = jar.getInputStream(entry);
ByteArrayOutputStream byteStream = new ByteArrayOutputStream();
int nextValue = is.read();
while (-1 != nextValue) {
byteStream.write(nextValue);
nextValue = is.read();
}
classByte = byteStream.toByteArray();
result = defineClass(className, classByte, 0, classByte.length,
null);
classes.put(className, result);
System.out.println(">>>>result: " + result);
return result;
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
}
What else should I do?
You are trying to have Neo4j and Lucene together in one jar, right.
Problem is, because Neo4j uses old Lucene version.
Alessandro Negro from GraphAware solved that problem and you can find his solution here - https://github.com/graphaware/neo4j-elasticsearch-tests
IMO the issue is that you are starting your database with a single jar. You need to be sure that in your classloader you are loading all the jar you need to start neo4j
Add this project to eclipse -> https://github.com/lagodiuk/neo4j-uber-jar.
Use mvn-install and create ~SNAPSHOT.jar
Add that .jar to your project (which has conflict)
Remove neo4j maven dependency from that project.

No meta factory found for feature 'default'; this usually means that axiom-impl.jar is not in the classpath

From the answer in the link below Link
I found that it can be resolved by adding it to classpath. But I am using Custom ClassLoader to load jar axiom-impl-1.2.14.
Is there any way to achieve this?
axiom jar is using ClassLoader. Enumeration getResources(String name) to load that xmls internally in jar. XML file in our case is residing in jar file. So I am looking for solution by which I can get file URL of the XML.
Source Code :
public class ExternalClassLoader extends ClassLoader {
private String jarFile = "";
private Hashtable<String, Class> classes = new Hashtable<String, Class>();
public ExternalClassLoader(String jarLocation) {
super(ExternalClassLoader.class.getClassLoader());
this.jarFile = jarLocation;
}
#Override
public Class loadClass(String className) throws ClassNotFoundException {
return findClass(className);
}
#Override
public Class findClass(String className) {
byte classByte[];
Class result = null;
System.out.println("CLASS : " + className);
result = (Class) classes.get(className);
if (result != null) {
return result;
}
try {
return findSystemClass(className);
} catch (Exception e) {
}
JarFile jar = null;
try {
jar = new JarFile(jarFile);
String classLocation = className.replace('.', '/');
JarEntry entry = jar.getJarEntry(classLocation + ".class");
InputStream is = jar.getInputStream(entry);
ByteArrayOutputStream byteStream = new ByteArrayOutputStream();
int nextValue = is.read();
while (-1 != nextValue) {
byteStream.write(nextValue);
nextValue = is.read();
}
classByte = byteStream.toByteArray();
result = defineClass(className, classByte, 0, classByte.length, null);
classes.put(className, result);
return result;
} catch (Exception e) {
System.out.println("ERROR CLASS : " + className);
return null;
} finally {
try {
jar.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
#Override
public InputStream getResourceAsStream(String name) {
try {
System.out.println("RESOURCE : " + jarFile + "//" + name);
JarFile jar = new JarFile(jarFile);
JarEntry entry = jar.getJarEntry(name);
return jar.getInputStream(entry);
} catch (IOException e) {
System.out.println("ERROR RESOURCE : " + jarFile + "//" + name);
return null;
}
}
}
Since you don't specify the details, I'm assuming that the conflict occurs with another version of axiom-impl that is in the classpath of the class loader the rest of your application is loaded from (Otherwise you could just use one or more URLClassLoader instances or change the class loading policy of your application class loader).
I'm also assuming that (as you mentioned in a comment) axiom-api and axiom-impl are both loaded by the same custom class loader or that you combined the classes from these two JARs into a single JAR (in which case I'm assuming that you don't include axiom-dom in the same JAR since that would cause additional problems).
If these assumptions are true, then what you need is a class loader that loads classes from one or more JAR files and that uses parent last as class loading policy. To achieve that, you don't need to reimplement the JAR loading logic as you attempted to do in the code you have posted. Instead you can use URLClassLoader, but you need to extend it to change its class loading policy from the default parent first to parent last. There is actually an example of this in the source code of Apache Axiom itself:
http://svn.apache.org/repos/asf/webservices/axiom/tags/1.2.15/axiom-api/src/test/java/org/apache/axiom/util/stax/dialect/ParentLastURLClassLoader.java
You can probably use that code as is, although you may want to remove the package filter on javax.* because that shouldn't be necessary in your case.

How to load a Jasper customizer class during runtime?

I need to render Jasper reports with charts and require individual ChartCustomizer classes for them. My application is running as a Java web-application.
Current state is, that the templates (.jasper files) are packaged with their required resources in a separate jar-file. These jar files themselves are stored as BLOBs in the Database. I load them with an own FileResolver, which I provide as a parameter to the Jasper Engine.
So far this works great for me, except I cannot load my Customizer classes. I tried to put them in another jar file and load them with an own ClassLoader and also provide that to the Jasper Engine:
URL customizersUrl = classLoader.findResource("customizers.jar");
if (customizersUrl != null) {
URI jarUri = customizersUrl.toURI();
JarFile jarFile = new JarFile(new File(jarUri));
Enumeration e = jarFile.entries();
URL[] jarContentUrls = {new URL("jar:file:" + jarUri.getPath() + "!/")};
customizerClassLoader = URLClassLoader.newInstance(jarContentUrls);
while (e.hasMoreElements()) {
JarEntry je = (JarEntry) e.nextElement();
if (je.isDirectory() || !je.getName().endsWith(".class")) {
continue;
}
// -6 because of .class
String className = je.getName().substring(0, je.getName().length() - 6);
className = className.replace('/', '.');
Class c = customizerClassLoader.loadClass(className);
}
}
parameters.put(JRParameter.REPORT_CLASS_LOADER, customizerClassLoader);
but I am still getting a java.lang.ClassNotFoundException, although I can see in the Debugger, that the classloading from jar works.
Any help is appreciated!
Ok, I figured out that I need to put the class loader into the current thread's context. I am also using an anonymous class loader now, so that only requested classes get loaded (also improves debugging).
// check if customizer classes are present and load them
final ClassLoader contextClassLoader = Thread.currentThread().getContextClassLoader();
final URL customizersUrl = classLoader.findResource("customizers.jar");
if (customizersUrl != null) {
ClassLoader cl = new ClassLoader() {
#Override
public Class loadClass(String className) throws ClassNotFoundException {
try {
return contextClassLoader.loadClass(className);
} catch (ClassNotFoundException ex) {
if (customizersUrl != null) {
try {
URI jarUri = customizersUrl.toURI();
URL[] jarContentUrls = {new URL("jar:file:" + jarUri.getPath() + "!/")};
URLClassLoader customizerInnerClassLoader = URLClassLoader.newInstance(jarContentUrls);
return customizerInnerClassLoader.loadClass(className);
} catch (URISyntaxException ex1) {
logger.debug("Exception during customizer class loading", ex1);
} catch (IOException ex1) {
logger.debug("Exception during customizer class loading", ex1);
} catch (ClassNotFoundException ex1) {
throw new ClassNotFoundException("Exception during customizer class loading", ex1);
}
}
}
return null;
}
};
// squeeze our own class loader in
Thread.currentThread().setContextClassLoader(cl);
}
byte[] result = generate(jasperReport, parameters);
// changing the class loader back to its origin... just to be safe
Thread.currentThread().setContextClassLoader(contextClassLoader);

Implementing a selective ClassLoader

I want to instrument the bytecode of some classes on the classpath at loading time. Since these are 3rd party libraries, I know exactly when they are loaded. The problem is that I need to do the instrumentation selectively, i.e. instrument only some classes. Now if I do not load a class with my classloader but with its parent, this parent gets set as the classes classloader and all succinct classes are loaded by that parent, effectively putting my classloader out of use. So I need to implement a parent-last classloader (see How to put custom ClassLoader to use?).
So I need to load classes myself. If those classes are system classes (starting with "java" or "sun") I delegate to the parent. Otherwise I read the bytecode and call defineClass(name, byteBuffer, 0, byteBuffer.length);. But now a java.lang.ClassNotFoundException: java.lang.Object is thrown.
Here is the code, any comment highly appreciated:
public class InstrumentingClassLoader extends ClassLoader {
private final BytecodeInstrumentation instrumentation = new BytecodeInstrumentation();
#Override
public Class<?> loadClass(String name) throws ClassNotFoundException {
Class<?> result = defineClass(name);
if (result != null) {
return result;
}
result = findLoadedClass(name);
if(result != null){
return result;
}
result = super.findClass(name);
return result;
}
private Class<?> defineClass(String name) throws ClassFormatError {
byte[] byteBuffer = null;
if (instrumentation.willInstrument(name)) {
byteBuffer = instrumentByteCode(name);
}
else {
byteBuffer = getRegularByteCode(name);
}
if (byteBuffer == null) {
return null;
}
Class<?> result = defineClass(name, byteBuffer, 0, byteBuffer.length);
return result;
}
private byte[] getRegularByteCode(String name) {
if (name.startsWith("java") || name.startsWith("sun")) {
return null;
}
try {
InputStream is = ClassLoader.getSystemResourceAsStream(name.replace('.', '/') + ".class");
ByteArrayOutputStream buffer = new ByteArrayOutputStream();
int nRead;
byte[] data = new byte[16384];
while ((nRead = is.read(data, 0, data.length)) != -1) {
buffer.write(data, 0, nRead);
}
buffer.flush();
return buffer.toByteArray();
} catch (IOException exc) {
return null;
}
}
private byte[] instrumentByteCode(String fullyQualifiedTargetClass) {
try {
String className = fullyQualifiedTargetClass.replace('.', '/');
return instrumentation.transformBytes(className, new ClassReader(fullyQualifiedTargetClass));
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
The code can be executed e.g. with:
InstrumentingClassLoader instrumentingClassLoader = new InstrumentingClassLoader();
Class<?> changedClass = instrumentingClassLoader.loadClass(ClassLoaderTestSubject.class.getName());
The ClassLoaderTestSubject should call some other classes, where the called classes are target of instrumentation, but the ClassLoaderTestSubject itself is not...
I'd recommend you to use regular class loader strategy, i.e. parent first. But put all classes that you want to instrument into separate jar file and do not add it to the classpath of the application. Instantiate these classes using your class loader that extends URL class loader and knows to search jars in other location. In this case all JDK classes will be known automatically and your code will be simpler. You do not have to "think" whether to instrument the class: if it is not loaded by parent class loader it is your class that has to be instrumented.
Stupid mistake. The parent classloader is not the parent as in the inheritance hierarchy. It is the parent as given to the constructor. So the correct code looks like this:
public InstrumentingClassLoader() {
super(InstrumentingClassLoader.class.getClassLoader());
this.classLoader = InstrumentingClassLoader.class.getClassLoader();
}
#Override
public Class<?> loadClass(String name) throws ClassNotFoundException {
[... as above ...]
result = classLoader.loadClass(name);
return result;
}

java classloader and runtime compilation

Despite warnings to drop my present course of action, I currently see no better way to solve my problem. I must generate Java code at runtime, then compile it, load it and reference it.
Problem is that the generated code imports code that has already been loaded by the system class loader (I suppose) - that is, code present in one of the jars on my classpath.
(I run inside a Tomcat 6 web container over Java 6.) You may ask yourselves why that is a problem - well I sure don't know - but fact is that I get compilation errors:
/W:/.../parser/v0.5/AssignELParser.java:6:
package com.xxx.yyy.zzz.configuration
does not exist
Following some examples off the internet I have defined the following classes:
class MemoryClassLoader extends ChainedAction {
private static final Logger LOG = Logger.getLogger(MemoryClassLoader.class);
private LoaderImpl impl;
private class LoaderImpl extends ClassLoader {
// The compiler tool
private final JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
// Compiler options
private final Iterable<String> options = Arrays.asList("-verbose");
// DiagnosticCollector, for collecting compilation problems
private final DiagnosticCollector<JavaFileObject> diagnostics = new DiagnosticCollector<JavaFileObject>();
// Our FileManager
private final MemoryFileManager manager = new MemoryFileManager(this.compiler);
public LoaderImpl(File sourceDirectory) {
List<Source> list = new ArrayList<Source>();
File[] files = sourceDirectory.listFiles(new FilenameFilter() {
#Override
public boolean accept(File dir, String name) {
return name.endsWith(Kind.SOURCE.extension);
}
});
for (File file : files) {
list.add(new Source(file));
}
CompilationTask task = compiler.getTask(null, manager, diagnostics, options, null, list);
Boolean compilationSuccessful = task.call();
LOG.info("Compilation has " + ((compilationSuccessful) ? "concluded successfully" : "failed"));
// report on all errors to screen
for (Diagnostic<? extends JavaFileObject> diagnostic : diagnostics.getDiagnostics()) {
LOG.warn(diagnostic.getMessage(null));
}
}
#Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
synchronized (this.manager) {
Output output = manager.map.remove(name);
if (output != null) {
byte[] array = output.toByteArray();
return defineClass(name, array, 0, array.length);
}
}
return super.findClass(name);
}
}
#Override
protected void run() {
impl = new LoaderImpl(new File(/* Some directory path */));
}
}
class MemoryFileManager extends ForwardingJavaFileManager<JavaFileManager> {
final Map<String, Output> map = new HashMap<String, Output>();
MemoryFileManager(JavaCompiler compiler) {
super(compiler.getStandardFileManager(null, null, null));
}
#Override
public Output getJavaFileForOutput(Location location, String name, Kind kind, FileObject source) {
Output output = new Output(name, kind);
map.put(name, output);
return output;
}
}
class Output extends SimpleJavaFileObject {
private final ByteArrayOutputStream baos = new ByteArrayOutputStream();
Output(String name, Kind kind) {
super(URI.create("memo:///" + name.replace('.', '/') + kind.extension), kind);
}
byte[] toByteArray() {
return this.baos.toByteArray();
}
#Override
public ByteArrayOutputStream openOutputStream() {
return this.baos;
}
}
class Source extends SimpleJavaFileObject {
public Source(File file) {
super(file.toURI(), Kind.SOURCE);
}
#Override
public CharSequence getCharContent(boolean ignoreEncodingErrors) {
StringBuilder sb = new StringBuilder("");
try {
File file = new File(uri);
FileReader fr = new FileReader(file);
BufferedReader br = new BufferedReader(fr);
sb = new StringBuilder((int) file.length());
String line = "";
while ((line = br.readLine()) != null) {
sb.append(line);
sb.append("\n");
}
} catch (FileNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return sb.toString();
}
}
It seems that the inner class LoaderImpl by extending the ClassLoader class and by not calling an explicit super constructor should reference as its parent class loader the system class loader.
If it does so then why do I get the "runtime" compilation error - above? Why does it not find the code for the imported class?
Not sure if it can help, but have you tried to specify classpath explicitly?
getClassPath()
{
ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
URL[] urls = ((URLClassLoader) classLoader).getURLs();
StringBuilder buf = new StringBuilder(1000);
buf.append(".");
String separator = System.getProperty("path.separator");
for (URL url : urls) {
buf.append(separator).append(url.getFile());
}
}
classPath = buf.toString();
and then
options.add("-classpath");
options.add(getClassPath());
I also can't see where do you pass LoaderImpl instance to the compiler. Shouldn't it be done explicitly?

Categories

Resources