ObjectInputStream#readObject raises ClassNotFoundException with External Jar classes - java

So I have a Spring Boot application that loads external jars from the paths below:
java -cp "main-0.0.1-SNAPSHOT.jar" -Dloader.path="%USERPROFILE%\Addons\" -Dloader.main=moe.ofs.backend.BackendApplication org.springframework.boot.loader.PropertiesLauncher
The main jar doesn't know external jars at compile time. External jars are loaded like "plugins" or "addons" by specifying
-Dloader.path=...
All of external jars depend on an interface from "main-0.0.1-SNAPSHOT.jar", and they are supposed to do object serializations more or less.
The interface is called Configurable, and it provides two default methods like these:
default <T extends Serializable> void writeFile(T object, String fileName) throws IOException {
Path configFilePath = configPath.resolve(fileName + ".data");
FileOutputStream fileOutputStream = new FileOutputStream(configFilePath.toFile());
ObjectOutputStream objectOutputStream = new ObjectOutputStream(fileOutputStream);
objectOutputStream.writeObject(object);
objectOutputStream.close();
}
default <T extends Serializable> T readFile(String fileName) throws IOException, ClassNotFoundException {
Path configFilePath = configPath.resolve(fileName + ".data");
FileInputStream fileInputStream = new FileInputStream(configFilePath.toFile());
ObjectInputStream objectInputStream = new ObjectInputStream(fileInputStream);
return (T) objectInputStream.readObject();
}
Classes in external jars implement this interface, and they call readFile() and writeFile().
writeFile() works perfectly fine and doesn't seem to cause any problem; readFile(), however, throws a ClassNotFoundException, and that's what I'm trying to figure out.
java.lang.ClassNotFoundException: moe.ofs.addon.navdata.domain.Navaid
at java.net.URLClassLoader.findClass(URLClassLoader.java:382)
at java.lang.ClassLoader.loadClass(ClassLoader.java:418)
at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:355)
at java.lang.ClassLoader.loadClass(ClassLoader.java:351)
at java.lang.Class.forName0(Native Method)
at java.lang.Class.forName(Class.java:348)
at java.io.ObjectInputStream.resolveClass(ObjectInputStream.java:719)
at java.io.ObjectInputStream.readNonProxyDesc(ObjectInputStream.java:1922)
at java.io.ObjectInputStream.readClassDesc(ObjectInputStream.java:1805)
at java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:2096)
at java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1624)
at java.io.ObjectInputStream.readObject(ObjectInputStream.java:464)
at java.io.ObjectInputStream.readObject(ObjectInputStream.java:422)
at java.util.ArrayList.readObject(ArrayList.java:797)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at java.io.ObjectStreamClass.invokeReadObject(ObjectStreamClass.java:1170)
at java.io.ObjectInputStream.readSerialData(ObjectInputStream.java:2232)
at java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:2123)
at java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1624)
at java.io.ObjectInputStream.readObject(ObjectInputStream.java:464)
at java.io.ObjectInputStream.readObject(ObjectInputStream.java:422)
at moe.ofs.backend.Configurable.lambda$readFile$0(Configurable.java:186)
at java.lang.Thread.run(Thread.java:748)
After some testing it seems to me that ClassNotFoundException is thrown by Class.forName() because the default ClassLoader has a hard time looking for moe.ofs.addon.navdata.domain.Navaid, which is the class I'm trying to deserialize.
Navaid implements Serializable, and it also has a static final long serialVersionUID.
I had hoped that I could solve this by setting a context class loader for current thread, so that ObjectInputStream will use Spring Boot class loader to resolve Navaid class:
Thread.currentThread().setContextClassLoader(getClass().getClassLoader());
This, when printed out, gives something like
Thread.currentThread().getContextClassLoader() = org.springframework.boot.loader.LaunchedURLClassLoader#7a0b4753
Except that ObjectInputStream#readObject still throws ClassNotFoundException.
If I explicitly make a call to load Navaid class from Spring Boot loader such as:
getClass().getClassLoader().loadClass("moe.ofs.addon.navdata.domain.Navaid");
It returns a Navaid instance without any issue.
And as expected, when directly calling
Class.forName("moe.ofs.addon.navdata.domain.Navaid")
a ClassNotFoundException is thrown, even if the thread context loader has been explicitly set to LaunchedURLClassLoader; ObjectInputStream#readObject always tries to resolve the class by making a call to system default classloader to load the class.
Then I tried to load an ObjectInputStream using LaunchedURLClassLoader, but the instance still used Class.forName() from system default class loader.
ClassLoader cl = getClass().getClassLoader();
Thread.currentThread().setContextClassLoader(cl);
System.out.println("Thread.currentThread().getContextClassLoader() = " + Thread.currentThread().getContextClassLoader());
Class<?> tClass = getClass().getClassLoader().loadClass("java.io.ObjectInputStream");
System.out.println("tClass = " + tClass);
Path configFilePath = configPath.resolve(fileName + ".data");
FileInputStream fileInputStream = new FileInputStream(configFilePath.toFile());
Constructor<?> constructor = tClass.getConstructor(InputStream.class);
ObjectInputStream objectInputStream = (ObjectInputStream) constructor.newInstance(fileInputStream);
objectInputStream.readObject(); // throws ClassNotFoundException
Any input is appreciated. Thanks in advance.

