transforming class has no effect - java

Based on this tutorial I try to get a java agent to work.
https://www.baeldung.com/java-instrumentation#loading-a-java-agent
I do get [Agent] Transforming class TestApplication
I have no errors, but I can't see any effect of transforming the class.
Eventually I would like to get both static load and dynamic load to work, but for now I focus on the static way.
public class Static_Agent {
public static void premain(String agentArgs, Instrumentation inst) {
String[] tokens = agentArgs.split(";");
String className = tokens[0];
String methodName = tokens[1];
System.out.println(">> "+className);
System.out.println(">> "+methodName);
transformClass(className, methodName, inst);
}
public static void transformClass(String className, String methodName, Instrumentation instrumentation) {
Class<?> targetCls = null;
ClassLoader targetClassLoader = null;
// see if we can get the class using forName
try {
targetCls = Class.forName(className);
targetClassLoader = targetCls.getClassLoader();
transform(targetCls, methodName, targetClassLoader, instrumentation);
return;
} catch (Exception ex) {
ex.printStackTrace();
}
// otherwise iterate all loaded classes and find what we want
for(Class<?> clazz: instrumentation.getAllLoadedClasses()) {
if(clazz.getName().equals(className)) {
targetCls = clazz;
targetClassLoader = targetCls.getClassLoader();
transform(targetCls, methodName, targetClassLoader, instrumentation);
return;
}
}
throw new RuntimeException("Failed to find class [" + className + "]");
}
public static void transform(Class<?> clazz, String methodName, ClassLoader classLoader, Instrumentation instrumentation) {
Transformer dt = new Transformer(clazz.getName(), methodName, classLoader);
instrumentation.addTransformer(dt, true);
try {
instrumentation.retransformClasses(clazz);
} catch (Exception ex) {
throw new RuntimeException("Transform failed for class: [" + clazz.getName() + "]", ex);
}
}
}
public class Transformer implements ClassFileTransformer {
/** The internal form class name of the class to transform */
private String targetClassName;
/** The class loader of the class we want to transform */
private ClassLoader targetClassLoader;
private String targetMethodName;
public Transformer(String targetClassName, String targetMethodName, ClassLoader targetClassLoader) {
this.targetClassName = targetClassName;
this.targetClassLoader = targetClassLoader;
this.targetMethodName = targetMethodName;
}
#Override
public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined,
ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException {
byte[] byteCode = classfileBuffer;
String finalTargetClassName = this.targetClassName.replaceAll("\\.", "/");
if (!className.equals(finalTargetClassName)) {
return byteCode;
}
if (className.equals(finalTargetClassName) && loader.equals(targetClassLoader)) {
System.out.println("[Agent] Transforming class TestApplication");
try {
ClassPool cp = ClassPool.getDefault();
CtClass cc = cp.get(targetClassName);
CtMethod m = cc.getDeclaredMethod(targetMethodName);
m.addLocalVariable("startTime", CtClass.longType);
m.insertBefore("startTime = System.currentTimeMillis();");
StringBuilder endBlock = new StringBuilder();
m.addLocalVariable("endTime", CtClass.longType);
m.addLocalVariable("opTime", CtClass.longType);
endBlock.append("endTime = System.currentTimeMillis();");
endBlock.append("opTime = (endTime-startTime)/1000;");
endBlock.append("System.out.println(\"[Application] Withdrawal operation completed in:\" + opTime + \" seconds!\");");
m.insertAfter(endBlock.toString());
byteCode = cc.toBytecode();
cc.detach();
} catch (Exception e) {
System.out.println("Exception"+e);
}
}
return byteCode;
}
}
public class TestApplication {
public static void main(String[] args) {
try {
TestApplication.run();
} catch (Exception e) {
e.printStackTrace();
}
}
public static void run() throws Exception {
System.out.println("--- start ---");
while (true) {
test();
Thread.sleep(4_000);
}
}
static int count = 0;
public static void test() {
System.out.println(count++);
}
}
I launch with:
java -javaagent:static_agent.jar="doeke.application.TestApplication;test" -jar application.jar
In case it helps, the project is here:
https://github.com/clankill3r/java_agent
Edit:
In the Transformer.java near the end of the file I use e.printStackTrace(); now.
I get the following error:
[Agent] Transforming class TestApplication
javassist.NotFoundException: doeke.application.TestApplication at
javassist.ClassPool.get(ClassPool.java:436) at
doeke.transformer.Transformer.transform(Transformer.java:48) at
java.instrument/java.lang.instrument.ClassFileTransformer.transform(ClassFileTransformer.java:246)
at
java.instrument/sun.instrument.TransformerManager.transform(TransformerManager.java:188)
at
java.instrument/sun.instrument.InstrumentationImpl.transform(InstrumentationImpl.java:563)
at
java.instrument/sun.instrument.InstrumentationImpl.retransformClasses0(Native
Method) at
java.instrument/sun.instrument.InstrumentationImpl.retransformClasses(InstrumentationImpl.java:167)
at doeke.static_agent.Static_Agent.transform(Static_Agent.java:56)
at
doeke.static_agent.Static_Agent.transformClass(Static_Agent.java:34)
at doeke.static_agent.Static_Agent.premain(Static_Agent.java:22) at
java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native
Method) at
java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at
java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.base/java.lang.reflect.Method.invoke(Method.java:566) at
java.instrument/sun.instrument.InstrumentationImpl.loadClassAndStartAgent(InstrumentationImpl.java:513)
at
java.instrument/sun.instrument.InstrumentationImpl.loadClassAndCallPremain(InstrumentationImpl.java:525)
--- start ---
0
1

