JarClassLoader - loading jars dynamically - how to? - java

I have seen many class loader questions, but still was not able to figure why, the error here.
I am writing a program which uses 2 versions of jars. One is needed to get content from older storage, and another to store content in new storage.
Since, I need either of the jar to be loaded at a time, I used JarClassLoader to create a proxy for adding one jar and loading its classes. But I face ClassNotFoundException.
public class HbaseMigrator implements Runnable {
public void run() {
JarClassLoader jcl = new JarClassLoader();
jcl.add("hadoop-0.13.0-core-modified-1.jar");
Object obj1 = JclObjectFactory.getInstance().create(jcl, "UserMigThreadImpl", toProcessQueue,threadName, latch,DBUtil,lock);
MigThread mig = JclUtils.cast(obj1, MigThread.class, jcl);
Thread.currentThread().setContextClassLoader(jcl);
try {
Method method = MigThread.class.getMethod("callthis", new Class[]{});
method.invoke(mig, new Object[]{});
// mig.callthis();
} catch( Exception e) {
e.printStackTrace();
} catch(Error er) {
er.printStackTrace();
}
}
}
Method called is:
public void callthis() {
DFSUtil = new DFSAccessAPIImpl();
.........
}
This class instantiation internally uses hadoop modified jar, which is not picked up from my classloader and it throws ClassNotFoundException.
What is that I am doing wrong ?
JarClassLoader used here is jcloader :
org.xeustechnologies.jcl.JarClassLoader

I experienced this problem with loading plugins to my application, so I decided to try to load all .class files from all jars in path. Maybe this code snipped from my app will help you.
https://bitbucket.org/rsohlich/plagdetector/src/432b52f252ff7647221b7e91b08731bd9cbe2a70/PlagDetectorSpring/src/main/java/cz/sohlich/app/service/impl/PluginHolderImpl.java

Related

Reading images from external JAR

I have a simple plugin system, that loads external JAR plugins into main application. I am using Mountainblade Modular to do so. Not sure how they do it "under the hood" but probably it's something standard.
This works fine, I instantiate classes from external jar and it all works. Except that some plugins come with icons/images. I am a bit unsure on how do I load/refer to images from that external JAR (with code inside that external JAR, as it is ran in context of the main JAR, kind of)
How should I approach this?
This issue is not as straightforward as it seems to be.
When you load classes from external jar, they are "loaded" into JVM. By "loading" into JVM I mean that JVM is responsible for their storage in memory. Usually it is done like this:
ClassLoader myClassLoader = new MyClassLoader(jarFileContent);
Class myExtClass = myClassLoader.loadClass(myClassName);
Resources from classpath jars can be accessed easily with
InputStream resourceStream = myClass.getResourceAsStream("/myFile.txt");
You can do that, because these jars are in classpath, I mean their location is known. These files are not stored in memory. When resource is accessed, JVM can search for it in classpath jars (for example on file system).
But for external jars it is completely different: jar comes from nowhere, is once processed and forgotten. JVM does not load resources from it in memory. In order to access these files, you have to manually organize their storage. I've done this once so I can share the code. It will help you to understand the basic idea (but probably won't help you with your specific library).
// Method from custom UrlClassLoader class.
// jarContent here is byte array of loaded jar file.
// important notes:
// resources can be accesed only with this custom class loader
// resource content is provided with the help of custom URLStreamHandler
#Override
protected URL findResource(String name) {
JarInputStream jarInputStream;
try {
jarInputStream = new JarInputStream(new ByteArrayInputStream(jarContent));
JarEntry jarEntry;
while (true) {
jarEntry = jarInputStream.getNextJarEntry();
if (jarEntry == null) {
break;
}
if (name.equals(jarEntry.getName())) {
final byte[] bytes = IOUtils.toByteArray(jarInputStream);
return new URL(null, "in-memory-bytes", new URLStreamHandler() {
#Override
protected URLConnection openConnection(URL u) throws IOException {
return new URLConnection(u) {
#Override
public void connect() throws IOException {
// nothing to do here
}
#Override
public InputStream getInputStream() throws IOException {
return new ByteArrayInputStream(bytes);
}
};
}
});
}
}
} catch (IOException e) {
e.printStackTrace();
}
return null;
}