As far as i know, you should override the method resolveClass on ObjectInputStream
Something like that:
default <T extends Serializable> T readFile(String fileName, ClassLoader loader) throws IOException, ClassNotFoundException {
Path configFilePath = configPath.resolve(fileName + ".data");
FileInputStream fileInputStream = new FileInputStream(configFilePath.toFile());
ObjectInputStream objectInputStream = new ObjectInputStream(fileInputStream){
protected Class<?> resolveClass(ObjectStreamClass desc)
throws IOException, ClassNotFoundException {
try {
return Class.forName(desc.getName(), false, loader);
} catch(ClassNotFoundException cnfe) {
return super.resolveClass(desc);
}
}
};
return (T) objectInputStream.readObject();
}
Never tried it myself, but it is worth a shot.
There is also http://commons.apache.org/proper/commons-io/javadocs/api-2.4/org/apache/commons/io/input/ClassLoaderObjectInputStream.html if you aready have commons-io in your project.
https://github.com/apache/commons-io/blob/master/src/main/java/org/apache/commons/io/input/ClassLoaderObjectInputStream.java

Related

.xls file not detected java

When I try to load the xls file it doesn't work even though is in the same folder. I also tried it with absolute paths.
(It all happened because of missing jars, here is the list with all of them. To solve the issue of relative paths the url.getResource() from below works fine.)
My Jar List(Image)
public class Main {
public static void main(String[] args) throws FileNotFoundException, IOException {
FileInputStream f = new FileInputStream("MP.xls");
HSSFWorkbook l = new HSSFWorkbook(f);
}
}
public class Main {
public static void main(String[] args) throws FileNotFoundException, IOException {
FileInputStream f = new FileInputStream("C:\\Users\\alumno.Alumno-PC\\Documents\\NetBeansProjects\\pruebas xlsx\\src\\pruebasxlsx\\MP.xls");
HSSFWorkbook l = new HSSFWorkbook(f);
}
}
This is where the file is located(picture)
This is the error with relative path:
Exception in thread "main" java.io.FileNotFoundException: MP.xls (El sistema no puede encontrar el archivo especificado)
at java.base/java.io.FileInputStream.open0(Native Method)
at java.base/java.io.FileInputStream.open(FileInputStream.java:216)
at java.base/java.io.FileInputStream.<init>(FileInputStream.java:157)
at java.base/java.io.FileInputStream.<init>(FileInputStream.java:111)
at pruebasxlsx.Main.main(Main.java:22)
this is the error with an absolute path
Exception in thread "main" java.lang.NoClassDefFoundError: org/apache/commons/math3/util/ArithmeticUtils
at org.apache.poi.poifs.property.RootProperty.setSize(RootProperty.java:59)
at org.apache.poi.poifs.property.DirectoryProperty.<init>(DirectoryProperty.java:52)
at org.apache.poi.poifs.property.RootProperty.<init>(RootProperty.java:31)
at org.apache.poi.poifs.property.PropertyTable.<init>(PropertyTable.java:58)
at org.apache.poi.poifs.filesystem.POIFSFileSystem.<init>(POIFSFileSystem.java:99)
at org.apache.poi.poifs.filesystem.POIFSFileSystem.<init>(POIFSFileSystem.java:272)
at org.apache.poi.hssf.usermodel.HSSFWorkbook.<init>(HSSFWorkbook.java:399)
at org.apache.poi.hssf.usermodel.HSSFWorkbook.<init>(HSSFWorkbook.java:381)
at pruebasxlsx.Main.main(Main.java:24)
Caused by: java.lang.ClassNotFoundException: org.apache.commons.math3.util.ArithmeticUtils
at java.base/jdk.internal.loader.BuiltinClassLoader.loadClass(BuiltinClassLoader.java:641)
at java.base/jdk.internal.loader.ClassLoaders$AppClassLoader.loadClass(ClassLoaders.java:188)
at java.base/java.lang.ClassLoader.loadClass(ClassLoader.java:520)
... 9 more
The FileInputStream with the absolute path works. Just the reading with HSSFWorkbook- misses a library.
The relative path is a volatile option. If the file is a read-only source, bundle it with the application, and do not use a File (file system file), but use a resource, a "file" on the class path, possibly bundled in an application jar.
InputStream f = Main.class.getResourceAsStream("/pruebasxslx/MP.xls");
HSSFWorkbook l = new HSSFWorkbook(f);
For HSSFWorkbook a library with org.apache.commons.math3 classes is not found,
an indirect needed library.
As these library dependencies are extra work, already done by others, and involving moving coherent versions of all libraries to work together, one should better use for instance maven, a build infrastructure. Maven (or gradle) projects are supported by most IDEs, and have a bit different directory structure.
File input stream gets the file named by the path name in the file system - that means an absolute path, as parameter, not relative path.
If you want to load a file from the location of your class use:
URL url = class.getResource("file.txt");
and then get absolute path of it by using:
url.getPath();
So, below solution should work (it can be polished but you will get an idea):
public class Main {
static URL url = Main.class.getResource("MP.xls");
public static void main(String[] args) throws FileNotFoundException, IOException {
FileInputStream f = new FileInputStream(url.getPath());
HSSFWorkbook l = new HSSFWorkbook(f);
}
}

