Idea
I have a jar (postgresql-9.4.1208.jre7.jar) in a byte[]. Id like to load and connect and run some basic SQL commands in runtime.
Implementation
Therefore I created a new Classloader:
public class JarClassloader extends ClassLoader {
public interface DriverProblemReporter {
void reportDriverProblem(String name, Throwable e);
}
private final byte[] driverdata;
private final DriverProblemReporter problemReporter;
public JarClassloader(byte[] jar, String drivername, DriverProblemReporter reporter) {
super(ClassLoader.getSystemClassLoader());
this.problemReporter = reporter;
try {
JarInputStream jis = new JarInputStream(new ByteArrayInputStream(jar));
JarEntry entry = jis.getNextJarEntry();
while (entry != null) {
handleEntry(entry, jis);
entry = jis.getNextJarEntry();
}
} catch (IOException e) {
e.printStackTrace();
}
this.driverdata = jar;
}
private void handleEntry(JarEntry entry, JarInputStream jis) {
if (!entry.isDirectory()) {
ByteArrayOutputStream baos;
try {
baos = new ByteArrayOutputStream();
IOUtils.copy(jis, baos);
baos.flush();
} catch (IOException e) {
problemReporter.reportDriverProblem(entry.getName(), e);
return;
}
try {
defineClass(baos.toByteArray(), 0, baos.size());
} catch (LinkageError e) {
problemReporter.reportDriverProblem(entry.getName(), e);
}
}
}
}
The Jar loads successfully and I am able to get a instance of the Driver.
Point of interrest
On the call to connect to a Database I get this Error:
java.lang.NoClassDefFoundError: org/postgresql/hostchooser/HostRequirement$1
at org.postgresql.core.v3.ConnectionFactoryImpl.openConnectionImpl(ConnectionFactoryImpl.java:107)
at org.postgresql.core.ConnectionFactory.openConnection(ConnectionFactory.java:66)
at org.postgresql.jdbc.PgConnection.<init>(PgConnection.java:215)
at org.postgresql.Driver.makeConnection(Driver.java:406)
at org.postgresql.Driver.connect(Driver.java:274)
In the stacktrace I see the working instance of org.postgresql.Driver looking for a class named org/postgresql/hostchooser/HostRequirement$1.
Assumption
My JarClassloader does not load anonymous nested classes.
Question
What shall I do to successfully load all classes in the jar?
You need to load the classes in the order the classloader needs them, not in the order they happen to be in the JAR file. So you need to override the findClass() method and search the JAR file at that point for the class being requested.
It would be a lot simpler to use a file.
Related
For some reason i have to place my *.properties files outside of java app. When the file km.properties resides in java/src/resources/km.properties the code reads the file but when i place the same file in C:\Users\abc\Desktop\km.properties
it throws
Exception: java.io.FileNotFoundException: property file 'C:\Users\abc\Desktop\km.properties' not found in the classpath
Exception in thread "main" java.lang.NullPointerException
at com.ir.Constants.<init>(Constants.java:44)
at com.Constants.main(Constants.java:64)
here is my code
public class Constants {
public Constants(){
System.out.println(System.getenv("km_config"));
try {
Properties prop = new Properties();
String propFileName = System.getenv("km_config");
inputStream = getClass().getClassLoader().getResourceAsStream(propFileName);
if (inputStream != null) {
prop.load(inputStream);
} else {
throw new FileNotFoundException("property file '" + propFileName + "' not found in the classpath");
}
}
catch (IOException e) {
System.out.println("Exception: " + e);
}
catch (Exception e) {
System.out.println("Exception: " + e);
} finally {
try {
inputStream.close();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
public static void main(String[] args) throws Exception {
Constants c = new Constants();
System.out.println(Constants.DB_PATH1);
System.out.println(Constants.GIT_REPO_PATH);
System.out.println(Constants.GIT_MAIN_BRANCH_NAME);
System.out.println(Constants.TAGGER_PATH);
}
Constants.java:44 is
inputStream.close();
Constants.java:64 is
Constants c = new Constants();
please help me i need to place km.properies file any where outside of the java app
command results in
echo %km_config%
C:\Users\abc\Desktop\km.properties
The API ClassLoader::getResourceAsStream(String) has a search path which is the classpath. Actually you are right that the configuration file should not be bundled with your .class files and read from the filesystem of the target machine instead.
Thus your API call becomes:
Properties conf = new Properties();
conf.load(new InputStreamReader(new FileInputStream(new File(file)));
Note: I did not specify a charset for converting the stream of bytes to a stream of character because I want the JVM to pick whatever character is the default for the system.
For testing I suggest you:
put the configuration file in a known location out of the sources (the Desktop) or anyway ignored by the version control system
pass the value as a system property (like -Dfile=C:\Users\me\Desktop\km.properties)
I am trying write to a csv file. After the execution of the code bellow the csv file is still empty.
File is in folder .../webapp/resources/.
This is my dao class:
public class UserDaoImpl implements UserDao {
private Resource cvsFile;
public void setCvsFile(Resource cvsFile) {
this.cvsFile = cvsFile;
}
#Override
public void createUser(User user) {
String userPropertiesAsString = user.getId() + "," + user.getName()
+ "," + user.getSurname() +"\n";;
System.out.println(cvsFile.getFilename());
FileWriter outputStream = null;
try {
outputStream = new FileWriter(cvsFile.getFile());
outputStream.append(userPropertiesAsString);
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
outputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
#Override
public List<User> getAll() {
return null;
}
}
This is a part of beans.xml.
<bean id="userDao" class="pl.project.dao.UserDaoImpl"
p:cvsFile="/resources/users.cvs"/>
Program compiles and doesn't throw any exceptions but CSV file is empty.
If you're running your app in IDE, the /webapp/resources used for running app will differ from the /webapp/resources in your IDE. Try to log full path to file and check there.
try using outputStream.flush() as the final statement in the first of the try block.
I think you're looking at the wrong file. If you specify an absolute path /resources/users.cvs, then it probably won't be written into the a folder relative to the webapp. Instead, it will be written to /resources/users.cvs
So the first step is to always log an absolute path to make sure the file is where you expect it.
Try with this code, it will at least tell you where the problem lies (Java 7+):
// Why doesn't this method throw an IOException?
#Override
public void createUser(final User user)
{
final String s = String.format("%s,%s,%s",
Objects.requireNonNull(user).getId(),
user.getName(), user.getSurname()
);
// Note: supposes that .getFile() returns a File object
final Path path = csvFile.getFile().toPath().toAbsolutePath();
final Path csv;
// Note: this supposes that the CSV is supposed to exist!
try {
csv = path.toRealPath();
} catch (IOException e) {
throw new RuntimeException("cannot locate CSV " + path, e);
}
try (
// Note: default is to TRUNCATE the destination.
// If you want to append, add StandardOpenOption.APPEND.
// See javadoc for more details.
final BufferedWriter writer = Files.newBufferedWriter(csv,
StandardCharsets.UTF_8);
) {
writer.write(s);
writer.newLine();
writer.flush();
} catch (IOException e) {
throw new RuntimeException("write failure", e);
}
}
I am trying to read a text file, "text.txt", packaged at the root as a part of my jar file. in one case, my code calls class.getResourceAsStream("/test.txt") and in another case, my code calls class.getClassLoader().getResourceAsStream("/test.txt").
The first call gets the correct data but the second one doesn't get anything. any idea?
public static void main(String[] args) {
InputStream is = null;
try {
is = TestLoadResourcesByClass.class.getResourceAsStream("/test.txt");
StringWriter writer = new StringWriter();
IOUtils.copy(is, writer);
System.out.println(writer.toString());
} catch(Exception ex) {
ex.printStackTrace();
} finally {
if(null != is) { try { is.close(); } catch(Exception ex) { } }
}
}
public static void main(String[] args) {
InputStream is = null;
try {
is = TestLoadResourcesByClassLoader.class.getClassLoader().getResourceAsStream("/test.txt");
StringWriter writer = new StringWriter();
IOUtils.copy(is, writer);
System.out.println(writer.toString());
} catch(Exception ex) {
ex.printStackTrace();
} finally {
if(null != is) { try { is.close(); } catch(Exception ex) { }
}
}
let's say i have 2 jar files
first.jar : contains TestLoadResourcesByClass.class (code to read test.txt)
second.jar : contains "test.txt" at the root
and then i run my code as follows
java -cp first.jar;second.jar;commons-io-2.4.jar test.TestLoadByClass
i also get no output at the console. is that because the classes/resources in second.jar have not been loaded? in fact, i get a null pointer exception (input stream is null).
any idea on what's going on?
It is explained in the javadoc for Class.getResourceAsStream. That method removes the / from the start of resource names to create the absolute resource name that it gives to ClassLoader.getResourceAsStream. If you want to call ClassLoader.getResourceAsStream directly then you should omit the / at the start.
I have started learning (I am new to this) , ASM API for a compiler project . I am using java Instrumentation and ASM ByteCode Library for developing a Javaagent.
I am passing classname and method name through properties.My goal is to change my className and methodName at run runtime ( means that after server started or premain() is called).
But , It works only for whatever the className or packageName passed at before started server.
I understand that , while calling the javaagent ( premain() ) , ASM set visitor for method for given pakage/class.
Even after server started or premain() is called , I wanted to visit to a specific class and method.
It Will be very helpful , if any one help on this.
This is my currently running program.
public class AddPrintlnAgent implements ClassFileTransformer {
public static void premain(String agentArgs, Instrumentation inst) {
Properties prop = new Properties();
try {
prop.load(new FileInputStream("C:\\locator.properties"));
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
inst.addTransformer(new AddPrintlnAgent());
}
public byte[] transform(ClassLoader loader, String className,
Class<?> classBeingRedefined, ProtectionDomain protectionDomain,
byte[] classfileBuffer) throws IllegalClassFormatException {
byte[] retVal = null;
if (className.equals(className)) {
ClassWriter cw = new ClassWriter(0);
ClassVisitor ca = new MyClassAdapter(cw);
ClassReader cr = new ClassReader(classfileBuffer);
cr.accept(ca, 0);
retVal = cw.toByteArray();
}
return retVal;
}
public class MyClassAdapter extends ClassNode implements Opcodes {
private ClassVisitor cv;
Properties prop = new Properties();
public MyClassAdapter(ClassVisitor cv) {
this.cv = cv;
}
#Override
public void visitEnd() {
try {
prop.load(new FileInputStream("C:\\locator.properties"));
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
for (MethodNode mn : (List<MethodNode>) methods) {
if (mn.name.equals(prop.getProperty("methodName").trim())) {
InsnList il = new InsnList();
il.add(new FieldInsnNode(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;"));
il.add(new LdcInsnNode(prop.getProperty("message")));
il.add(new MethodInsnNode(INVOKEVIRTUAL, "java/io/PrintStream",
"println", "(Ljava/lang/String;)V"));
mn.instructions.insert(il);
mn.maxStack +=2;
}
}
accept(cv);
}
}
}
Thanks in advance
Sathish V J
You should cleanup your code. In your premain method you load a properties file into a Properties object but that instance is never used. Instead you reload that file into another Properties instance at every visitEnd() invocation. I’m not sure whether this is really what you want.
However, transformers are invoked during class loading but not for classes that have been loaded already. You can try to retransform or redefine loaded classes, but changing class or method names after loading is not supported by instrumentation.
Is it possible to save a loaded class to a file?
Class cc = Class.forName("projects.implementation.JBean");
Or, maybe to get the physical location of that class?
Yes you can as Class.class implements Serializable interface you can serialize it into file and as well deserialize again.
Example -
Class Test{
public static void main(String[] args) throws ClassNotFoundException {
try {
OutputStream file = new FileOutputStream("test.ser");
OutputStream buffer = new BufferedOutputStream(file);
ObjectOutput output = new ObjectOutputStream(buffer);
try {
Class cc = Class.forName("com.test.Test");
System.out.println(cc);
output.writeObject(cc);
} finally {
output.close();
}
} catch (IOException ex) {
ex.printStackTrace();
}
try {
// use buffering
InputStream file = new FileInputStream("test.ser");
InputStream buffer = new BufferedInputStream(file);
ObjectInput input = new ObjectInputStream(buffer);
try {
// deserialize the class
Class cc = (Class) input
.readObject();
// display
System.out.println("Recovered Class: " + cc);
} finally {
input.close();
}
} catch (ClassNotFoundException ex) {
ex.printStackTrace();
} catch (IOException ex) {
ex.printStackTrace();
}
}
}
Serializing a Class object in Java serializes little more than the qualified name of the class. When deserializing the class is looked up by name.
In the general case, I don't think it's possible to get the bytes corresponding to a class definition (the bytecode) once it has been loaded. Java permits classes to be defined at runtime and, so far as I'm aware, doesn't expose the bytes after the class has been loaded.
However, depending on the ClassLoader in use, you may find that
cc.getResourceAsStream("JBean.class")
is all you need, loading the class stream as a resource using its own ClassLoader.
Another option could be to intercept the loading of the class. The ClassLoader will get to see the bytes in "defineClass", so a custom ClassLoader could store them somewhere.