Eclipse - JAR creation failed "Class files on classpath not found or not accessible for..."

I have a project in Eclipse that has a red cross on it and will not export to a runnable JAR. I can't remember if I have looked at it since I reinstalled Windows on my laptop, but I know that I haven't changed any code. There are no errors in any of the classes, however the error I get points to the following class that deals with the menu items on Mac OSx:
import java.lang.reflect.*;
public class osxhandler implements InvocationHandler {
protected Object targetObject;
protected Method targetMethod;
protected String proxySignature;
static Object macOSXApplication;
// Pass this method an Object and Method equipped to perform application shutdown logic
// The method passed should return a boolean stating whether or not the quit should occur
public static void setQuitHandler(Object target, Method quitHandler) {
setHandler(new HOsx("handleQuit", target, quitHandler));
}
public static void setAboutHandler(Object target, Method aboutHandler) {
boolean enableAboutMenu = (target != null && aboutHandler != null);
if (enableAboutMenu) {
setHandler(new HOsx("handleAbout", target, aboutHandler));
}
// If we're setting a handler, enable the About menu item by calling
// com.apple.eawt.Application reflectively
try {
Method enableAboutMethod = macOSXApplication.getClass().getDeclaredMethod("setEnabledAboutMenu", new Class[] { boolean.class });
enableAboutMethod.invoke(macOSXApplication, new Object[] { Boolean.valueOf(enableAboutMenu) });
} catch (Exception ex) {
System.err.println("MacOSHandler could not access the About Menu");
ex.printStackTrace();
}
}
public static void setPreferencesHandler(Object target, Method prefsHandler) {
boolean enablePrefsMenu = (target != null && prefsHandler != null);
if (enablePrefsMenu) {
setHandler(new HOsx("handlePreferences", target, prefsHandler));
}
// If we're setting a handler, enable the Preferences menu item by calling
// com.apple.eawt.Application reflectively
try {
Method enablePrefsMethod = macOSXApplication.getClass().getDeclaredMethod("setEnabledPreferencesMenu", new Class[] { boolean.class });
enablePrefsMethod.invoke(macOSXApplication, new Object[] { Boolean.valueOf(enablePrefsMenu) });
} catch (Exception ex) {
System.err.println("MacOSHandler could not access the About Menu");
ex.printStackTrace();
}
}
// Pass this method an Object and a Method equipped to handle document events from the Finder
// Documents are registered with the Finder via the CFBundleDocumentTypes dictionary in the
// application bundle's Info.plist
public static void setFileHandler(Object target, Method fileHandler) {
setHandler(new HOsx("handleOpenFile", target, fileHandler) {
// Override MacOSHandler.callTarget to send information on the
// file to be opened
public boolean callTarget(Object appleEvent) {
if (appleEvent != null) {
try {
Method getFilenameMethod = appleEvent.getClass().getDeclaredMethod("getFilename", (Class[])null);
String filename = (String) getFilenameMethod.invoke(appleEvent, (Object[])null);
this.targetMethod.invoke(this.targetObject, new Object[] { filename });
} catch (Exception ex) {
}
}
return true;
}
});
}
// setHandler creates a Proxy object from the passed MacOSHandler and adds it as an ApplicationListener
#SuppressWarnings({ "unchecked", "rawtypes" })
public static void setHandler(HOsx adapter) {
try {
Class applicationClass = Class.forName("com.apple.eawt.Application");
if (macOSXApplication == null) {
macOSXApplication = applicationClass.getConstructor((Class[])null).newInstance((Object[])null);
}
Class applicationListenerClass = Class.forName("com.apple.eawt.ApplicationListener");
Method addListenerMethod = applicationClass.getDeclaredMethod("addApplicationListener", new Class[] { applicationListenerClass });
// Create a proxy object around this handler that can be reflectively added as an Apple ApplicationListener
Object MacOSHandlerProxy = Proxy.newProxyInstance(HOsx.class.getClassLoader(), new Class[] { applicationListenerClass }, adapter);
addListenerMethod.invoke(macOSXApplication, new Object[] { MacOSHandlerProxy });
} catch (ClassNotFoundException cnfe) {
System.err.println("This version of Mac OS X does not support the Apple EAWT. ApplicationEvent handling has been disabled (" + cnfe + ")");
} catch (Exception ex) { // Likely a NoSuchMethodException or an IllegalAccessException loading/invoking eawt.Application methods
System.err.println("Mac OS X Adapter could not talk to EAWT:");
ex.printStackTrace();
}
}
// Each MacOSHandler has the name of the EAWT method it intends to listen for (handleAbout, for example),
// the Object that will ultimately perform the task, and the Method to be called on that Object
protected HOsx(String proxySignature, Object target, Method handler) {
this.proxySignature = proxySignature;
this.targetObject = target;
this.targetMethod = handler;
}
// Override this method to perform any operations on the event
// that comes with the various callbacks
// See setFileHandler above for an example
public boolean callTarget(Object appleEvent) throws InvocationTargetException, IllegalAccessException {
Object result = targetMethod.invoke(targetObject, (Object[])null);
if (result == null) {
return true;
}
return Boolean.valueOf(result.toString()).booleanValue();
}
// InvocationHandler implementation
// This is the entry point for our proxy object; it is called every time an ApplicationListener method is invoked
public Object invoke (Object proxy, Method method, Object[] args) throws Throwable {
if (isCorrectMethod(method, args)) {
boolean handled = callTarget(args[0]);
setApplicationEventHandled(args[0], handled);
}
// All of the ApplicationListener methods are void; return null regardless of what happens
return null;
}
// Compare the method that was called to the intended method when the MacOSHandler instance was created
// (e.g. handleAbout, handleQuit, handleOpenFile, etc.)
protected boolean isCorrectMethod(Method method, Object[] args) {
return (targetMethod != null && proxySignature.equals(method.getName()) && args.length == 1);
}
// It is important to mark the ApplicationEvent as handled and cancel the default behavior
// This method checks for a boolean result from the proxy method and sets the event accordingly
protected void setApplicationEventHandled(Object event, boolean handled) {
if (event != null) {
try {
Method setHandledMethod = event.getClass().getDeclaredMethod("setHandled", new Class[] { boolean.class });
// If the target method returns a boolean, use that as a hint
setHandledMethod.invoke(event, new Object[] { Boolean.valueOf(handled) });
} catch (Exception ex) {
System.err.println("MacOSHandler was unable to handle an ApplicationEvent: " + event);
ex.printStackTrace();
}
}
}
}
Any ideas as to why I can't export/compile? I've never had this issue before.
Just do a clean and/or rebuild on the project.
You can find it under the Project menu of Eclipse.
I also had a different, degenerate case of this problem. Turned out, we had a class in our project that had a file (so Eclipse kept it on the classpath) but no actual class defined in the file (the file only had imports and a class comment... probably a merge gone wrong). Anyway, deleting the file solved the issue.
It’s quite hateful that Eclipse always generates hidden files .project
and .classpath in project folder. Sometimes you’re not aware if
something goes wrong in these files.
After upgrading your Eclipse and if you found the following compile
error, I’d suggest you to check .classpath in your project folder.
The project was not built since its build path is incomplete. Cannot
find the class file for java.lang.Object. Fix the build path then try
building this project
Most likely you would see a line like this.
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/ org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/j2re1.4.2_03"/>
The stupid Eclipse appended this for no reason. Just simply remove it
to make it work again. ;)
/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/j2re1.4.2_xx
Source: http://hochit.com/2006/07/06/eclipse-upgrading-problem-javalangobject-not-found/
In addition, you can check your project settings in eclipse. Right click on your project and choose properties. Go to Java Build Path and there should be more specific information of the problem. Most likely you set the JDK to an Version which doesn't exist on the new System.
If this doesn't help too, select your project and then use the menu entry Source->Clean Up.
In my case, the classes were empty, and the compiler whined:
Class files on classpath not found or not accessible for: 'ibDemo/src/com/ib/controller/LocationCode.java'
Class files on classpath not found or not accessible for: 'ibDemo/src/com/ib/controller/PairPanel.java'
To solve this I'd to add a class declaration:
public class LocationCode
{
}
and
public class PairPanel
{
}
I got referred here, because I had the same error.
I am using maven on eclipse. I did right click on repo, chose build path->Conifgure build->Project References and checked the project references for my repo. This worked for me.
I was also getting the same error. In my case problem was, I had put same jar multiple times once through "user library" & next time through "build path" on the same Project. Just deleted the repeated jars from the classpath & got ride of the above error.
I had the same error and after trying out multiple recommendations, nothing had worked out. So I created a new workspace and refer to this project. After that, it got successfully built and exported the JAR without errors.
Not sure this might be the best possible solution, but do check java build path. I had it pointing to a wrong location because of which I was facing class not found error.
Once java build path was fixed, the problem was resolved.
I came here on same error. In my case, nothing was compiling (building?) and Eclipse didn't tell me there was any issue with the build other than these cryptic messages. I eventually unzipped the jar file and saw that it had no classes in it. It was because because the project I referenced in my build path wasn't built. In my case, the project would not compile in a million years, but I had access to jar files from R&D dept who could and did compile it in their own way. So I referenced those jar files instead. Now my classes compile and the error went away. I'm sure I would have done that in the first place but "Helpful" Eclipse suggested for me to reference the unbuilt project so I went along with the bad suggestion!
I closed all tabs with files in Eclipse, and it's fixed problem.
In my case, I was getting the same problem and I noticed I mvn clean and tried to export the jar and end-up getting the same error.
It worked for me after mvn install.