Encrypted Classloader

I created myself a framework with "reverse-RSA" (Encryption with PrivateKey) in java and wanted to expand this to loading classes at runtime. I know that this is no efficient protection against theft of my code but my goal is to prevent (with best effort) people to alter my code.
For the past few hours I have searched for a method to load classes at runtime (and found some answers here, but none of them helped me), given only a byteArray containing the decrypted JarFile. I am working with a custom Classloader which is supposed to load the .class file which you can see below:
public class MemoryClassLoader extends ClassLoader{
private byte[] current = null;
public MemoryClassLoader(){
super(ClassLoader.getSystemClassLoader());
}
#Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
Class res = null;
try {
res = defineClass(name, current,0,current.length,null);
}catch(SecurityException e) {
System.out.println("Could not load the Class with our custom method(" + e.getMessage() + ") Falling back to the super method.");
res = super.loadClass(name,true);
}catch(Exception e){
System.out.println("An error occured while loading the class: " + e.getMessage());
}
return res;
}
#Override
public Class<?> loadClass(String name) throws ClassNotFoundException {
return findClass(name);
}
public final void setContents(final byte[] data){
current = new byte[data.length];
System.arraycopy(data, 0, current, 0, data.length);
}
I am feeding the loader like this:
public final void loadClasses(final File jarFile) throws InvalidKeyException, IllegalBlockSizeException, BadPaddingException, NoSuchAlgorithmException, NoSuchPaddingException, IOException, ClassNotFoundException, IllegalAccessException, IllegalArgumentException, InvocationTargetException, NoSuchMethodException, SecurityException, InstantiationException{
JarInputStream in = new JarInputStream(new ByteArrayInputStream(decrypt(jarFile)));//Block-like decryption
JarEntry je;
MemoryClassLoader loader = new MemoryClassLoader();
byte[] buffer = new byte[251658240];//30 MB
while((je=in.getNextJarEntry())!=null){
if(je.isDirectory() || !je.getName().endsWith(".class"))continue;
in.read(buffer);
buffer = trim(buffer);//Trim off all 0's at the end
loader.setContents(buffer);
Class c = loader.loadClass(je.getName().substring(0,je.getName().length()-6).replace("/", "."));//Try to load the class
}
in.close();
}
The decryption and the rest works just fine, but the class loader prints
Could not load the Class with our custom method(Prohibited package name: java.lang) Falling back to the super method.
into the console, then uses the superordinate loadClass which surprisingly works (since the original file remains encrypted).
This works thought but my question is, why does this error occur and how can I fix it?
Thank you in advance,
Daniel
I think your current class loader is trying to load Java SDK classes as well. That's why you get that error. Try checking the package names in findClass() and use the System Class Loader to load the ones that are not in your packages.

