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);
Related
i have a mehod getAllTests() that load external jar in a folder and with Reflection search method with Annotation Test (#Test).
I use:
URLClassLoader class for load Jar File;
Class currentClass=child.loadClass(classname); for load class;
Method method = currentClass.getMethods()[i];
Annotation annTest = method.getAnnotation(Test.class);
for get Method and get Annotation.
My code is this:
public static void getAllTests() throws IllegalArgumentException, NoSuchMethodException, InvocationTargetException, IllegalAccessException, IOException,LinkageError, ClassNotFoundException {
TestLoaderApplication.testClassObjMap.clear(); <--is a HashMap class
LoadLibrary loadLibrary=new LoadLibrary();<--used for search all file .jar in folder
List<JarFile> jarList= loadLibrary.getListJar(pathJars).stream().map(f -> {
try {
return new JarFile(f);
} catch (IOException e) {
e.printStackTrace();
}
return null;
})
.collect(Collectors.toList());
for (JarFile j : jarList) {
URLClassLoader child = new URLClassLoader(
new URL[] {new File(j.getName()).toURI().toURL()},
ServiceUtil.class.getClassLoader()
);
for (Enumeration<JarEntry> entries = j.entries(); entries.hasMoreElements(); ) {
JarEntry entry = entries.nextElement();
String file = entry.getName();
if (file.endsWith(".class")) {
String classname = file.replaceAll("/", ".")
.substring(0, file.lastIndexOf("."));
try {
Class currentClass=child.loadClass(classname);
List<String> testMethods = new ArrayList<>();
for (int i = 0; i < currentClass.getMethods().length; i++) {
Method method = currentClass.getMethods()[i];
Annotation annTest = method.getAnnotation(Test.class);
Annotation annTestFactory = method.getAnnotation(TestFactory.class);
if (annTest != null || annTestFactory != null) {
testMethods.add(method.getName());
}
}//fine for metodi
if (testMethods.size() >=1) {
testClassObjMap.put(j.getName().substring(j.getName().lastIndexOf("\\")+1),classname,testMethods);
TestLoaderApplication.testClassObjMap.put(j.getName().substring(j.getName().lastIndexOf("/")+1),classname,testMethods);
LOGGER.info(String.format("%s %s %s",j.toString(),classname,testMethods));
}
}catch (NoClassDefFoundError e) {
LOGGER.warn("WARNING: failed NoClassDefFoundError " + classname + " from " + file);
}
catch (Throwable e) {
LOGGER.warn("WARNING: failed to instantiate " + classname + " from " + file);
}
}//if .class
}//chiudo jarentry for
j.close();
child.close();
child=null;
}//chiudo jarfile for
System.gc();
LOGGER.info("Test Loader Console Back-End:\tFine Reflection Scan");
}
At end of this function i close both JarFile(j) and classLoader(child) and set child=null and agter call garbage collector.
But if i call this method many times at end i have about 100% memory used by java webapps.
This method is in a spring-boot project and i use Tomcat for webserver.
Can you recommend some optimization in the use of the classLoader and if this is the correct way to look for methods with annotation #Test in Java?
Thanks
I am extracting a WAR in a temporary folder and adding all the classes and libs to a URLClassLoader.
When I start to load the classes into the classpath, the JAR files under "WEB-INF/lib/" are locked by the JVM.
After doing the job, I close the classpath, call the GC and delete the temporary directory.
When I am deleting the temporary directory, I can delete all the files except the JAR files under "WEB-INF/lib".
My code:
#Override
public void generateRegressionTestClasses(Path fileJARorWarSolution, List<String> canonicalNameClassesToGenerateTests) throws Exception {
Path tempPath = null;
URLClassLoader classLoader = null;
TryTest tryTest = null;
RDP execution = null;
try {
// This extracts the JAR/WAR and prepares the URLClassLoader.
GenFilterResponse response = genFilterClass.runGenFilterClass(path);
classLoader = response.getCl();
tempPath = response.getTempPath();
// Verify that all the Test classes is in the ClassLoader.
// Here is where the JAR files are locked.
for (String s : canonicalNameClassesToGenerateTests) {
try {
classLoader.loadClass(s);
} catch (ClassNotFoundException e) {
throw new CustomException(CustomTexts.ERROR_CLASS_NOT_LOADED + s);
}
}
execution = new RDP();
execution.setClassLoader(classLoader);
TryTest = new TryTest(execution);
tryTest.handle(null);
} finally {
tryTest = null;
execution = null;
if (classLoader != null) {
try {
classLoader.close();
} catch (Exception e) {
//
} finally {
classLoader = null;
System.gc();
}
}
if (tempPath != null) {
FileSystemUtils.deleteRecursively(tempPath.toFile());
}
}
Anybody knows how to unlock these files?
Solved.
If I load the JAR into the Class Loader with this kind of URI:
new URL("jar:file:" + jar.toAbsolutePath().toString() + "!/").toURI().toURL()
Example URI:
jar:file:c:/myJar.jar
The file is locked after ClassLoader is closed.
If I load the JAR into the Class Loader with this kind of URI:
warDirectory.toFile().toURI().toURL()
Example URI:
file:c:/myJar.jar
I can delete the JAR's when the Classloader is closed.
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.
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.
Here's my method:
Feature[] getFeatures(File dir)
I'm trying to go through the directory, check each class file. Any class that is of type 'Feature', I want to load an instance of it. Then I want to return an array of these instances.
How is this done?
Thank you.
EDIT:
Here's what I have now:
private static LinkedList<Feature> getFeaturesInDirectory(File dir) throws ClassNotFoundException {
LinkedList<Feature> features = new LinkedList<Feature>();
ClassLoader cl = new IndFeaClassLoader();
// list all files in directory
for(String s : dir.list()) {
Class klass = cl.loadClass(s);
try {
Object x = klass.newInstance();
if (x instanceof Feature) {
features.add((Feature)x);
}
} catch (InstantiationException ex) {
} catch (IllegalAccessException ex) {
}
}
return features;
}
Using:
MyClassName mcn = (MyClassName) Class.forName("MyClassName").newInstance();
Note, however, that this relies on the ClassLoader. If the classes are not coming from the same location as your current class (or the system classloader) you need to specify a classloader:
File myDir = new File("/some/directory/");
ClassLoader loader = null;
try {
URL url = myDir.toURL();
URL[] urls = new URL[]{url};
ClassLoader loader = new URLClassLoader(urls);
}
catch (MalformedURLException e)
{
// oops
}
MyClassName mcn =
(MyClassName) Class.forName("MyClassName", true, loader).newInstance();
I think that should work, but if not it should at least put you on the right path.