Thanks for raising this question to let me have chance to take a look of Java Instrumentation.
After spending some time to cross check your sample codes and the provided tutorial. The problem is not from the programming codes, but the way how to launch your program.
If you add some loggers to the transform() method in Transformer.java, you will find that the code path is broken after running:
ClassPool cp = ClassPool.getDefault();
And, after replacing the exception catching code in the same method from:
} catch (Exception e) {
to:
} catch (NotFoundException | CannotCompileException | IOException e) {
It would give your more hints as below:
Exception in thread "main" java.lang.reflect.InvocationTargetException
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(Unknown Source)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)
at java.lang.reflect.Method.invoke(Unknown Source)
at sun.instrument.InstrumentationImpl.loadClassAndStartAgent(Unknown Source)
at sun.instrument.InstrumentationImpl.loadClassAndCallPremain(Unknown Source)
Caused by: java.lang.NoClassDefFoundError: javassist/NotFoundException
at doeke.static_agent.Static_Agent.transform(Static_Agent.java:60)
at doeke.static_agent.Static_Agent.transformClass(Static_Agent.java:40)
at doeke.static_agent.Static_Agent.premain(Static_Agent.java:28)
... 6 more
Caused by: java.lang.ClassNotFoundException: javassist.NotFoundException
at java.net.URLClassLoader.findClass(Unknown Source)
at java.lang.ClassLoader.loadClass(Unknown Source)
at sun.misc.Launcher$AppClassLoader.loadClass(Unknown Source)
at java.lang.ClassLoader.loadClass(Unknown Source)
... 9 more
FATAL ERROR in native method: processing of -javaagent failed
Up to this point, the root cause is more apparent. It is because while launching the program, those javassist relevant classes (e.g. ClassPool, CtClass, CtMethod, etc.) cannot refer to its corresponding libraries during the runtime.
So, the solution is:
assuming you have exported the static_agent.jar in the same "build" folder as of application.jar
all other folder structure remain the same as shown in your provided github
let's "cd" to the build folder in the command console
revising the original program launching script as below
Windows OS:
java -javaagent:static_agent.jar="doeke.application.TestApplication;test" -cp ../libs/javassist-3.12.1.GA.jar;application.jar doeke.application.TestApplication
Unix/Linux OS:
java -javaagent:static_agent.jar="doeke.application.TestApplication;test" -cp ../libs/javassist-3.12.1.GA.jar:application.jar doeke.application.TestApplication
You would finally get your expected result:
[Agent] In premain method.
>> doeke.application.TestApplication
>> test
[Agent] Transforming class
--- start ---
0
[Application] Withdrawal operation completed in:0 seconds!
1
[Application] Withdrawal operation completed in:0 seconds!
EDIT
In addition, let me paste some codes regarding how to insert codes in the middle of a method through javassist.
In case the test() method in TestApplication.java is changed as:
line 30 public static void test() {
line 31 System.out.println(count++);
line 32
line 33 System.out.println("Last line of test() method");
line 34 }
Assume that we want to add a line between the count and the =========, let's say "This is line separator", which the result would look like:
1
-- This is line separator --
Last line of test() method
Then, in the transform(...) method of Transformer.java, you could add a code line as of below:
m.insertAt(32,"System.out.println(\"-- This is line separator --\");");
which makes it becomes:
#Override
public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined,
ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException {
byte[] byteCode = classfileBuffer;
String finalTargetClassName = this.targetClassName.replaceAll("\\.", "/");
if (!className.equals(finalTargetClassName)) {
return byteCode;
}
if (className.equals(finalTargetClassName) && loader.equals(targetClassLoader)) {
System.out.println("[Agent] Transforming class TestApplication");
try {
// Step 1 Preparation
ClassPool cp = ClassPool.getDefault();
CtClass cc = cp.get(targetClassName);
CtMethod m = cc.getDeclaredMethod(targetMethodName);
// Step 2 Declare variables
m.addLocalVariable("startTime", CtClass.longType);
m.addLocalVariable("endTime", CtClass.longType);
m.addLocalVariable("opTime", CtClass.longType);
// Step 3 Insertion of extra logics/implementation
m.insertBefore("startTime = System.currentTimeMillis();");
m.insertAt(32,"System.out.println(\"-- This is line separator --\");");
StringBuilder endBlock = new StringBuilder();
endBlock.append("endTime = System.currentTimeMillis();");
endBlock.append("opTime = (endTime-startTime)/1000;");
endBlock.append("System.out.println(\"[Application] Withdrawal operation completed in:\" + opTime + \" seconds!\");");
m.insertAfter(endBlock.toString());
// Step 4 Detach from ClassPool and clean up stuff
byteCode = cc.toBytecode();
cc.detach();
} catch (NotFoundException | CannotCompileException | IOException e) {
e.printStackTrace();
} catch (Exception e) {
e.printStackTrace();
}
}
return byteCode;
}
Finally, would get result like below of printing the code in the middle of a method:
[Agent] In premain method.
className=doeke.application.TestApplication
methodName=test
>> doeke.application.TestApplication
>> test
[Agent] Transforming class TestApplication
--- start ---
0
-- This is line separator --
=========
[Application] Withdrawal operation completed in:0 seconds!
1
-- This is line separator --
=========
[Application] Withdrawal operation completed in:0 seconds!
2
-- This is line separator --
=========
[Application] Withdrawal operation completed in:0 seconds!