Get dependencies Using ASM5.0.2

I have a Java program based on ASM 5.0.2 to extract dependency between classes. The program works fine with an ordinary Java application. However, when I run the program as a plugin then it crashes with the bug: java.lang.ClassNotFoundException.
As an example if the example class uses junit.Assert, then when I run the project as an ordinary java application, it find this dependency, but when as plugin the below error:
java.lang.ClassNotFoundException: org.junit.Assert
at java.net.URLClassLoader$1.run(URLClassLoader.java:372)
at java.net.URLClassLoader$1.run(URLClassLoader.java:361)
at java.security.AccessController.doPrivileged(Native Method)
at java.net.URLClassLoader.findClass(URLClassLoader.java:360)
at java.lang.ClassLoader.loadClass(ClassLoader.java:424)
at java.net.FactoryURLClassLoader.loadClass(URLClassLoader.java:798)
at java.lang.ClassLoader.loadClass(ClassLoader.java:357)
at java.lang.Class.forName0(Native Method)
at java.lang.Class.forName(Class.java:340)
Part of code that I think error is because of that is as below, and the whole code can be find in this link:
class ClassCollector extends Remapper {
static Set<Class<?>> getClassesUsedBy(final String name, final String prefix, File root) throws IOException {
final ClassReader reader = new ClassReader(name);
final Set<Class<?>> classes = new TreeSet<Class<?>> (new Comparator<Class<?>>() {
#Override
public int compare (final Class<?> o1, final Class<?> o2) {
return o1.getName().compareTo (o2.getName());
}
});
final Remapper remapper = new ClassCollector(classes, prefix, root);
final ClassWriter inner = new ClassWriter(ClassWriter.COMPUTE_MAXS);
final RemappingClassAdapter visitor = new RemappingClassAdapter(inner, remapper);
try {
reader.accept(visitor, ClassReader.EXPAND_FRAMES);
}
catch (Exception ex) {
ex.toString();
}
return classes;
}
Important: when I initialized inner (as below) with null, then the program does not crash, but cannot detect all dependencies, and for example cannot detect assert dependency in the above example.
final ClassVisitor inner = null; //new ClassWriter(ClassWriter.COMPUTE_MAXS);
Please let me know if any one knows why the program is correct as an ordinary java application, but crash as plugin.
ClassReader(String name) uses the ClassLoader.loadSystemResourceAsStream() method to access the bytes for a requested class. If the classes you want to analyze are not in the class path, this won't work, since the class path is what loadSystemResourceAsStream searches.

Java: Dynamically loading external classes

I am writing an application that will load Java scripts. I currently have a GUI which utilizes a JFileChooser to allow the user to select a script from their machine. The script file can be anywhere. It is not on the classpath. Having only a File object to represent that script file, how can I obtain a Class representation of it?
I know that to load a class you need its binary name, so in.this.format. However, the problem with that is I don't know how the script writer may have packaged it. For example, he/she may have, while developing it, put the script file in the package foo.bar. After I download this script and place it in my documents (i.e., not in foo/bar), I can't load the script without knowing that it was packaged in foo.bar. If the class name is Test and I try to create a URLClassLoader pointing to the script file by doing new URLClassLoader(new URL[] { new URL(scriptFile.toURI().toURL()) }) and I do classLoader.loadClass("Test") I will get an exception saying that the class had the wrong name, and the correct name is foo.bar.Test. But how am I supposed to know that ahead of time?
This is what I have right now:
public class ScriptClassLoader extends URLClassLoader {
private final File script;
public ScriptClassLoader(File script) throws MalformedURLException {
super(new URL[] { script.toURI().toURL() });
this.script = script;
}
public Class<?> load() throws ClassNotFoundException {
String fileName = script.getName();
String className = fileName.substring(0, fileName.indexOf(".class"));
return loadClass(className);
}
}
How do people load scripts at runtime that are not part of the program's classpath, and the binary name of the class is not known?
If you just need to load a class from a given .class file, no matter how this classes is named, you can load the data yourself and then call ClassLoader's defineClass() method:
RandomAccessFile raf = new RandomAccessFile(script, "r");
try {
byte[] classData = new byte[(int) raf.length()];
raf.readFully(classData);
return super.defineClass(null, classData, 0, classData.length);
} finally {
raf.close();
}

Loading jars at runtime

I am trying to add jar file to classpath at runtime. I use this code
public static void addURL(URL u) throws IOException {
URLClassLoader sysloader = (URLClassLoader) ClassLoader
.getSystemClassLoader();
Class<URLClassLoader> sysclass = URLClassLoader.class;
try {
Method method = sysclass.getDeclaredMethod("addURL", parameters);
method.setAccessible(true);
method.invoke(sysloader, new Object[] { u });
System.out.println(u);
} catch (Throwable t) {
t.printStackTrace();
throw new IOException("Error");
}
}
System out prints this url:
file:/B:/Java/Tools/mysql-connector-java-5.1.18/mysql-connector-java-5.1.18/mysql-connector-java-5.1.18-bin.jar
I was check this path carefully, this jar exist. Even this test show that com.mysql.jdbc.
Driver class exists.
javap -classpath "B:\Java\Tools\mysql-connector-java-5.1.18\
mysql-connector-java-5.1.18\mysql-connector-java-5.1.18-bin.jar" com.mysql.jdbc.
Driver
Compiled from "Driver.java"
public class com.mysql.jdbc.Driver extends com.mysql.jdbc.NonRegisteringDriver i
mplements java.sql.Driver{
public com.mysql.jdbc.Driver() throws java.sql.SQLException;
static {};
}
But I still get java.lang.ClassNotFoundException when I use this Class.forName(driver).
What is wrong with this code?
The URL is ok, nevertheless you try to load a jar from classpath, so it means that yo need to have the file in cp first.
In your case you want to load a jar that is not in classpath so you have to use
URLClassLoader and for JAR you can use also the JARClassLoader
If you want some sample lesson on it:
http://docs.oracle.com/javase/tutorial/deployment/jar/jarclassloader.html
Here a sample I ran by myself see if helps you. It search the Logger class of Log4j that is not in my classpath, of course i got exception on invocation of the constructor since i did not pass the right params to the constructor
package org.stackoverflow;
import java.io.File;
import java.net.URL;
import java.net.URLClassLoader;
public class URLClassLoaderSample
{
public static void main(String[] args) throws Exception
{
File f = new File("C:\\_programs\\apache\\log4j\\v1.1.16\\log4j-1.2.16.jar");
URLClassLoader urlCl = new URLClassLoader(new URL[] { f.toURL()},System.class.getClassLoader());
Class log4jClass = urlCl.loadClass("org.apache.log4j.Logger");
log4jClass.newInstance();
}
}
Exception in thread "main" java.lang.InstantiationException: org.apache.log4j.Logger
at java.lang.Class.newInstance0(Class.java:357)
at java.lang.Class.newInstance(Class.java:325)
at org.stackoverflow.URLClassLoaderSample.main(URLClassLoaderSample.java:19)
Exception due to the wrong invocation, nevertheless at this stage we already found the class
Ok try the alternative approach with DataSource and not directly the Driver
Below is the code (working with oracle driver, i don't have my sql db, but the properties are the same)
Generally using the DataSource interface is the preferred approach since JDBC 2.0
The DataSource jar was not in the classpath neither for the test below
public static void urlCLSample2() throws Exception
{
File f = new File("C:\\_programs\\jdbc_drivers\\oracle\\v11.2\\ojdbc6.jar");
URLClassLoader urlCl = new URLClassLoader(new URL[] { f.toURL() }, System.class.getClassLoader());
// replace the data source class with MySQL data source class.
Class dsClass = urlCl.loadClass("oracle.jdbc.pool.OracleDataSource");
DataSource ds = (DataSource) dsClass.newInstance();
invokeProperty(dsClass, ds, "setServerName", String.class, "<put your server here>");
invokeProperty(dsClass, ds, "setDatabaseName", String.class, "<put your db instance here>");
invokeProperty(dsClass, ds, "setPortNumber", int.class, <put your port here>);
invokeProperty(dsClass, ds, "setDriverType",String.class, "thin");
ds.getConnection("<put your username here>", "<put your username password here>");
System.out.println("Got Connection");
}
// Helper method to invoke properties
private static void invokeProperty(Class dsClass, DataSource ds, String propertyName, Class paramClass,
Object paramValue) throws Exception
{
try
{
Method method = dsClass.getDeclaredMethod(propertyName, paramClass);
method.setAccessible(true);
method.invoke(ds, paramValue);
}
catch (Exception e)
{
throw new Exception("Failed to invoke method");
}
}

Categories

Resources