Should a script run by jjs.exe be able to locate services with ServiceLoader just as any Java program could?
I have reduced my case to the following script:
function dump (stream)
{
(new BufferedReader(new InputStreamReader(stream))).lines().forEach(function (x) { print(x); });
}
var BufferedReader = Java.type("java.io.BufferedReader");
var InputStreamReader = Java.type("java.io.InputStreamReader");
var ServiceLoader = Java.type("java.util.ServiceLoader");
var Sts = Java.type("prodist.sts.Sts");
print(Sts);
// A
var stsConfigStream = Sts.class.getResourceAsStream("/META-INF/services/prodist.sts.Sts");
dump(stsConfigStream);
// B
var StsImpl = Java.type("prodist.sts.internal.StsImpl");
print(new StsImpl());
// C
var stsLoader = ServiceLoader.load(Sts.class);
var stsIterator = stsLoader.iterator();
stsIterator.next();
// D
I call jjs.exe setting up the Class-Path on the command line. My script correctly finds and prints the interface name in point A. It locates the service description resource; when I dump the content of the resource, I see the expected content in point B. I make sure the expected implementation class is available in point C.
In point D, the program throws a NoSuchElementException, which I interpret as ServiceLoader not finding any service description resource for the interface.
Is this supposed to work?
Am I missing something?
You need to set the thread context class loader. Refer to any class from your jjs classpath, get it's Class object and then get it's class loader. You then set that loader as thread context class loader. This should be done before you use service loader API:
var StsClass = Java.type("prodist.sts.Sts").class;
java.lang.Thread.currentThread().contextClassLoader = StsClass.classLoader;
Related
This is a complicated question but I will do my best to describe my problem.
I need to load 2 versions of the same JAR in a top level class (v1.jar and v2.jar) so I have access to both versions of the jar. The reason for this is because I want to test if any feature in v2.jar has regressed from v1.jar
In my top level class, I want to call methods of v1.jar and v2.jar and then validate the output from v1 against v2 output. This way I can be certain nothing got screwed up.
class Common {
// Names of the classes would be the same so not sure how I would invoke the classes from the 2 different jars?
String resultv1 = EngineV1.run("a","b","c");
String resultv2 = EngineV2.run("a","b","c");
Assert.equals(resultv1, resultv2, "Regression has been introduced...");
}
I can't import v1 and v2 jars as maven dependencies since this will create a version conflict in maven and by default maven will use the newest jar. So I thought about creating a common interface and having 2 different implementation classes of that interface. Then in the toplevel I can use class loaders to load v1 and v2 jars, etc. But this way not work since I would have to change production v1.jar to implement the common interface.
Any help or insight will be much appreciated. I'd very much like to see samples if possible. And please don't refer me to other threads
Your test class can set up a ClassLoader for each .jar file. The easiest way to do that, is to use URLClassLoader.
Example:
File jar1 = new File("/path/to/v1.jar");
File jar2 = new File("/path/to/v2.jar");
URLClassLoader v1Loader = URLClassLoader.newInstance(new URL[] { jar1.toURI().toURL() });
URLClassLoader v2Loader = URLClassLoader.newInstance(new URL[] { jar2.toURI().toURL() });
Class<?> engineClass1 = v1Loader.loadClass("org.example.Engine");
Class<?> engineClass2 = v2Loader.loadClass("org.example.Engine");
Method runMethod1 = engineClass1.getMethod("run");
Method runMethod2 = engineClass2.getMethod("run");
Object engine1 = engineClass1.newInstance();
Object engine2 = engineClass2.newInstance();
String result1 = (String) runMethod1.invoke(engine1);
String result2 = (String) runMethod2.invoke(engine2);
Note that since neither .jar file is on the classpath of the test code, the code cannot declare any variables of types from the .jar files. All access from test code must be done using reflection.
UPDATE
You might also need to change the context class loader when making the calls:
String result1, result2;
Thread thread = Thread.currentThread();
ClassLoader myLoader = thread.getContextClassLoader();
try {
thread.setContextClassLoader(v1Loader);
result1 = (String) runMethod1.invoke(engine1);
thread.setContextClassLoader(v2Loader);
result2 = (String) runMethod2.invoke(engine2);
} finally {
thread.setContextClassLoader(myLoader);
}
// Compare result1 and result2
I found this from a different Stackoverflow question where I needed to load a jar during runtime
/*
* Adds the supplied Java Archive library to java.class.path. This is benign
* if the library is already loaded.
*/
public static synchronized void loadLibrary(java.io.File jar) throws Exception {
try {
/*We are using reflection here to circumvent encapsulation; addURL is not public*/
java.net.URLClassLoader loader = (java.net.URLClassLoader)ClassLoader.getSystemClassLoader();
java.net.URL url = jar.toURI().toURL();
/*Disallow if already loaded*/
for (java.net.URL it : java.util.Arrays.asList(loader.getURLs())){
if (it.equals(url)){
return;
}
}
java.lang.reflect.Method method = java.net.URLClassLoader.class.getDeclaredMethod("addURL", new Class[]{java.net.URL.class});
method.setAccessible(true); /*promote the method to public access*/
method.invoke(loader, new Object[]{url});
} catch (final java.lang.NoSuchMethodException |
java.lang.IllegalAccessException |
java.net.MalformedURLException |
java.lang.reflect.InvocationTargetException e){
throw new Exception(e);
}
}
Works for my purposes
I have a simple annotation processor that needs to read a configuration file from the same project as the annotated classes. Example structure:
- myproject
- src
- main
- java
- my.package.SourceFile
- resources
- config.json
In the annotation processor, I try to read the file:
FileObject resource = processingEnv.getFiler().getResource(StandardLocation.SOURCE_PATH, "", "config.json");
but it throws FileNotFoundException. I also tried other paths, such as ../resources/config.json, (which throws Invalid relative name: ../resources/config.json). And I tried putting the config file in src/main/java (and even src/main/java/my/package) instead, which I don't like, but that also still throws FileNotFoundException.
It would already help if I could get filer.getResource() to tell me where it's looking. To find that out, I tried generating a file:
filer.createResource(StandardLocation.SOURCE_OUTPUT, "", "dummy");
which generated in myproject/build/classes/main/dummy. Unfortunately, I can't generate in SOURCE_PATH, so that doesn't help with finding this out.
I'd expect that the stuff from src/main/resources gets copied to target/classes during the build (prior to annotation processing). In that case you can open them like this:
ProcessingEnvironment pe = ...;
FileObject fileObject = pe.getFiler()
.getResource( StandardLocation.CLASS_OUTPUT, "", "config.json" );
InputStream jsonStream = fileObject.openInputStream();
I've looked at this with one of the Project Lombok developers. If anyone knows annotation processing, it's them ;)
Our conclusion was, that the JavacFileManager that handles the request internally, does not have a path to resolve StandardLocation.SOURCE_PATH to. We're not sure, but it might be related to building with Gradle.
I had the same problem and was searching for solution for a while and found this cool hack that does the trick for Android
And below you can see my solution from pure Java/Kotlin project
fun ProcessingEnvironment.getResourcesDirectory(): File {
val dummySourceFile = filer.createSourceFile("dummy" + System.currentTimeMillis())
var dummySourceFilePath = dummySourceFile.toUri().toString()
if (dummySourceFilePath.startsWith("file:")) {
if (!dummySourceFilePath.startsWith("file://")) {
dummySourceFilePath = "file://" + dummySourceFilePath.substring("file:".length)
}
} else {
dummySourceFilePath = "file://$dummySourceFilePath"
}
val cleanURI = URI(dummySourceFilePath)
val dummyFile = File(cleanURI)
val projectRoot = dummyFile.parentFile.parentFile.parentFile.parentFile.parentFile
return File(projectRoot.absolutePath + "/resources")
}
Following function works for me with annotation processor being triggered by gradle, it's not the pretties one but works:
private fun resolveApplicationPropertiesFile(): File {
val projectRoot = Path.of(processingEnv.filer.getResource(StandardLocation.CLASS_OUTPUT, "", "doesntmatter")
.toUri())
.parent
.parent
.parent
.parent
.parent
.parent
val properties = Path.of(projectRoot.toString(), "src", "main", "resources", "application.yml")
return properties.toFile()
}
where processingEnv is a member of AbstractProcessor
If your element is instance of TypeElement,then you can use these code to find your source code
FileObject fileObject = processingEnv.getFiler().getResource(
StandardLocation.SOURCE_PATH, element.getEnclosingElement().toString(),
element.getSimpleName() + ".java");
element.getEnclosingElement() is your class package, eg: com.fool
element.getSimpleName() is your class name, eg: Person
then you can print them:
CharSequence content = fileObject.getCharContent(true);
I create a groovy engine with the GroovyShell class.
Then I run a bunch of statements with the "evaluate" method.
Is there a way to catch the output of the engine so I can get "println" calls output?
Currently it goes to stdout although it is a swing application.
You can just assign your custom Writer (eg. StringWriter) to out property in the binding and pass it to the GroovyShell.
def binding = new Binding();
binding.setProperty("out", new YourWriter())
new GroovyShell(binding);
You can set a scriptBaseClass with a println method and you are free to operate on top of the value. Remember the user still can do System.out.println, but you can blacklist it, if needed.
import org.codehaus.groovy.control.CompilerConfiguration
def script = """
a = 10
println a
println "echo"
"""
abstract class Printer extends Script {
void println(obj) {
this.binding.printed << obj
}
}
def config = new CompilerConfiguration(scriptBaseClass: Printer.class.name)
def binding = new Binding([printed: []])
new GroovyShell(this.class.classLoader, binding, config).evaluate script
assert binding.variables.printed.contains( 10 )
assert binding.variables.printed.contains( "echo" )
I'm going to develop a Firefox extension which uses some Java classes.
The extension gets the value of <input type="file"> fields, using Javascript.
The Java class I'm going to create is the following:
public class Firefox {
public static String inFileName;
public static void main(String[] args) throws IOException {
inFileName = "";
try {
inFileName = args[0];
} catch (Exception ex) {}
}
On Javascript, I have to use Java reflection in order to access Java classes.
In this manner I can create my Java object:
var fileInput = e.explicitOriginalTarget; // is an object HTMLInputElement, I get this when a file is selected
strArray = java.lang.reflect.Array.newInstance(java.lang.Class.forName("java.net.URL"),3);
classLoader = java.net.URLClassLoader.newInstance(strArray);
parameters = java.lang.reflect.Array.newInstance(java.lang.Class.forName("java.lang.String"),1);
parameters[0]= fileInput.value;
var aClass = java.lang.Class.forName("Firefox", true, classLoader);
var aStaticMethod = aClass.getMethod("main", [parameters.getClass()]); //gets the main(String[] args) method, here I get the exception*
var myJava = aStaticMethod.invoke(null, [parameters]); //invokes the main method
I've been trying this extension on Firefox-3.5b4 and everything goes fine, but when I try it on Firefox-3.0.10 I get the following exception*:
`InternalError: Unable to convert JavaScript value class [Ljava.lang.String; to Java value of type java.lang.Class[]`
For example, putting the following line before the main method invokation:
alert(parameters + " - " + parameters[0]);
on both Firefox-3.0.10 and 3.5b4 I get an alert window which says:
`[Ljava.lang.String;#194ca6c - /root/demo.pdf` //demo.pdf is the selected file
But only on 3.0.10 I get the exception, ONLY on my GNU/Linux machine. On Windows XP instead I have no problems on both Firefox versions!
Note that on both Windows and Linux the Java plugin version is 6 update 13.
Where am I wrong? Is it a possible bug on Firefox-3.0.10 Javascript engine or must I do any other thing before getting the main(...) method?
assuming your complete class name is "your.package.Firefox" you could do:
importPackage("your.package");
args = java.lang.reflect.Array.newInstance(java.lang.String.TYPE, 1);
Firefox.main(args)
you are incorrectly invoiking the method using;
[parameters.getClass()]
which is passing an argument of type java.lang.Class[] into the signature that is expecting a String object. simply pass the parameters object in as it is.
Does anyone know how to programmaticly find out where the java classloader actually loads the class from?
I often work on large projects where the classpath gets very long and manual searching is not really an option. I recently had a problem where the classloader was loading an incorrect version of a class because it was on the classpath in two different places.
So how can I get the classloader to tell me where on disk the actual class file is coming from?
Edit: What about if the classloader actually fails to load the class due to a version mismatch (or something else), is there anyway we could find out what file its trying to read before it reads it?
Here's an example:
package foo;
public class Test
{
public static void main(String[] args)
{
ClassLoader loader = Test.class.getClassLoader();
System.out.println(loader.getResource("foo/Test.class"));
}
}
This printed out:
file:/C:/Users/Jon/Test/foo/Test.class
Another way to find out where a class is loaded from (without manipulating the source) is to start the Java VM with the option: -verbose:class
getClass().getProtectionDomain().getCodeSource().getLocation();
This is what we use:
public static String getClassResource(Class<?> klass) {
return klass.getClassLoader().getResource(
klass.getName().replace('.', '/') + ".class").toString();
}
This will work depending on the ClassLoader implementation:
getClass().getProtectionDomain().getCodeSource().getLocation()
Jon's version fails when the object's ClassLoader is registered as null which seems to imply that it was loaded by the Boot ClassLoader.
This method deals with that issue:
public static String whereFrom(Object o) {
if ( o == null ) {
return null;
}
Class<?> c = o.getClass();
ClassLoader loader = c.getClassLoader();
if ( loader == null ) {
// Try the bootstrap classloader - obtained from the ultimate parent of the System Class Loader.
loader = ClassLoader.getSystemClassLoader();
while ( loader != null && loader.getParent() != null ) {
loader = loader.getParent();
}
}
if (loader != null) {
String name = c.getCanonicalName();
URL resource = loader.getResource(name.replace(".", "/") + ".class");
if ( resource != null ) {
return resource.toString();
}
}
return "Unknown";
}
Edit just 1st line: Main.class
Class<?> c = Main.class;
String path = c.getResource(c.getSimpleName() + ".class").getPath().replace(c.getSimpleName() + ".class", "");
System.out.println(path);
Output:
/C:/Users/Test/bin/
Maybe bad style but works fine!
Typically, we don't what to use hardcoding. We can get className first, and then use ClassLoader to get the class URL.
String className = MyClass.class.getName().replace(".", "/")+".class";
URL classUrl = MyClass.class.getClassLoader().getResource(className);
String fullPath = classUrl==null ? null : classUrl.getPath();
Take a look at this similar question.
Tool to discover same class..
I think the most relevant obstacle is if you have a custom classloader ( loading from a db or ldap )
Simple way:
System.out.println(java.lang.String.class.getResource(String.class.getSimpleName()+".class"));
Out Example:
jar:file:/D:/Java/jdk1.8/jre/lib/rt.jar!/java/lang/String.class
Or
String obj = "simple test";
System.out.println(obj.getClass().getResource(obj.getClass().getSimpleName()+".class"));
Out Example:
jar:file:/D:/Java/jdk1.8/jre/lib/rt.jar!/java/lang/String.class
This approach works for both files and jars:
Class clazz = Class.forName(nameOfClassYouWant);
URL resourceUrl = clazz.getResource("/" + clazz.getCanonicalName().replace(".", "/") + ".class");
InputStream classStream = resourceUrl.openStream(); // load the bytecode, if you wish
for unit test
If you need the location of a class called Coverage.java located in package com.acme.api
Class clazz = Coverage.class;
ClassLoader loader = clazz.getClassLoader();
String coverageClazzCompiledAbsoluteTargetLocation = loader
.getResource(clazz.getCanonicalName().replace(".", File.separator) + ".class")
.getFile().replace(File.separatorChar + "$", "");
System.out.println(coverageClazzCompiledAbsoluteTargetLocation);
Result
/home/Joe/repositories/acme-api/target/test-classes/com/acme/api/Coverage.class
Note: Since java needs compilation, you cannot obtain the .java location (src/main/java/...). You need to compile .java into .class, so the location if you are using maven, will be inside of target folder
at runtime: JAR
If you build your app as jar, the location of class will be the jar location plus clazz package
at runtime: WAR
If you build your app as war, the location of class will be the war location (/bin folder in tomcat) plus clazz package
Assuming that you're working with a class named MyClass, the following should work:
MyClass.class.getClassLoader();
Whether or not you can get the on-disk location of the .class file is dependent on the classloader itself. For example, if you're using something like BCEL, a certain class may not even have an on-disk representation.