Related

How to make SlingHttpServletRequest.getParts() return the proper value in JUnit Test?

I am facing some difficulties in writing junit test to pass the for loop condition to getParts() method from SlingHttpServletRequest.getParts(). There is no problem with the implementation, I am able to process the file attachment properly. However, I am unable to do so in the junit test.
The following is my implementation:
#Model(adaptables = SlingHttpServletRequest.class)
public class Comment {
//Variables declaration
#Inject
private CommentService service;
#PostConstruct
public void setup() {
requestData = new JSONObject();
for (String item : request.getRequestParameterMap().keySet()) {
try {
requestData.put(item, request.getParameter(item));
}
} catch (Exception e) {
Throw error message
}
}
//Upload attachment to server
try {
for (Part part : request.getParts()) { <= The JUnit test stopped at this line and throw the error below
} catch (Exception e) {
Throw error message
}
I have tried using a SlingHttpServletRequestWrapper class to override the getParts method but to no avail.
The following is my junit test:
public class CommentTest {
public final AemContext context = new AemContext();
private CommentService commentService = mock(CommentService.class);
#InjectMocks
private Comment comment;
private static String PATH = "/content/testproject/en/page/sub-page";
#Before
public void setUp() throws Exception {
context.addModelsForPackage("de.com.adsl.sightly.model");
context.load().json("/components/textrte.json", PATH);
context.currentPage(PATH);
}
#Test
public void testSetup() throws IOException, ServletException {
//before
context.request().setParameterMap(getRequestCat1());
context.registerService(CommentService.class, commentService);
Resource resource = context.resourceResolver().getResource(PATH + "/jcr:content/root/responsivegrid/textrte");
assertNotNull(resource);
//when
comment = new CustomRequest(context.request()).adaptTo(Comment.class);
//then
comment.setup();
}
private class CustomRequest extends SlingHttpServletRequestWrapper {
public CustomRequest(SlingHttpServletRequest request) {
super(request);
}
#Override
public Collection<Part> getParts() {
final String mockContent =
"------WebKitFormBoundarycTqA2AimXQHBAJbZ\n" +
"Content-Disposition: form-data; name=\"key\"\n" +
"\n" +
"myvalue1\n" +
"------WebKitFormBoundarycTqA2AimXQHBAJbZ";
final List<Part> parts = MockPart.parseAll(mockContent);
assertNotNull(parts);
return parts;
}
};
}
The following is the error message that I encountered:
14:53:04.918 [main] ERROR de.com.adsl.sightly.model.Comment - Error Message: null
java.lang.UnsupportedOperationException: null
at org.apache.sling.servlethelpers.MockSlingHttpServletRequest.getParts(MockSlingHttpServletRequest.java:882) ~[org.apache.sling.servlet-helpers-1.1.10.jar:?]
at de.com.adsl.sightly.model.Comment.uploadFile(Feedback.java:137) ~[classes/:?]
at de.com.adsl.sightly.model.Comment.setup(Feedback.java:82) [classes/:?]
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[?:1.8.0_201]
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) ~[?:1.8.0_201]
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[?:1.8.0_201]
at java.lang.reflect.Method.invoke(Method.java:498) ~[?:1.8.0_201]
at org.apache.sling.models.impl.ModelAdapterFactory.invokePostConstruct(ModelAdapterFactory.java:792) [org.apache.sling.models.impl-1.3.8.jar:?]
at org.apache.sling.models.impl.ModelAdapterFactory.createObject(ModelAdapterFactory.java:607) [org.apache.sling.models.impl-1.3.8.jar:?]
at org.apache.sling.models.impl.ModelAdapterFactory.internalCreateModel(ModelAdapterFactory.java:335) [org.apache.sling.models.impl-1.3.8.jar:?]
at org.apache.sling.models.impl.ModelAdapterFactory.getAdapter(ModelAdapterFactory.java:211) [org.apache.sling.models.impl-1.3.8.jar:?]
...
I have looked up various solutions online such as writing two mockito when statements but has not been successful. I would greatly appreciate any form of help or sharing of knowledge if you have encountered the following issue previously. Thank you!
From the source code of MockSlingServletResquest it always throws that exception as it's not supported yet by the mocked class.
https://github.com/apache/sling-org-apache-sling-servlet-helpers/blob/71ef769e5564cf78e49d6679a3270ba8706ae406/src/main/java/org/apache/sling/servlethelpers/MockSlingHttpServletRequest.java#L953
Maybe you should consider writing a servlet, or another approach.

Error caused by java.lang.NoClassDefFoundError and ClassNotFoundException

I am trying to implement a simple code to read the data from the ultrasonic and send it to a server by using Californium. The problem is that when I use debug, it doesn't have any error. However when I export the code to a runable jar file and run it on my raspberry pi, it throws the following errors:
Exception in thread "main" java.lang.reflect.InvocationTargetException
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:497)
at org.eclipse.jdt.internal.jarinjarloader.JarRsrcLoader.main(JarRsrcLoader.java:58)
Caused by: java.lang.NoClassDefFoundError: org/eclipse/californium/elements/util/NamedThreadFactory
at org.eclipse.californium.core.CoapServer.<init>(CoapServer.java:163)
at org.eclipse.californium.core.CoapServer.<init>(CoapServer.java:120)
at iot.main.device.DeviceClient.main(DeviceClient.java:34)
... 5 more
Caused by: java.lang.ClassNotFoundException: org.eclipse.californium.elements.util.NamedThreadFactory
at java.net.URLClassLoader.findClass(URLClassLoader.java:381)
at java.lang.ClassLoader.loadClass(ClassLoader.java:424)
at java.lang.ClassLoader.loadClass(ClassLoader.java:357)
... 8 more
I am not sure what caused this and how to solve it, any suggestion would be much helpful. Below is the code:
package iot.main.device;
import static org.eclipse.californium.core.coap.CoAP.ResponseCode.BAD_REQUEST;
import static org.eclipse.californium.core.coap.CoAP.ResponseCode.CHANGED;
import org.eclipse.californium.core.CoapClient;
import org.eclipse.californium.core.CoapResource;
import org.eclipse.californium.core.CoapResponse;
import org.eclipse.californium.core.CoapServer;
import org.eclipse.californium.core.coap.MediaTypeRegistry;
import org.eclipse.californium.core.server.resources.CoapExchange;
import com.pi4j.io.gpio.*;
public class DeviceClient {
private static GpioPinDigitalOutput sensorTriggerPin ;
private static GpioPinDigitalInput sensorEchoPin ;
final static GpioController gpio = GpioFactory.getInstance();
public static void main(String[] args) {
double ultraVal;
sensorTriggerPin = gpio.provisionDigitalOutputPin(RaspiPin.GPIO_04); // Trigger pin as OUTPUT
sensorEchoPin = gpio.provisionDigitalInputPin(RaspiPin.GPIO_05,PinPullResistance.PULL_DOWN); // Echo pin as INPUT
CoapClient client = new CoapClient("coap://localhost/Ultrasonic");
CoapServer server = new CoapServer();
server.add(new UltraResource());
server.start();
while(true){
try {
Thread.sleep(2000);
sensorTriggerPin.high(); // Make trigger pin HIGH
Thread.sleep((long) 0.00001);// Delay for 10 microseconds
sensorTriggerPin.low(); //Make trigger pin LOW
while(sensorEchoPin.isLow()){ //Wait until the ECHO pin gets HIGH
}
long startTime= System.nanoTime(); // Store the surrent time to calculate ECHO pin HIGH time.
while(sensorEchoPin.isHigh()){ //Wait until the ECHO pin gets LOW
}
long endTime= System.nanoTime(); // Store the echo pin HIGH end time to calculate ECHO pin HIGH time.
ultraVal = ((((endTime - startTime)/1e3)/2) / 29.1);
System.out.println("Distance : " + ultraVal + " cm"); //Printing out the distance in cm
Thread.sleep(1000);
CoapResponse response = client.put(Double.toString(ultraVal), MediaTypeRegistry.TEXT_PLAIN);
if (response!=null) {
System.out.println( response.getCode() );
System.out.println( response.getOptions() );
System.out.println( response.getResponseText() );
} else {
System.out.println("Request failed");
}
}
catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public static class UltraResource extends CoapResource {
public String value = "Resource has not changed yet !!!";
public UltraResource() {
super("Ultrasonic");
setObservable(true);
}
#Override
public void handleGET(CoapExchange exchange) {
exchange.respond(value);
}
#Override
public void handlePUT(CoapExchange exchange) {
String payload = exchange.getRequestText();
System.out.println(payload + " cm");
try {
exchange.respond(CHANGED, payload);
value = new String(payload);
changed();
} catch (Exception e) {
e.printStackTrace();
exchange.respond(BAD_REQUEST, "Invalid String");
}
}
}
}
The error states that
Caused by: java.lang.ClassNotFoundException: org.eclipse.californium.elements.util.NamedThreadFactory
This means you are writing code that uses a dependency from Californium. That dependency is within your dev. environment but it is not within the runtime environment of your Raspberry Pi.
Have a look at this other post which may help you. Also this SO site may be better.

MissingResourceException when starting 1.4.2_12 application with webstart 1.6

After I could eventually figure out why JWS 1.6.0_29 failed to launch a 1.4.2_12 application (see this question), I faced another exception when launching a 1.4.2_12 app. with JWS 1.6.0_29.
I get a MissingResourceException when loading a ResourceBundle. Yet a message.properties file do exists in the same package as the class that's loading it.
When JWS 1.4 or 1.5 is used to launch the application, the exception is not raised.
The exception is raised only when launching the app. with JWS 1.6.
Full stackstrace is :
java.lang.ExceptionInInitializerError
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(Unknown Source)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)
at java.lang.reflect.Method.invoke(Unknown Source)
at com.sun.javaws.Launcher.executeApplication(Unknown Source)
at com.sun.javaws.Launcher.executeMainClass(Unknown Source)
at com.sun.javaws.Launcher.doLaunchApp(Unknown Source)
at com.sun.javaws.Launcher.run(Unknown Source)
at java.lang.Thread.run(Unknown Source)
Caused by: java.util.MissingResourceException: Can't find bundle for base name com.test.hello.messages, locale fr_FR
at java.util.ResourceBundle.throwMissingResourceException(Unknown Source)
at java.util.ResourceBundle.getBundleImpl(Unknown Source)
at java.util.ResourceBundle.getBundle(Unknown Source)
at com.test.hello.Main.<clinit>(Main.java:10)
... 9 more
Test case to reproduce
JNLP descriptor is:
<?xml version="1.0" encoding="utf-8"?>
<jnlp spec="1.0+" codebase="http://localhost:80/meslegacy/apps" href="testJwsXXTo142.jnlp">
<information>
<title>JWS TEST 1.6 -> 1.4.2</title>
<vendor>Hello World Vendor</vendor>
<description>Hello World</description>
</information>
<security>
<all-permissions />
</security>
<resources>
<j2se version="1.4.2_12" href="http://java.sun.com/products/autodl/j2se" />
<jar href="jar/helloworld.jar" main="true" />
</resources>
<application-desc main-class="com.test.hello.Main" />
</jnlp>
com.test.hello.Main class is:
package com.test.hello;
import java.util.ResourceBundle;
import javax.swing.JFrame;
public class Main {
private static final ResourceBundle BUNDLE = ResourceBundle.getBundle(Main.class.getPackage().getName()+".messages");
public static void main(String[] args) {
JFrame frame = new JFrame("Hello world !");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setSize(800,600);
frame.setVisible(true);
}
}
Complementary tests
Specifying ClassLoader and Locale to the ResourceBundle.getBundle()
method does not fix the problem.
Main.class.getClassLaoder() and
Thread.currentThread().getContextClassLaoder() have been tested and spawn the same exception.
Loading resource "by hand" does work (see below).
Test code to load resource manually :
ClassLoader cl = Main.class.getClassLoader();
String resourcePath = baseName.replaceAll("\\.", "/");
System.out.println(resourcePath);
URL resourceUrl = cl.getResource(resourcePath+".properties");
System.out.println("Resource manually loaded :"+resourceUrl);
Will produce :
com/test/hello/messages.properties
Resource manually loaded :jar:http://localhost:80/meslegacy/apps/jar/helloworld.jar!/com%2ftest%2fhello%2fmessages.properties
However, while it is possible to find the resource, get the resource content is not.
Example:
ClassLoader cl = Main.class.getClassLoader();
String resourcePath = baseName.replaceAll("\\.", "/") + ".properties";
URL resourceUrl = cl.getResource(resourcePath);
// here, resourceUrl is not null. Then build bundle by hand
ResourceBundle prb = new PropertyResourceBundle(resourceUrl.openStream());
Which spawns :
java.io.FileNotFoundException: JAR entry com%2ftest%2fhello%2fmessages.properties not found in C:\Documents and Settings\firstname.lastname\Application Data\Sun\Java\Deployment\cache\6.0\18\3bfe5d92-3dfda9ef
at com.sun.jnlp.JNLPCachedJarURLConnection.connect(Unknown Source)
at com.sun.jnlp.JNLPCachedJarURLConnection.getInputStream(Unknown Source)
at java.net.URL.openStream(Unknown Source)
at com.test.hello.Main.main(Main.java:77)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(Unknown Source)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)
at java.lang.reflect.Method.invoke(Unknown Source)
at com.sun.javaws.Launcher.executeApplication(Unknown Source)
at com.sun.javaws.Launcher.executeMainClass(Unknown Source)
at com.sun.javaws.Launcher.doLaunchApp(Unknown Source)
at com.sun.javaws.Launcher.run(Unknown Source)
at java.lang.Thread.run(Unknown Source)
Seems to be more a kind of cache issue...
I any of you had a hint, it would be greatly appreciated,
Thanks for reading.
Here is the explanation and workarround for this problem.
1 - Explanation
The problem comes from the URLs returned by the system ClassCloader (JWS6 system ClassLoader).
With JWS 1.6, URL returned by the system ClassLoader contain escape sequences such as the one shown in the following :
jar:http://localhost:80/meslegacy/apps/jar/helloworld.jar!/com%2ftest%2fhello%2fmessages.properties
Locating resources in classpath is possible but when it comes to actually access the content of that resource a FileNotFoundException is raised: This is what causes the FileNotFoundException in ResourceBundle.
Please note that when no escape sequence appears in the URL, for example when the resource is at the root of the claspath, there is no problem to access the resource content. Problem appears only when you get %xx stuff in the URL path part.
2 - Workarround
Once the problem had been focused (it took me days to figure this out !), it was time to find a workarround for this.
While it would have been possible for me to fix my problem on specific localized code parts, it quickly turned out that is was possible to fix the issue globaly by coding a specific ClassLoader to "replace" the JNLPClassLoader.
I don't acutally "replace" because it seems impossible to me but I rather do the following :
Disable SecurityManager to be abled to play with my custom
classloader
Code my own classloader derived from URLClassLoader that fix URL when they are returned
Set its classpath with the claspath extracted from the
JNLPClassLoader
Set this custom classloader to be the context classloader
Set this custom classloader to be the AWT-Event-Thread context
classloader
Use this custom classloader to load my application entry point.
This gives the following ClassLoader
public class JwsUrlFixerClassLoader extends URLClassLoader {
private final static Logger LOG = Logger.getLogger(JwsUrlFixerClassLoader.class);
private static String SIMPLE_CLASS_NAME = null;
private static boolean LOG_ENABLED = "true".equals(System.getProperty("classloader.debug"));
static {
SIMPLE_CLASS_NAME = JwsUrlFixerClassLoader.class.getName();
int idx = SIMPLE_CLASS_NAME.lastIndexOf('.');
if (idx >= 0 && idx < SIMPLE_CLASS_NAME.length()-1) {
SIMPLE_CLASS_NAME = SIMPLE_CLASS_NAME.substring(idx + 1);
}
}
public JwsUrlFixerClassLoader(URL[] urls, ClassLoader parent) {
super(urls, parent);
}
public URL getResource(String name) {
if (LOG.isDebugEnabled()) {
LOG.debug("getResource(): getResource(" + name + ")");
}
if (LOG_ENABLED) {
login("getResource(" + name + ")");
}
URL out = super.getResource(name);
if (out != null) {
out = URLFixerTool.fixUrl(out);
}
if (LOG_ENABLED) {
logout("getResource returning " + out);
}
return out;
}
public URL findResource(String name) {
if (LOG_ENABLED) {
login("findResource(" + name + ")");
}
URL out = super.findResource(name);
if (out != null) {
out = URLFixerTool.fixUrl(out);
}
if (LOG_ENABLED) {
logout("findResource returning " + out);
}
return out;
}
public InputStream getResourceAsStream(String name) {
if (LOG_ENABLED) {
login("getResourceAsStream(" + name + ")");
}
InputStream out = super.getResourceAsStream(name);
if (LOG_ENABLED) {
logout("getResourceAsStream returning " + out);
}
return out;
}
protected synchronized Class loadClass(String name, boolean resolve) throws ClassNotFoundException {
if (LOG_ENABLED) {
login("loadClass(" + name + ")");
}
// First, check if the class has already been loaded
Class c = findLoadedClass(name);
if (c == null) {
try {
c = findClass(name);
} catch (ClassNotFoundException cnfe) {
if (getParent() == null) {
// c = findBootstrapClass0(name);
Method m = null;
try {
m = URLClassLoader.class.getMethod("findBootstrapClass0", new Class[] {});
m.setAccessible(true);
c = (Class) m.invoke(this, new Object[] { name });
} catch (Exception e) {
throw new ClassNotFoundException();
}
} else {
c = getParent().loadClass(name);
}
}
}
if (resolve) {
resolveClass(c);
}
if (LOG_ENABLED) {
logout("loadClass returning " + c);
}
return c;
}
private static void login(String message) {
System.out.println("---> [" + Thread.currentThread().getName() + "] " + SIMPLE_CLASS_NAME + ": " + message);
}
private static void logout(String message) {
System.out.println("<--- [" + Thread.currentThread().getName() + "] " + SIMPLE_CLASS_NAME + ": " + message);
}
}
Now in a AppBoostrap class which I set to be the main-class in the JNLP descriptor, I do the following :
System.setSecurityManager(null);
ClassLoader parentCL = AppBootstrap.class.getClassLoader();
URL[] classpath = new URL[] {};
if (parentCL instanceof URLClassLoader) {
URLClassLoader ucl = (URLClassLoader) parentCL;
classpath = ucl.getURLs();
}
final JwsUrlFixerClassLoader vlrCL = new JwsUrlFixerClassLoader(classpath, parentCL);
Thread.currentThread().setContextClassLoader(vlrCL);
try {
SwingUtilities.invokeAndWait(new Runnable() {
public void run() {
Thread.currentThread().setContextClassLoader(vlrCL);
}
});
} catch (Exception e) {
LOG.error("main(): Failed to set context classloader !", e);
}
In the previous excerpt I get the ClassLoader that loaded my AppBootstrap class and use it as the parent classloader of my JwsUrlFixerClassLoader.
I had to fix the problem of the default parent delegation strategy of the URLClassLodaer.loadClass() and replace it with the "try my classpath first then parent".
After that has been done everything went right and a couple of other bugs that we so far couldn't explain have disapeared.
That's magic ! After a lot of pain though...
Hope this can help someone one day...

