java.lang.NoClassDefFoundError from java.net.ServerSocket changed with Javassist - java

I am writing an agent that changes the constructor of java.net.ServerSocket using Javassist. The very simply change I do is to append a call to the default constructor that calls a static method in my class. Even though I see while debugging that the JAR that contains the class (which is the JAR that contains the agent) is available in the classpath, I get a java.lang.NoClassDefFoundError error from java.net.ServerSocket..
Has someone come across such a problem? I am not aware of a limitation in the system classloader that could stop my class my being made available to a class from java.net.
EDIT 1: The relevant code snippet I use for class transformer:
package my.package;
import java.lang.instrument.ClassFileTransformer;
...
public class QClassFileTransformer implements ClassFileTransformer {
...
public byte[] transform(...) {
...
classPool.insertClassPath(new ByteArrayClassPath(className, classfileBuffer));
classPool.importPackage("my.package");
CtClass ctClass = classPool.get(className.replaceAll("/", "."));
if (!ctClass.isFrozen()) {
for (CtConstructor constructor : ctClass.getDeclaredConstructors()) {
if (constructor.getParameterTypes().length != 0) {
continue;
}
constructor.insertAfter("my.package.QClassFileTransformer.call(this);");
break;
}
byte[] bytecode = ctClass.toBytecode();
ctClass.detach();
return bytecode;
}
...
In the transformed class (java.net.ServerSocket), the NoClassDefFoundError exception for my.package.QClassFileTransformer is thrown when the class is loaded.

Related

Reload class in Groovy

I have a custom ClassLoader extending GroovyClassLoader which compiles the source code to .class files on disk and then loads the resulting class:
class MyClassLoader extends GroovyClassLoader {
File cache = new File( './cache' )
Compiler compiler
MyClassLoader() {
CompilerConfiguration cc = new CompilerConfiguration( targetDirectory:cache )
compiler = new Compiler( cc )
addClasspath cache.path
}
#Override
Class findClass( name ) {
try{
parent.findClass name
}catch( ClassNotFoundException e ){
compiler.compile name, getBodySomehow()
byte[] blob = loadFromFileSystem name
Class c = defineClass name, blob, 0, blob.length
setClassCacheEntry c
c
}
}
#Override
void removeClassCacheEntry​(String name) {
Class c = cache[ name ]
super.removeClassCacheEntry​(name)
GroovySystem.metaClassRegistry.removeMetaClass c
deleteFiles name
}
}
Class clazz = myClassLoader.loadClass 'some.pckg.SomeClass'
Now if I change the source code, call myClassLoader.removeClassCacheEntry​(name) and try myClassLoader.loadClass() again I'm getting:
java.lang.LinkageError: loader (instance of com/my/MyClassLoader): attempted duplicate class definition for name some/pckg/SomeClass
I read the greater half of the Internet and found a "solution" to initialize a class-loader for each class:
MyClassLoader myClassLoader = new MyClassLoader()
Class clazz = myClassLoader.loadClass 'some.pckg.SomeClass'
This seems to be working but raises performance concerns of mine...
What is the proper way to reload classes? How can I reuse the same class-loader? What am I missing?
Actually there is a trick that could be used
Originally, when you call
classLoader.defineClass(className, classBytes, 0, classBytes.length)
It calls java native method defineClass1 that actually calls loadClass method.
So, possible to intercept this method and process it a bit different then original.
In the folder that contains cached class files I have the following groovy compiled to class: A.class
println "Hello World!"
B.class to check dependent class loading
class B extends A {
def run(){
super.run()
println "Hello from ${this.getClass()}!"
}
}
and C.class to check multi-level class loading
i used this jar to compile following class and run the class re-loading example
class C extends org.apache.commons.lang3.RandomUtils {
def rnd(){ nextInt() }
}
the following class + code loads and reloads the same class:
import java.security.PrivilegedAction;
import java.security.AccessController;
import org.codehaus.groovy.control.CompilationFailedException;
#groovy.transform.CompileStatic
class CacheClassLoader extends GroovyClassLoader{
private File cacheDir = new File('/11/tmp/a__cache')
private CacheClassLoader(){throw new RuntimeException("default constructor not allowed")}
public CacheClassLoader(ClassLoader parent){
super(parent)
}
public CacheClassLoader(Script parent){
this(parent.getClass().getClassLoader())
}
#Override
protected Class getClassCacheEntry(String name) {
Class clazz = super.getClassCacheEntry(name)
if( clazz ){
println "getClassCacheEntry $name -> got from memory cache"
return clazz
}
def cacheFile = new File(cacheDir, name.tr('.','/')+'.class')
if( cacheFile.exists() ){
println "getClassCacheEntry $name -> cache file exists, try to load it"
//clazz = getPrivelegedLoader().defineClass(name, cacheFile.bytes)
clazz = getPrivelegedLoader().defineClass(name, cacheFile.bytes)
super.setClassCacheEntry(clazz)
}
return clazz
}
private PrivelegedLoader getPrivelegedLoader(){
PrivelegedLoader loader = AccessController.doPrivileged(new PrivilegedAction<PrivelegedLoader>() {
public PrivelegedLoader run() {
return new PrivelegedLoader();
}
});
}
public class PrivelegedLoader extends CacheClassLoader {
private final CacheClassLoader delegate
public PrivelegedLoader(){
super(CacheClassLoader.this)
this.delegate = CacheClassLoader.this
}
public Class loadClass(String name, boolean lookupScriptFiles, boolean preferClassOverScript, boolean resolve) throws ClassNotFoundException, CompilationFailedException {
Class c = findLoadedClass(name);
if (c != null) return c;
return delegate.loadClass(name, lookupScriptFiles, preferClassOverScript, resolve);
}
}
}
def c=null
//just to show intermediate class loaders could load some classes that will be used in CacheClassLoader
def cl_0 = new GroovyClassLoader(this.getClass().getClassLoader())
cl_0.addClasspath('/11/tmp/a__cache/commons-lang3-3.5.jar')
//create cache class loader
def cl = new CacheClassLoader(cl_0)
println "---1---"
c = cl.loadClass('A')
c.newInstance().run()
println "---2---"
c = cl.loadClass('A')
c.newInstance().run()
println "---3---"
cl.removeClassCacheEntry('A')
c = cl.loadClass('A')
c.newInstance().run()
println "---4---"
c = cl.loadClass('B')
c.newInstance().run()
println "---5---"
cl.removeClassCacheEntry('A')
cl.removeClassCacheEntry('B')
c = cl.loadClass('B')
c.newInstance().run()
println "---6---"
c = cl.loadClass('C')
println c.newInstance().rnd()
result:
---1---
getClassCacheEntry A -> cache file exists, try to load it
Hello World!
---2---
getClassCacheEntry A -> got from memory cache
Hello World!
---3---
getClassCacheEntry A -> cache file exists, try to load it
Hello World!
---4---
getClassCacheEntry B -> cache file exists, try to load it
getClassCacheEntry A -> got from memory cache
Hello World!
Hello from class B!
---5---
getClassCacheEntry B -> cache file exists, try to load it
getClassCacheEntry A -> cache file exists, try to load it
Hello World!
Hello from class B!
---6---
getClassCacheEntry C -> cache file exists, try to load it
226399895
PS: not sure priviledged access required
JVM does not allow to just unload some class, the only way to unload a class is to GC it. And class can be GC just like every other object -> all reachable references must be removed and GC run.
The tricky part is... class loader hold references to all classes. So the only way to unload a class is to get rid of both class and class loader.
You can find more information in language specification: https://docs.oracle.com/javase/specs/jvms/se13/jvms13.pdf 12.7 Unloading of Classes and Interfaces
An implementation of the Java programming language may unload classes.
A class or interface may be unloaded if and only if its defining class
loader may be reclaimed by the garbage collector as discussed in
§12.6. Classes and interfaces loaded by the bootstrap loader may not
be unloaded.
And class unloading does not need to be implemented at all in some JVM implementations:
Class unloading is an optimization that helps reduce memory use.
[...] system chooses to implement an optimization such as class
unloading. [...] Consequently, whether a class or interface has been unloaded
or not should be transparent to a program.
There is also explanation why class loader can't be reachable to unload class, as class might contain static variables and blocks of code that would be reset and executed again if this same class would be later loaded again. It's quite long and already a bit off topic so I will not paste it here.
So each your script should just use own class loader as that's the only way to actually not waste memory, so class can be GC later. Just make sure that you don't use any libraries that might cache references to your class - like many serialization/ORM libraries might do this for data types, or some other reflection libraries.
Another solution would be to use different scripting language that does not create java classes and just execute some kind of AST structure.
There is also one more solution to this problem, but it is very tricky and it's not something you should use on production, it even requires you to provide special JVM arguments or JVM from JDK that contains all needed modules. As java supports instrumentation API that can allow you to change bytecode of class at runtime, but if class is already loaded you can only change bytecode of methods, you can't add/remove/edit method/field/class signatures. So it could be very bad idea to use it for such scripts, so I will stop here.

Java 9, compatability issue with ClassLoader.getSystemClassLoader

The following code adds jar file to the build path, it works fine with Java 8. However, it throws exception with Java 9, the exception is related to the cast to URLClassLoader. Any ideas how this can be solved? an optimal solution will edit it to work with both Java 8 & 9.
private static int AddtoBuildPath(File f) {
try {
URI u = f.toURI();
URLClassLoader urlClassLoader = (URLClassLoader) ClassLoader.getSystemClassLoader();
Class<URLClassLoader> urlClass = URLClassLoader.class;
Method method = urlClass.getDeclaredMethod("addURL", URL.class);
method.setAccessible(true);
method.invoke(urlClassLoader, u.toURL());
} catch (NoSuchMethodException | SecurityException | IllegalArgumentException | InvocationTargetException | MalformedURLException | IllegalAccessException ex) {
return 1;
}
return 0;
}
You've run into the fact that the system class loader is no longer a URLClassLoader. As indicated by ClassLoader::getSystemClassLoader's return type, this was an implementation detail, albeit one that a non-negligible amount of code relied upon.
Judging by the comments, you are looking for a way to dynamically load classes at run time. As Alan Bateman points out, this can not be done in Java 9 by appending to the class path.
You should instead consider creating a new class loader for that. This has the added advantage that you'll be able to get rid of the new classes as they are not loaded into the application class loader. If you're compiling against Java 9, you should read up on layers - they give you a clean abstraction for loading an entirely new module graph.
I have stumbled over this issue a while ago. As many, I had used a method similar to that in the question
private static int AddtoBuildPath(File f)
to dynamically add paths to the classpath at runtime. The code in the question is probably bad style in multiple aspects: 1) assuming that ClassLoader.getSystemClassLoader() returns an URLClassLoader is an undocumented implementation detail and 2) using reflection to make addURL public is maybe another one.
Cleaner way to dynamically add classpaths
In case that you need to use the additional classpath URLs for class loading through „Class.forName“, a clean, elegant and compatible (Java 8 to 10) solution is the following:
1) Write your own class loader by extending URL classloader, having a public addURL method
public class MyClassloader extends URLClassLoader {
public MyClassloader(URL[] urls, ClassLoader parent) {
super(urls, parent);
}
public void addURL(URL url) {
super.addURL(url);
}
}
2) Declare a (singleton/app wide) object of your classloader
private final MyClassloader classLoader;
and instanciate it via
classLoader = new MyClassloader(new URL[0], this.getClass().getClassLoader());
Note: The system class loader is the parent. Classes loaded though classLoader know those who can be loaded through this.getClass().getClassLoader() but not the other way around.
3) Add additional classpaths whenever needed (dynamically):
File file = new File(path);
if(file.exists()) {
URL url = file.toURI().toURL();
classLoader.addURL(url);
}
4) Instanciate objects or your app though your singleton classloader via
cls = Class.forName(name, true, classLoader);
Note: Since class loaders try a delegation to the parent class loader prior loading a class (and the parent to its parent), you have to make sure that the class to load is not visible to the parent class loader to make sure that it is loaded through the given class loader. To make this clearer: if you have ClassPathB on your system class path and later add ClassPathB and some ClassPathA to your custom classLoader, then classes under ClassPathB will be loaded through the system classloader and classes under ClassPathA are not known to them. However, if you remove ClassPathB from you system class path, such classes will be loaded through your custom classLoader, and then classes under ClassPathA are known to those under ClassPathB.
5) You may consider passing your class loader to a thread via
setContextClassLoader(classLoader)
in case that thread uses getContextClassLoader.
If you're just looking to read the current classpath, for example because you want to spin up another JVM with the same classpath as the current one, you can do the following:
object ClassloaderHelper {
def getURLs(classloader: ClassLoader) = {
// jdk9+ need to use reflection
val clazz = classloader.getClass
val field = clazz.getDeclaredField("ucp")
field.setAccessible(true)
val value = field.get(classloader)
value.asInstanceOf[URLClassPath].getURLs
}
}
val classpath =
(
// jdk8
// ClassLoader.getSystemClassLoader.asInstanceOf[URLClassLoader].getURLs ++
// getClass.getClassLoader.asInstanceOf[URLClassLoader].getURLs
// jdk9+
ClassloaderHelper.getURLs(ClassLoader.getSystemClassLoader) ++
ClassloaderHelper.getURLs(getClass.getClassLoader)
)
By default the final fields in the $AppClassLoader class cannot be accesed via reflection, an extra flag needs to be passed to the JVM:
--add-opens java.base/jdk.internal.loader=ALL-UNNAMED
I was given a spring boot application that runs in Java 8. I had the task to upgrade it to Java 11 version.
Issue faced:
Caused by: java.lang.ClassCastException: jdk.internal.loader.ClassLoaders$AppClassLoader (in module: java.base) cannot be cast to java.net.URLClassLoader (in module: java.base)
Way around used:
Create a class:
import java.net.URL;
/**
* This class has been created to make the code compatible after migration to Java 11
* From the JDK 9 release notes: "The application class loader is no longer an instance of
* java.net.URLClassLoader (an implementation detail that was never specified in previous releases).
* Code that assumes that ClassLoader.getSytemClassLoader() returns a URLClassLoader object will
* need to be updated. Note that Java SE and the JDK do not provide an API for applications or
* libraries to dynamically augment the class path at run-time."
*/
public class ClassLoaderConfig {
private final MockClassLoader classLoader;
ClassLoaderConfig() {
this.classLoader = new MockClassLoader(new URL[0], this.getClass().getClassLoader());
}
public MockClassLoader getClassLoader() {
return this.classLoader;
}
}
Create Another class:
import java.net.URL;
import java.net.URLClassLoader;
public class MockClassLoader extends URLClassLoader {
public MockClassLoader(URL[] urls, ClassLoader parent) {
super(urls, parent);
}
public void addURL(URL url) {
super.addURL(url);
}
}
Now set it in the current thread from your main class (Right at the beginning of your application)
Thread.currentThread().setContextClassLoader(new ClassLoaderConfig().getClassLoader());
Hope this solution works for your!!!
Shadov pointed to a thread at the oracle community. There is the correct answer:
Class.forName("nameofclass", true, new URLClassLoader(urlarrayofextrajarsordirs));
The caveats mentioned there are also important:
Caveats:
java.util.ServiceLoader uses the thread's ClassLoader context Thread.currentThread().setContextClassLoader(specialloader);
java.sql.DriverManager does honors the calling class' ClassLoader, -not- the Thread's ClassLoader. Create Driver directly using Class.forName("drivername", true, new URLClassLoader(urlarrayofextrajarsordirs).newInstance();
javax.activation uses the thread's ClassLoader context (important for javax.mail).
Referring to Edi's Solution this worked for me:
public final class IndependentClassLoader extends URLClassLoader {
private static final ClassLoader INSTANCE = new IndependentClassLoader();
/**
* #return instance
*/
public static ClassLoader getInstance() {
return INSTANCE;
}
private IndependentClassLoader() {
super(getAppClassLoaderUrls(), null);
}
private static URL[] getAppClassLoaderUrls() {
return getURLs(IndependentClassLoader.class.getClassLoader());
}
private static URL[] getURLs(ClassLoader classLoader) {
Class<?> clazz = classLoader.getClass();
try {
Field field = null;
field = clazz.getDeclaredField("ucp");
field.setAccessible(true);
Object urlClassPath = field.get(classLoader);
Method method = urlClassPath.getClass().getDeclaredMethod("getURLs", new Class[] {});
method.setAccessible(true);
URL[] urls = (URL[]) method.invoke(urlClassPath, new Object[] {});
return urls;
} catch (Exception e) {
throw new NestableRuntimeException(e);
}
}
}
Running within Eclipse, you need to set VM Arguments to JUnit Launch/Debug Configuration.
Running with maven via command line you have two options:
Option 1
Add following lines to pom.xml :
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>2.16</version>
<configuration>
<argLine>--add-opens java.base/jdk.internal.loader=ALL-UNNAMED</argLine>
</configuration>
</plugin>
Option 2
run mvn test -DargLine="-Dsystem.test.property=--add-opens java.base/jdk.internal.loader=ALL-UNNAMED"
There's also this guys article that helped me.
I could not find the article but... here: https://github.com/CGJennings/jar-loader
Here's a part of guide inside there there's a jar at release you could read his guide & setup it up.
I just tried it myself download the jar file which include the class file
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.lang.instrument.Instrumentation;
import java.lang.reflect.Method;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.jar.JarFile;
public final class classname{
public static void premain(String agentArgs, Instrumentation instrumentation) {
loadedViaPreMain = true;
agentmain(agentArgs,instrumentation);
}
public final static void addToClassPath(File jarfile)throws IOException{inst.appendToSystemClassLoaderSearch(new JarFile(jarfile));}
public final static void agentmain(String agentArgs, Instrumentation instrumentation) {
if (instrumentation == null){throw new NullPointerException("instrumentation");}
if (inst == null) {inst = instrumentation;}
}
private static Instrumentation inst;
private static boolean loadedViaPreMain = false;
}
I just try it out myself package these code as a package then start the application class with -javaagent:plugin......jar option then call this function.It doesn't change my classpath.I am probably missing some details here.
Hope you can make it work though.
i found this, and worked for me.
String pathSeparator = Syste .getProperty("path.separator");
String[] classPathEntries = System.getProperty("java.class.path") .split(pathSeparator);
from the web site https://blog.codefx.org/java/java-11-migration-guide/#Casting-To-URL-Class-Loader

Parent-Child Classloader Class Resolution

The tasks and initial investigation
I try to set up two Oracle Coherence near cache instances at one java swing application. The idea a solution could be found here. My case is a bit more complicated and this is where the game starts.
Short description
In my case there is an account service. It can have two endpoints: SIT and UAT. In order to create two such services, I need to load two 'instances' of the Coherence in order to override the endpoints with system variables (tangosol.coherence.cacheconfig).
I have:
the main code of the app is located in the mainapp.jar;
the AccountService interface that is located in the account-interfaces.jar;
the AccountServiceImpl class that is located in the account-impl.jar and implements the AccountService interface;
my main application has the following structure
bin: startup.bat, startup.sh
conf: app.properties
lib: mainapp.jar, account-interfaces.jar, account-impl.jar, coherence.jar
Approach tried
I created a dedicated child-first classLoader - InverseClassLoader and made the AppLaunchClassLoader (the default Thread.currentThread().GetContextClassLoader() classLoader) it's parent. With the InverseClassLoader I load the AccountServiceImpl class:
Class<AccountServiceImpl> acImplClass = contextClassLoader.selfLoad(AccountServiceImpl.class).loadClass(AccountServiceImpl.class);
Constructor<AccountServiceImpl> acConstructor =
acImplClass .getConstructor(String.class);
AccountService acService = acConstructor .newInstance(serviceURL);
Issues and questions
I get the 'AccountServiceImpl cannot be cast to AccountService' exceptions, which means that those two classes loaded by different classloaders. But those classloaders are in the parent-child relationship. So am I right that even if a class is loaded by a parent (interface - 'abstract' type) it can't be used with a class (concrete impl) loaded by a child classloader? Why then we need this parent-child relation?
I specified the AccountService interface in a code and it got loaded by a default classloader. I tried wrap the code above is a thread and set the InverseClassLoader it's context classloader. Nothing changed. So am I right that I can't use such interface-implementation coding (as usual coding) and need to use reflection all the time to invoke concrete methods all the time? (Hope there is a solution) ;
Say, I listed both the AccountService and AccountServiceImpl classes for being loaded by the InverseClassLoader. What if I need other classes, that are accessible by those two, to be also loaded by the InverseClassLoader? It there a way to say that all 'related' classes must be loaded by the same classloader?
Update
Here is the InverseClassLoader:
public class InvertedClassLoader extends URLClassLoader {
private final Set<String> classesToNotDelegate = new HashSet<>();
public InvertedClassLoader(URL... urls) {
super(urls, Thread.currentThread().getContextClassLoader());
}
public InvertedClassLoader selfLoad(Class<?> classToNotDelegate) {
classesToNotDelegate.add(classToNotDelegate.getName());
return this;
}
#Override
public Class<?> loadClass(String className, boolean resolve) throws ClassNotFoundException {
if (shouldNotDelegate(className)) {
System.out.println("CHILD LOADER: " + className);
Class<?> clazz = findClass(className);
if (resolve) {
resolveClass(clazz);
}
return clazz;
}
else {
System.out.println("PARENT LOADER: " + className);
return super.loadClass(className, resolve);
}
}
public <T> Class<T> loadClass(Class<? extends T> classToLoad) throws ClassNotFoundException {
final Class<?> clazz = loadClass(classToLoad.getName());
#SuppressWarnings("unchecked")
final Class<T> castedClass = (Class<T>) clazz;
return castedClass;
}
private boolean shouldNotDelegate(String className) {
if (classesToNotDelegate.contains(className) || className.contains("tangosol") ) {
return true;
}
return false;
}
Issue 1, part one I cannot reproduce (see below). As for part 2:
the hierarchy of class-loaders is to prevent the "X cannot be cast to X" exceptions.
But if you break the parent-first rule, you can get into trouble.
About issue 2: setting a thread's context classloader does not do anything in itself, see also this article (javaworld.com)
for some more background. Also, in relation to issue 1, part 2, a quote from the article
that describes what can happen if there is no parent-child relation between the current classloader
and the thread's context classloader:
Remember that the classloader that loads and defines a class is part of the internal JVM's ID for that class.
If the current classloader loads a class X that subsequently executes, say, a JNDI lookup for some data of type Y,
the context loader could load and define Y.
This Y definition will differ from the one by the same name but seen by the current loader.
Enter obscure class cast and loader constraint violation exceptions.
Below is a simple demo-program to show that a cast to an interface from another classloader can work
(note I'm using a simple Java project with classes in a bin-folder and the InvertedClassLoader from your question in the same (test) package):
import java.io.File;
public class ChildFirstClassLoading {
public static void main(String[] args) {
InvertedClassLoader cl = null;
try {
File classesDir = new File(new File("./bin").getCanonicalPath());
System.out.println("Classes dir: " + classesDir);
cl = new InvertedClassLoader(classesDir.toURI().toURL());
cl.selfLoad(CTest.class);
System.out.println("InvertedClassLoader configured.");
new CTest("Test 1").test();
ITest t2 = cl.loadClass(CTest.class)
.getConstructor(String.class)
.newInstance("Test 2");
t2.test();
} catch (Exception e) {
e.printStackTrace();
} finally {
if (cl != null) {
try { cl.close(); } catch (Exception ignored) {}
}
}
}
public interface ITest {
void test();
}
public static class CTest implements ITest {
static {
System.out.println("CTest initialized.");
}
private String s;
public CTest(String s) {
this.s = s;
}
public void test() {
System.out.println(s);
}
}
}
If you change ITest t2 = to CTest t2 = you will get the "CTest cannot be cast to CTest" exception,
but using the interface prevents that exception.
Since this little demo works fine, I'm guessing there is more going on in your application which somehow breaks the class-loading.
I suggest you work from a situation where the class-loading works and keep adding code until it breaks the class-loading.
The InvertedClassLoader looks a lot like the "child first classloader", see this question
for some good answers discussing this manner of class-loading.
The child first classloader can be used to load "related classes" (from your third issue) separately.
You could also update the InvertedClassLoader to always "self-load" classes in certain packages.
And remember that "once a class is loaded by a classloader it uses that classloader to load every other class it needs"
(quote from this blog article).

Overriding the classloader to get every loaded class bytes and name

Yes , so what im trying to do is getting the class bytes of every loaded class loaded by the jvm during run time . Instrumentation wont work for this case because the program im trying to load has encrypted his classes files and load it with its own class loader.
Here's my attempt : https://gist.github.com/MalikDz/944cae9c168fa05fbd0a
here the output (error) : https://gist.github.com/MalikDz/fdf20df16b951d41cb78
Thanks a lot !
You can use a Java Agent to do this trick:
The Agent is very straightforward: It registers a class transformer, which can get access to the byte-code:
import java.lang.instrument.Instrumentation;
import java.lang.instrument.ClassFileTransformer;
public class ClassDumpAgent
{
/**
* This method is called before the application’s main-method is called, when
* this agent is specified to the Java VM.
**/
public static void premain(String agentArgs, Instrumentation inst)
{
ClassFileTransformer trans = new ClassDumpTransformer();
inst.addTransformer(trans);
}
}
The ClassFileTransformer that is used simply dumps the byte-array with byte-code to the file system:
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.lang.instrument.ClassFileTransformer;
import java.lang.instrument.IllegalClassFormatException;
import java.security.ProtectionDomain;
public class ClassDumpTransformer implements ClassFileTransformer
{
private File rootFolder = new File("C:\\temp\\dump");
public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined,
ProtectionDomain protectionDomain, byte[] classfileBuffer)
throws IllegalClassFormatException
{
File dumpFile = new File(rootFolder,className+".class");
dumpFile.getParentFile().mkdirs();
try {
FileOutputStream out = new FileOutputStream(dumpFile);
try {
out.write(classfileBuffer);
} finally {
out.close();
}
} catch (IOException e) {
throw new IllegalClassFormatException(e.getMessage());
}
return classfileBuffer;
}
}
To package this class dumping agent, you need to JAR the two classes and include a MANIFEST.MF for this agent:
Manifest-Version: 1.0
PreMain-Class: ClassDumpAgent
To run the application with this agent, use a command-line like this:
java -javaagent:cldumpagent.jar MyApplication
Some remarks about the solution:
The classes are dumped in a hardcoded folder (C:\TEMP\DUMP), you
might want to change this.
The transformer will dump all classes, including the JDK Runtime. You might want to filter which packages are dumped.
Be careful with reverse-engineering, in some countries this might be considered illegal.
Barry

How to call a java shared script library from a Java agent in Xpages?

I have an agent that is set to run every day at 8:00
I want to write a java code (in a shared library) and call that library from the agent with parameters.
For Example:
Agent code:
// ....
checkAndSendMail(email_1);
checkAndSendMail(email_2);
// ....
java library code:
public class Check{
public void checkAndSendMail(String email_param){
// ...
mail.send(email_param);
// ...
}
}
Can I call a java shared script library from a Java agent in Xpages?
if yes, then how to call?
The JVM in XPages and Domino Java Agents is separate so you can't share java code between them.
You can create java code if you go to script libraries section in the designer
not the Java/Jar section that is for XPages. And create a new Java Library that can be included inside a Java agent.
You can do this, but this is only possible with a lot of "overhead". Assuming you want to load a Java class in an Agent you could do the following:
Get the design note containing your class (f.e. with a special design view or the Java NAPI)
Export the note with DXL
Extract the content all "$ClassData" fields
Base64 decode the content
Skip the first 42 bytes , and load the resulting byte array with your own class loader (override the findClass method which does a defineClass call)
Now you can instantiate the class in your agent and access it via reflection
As you can see, it is possible, but for a higher effort than just "doubling" the libraries in the DDE.
EDIT:
Here is an example class loader for an agent. The Base64 encoded DXL is already added.
The agent instantiates the class ch.hasselba.demo.LoadedClass and calls the method printTime():
package ch.hasselba.demo;
public class LoadedClass {
public void printTime(){
System.out.println("Time: " + System.currentTimeMillis() );
}
}
The code of the agent (uses lwpd.commons.jar)
import lotus.domino.AgentBase;
import com.ibm.commons.util.io.base64.Base64;
import java.lang.reflect.Method;
public class JavaAgent extends AgentBase {
public void NotesMain() {
try {
// trucated DXL string
String dataDXL = "YQAYAAAAAACqAgAAAQAAAAAAAAAAAAAAYAC8AgAAqgKqAgAAAAAAAAAAyv66vgAAADEALwcAAgEAFWNoL2hhc3NlbGJhL3hwYWdlcy9aWgcA";
// base64 decode the string
String b64 = Base64.decode(dataDXL);
byte[] b64Bytes = b64.getBytes();
byte[] classBytes = new byte[b64Bytes.length - 42];
// skip the first 42 bytes
System.arraycopy( b64Bytes, 42, classBytes, 0, b64Bytes.length - 42);
try {
// load the class
ByteClassLoader obj = new ByteClassLoader();
Class theClass = obj.findClass("ch.hasselba.demo.LoadedClass", classBytes);
// instantiate it
Object theInstance = theClass.newInstance();
// get the method printTime via Reflection & call it
Method theMethod = theInstance.getClass().getMethod("printTime", null);
theMethod.invoke( theInstance, null);
} catch (Exception e) {
e.printStackTrace();
}
} catch (Exception e) {
e.printStackTrace();
}
}
// the class loader
public static class ByteClassLoader extends ClassLoader {
public Class findClass(String name, byte[] data) {
return defineClass(name, data, 0, data.length);
}
}
}
Mike, Fredrik is right - no sharing. Unless...
you package your shared code into a Jar and deploy that one to the jvm/lib/ext directory of your server and/or client. Your admin will not like that potentially. There was a patched version of the updatesite.ntf on OpenNTF that allowed to deploy plug-ins into the server OS. You could hack the script to deploy a jar into the ext directory. But please only with admin's consent.
:-) stw

Categories