How do I load resources when using a classloader?

I'm using JarFile and JarURLConnection to load files out of a jar file. I'm then taking the classes, and loading them via BCEL (ByteCode Engineering Library, apache library). I cant just directly use a class loader because im modifying some classes slightly with the BCEL. I need to load the classes by their bytes into my bcel loader. However, one of the classes I'm loading references a resource. This resource is inside of the jar, so I can get the file (When iterating over the entries in the JarFile, I ignore the regular files, and take the class files for loading later). But just having the file won't do me any good, as the class loads it as a resource. Is there any way I can take that resource from the jar (well I can take it and load it into a byte[], the next part is the issue) and dynamically add it as a resource for my program, so that the classes that I load wont be missing their resources?
Got a lot of stuff here, if anythings confusing, ask in comments, I might've said something wrong, or missed something altogether :) Thanks
I'll show a little of my class loader here (extends ClassLoader):
#Override
public URL getResource(String name) {
System.out.println("LOADING RESOURCE: " + name);
try {
return new URL(null, name, new Handler(files));
} catch (MalformedURLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return null;
}
Now, it is printing out "LOADING RESOURCE: filename", but its then giving me a MalformedURLException (I have no protocol atm, just a file path, that's not a true valid path, but it's just an attempt to give it to my Handler class below).
class Handler extends URLStreamHandler {
#Override
protected URLConnection openConnection(URL u) throws IOException {
return new URLConnection(u) {
#Override
public void connect() throws IOException {
}
#Override
public InputStream getInputStream() throws IOException {
System.out.println("IS: " + url);
return /*method to get input steam*/;
}
};
}
}
The /*method to get input steam*/ is set in my real code, but that's not relevant here. So any further ideas with this?

Chained classloader conundrum

I'm having some hard time with Java classloaders, maybe somebody could shed some light on this. I have extracted the essence of the problem to the follwing:
There are three classes - ClassLoaderTest, LoadedClass and LoadedClassDep. They are all on different paths.
ClassLoaderTest instantiates a new URLClassLoader - myClassLoader, priming it with the paths to the remaining two classes and it's own classloader (i.e. the application classloader) as parent. It then uses Class.forName("com.example.LoadedClass", true, myClassLoader) to load the LoadedClass through reflection. The LoadedClass imports the LoadedClassDep. If I run the above, using:
java -cp /path/to/the/ClassLoaderTest ClassLoaderTest "/path/to/LoadedClass" "/path/to/LoadedClassDep"
and using the command line arguments to prime the URLClassLoader everything works fine. Using static initialisers I confirm that the two classes are loaded with an instance of a URLClassLoader.
HOWEVER, and this is the problem, if I do:
java -cp /path/to/the/ClassLoaderTest:/path/to/the/LoadedClass ClassLoaderTest "/path/to/LoadedClassDep"
this fails to load the LoadedClassDep (ClassNotFoundException). The LoadedClass is loaded correctly, but with sun.misc.Launcher$AppClassLoader, not the URLClassLoader!
It would appear that since the application classloader is capable of loading the LoadedClass it also attempts to load the LoadedClassDep, disregarding the URLClassLoader.
Here's the full source code:
package example.bc;
public class ClassloaderTest {
public static void main(String[] args) {
new ClassloaderTest().run(args);
}
private void run(String[] args) {
URLClassLoader myClasLoader = initClassLoader(args);
try {
Class<?> cls = Class.forName("com.example.bc.LoadedClass", true, myClasLoader);
Object obj = cls.newInstance();
cls.getMethod("call").invoke(obj);
} catch (Exception e) {
e.printStackTrace();
}
}
private URLClassLoader initClassLoader(String[] args) {
URL[] urls = new URL[args.length];
try {
for (int i = 0; i < args.length; i++) {
urls[i] = new File(args[i]).toURI().toURL();
}
} catch (MalformedURLException e) {
e.printStackTrace();
}
return new URLClassLoader(urls, getClass().getClassLoader());
}
}
package com.example.bc;
import com.bc.LoadedClassDep;
public class LoadedClass {
static {
System.out.println("LoadedClass " + LoadedClass.class.getClassLoader().getClass());
}
public void call() {
new LoadedClassDep();
}
}
package com.bc;
public class LoadedClassDep {
static {
System.out.println("LoadedClassDep " + LoadedClassDep.class.getClassLoader().getClass());
}
}
I hope I made this clear enough. My issue is, I only know the path to ClassLoadeTest at compile time, I have to use strings at runtime for the other paths. So, any ideas how to make the second scenario work?
I'd expect the application classloader to load LoadedClass in the second case, since classloaders delegate to their parent initially - this is the standard behaviour. In the second case, LoadedClass is on the parent's classpath, so it loads the class instead of giving up and letting the URLClassLoader try.
The application classloader then attempts to load the LoadedClassDep because it is imported and referenced directly in LoadedClass:
public void call() {
new LoadedClassDep();
}
If you need to load these classes dynamically and independently at runtime, you can't have direct references between them in this way.
It is also possible to change the order in which classloaders are tried - see Java classloaders: why search the parent classloader first? for some discussion of this.

Detect whether Java class is loadable

I have two programs: one CLI program, and one GUI. The GUI is a frontend for the CLI, but also a GUI for another program as well.
I am importing the CLI's classes and extending them in the GUI to add GUI elements to the classes, and all is great.
But now I want to split the CLI that I currently have embedded in the GUI (as an included JAR). The JAR is in a fixed location (/opt/program/prog.jar), and the application will only be used on Linux, so I realize that this breaks traditional Java thought.
I've edited the ClassPath in the Manifest file to reflect this change, and it works fine. However, when I remove the file, the GUI fails to load, citing not being able to load the class.
Is there a way to try to load a class and if it does not work, then do something else? In essence, I'm trying to catch the ClassNotFound exception, but have not had any luck yet.
One common way to check for class existence is to just do a Class.forName("my.Class"). You can wrap that with a try/catch that catches ClassNotFoundException and decide what to do. If you want, you could do that in a wrapper class that has a main(). You could try to load the class and if it succeeds, then call main() on the loaded class and if not, do something else.
public static void main(String arg[]) {
try {
Class.forName("my.OtherMain");
// worked, call it
OtherMain.main();
} catch(ClassNotFoundException e) {
// fallback to some other behavior
doOtherThing();
}
}
Is there a way to try to load a class and if it does not work, then do something else?
Assuming you had a class file in C:\ called Foo.class
public static void main(String[] args) {
File f = new File("c:\\");
if (f.exists()) {
URLClassLoader CLoader;
try {
CLoader = new URLClassLoader(new URL[]{f.toURL()});
Class loadedClass = CLoader.loadClass("Foo");
} catch (ClassNotFoundException ex) {
} catch (MalformedURLException ex) {
}
} else {
//do something else...
}
}

Categories

Resources