Finding an Enumeration Class using Reflection in Java

I think I need some help with finding an enumeration class within another class using reflection in Java. I have been battling with this for far too long now. I have read this as well as a number of other posts and they all make me believe it should work as below.
public class ModelActivity {
public enum AttributeEnumeration { MODELID, MODELURGENCY, MODELDUEDATEANDTIME }
public static void main(String[] args) {
// Find the class with the given name
String className = "ModelActivity";
Class modelClass = null;
try {
// Retrieve the Class with the given className...
modelClass = Class.forName(className);
} catch (ClassNotFoundException e) {
throw new RuntimeException("Class by name '" + className + "' not found.", e);
}
// Find the AttributeEnumeration within the class
String attributeEnumerationClassName = className + ".AttributeEnumeration";
Class attributeEnumerationClass = null;
try {
attributeEnumerationClass = Class.forName(attributeEnumerationClassName);
} catch (ClassNotFoundException e) {
throw new RuntimeException("Class by name '" + attributeEnumerationClassName + "' not found.", e);
}
}
}
However, what actually happens is that the modelClass is found correctly, but the attributeEnumerationClass is not, that is, I get the second ClassNotFoundException as follows:
Exception in thread "main" java.lang.RuntimeException: Class by name 'ModelActivity.AttributeEnumeration' not found.
at ModelActivity.main(ModelActivity.java:27)
Caused by: java.lang.ClassNotFoundException: ModelActivity.AttributeEnumeration
at java.net.URLClassLoader$1.run(URLClassLoader.java:202)
at java.security.AccessController.doPrivileged(Native Method)
at java.net.URLClassLoader.findClass(URLClassLoader.java:190)
at java.lang.ClassLoader.loadClass(ClassLoader.java:307)
at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:301)
at java.lang.ClassLoader.loadClass(ClassLoader.java:248)
at java.lang.Class.forName0(Native Method)
at java.lang.Class.forName(Class.java:169)
at ModelActivity.main(ModelActivity.java:25)
Could anyone please point the--probably obvious--mistake out to me. Thank you.
See for yourself:
package foo.bar;
public class Outer{
public enum Inner{}
public static void main(final String[] args){
System.out.println(Inner.class.getName());
}
}
Output:
foo.bar.Outer$Inner
Inner class names are delimited with $, not with a period, so you want ModelActivity$AttributeEnumeration.
BTW:
The $ syntax is valid for class loading only. Use periods to access instances of the class in source as follows:
import foo.bar.Outer.Inner;
// ...
private Inner myEnumValue;
or like this:
private foo.bar.Outer.Inner myEnumValue;
Or, to put it this way:
assertEquals( // two ways to reference the same class
foo.bar.Outer.Inner.class,
Class.forName("foo.bar.Outer$Inner")
);

