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...
Related
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!
I have a Maven plugin I am writing where I need to know the type of the List<Person> where Person is an object defined within a dependency. This plugin runs during theprocess-classes phase to generate some files from what it finds on annotated classes within the host project. In this case the host project contains references to libraries within other projects which are included as Maven dependencies.
The Person class:
package com.dependency.models;
public class Person {
// Irrelevent
}
The use within the class I am performing the query on:
package com.project.wrappers;
import com.dependency.Person;
#MyAnnotation
public class Wrapper {
List<Person> people;
// Other irrelevant stuff
}
The annotation:
#Documented
#Retention(RetentionPolicy.RUNTIME)
#Target(value={ElementType.TYPE})
public #interface MyAnnotation {}
From within a Maven plugin use context I load all the Wrapper objects via annotation and then process. To do this I need to use a custom classloader via the Reflections library:
public static Set<Class<?>> findAnnotatedClasses(MavenProject mavenProject, Class<? extends Annotation> input) throws MojoExecutionException {
List<String> classpathElements = null;
try {
classpathElements = mavenProject.getCompileClasspathElements();
List<URL> projectClasspathList = new ArrayList<URL>();
for (String element : classpathElements) {
Application.getLogger().debug("Considering compile classpath element (via MavenProject): " + element);
try {
projectClasspathList.add(new File(element).toURI().toURL());
} catch (MalformedURLException e) {
throw new MojoExecutionException(element + " is an invalid classpath element", e);
}
}
// Retain annotations
JavassistAdapter javassistAdapter = new JavassistAdapter();
javassistAdapter.includeInvisibleTag = false;
URLClassLoader urlClassLoader = new URLClassLoader(projectClasspathList.toArray(new URL[]{}),
Thread.currentThread().getContextClassLoader());
Reflections reflections = new Reflections(
new ConfigurationBuilder().setUrls(
ClasspathHelper.forClassLoader(urlClassLoader)
).addClassLoader(urlClassLoader).setScanners(new TypeAnnotationsScanner(), new TypeElementsScanner(),
new FieldAnnotationsScanner(), new TypeAnnotationsScanner(), new SubTypesScanner(false)
).setMetadataAdapter(javassistAdapter)
);
return findAnnotatedClasses(reflections, input);
} catch (DependencyResolutionRequiredException e) {
throw new MojoExecutionException("Dependency resolution failed", e);
}
}
Everything up to here works great. But if I try the following, within my parser, it fails:
Field field = Wrapper.class.getDeclaredField("people");
ParameterizedType listType = (ParameterizedType) field.getGenericType();
Class<?> listTypeClass = (Class<?>) listType.getActualTypeArguments()[0];
The following exception is thrown:
Caused by: java.lang.TypeNotPresentException: Type com.dependency.models.Person not present
at sun.reflect.generics.factory.CoreReflectionFactory.makeNamedType(CoreReflectionFactory.java:117)
at sun.reflect.generics.visitor.Reifier.visitClassTypeSignature(Reifier.java:125)
at sun.reflect.generics.tree.ClassTypeSignature.accept(ClassTypeSignature.java:49)
at sun.reflect.generics.visitor.Reifier.reifyTypeArguments(Reifier.java:68)
at sun.reflect.generics.visitor.Reifier.visitClassTypeSignature(Reifier.java:138)
at sun.reflect.generics.tree.ClassTypeSignature.accept(ClassTypeSignature.java:49)
at sun.reflect.generics.repository.FieldRepository.getGenericType(FieldRepository.java:85)
at java.lang.reflect.Field.getGenericType(Field.java:247)
Which if one follows further:
Caused by: java.lang.ClassNotFoundException: com.dependency.models.Person
at java.net.URLClassLoader.findClass(URLClassLoader.java:381)
at java.lang.ClassLoader.loadClass(ClassLoader.java:424)
at java.lang.ClassLoader.loadClass(ClassLoader.java:357)
at java.lang.Class.forName0(Native Method)
at java.lang.Class.forName(Class.java:348)
at sun.reflect.generics.factory.CoreReflectionFactory.makeNamedType(CoreReflectionFactory.java:114)
Which means that my classloading is working for everything prior to this last step. I think I need a way to override the classloader used within the sun.reflect library but it may not be possible. Any recommendations on a better approach?
UPDATE
I have added the dependency jars to a URLClassLoader and am attempting to override the current classloader which is being used by the above CoreReflectionFactory to no avail.
The solution was not to just add the dependencies to the classloader but to actually resolve the jar paths and then add the path of those jars to the classpath for the reflections library to include it in their scanning.
So, get all the artifacts from Maven and then resolve and add each path to Reflections when building that piece. Something like this:
public static URL[] buildProjectClasspathList(ArtifactReference artifactReference) throws ArtifactResolutionException, MalformedURLException, DependencyResolutionRequiredException {
List<URL> projectClasspathList = new ArrayList<URL>();
// Load build class path
if(classpathElementsCache == null) {
Application.getLogger().debug("Compile Classpath Elements Cache was null; fetching update");
classpathElementsCache = artifactReference.getMavenProject().getCompileClasspathElements();
}
for (String element : classpathElementsCache) {
Application.getLogger().debug("Looking at compile classpath element (via MavenProject): " + element);
Application.getLogger().debug(" Adding: " + element);
projectClasspathList.add(new File(element).toURI().toURL());
}
// Load artifact(s) jars using resolver
if(dependencyArtifactsCache == null) {
Application.getLogger().debug("Dependency Artifacts Cache was null; fetching update");
dependencyArtifactsCache = artifactReference.getMavenProject().getDependencyArtifacts();
}
Application.getLogger().debug("Number of artifacts to resolve: "
+ dependencyArtifactsCache.size());
for (Artifact unresolvedArtifact : dependencyArtifactsCache) {
String artifactId = unresolvedArtifact.getArtifactId();
if (!isArtifactResolutionRequired(unresolvedArtifact, artifactReference)) {
Application.getLogger().debug(" Skipping: " + unresolvedArtifact.toString());
continue;
}
org.eclipse.aether.artifact.Artifact aetherArtifact = new DefaultArtifact(
unresolvedArtifact.getGroupId(),
unresolvedArtifact.getArtifactId(),
unresolvedArtifact.getClassifier(),
unresolvedArtifact.getType(),
unresolvedArtifact.getVersion());
ArtifactRequest artifactRequest = new ArtifactRequest()
.setRepositories(artifactReference.getRepositories())
.setArtifact(aetherArtifact);
// This takes time; minimizing what needs to be resolved is the goal of the specified dependency code block
ArtifactResult resolutionResult = artifactReference.getRepoSystem()
.resolveArtifact(artifactReference.getRepoSession(), artifactRequest);
// The file should exist, but we never know.
File file = resolutionResult.getArtifact().getFile();
if (file == null || !file.exists()) {
Application.getLogger().warn("Artifact " + artifactId +
" has no attached file. Its content will not be copied in the target model directory.");
continue;
}
String jarPath = "jar:file:" + file.getAbsolutePath() + "!/";
Application.getLogger().debug("Adding resolved artifact: " + file.getAbsolutePath());
projectClasspathList.add(new URL(jarPath));
}
return projectClasspathList.toArray(new URL[]{});
}
Where ArtifactReference includes the MavenProject and Repo objects:
public class ArtifactReference {
private MavenProject mavenProject;
private RepositorySystem repoSystem;
private RepositorySystemSession repoSession;
private List<RemoteRepository> repositories;
private List<String> specifiedDependencies;
}
I am trying to call "getOrderList" from the ST312_TestMain class. I am getting the java.lang.ExceptionInInitializerError for the below mentioned class.
package com.Main;
import org.w3c.dom.Document;
import com.yantra.ycp.core.YCPContext;
import com.Main.XMLUtil;
import com.Main.SterlingUtil;
public class ST312_TestMain {
public static void main(String[] args) throws Exception {
String ServiceName = "getOrderList";
String sServiceFlag = "N";
Document dTemplate = null;
//ServiceName = "SendDN";
//sServiceFlag = "Y";
Document inputXML=null;
inputXML = XMLUtil.getDocument("<Order OrderHeaderKey='201407181105267340509' />");
//inputXML = XMLUtil.getXmlFromFile("src/Test.xml");
dTemplate = XMLUtil.getDocument("<Order OrderHeaderKey='' OrderNo=''/>");
if (args.length == 3) {
ServiceName = args[0];
sServiceFlag = args[1].equals("Y") ? "Y" : "N";
inputXML = XMLUtil.getXmlFromFile(args[2]);
} else {
System.out
.println("Usage: TestMain <API/Service Name> <API/Service(N/Y)> <Input XML File>");
System.out
.println("No Input received using preset XML to call preset Service");
System.out.println("Service Name=" + ServiceName);
}
***YCPContext env = new YCPContext("admin", "admin");***
System.out.println("Input XML \n" + XMLUtil.getXmlString(inputXML));
try {
Document outputXML = null;
if ("Y".equals(sServiceFlag)) {
outputXML = SterlingUtil.callService(env, inputXML, ServiceName, null);
} else {
outputXML = SterlingUtil.callAPI(env, inputXML, ServiceName, dTemplate);
}
env.commit();
} catch (Exception ex) {
ex.printStackTrace();
System.out.println("Service Invocation Failed");
}
}
}
The exception is as follows:
Usage: TestMain <API/Service Name> <API/Service(N/Y)> <Input XML File>
No Input received using preset XML to call preset Service
Service Name=getOrderList
log4j:WARN No appenders could be found for logger (com.yantra.ycp.core.YCPContext).
log4j:WARN Please initialize the log4j system properly.
Exception in thread "main" java.lang.ExceptionInInitializerError
at com.sterlingcommerce.woodstock.util.frame.Manager.getProperty(Manager.java:1365)
at com.yantra.yfc.util.YFCConfigurator.setStandalone(YFCConfigurator.java:37)
at com.yantra.yfs.core.YFSSystem.init(YFSSystem.java:62)
at com.yantra.yfs.core.YFSSystem.<clinit>(YFSSystem.java:47)
at com.yantra.ycp.core.YCPContext.<init>(YCPContext.java:288)
at com.yantra.ycp.core.YCPContext.<init>(YCPContext.java:276)
at com.Main.ST312_TestMain.main(ST312_TestMain.java:31)
Caused by: java.lang.NullPointerException
at com.sterlingcommerce.woodstock.util.frame.log.base.SCILogBaseConfig.doConfigure(SCILogBaseConfig.java:35)
at com.sterlingcommerce.woodstock.util.frame.log.LogService.<clinit>(LogService.java:110)
... 7 more
Please help me on this problem, since I am not sure how to handle the YCPContext object. ("YCPContext env = new YCPContext("admin", "admin");"). Thanks in advance.
Request support from IBM about this.
No matter what mistakes you may have made configuring it (if any), the Sterling code should not be throwing an NPE at you.
If you are testing in local using main() method, then you should comment all the lines where you are using environment varilable. For your code its env variable.
Probably you wouldn't have added the database driver jars to your project build path... For e.g.
For DB2 database, db2jcc.jar has to be added
For Oracle database, ojdbc.jar has to be added
I exported my plugin project as a Product and when I run the product (eclipse application), the main plugin (org.example.framework.core) cannot find a class defined in another plugin (org.example.abc) which implements an extension to an extension point provided by the main plugin. The class is one of the elements defined in the extension. However, when I run the project in Eclipse, everything runs fine!
Here is the log (atvste.ppt.ptfwDescription.abc.decoderInfo is a package in org.example.abc plugin):
0 [Worker-2] ERROR org.example.framework.core.ptfw.codec.decode.MsgDecoderInfo org.example.framework.core.ptfw.codec.decode.MsgDecoderInfo.createInstance(MsgDecoderInfo.java:114) : can not create class for :atvste.ppt.ptfwDescription.abc.decoderInfo.MsgDecoderInfoABC atvste.ppt.ptfwDescription.abc.decoderInfo.MsgDecoderInfoABC cannot be found by org.example.framework.core_1.0.0.201404111439
java.lang.ClassNotFoundException: atvste.ppt.ptfwDescription.abc.decoderInfo.MsgDecoderInfoABC cannot be found by org.example.framework.core_1.0.0.201404111439
at org.eclipse.osgi.internal.loader.BundleLoader.findClassInternal(BundleLoader.java:501)
at org.eclipse.osgi.internal.loader.BundleLoader.findClass(BundleLoader.java:421)
at org.eclipse.osgi.internal.loader.BundleLoader.findClass(BundleLoader.java:412)
at org.eclipse.osgi.internal.baseadaptor.DefaultClassLoader.loadClass(DefaultClassLoader.java:107)
at java.lang.ClassLoader.loadClass(Unknown Source)
at java.lang.Class.forName0(Native Method)
at java.lang.Class.forName(Unknown Source)
at org.example.framework.core.ptfw.codec.decode.MsgDecoderInfo.createInstance(MsgDecoderInfo.java:104)
at org.example.framework.core.ptfw.codec.decode.DecoderInfo.<init>(DecoderInfo.java:56)
at org.example.framework.core.ptfw.codec.decode.DecoderInfo.createInstance(DecoderInfo.java:125)
at org.example.framework.core.ptfw.codec.ptfwObjectsFactory.decoderInfoItf_createInstance(ptfwObjectsFactory.java:200)
at org.example.framework.persistence.jaxb.ptfwDescriptionPersistenceJaxb.createptfwDescription(ptfwDescriptionPersistenceJaxb.java:326)
at org.example.framework.persistence.jaxb.ptfwDescriptionPersistenceJaxb.fillptfwDescription(ptfwDescriptionPersistenceJaxb.java:247)
at org.example.framework.persistence.jaxb.ptfwDescriptionPersistenceJaxb.createInstance(ptfwDescriptionPersistenceJaxb.java:232)
at org.example.framework.persistence.jaxb.ptfwDescriptionPersistenceJaxb.open(ptfwDescriptionPersistenceJaxb.java:146)
at org.example.framework.core.ptfw.codec.ptfwDescription.createInstance(ptfwDescription.java:152)
at org.example.framework.core.ptfw.codec.command.CmdLoadptfwDescription.loadptfwDescription(CmdLoadptfwDescription.java:50)
at org.example.framework.core.ptfw.codec.command.CmdLoadptfwDescription.execute(CmdLoadptfwDescription.java:40)
at org.example.framework.core.runtime.JobService$2.run(JobService.java:93)
at org.eclipse.core.internal.jobs.Worker.run(Worker.java:53)
EDIT: Function for creating instance of the class not found
public static IMessageDecoderInfo createInstance(XmlgMsgDecoderInfo pMsgDecoderInfoType,
IMessageDecoderInfo msgDecoder)
{
String className = pMsgDecoderInfoType.getClassName();
if(className!=null)
{
try
{
Class<?> formalArgs[] = new Class[1];
formalArgs[0] = XmlgMsgDecoderInfo.class;
Class<?> clazz;
if (msgDecoder != null)
{
clazz = msgDecoder.getClass();
}
else
{
clazz = Class.forName( className );
}
Constructor<?> constructor = clazz.getConstructor(formalArgs);
java.lang.Object actualArgs[] =
{ pMsgDecoderInfoType };
return (IMessageDecoderInfo)constructor.newInstance(actualArgs);
}catch(Exception e) {
String error = "can not create class for :" +className+ " " + e.getMessage();
if (LOGGER.isEnabledFor(Level.ERROR)) {
LOGGER.error(error, e);
}
throw new CreatePtfwElementRuntimeException(error, e);
}
}
return new MsgDecoderInfo(pMsgDecoderInfoType);
}`
Because of the complex class loader system used by Eclipse you cannot use Class.forName to load a class in another plugin.
If your code is processing an extension point definition you will have the IConfigurationElement for the configuration element that specifies the class name. You can use
IConfigurationElement configElement = ....;
Object classInstance = configElement.createExecutableExtension("class attribute name");
where 'class attribute name' is the name of the attribute in the extension point element that specifies the class to load. The class being created must have a no argument constructor.
Alternatively you can load a class using:
Bundle bundle = Platform.getBundle("plugin id");
Class<?> theClass = bundle.loadClass("class name");
... construct as usual ...
If you have the IConfigurationElement you can get the plugin id using
String pluginId = configElement.getContributor().getName();
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...