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.
Related
I'm trying to get the names of all the files in a folder "clock" which is inside the working directory "src".
The snippet below works fine if I run it but when I build the JAR file and run that I get a null error.
try {
File directory = new File("src/clock/");
File[] files = directory.listFiles();
for (File f: files) {
text.appendText(f.getName() + " ");
}
} catch (Exception e) {
text.appendText(e.getMessage() + " ");
}
File structure:
Update: (I'm using the ResourceAsStream now but same problem runs fine, deployed JAR doesn't work)
public void setImage() {
List<String>fn;
try {
fn = getResourceFiles("/clock/graphics/backgrounds/");
for (String s: fn) {
text.appendText(s);
}
} catch (Exception e) {
label.setText(e.getMessage());
}
}
private List<String>getResourceFiles(String path) throws IOException {
List<String> filenames = new ArrayList<>();
try (
InputStream in = getResourceAsStream(path);
BufferedReader br = new BufferedReader(new InputStreamReader( in ))) {
String resource;
while ((resource = br.readLine()) != null) {
filenames.add(resource);
}
}
return filenames;
}
private InputStream getResourceAsStream(String resource) {
final InputStream in = getContextClassLoader().getResourceAsStream(resource);
return in == null ? getClass().getResourceAsStream(resource) : in ;
}
private ClassLoader getContextClassLoader() {
return Thread.currentThread().getContextClassLoader();
}
That's because File searches in the
/path/to/your/application.jar
which File cant unzip to find the files in the specified path. It considers application.jar as a folder name and tries to read it.
Instead use
ClassLoader classLoader = YourClassName.class.getClassLoader();
InputStream sam = classLoader.getResourceAsStream(fileName);
to read the file from resources folder of the project when you want your jar to read a file.
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.
I'm noob in javassist. Anyone can give the sample how to load classes from jar and save them using javassist?
jar = new JarFile(fileName);
Enumeration<JarEntry> entries = jar.entries();
while (entries.hasMoreElements()) {
JarEntry jarEntry = (JarEntry) entries.nextElement();
if(jarEntry == null)
break;
if(jarEntry.getName().endsWith(".class")) {
// ??????
} else {
resources.add(new RResource(jarEntry.getName(), jar.getInputStream(jarEntry)));
}
You can load the bytes from the respective class inside the JAR file via the code below:
JarFile jarFile = new JarFile(file);
// lets get a reference to the .class-file contained in the JAR
ZipEntry zipEntry = jarFile.getEntry(className.replace(".", "/")+".class");
if (zipEntry == null) {
jarFile.close();
return null;
}
// with our valid reference, we are now able to get the bytes out of the jar-archive
InputStream fis = jarFile.getInputStream(zipEntry);
byte[] classBytes = new byte[fis.available()];
fis.read(classBytes);
To load the bytes in javassist you can do the following stuff:
ClassPool cp = ClassPool.getDefault();
cp.insertClassPath(new ClassClassPath(this.getClass()));
ClassPath cp1 = null;
ClassPath cp2 = null;
// add the JAR file to the classpath
try {
cp1 = cp.insertClassPath(jarFile.getAbsolutePath());
} catch (NotFoundException e1) {
e1.printStackTrace();
return null;
}
// add the class file we are going to modify to the classpath
cp2 = cp.appendClassPath(new ByteArrayClassPath(className, classBytes));
byte[] modifiedBytes;
try {
CtClass cc = cp.get(className);
// skip instrumentation if the class is frozen and therefore
// can't be modified
if (!cc.isFrozen()) {
// do your javassist stuff here
}
modifiedBytes = cc.toBytecode();
} catch (NotFoundException | IOException | CannotCompileException | ClassNotFoundException e) {
handleException(e);
} finally {
// free the locked resource files
cp.removeClassPath(cp1);
cp.removeClassPath(cp2);
}
// write your modified bytes somewhere
if (modifiedBytes.length > 0) {
try(FileOutputStream fos = new FileOutputStream("pathname")) {
fos.write(modifiedBytes);
}
}
Maybe some of the code can be reduced, but this is how I load bytes from a JAR file and load them into Javassist. The JAR file is loaded to the Javassist classpath due to eventual dependencies. Also the class which I instrument with Javassist needed to be added to the classpath for some reason.
You might have a look at how I use them in a plugin-use-case:
Loading class-bytes from a JAR file
Javassist instumentation
HTH
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);
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.