On the fly class loading with jars

I've got a ClassLoader extending class with following method
#Override
public Class<?> findClass(String className) throws ClassNotFoundException {
try {
/**
* Get a bytecode from file
*/
byte b[] = fetchClassFromFS(pathtobin + File.separator
+ className.replaceAll("\\.", escapeSeparator(File.separator)) + ".class");
return defineClass(className, b, 0, b.length);
} catch (FileNotFoundException ex) {
return super.findClass(className);
} catch (IOException ex) {
return super.findClass(className);
}
}
That as u can see uses defineClass() method from its parent - ClassLoader. The issue is when i'm trying to execute a class' (i recieve with my ClassLoader extension - let it be ru.xmppTesting.test.Disco) method getMethods() while getting an instance of this class i get the following
Exception in thread "main" java.lang.NoClassDefFoundError: org/apache/http/Header
at java.lang.Class.getDeclaredMethods0(Native Method)
at java.lang.Class.privateGetDeclaredMethods(Unknown Source)
at java.lang.Class.privateGetPublicMethods(Unknown Source)
at java.lang.Class.getMethods(Unknown Source)
at DOTGraphCreator.createGraphFromClasses(DOTGraphCreator.java:85)
at DOTGraphCreator.generateDotGraphFile(DOTGraphCreator.java:56)
at DOTGraphCreator.main(DOTGraphCreator.java:46)
Caused by: java.lang.ClassNotFoundException: org.apache.http.Header
at java.lang.ClassLoader.findClass(Unknown Source)
at SourceClassLoader.findClass(SourceClassLoader.java:27)
at java.lang.ClassLoader.loadClass(Unknown Source)
at java.lang.ClassLoader.loadClass(Unknown Source)
... 7 more
As far as i can see that is because class org.apache.http.Header could not be found as defined. Because it is not.
So here's a question:
how can and must i define and link this Header class (and lots of others from my .jar libs) along with definition of ru.xmppTesting.test.Disco and others similar to have them defined on the fly?
If your are importing org.apache.http.Header from your dinamic loaded class, you need it to be accesible at your classpath.
If you don't want to load all the potentially needed jars on your classpath, you could try with a hack i have found here:
import java.lang.reflect.*;
import java.io.*;
import java.net.*;
public class ClassPathHacker {
private static final Class[] parameters = new Class[]{URL.class};
public static void addFile(String s) throws IOException {
File f = new File(s);
addFile(f);
}//end method
public static void addFile(File f) throws IOException {
addURL(f.toURL());
}//end method
public static void addURL(URL u) throws IOException {
URLClassLoader sysloader = (URLClassLoader)ClassLoader.getSystemClassLoader();
Class sysclass = URLClassLoader.class;
try {
Method method = sysclass.getDeclaredMethod("addURL",parameters);
method.setAccessible(true);
method.invoke(sysloader,new Object[]{ u });
} catch (Throwable t) {
t.printStackTrace();
throw new IOException("Error, could not add URL to system classloader");
}//end try catch
}//end method
}//end class
But, I must say, it could not be portable to some JVMs (not always the SystemClassLoader is a subclass of URLClassLoader)...
*EDIT: * In fact, as you have replaced the classloader with your own, perhaps you have some troubles...

Categories

Resources