I have created a Java project that implements a State Machine. The idea behind it is that the SM itself runs in a background thread as a simple loop over an array of objects representing different actions.
The actions in question are from another object called an ActionPack that contains the Action objects.
This works well using Java reflection to look into the instantiated ActionPack at run time and import them into the state machine where they are pushed onto the array. The state machine then iterates over the array when told to run and calls the execute method of each action.
As I said, this works well and all unit tests are green ... but only when I run the them from within IntelliJ. If I try running them via "mvn test", the Reflection code fails to find the classes within the action pack.
This is the failing reflection method:
/**
* Use introspection to read in all of the classes in a given action pack
* Filter out the ones that are actions and return them in an array
*/
public ArrayList getActionsFromActionPack(
DataAccessLayer dataAccessLayer,
String runRoot
) {
String packageName = this.getClass().getPackage().getName();
System.out.println("PACKAGE NAME = " + packageName);
List<ClassLoader> classLoadersList = new LinkedList<>();
classLoadersList.add(ClasspathHelper.contextClassLoader());
classLoadersList.add(ClasspathHelper.staticClassLoader());
Reflections reflections = new Reflections(new ConfigurationBuilder()
.setScanners(new SubTypesScanner(false /* don't exclude Object.class */), new ResourcesScanner())
.setUrls(ClasspathHelper.forClassLoader(classLoadersList.toArray(new ClassLoader[0])))
.filterInputsBy(new FilterBuilder().include(FilterBuilder.prefix(packageName))));
Set<Class<? extends Action>> classes = reflections.getSubTypesOf(Action.class);
System.out.println("Number of classes = " + classes.size());
ArrayList<IAction> actions = new ArrayList<>();
for (Class a : classes) {
try {
Action action = (Action) Class.forName(a.getName()).newInstance();
// Give the action access to the DAL and path to control directory
action.setDataAccessLayer(dataAccessLayer);
action.setRunRoot(runRoot);
actions.add(action);
} catch (ClassNotFoundException e) {
e.printStackTrace();
System.err.println(e.getClass().getName() + ": " + e.getMessage());
System.exit(1);
} catch (IllegalAccessException e) {
e.printStackTrace();
System.err.println(e.getClass().getName() + ": " + e.getMessage());
System.exit(1);
} catch (InstantiationException e) {
e.printStackTrace();
System.err.println(e.getClass().getName() + ": " + e.getMessage());
System.exit(1);
}
}
return actions;
}
With those debug print statements in there, when I run the Unit Tests from within IntelliJ I see this output:
PACKAGE NAME = com.github.museadmin.infinite_state_machine.core.action_pack
Number of classes = 7
PACKAGE NAME = com.github.museadmin.infinite_state_machine.core.action_pack
Number of classes = 7
PACKAGE NAME = com.github.museadmin.infinite_state_machine.core.action_pack
Number of classes = 7
PACKAGE NAME = com.github.museadmin.infinite_state_machine.test.classes
Number of classes = 1
PACKAGE NAME = com.github.museadmin.infinite_state_machine.core.action_pack
Number of classes = 7
PACKAGE NAME = com.github.museadmin.infinite_state_machine.core.action_pack
Number of classes = 7
Process finished with exit code 0
But when I run via mvn test:
PACKAGE NAME = com.github.museadmin.infinite_state_machine.core.action_pack
Number of classes = 0
Exception in thread "UnitTestThread" java.lang.IndexOutOfBoundsException: Index: 0, Size: 0
at java.util.ArrayList.rangeCheck(ArrayList.java:653)
at java.util.ArrayList.get(ArrayList.java:429)
at com.github.museadmin.infinite_state_machine.core.InfiniteStateMachine.run(InfiniteStateMachine.java:36)
at java.lang.Thread.run(Thread.java:745)
So it looks like the reflective method correctly identifies the package that we're in, but then fails to find the classes within it if called via maven.
The failure occurs in the final line of setup() of the unit test when I call importActionPack
#Before
public void setup() {
ClassLoader loader = Thread.currentThread().getContextClassLoader();
URL is = loader.getResource(PROPERTIES);
ismCoreActionPack = new ISMCoreActionPack();
infiniteStateMachine = new InfiniteStateMachine(is.getPath());
infiniteStateMachine.importActionPack(ismCoreActionPack);
}
This code is spread across three projects:
infinite-state-machine (The state machine itself)
infinite-state-machine-common (Where the action pack and action types are defined)
infinite-state-machine-core (The action pack I'm failing to import)
The Action Pack has an interface in infinite-state-machine-common:
package com.github.museadmin.infinite_state_machine.common.action;
import com.github.museadmin.infinite_state_machine.common.dal.DataAccessLayer;
import org.json.JSONObject;
import java.util.ArrayList;
public interface IActionPack {
JSONObject getJsonObjectFromResourceFile(String fileName);
ArrayList getActionsFromActionPack(
DataAccessLayer dataAccessLayer,
String runRoot
);
}
The actual implementation of the action pack is also in infinite-state-machine-common:
public class ActionPack implements IActionPack
And that is where the reflections method public ArrayList getActionsFromActionPack() is found.
The Action class and its interface are also in infinite-state-machine-common:
package com.github.museadmin.infinite_state_machine.common.action;
import com.github.museadmin.infinite_state_machine.common.dal.DataAccessLayer;
import org.json.JSONObject;
import java.util.ArrayList;
public interface IAction {
void execute();
Boolean active();
boolean active(String action);
void activate(String actionName);
Boolean afterActionsComplete();
Boolean beforeActionsComplete();
void clearPayload(String actionName);
String createRunDirectory(String directory);
void deactivate();
void deactivate(String actionFlag);
JSONObject getJsonObjectFromFile(String fileName);
ArrayList<JSONObject> getUnprocessedMessages();
void insertMessage(JSONObject message);
void insertProperty(String property, String value);
void markMessageProcessed(Integer id);
void setDataAccessLayer(DataAccessLayer dataAccessLayer);
void setRunRoot(String runRoot);
String queryProperty(String property);
String queryRunPhase();
void setState(String stateName);
void updatePayload(String actionName, String payload);
void updateProperty(String property, String value);
void updateRunPhase(String runPhase);
void unsetState(String stateName);
}
The Action parent class itself is too big to post here but is defined in the same package:
public abstract class Action implements IAction {
}
If it helps, this is a typical action from the core package showing the execute method:
package com.github.museadmin.infinite_state_machine.core.action_pack;
import com.github.museadmin.infinite_state_machine.common.action.Action;
/**
* Action that verifies the state of the ISM and confirms
* we have booted up correctly and can now run
*/
public class ActionConfirmReadyToRun extends Action {
/**
* Confirm we've got through the bootstrapping process ok.
* and all BEFORE actions completed
*/
public void execute() {
if (active()) {
if (beforeActionsComplete()) {
setState("READY_TO_RUN");
updateRunPhase("RUNNING");
deactivate();
}
}
}
}
SUMMARY
I don't know if this is a problem with the way I am using the Reflections library as I would expect it to fail when I ran the tests from IntelliJ...
So perhaps this is an issue with Maven?
I have built and locally installed all of the projects, so they are definitely available and built at this point.
FYI - At the moment I have put the Unit tests in their own separate project to prevent this issue from breaking the build as I push the projects to Nexus.
Any help or guidance on debugging this issue would be much appreciated.
Brad
In my java code I call another 3rd party java class.
I want to catch that latter System.exit() exit code
So I use security-manager as suggested in this post
The problem is that I cannot read files now, I get permissions error
as seen in that post.
How can I catch the exit code and still read files?
Published class MyClass {
class MySecurityManager extends SecurityManager {
#Override
public void checkExit(int status) {
throw new SecurityException();
}
}
public void foo() {
MySecurityManager secManager = new MySecurityManager();
System.setSecurityManager(secManager);
try {
ConfigValidator.main(new String[]{"-dirs", SdkServiceConfig.s.PROPERTIES_FILE_PATH});
new FileInputStream(new File("/Users/eladb/WorkspaceQa/sdk-service/src/main/resources/convert_conditions.sh"));
} catch (SecurityException e) {
//Do something if the external code used System.exit()
String a = "1";
}
catch (Exception e) {
logger.error("failed converting properties file to proto", e);
}
}
}
You have two separate problems: Your trusted code cannot read the file, while the untrusted third-party library can still call System#exit unhindered. The former can be easily circumvented by granting further privileges to the trusted code; the latter is a tad trickier to address.
A bit of background
Privilege assignment
Code (the ProtectionDomains encapsulated by a thread's AccessControlContext) generally gets assigned Permissions in two ways: Statically, by the ClassLoader, upon class definition, and/or dynamically, by the Policy in effect. Other, less frequently encountered alternatives, exist as well: DomainCombiners, for instance, can modify AccessControlContexts' domains (and therefore the effective permissions of their respective code that is subject to authorization) on the fly, and custom domain implementations may use their own logic for permission implication, possibly disregarding or altering the semantics of the policy. By default the permission set of a domain is the union of its static and dynamic permissions. As for how exactly classes are mapped to domains, it is, for the most part, up to the loader's implementation. By default, all classes, JAR'ed or otherwise, residing beneath the same class path entry, are grouped under the same domain. More restrictive class loaders may choose to e.g. allocate a domain per class, which could be used to prevent communication even between classes within the same package.
Privilege evaluation
Under the default SecurityManager, for a privileged operation (an invocation of any method having a SecurityManager#checkXXX within its body) to succeed, every domain (of every class of every method) of the effective AccessControlContext must have been assigned, as explained above, the permission being checked. Recall however that the context need not necessarily represent "the truth" (the actual call stack)—system code gets optimized away early on, while AccessController#doPrivileged calls, along with the DomainCombiner potentially coupled to the AccessControlContext can modify the context's domains, and the authorization algorithm in its entirety, consequently.
Problem and workarounds
The issue with System#exit is that the corresponding permission (RuntimePermission("exitVM.*")) is one amongst few that are statically assigned by the default application class loader (sun.misc.Launcher$AppClassLoader) to all domains associated with classes loaded from the class path.
A number of alternatives come to mind:
Installing a custom SecurityManager which denies the particular right based on, e.g., the class attempting to terminate the JVM process.
Loading the third-party library from a "remote" location (a directory outside of the class path), so that it gets treated as "untrusted" code by its class loader.
Authoring and installing a different application class loader, which does not assign the extraneous permission.
Plugging a custom domain combiner into the access control context, which replaces, at the time of an authorization decision, all third-party domains with equivalent ones that do not have the offending permission.
I should, for the sake of completeness, note that at the Policy level, unfortunately, nothing can be done to negate statically assigned permissions.
The first option is overall the most convenient one, but I will not explore it further because:
The default SecurityManager is quite flexible, thanks to the handful of components (AccessController et al.) it interacts with. The background section in the beginning aimed to serve as a reminder of that flexibility, which "quick-n'-dirty" method overrides tend to cripple.
Careless modifications of the default implementation might cause (system) code to misbehave in curious ways.
Frankly, because it's boring―it's the one-size-fits-all solution perpetually advocated, while the fact that the default manager was standardized in 1.2 for a reason has long been forgotten.
The second alternative is easy to implement but impractical, complicating either development or the build process. Assuming you are not planning to invoke the library solely reflectively, or aided by interfaces present on the class path, it would have to be present initially, during development, and relocated before execution.
The third is, at least in the context of a standalone Java SE application, fairly straightforward and should not pose too much of a burden on performance. It is the approach I will favour herein.
The last option is the most novel and least convenient. It is hard to securely implement, has the greatest potential for performance degradation, and burdens client code with ensuring presence of the combiner prior to every delegation to untrusted code.
Proposed solution
The custom ClassLoader
The following is to be used as the replacement of the default application loader, or alternatively as the context class loader, or the loader used to load at least the untrusted classes. There is nothing novel to this implementation—all it does is prevent delegation to the default application class loader when the class in question is assumed to not be a system one. URLClassLoader#findClass, in turn, does not assign RuntimePermission("exitVM.*") to the domains of the classes it defines.
package com.example.trusted;
import java.io.File;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.regex.Pattern;
public class ClasspathClassLoader extends URLClassLoader {
private static final Pattern SYSTEM_CLASS_PREFIX = Pattern.compile("((java(x)?|sun|oracle)\\.).*");
public ClasspathClassLoader(ClassLoader parent) {
super(new URL[0], parent);
String[] classpath = System.getProperty("java.class.path").split(File.pathSeparator);
for (String classpathEntry : classpath) {
try {
if (!classpathEntry.endsWith(".jar") && !classpathEntry.endsWith("/")) {
// URLClassLoader assumes paths without a trailing '/' to be JARs by default
classpathEntry += "/";
}
addURL(new URL("file:" + classpathEntry));
}
catch (MalformedURLException mue) {
System.err.println(MessageFormat.format("Erroneous class path entry [{0}] skipped.", classpathEntry));
}
}
}
#Override
protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
Class<?> ret;
synchronized (getClassLoadingLock(name)) {
ret = findLoadedClass(name);
if (ret != null) {
return ret;
}
if (SYSTEM_CLASS_PREFIX.matcher(name).matches()) {
return super.loadClass(name, resolve);
}
ret = findClass(name);
if (resolve) {
resolveClass(ret);
}
}
return ret;
}
}
If you also wish to fine-tune the domains assigned to loaded classes, you will additionally have to override findClass. The following variant of the loader is a very crude attempt at doing so. constructClassDomain therein merely creates one domain per class path entry (which is more or less the default), but can be modified to do something different.
package com.example.trusted;
import java.io.BufferedInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.lang.ref.WeakReference;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;
import java.nio.ByteBuffer;
import java.security.AccessController;
import java.security.CodeSource;
import java.security.PrivilegedAction;
import java.security.ProtectionDomain;
import java.security.cert.Certificate;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.List;
import java.util.regex.Pattern;
public final class ClasspathClassLoader extends URLClassLoader {
private static final Pattern SYSTEM_CLASS_PREFIX = Pattern.compile("((java(x)?|sun|oracle)\\.).*");
private static final List<WeakReference<ProtectionDomain>> DOMAIN_CACHE = new ArrayList<>();
// constructor, loadClass same as above
#Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
URL classOrigin = getClassResource(name);
if (classOrigin == null) {
return super.findClass(name);
}
URL classCodeSourceOrigin = getClassCodeSourceResource(classOrigin);
if (classCodeSourceOrigin == null) {
return super.findClass(name);
}
return defineClass(name, readClassData(classOrigin), constructClassDomain(classCodeSourceOrigin));
}
private URL getClassResource(String name) {
return AccessController.doPrivileged((PrivilegedAction<URL>) () -> getResource(name.replace(".", "/") + ".class"));
}
private URL getClassCodeSourceResource(URL classResource) {
for (URL classpathEntry : getURLs()) {
if (classResource.getPath().startsWith(classpathEntry.getPath())) {
return classpathEntry;
}
}
return null;
}
private ByteBuffer readClassData(URL classResource) {
try {
BufferedInputStream in = new BufferedInputStream(classResource.openStream());
ByteArrayOutputStream out = new ByteArrayOutputStream();
int i;
while ((i = in.read()) != -1) {
out.write(i);
}
return ByteBuffer.wrap(out.toByteArray());
}
catch (IOException ioe) {
throw new RuntimeException(ioe);
}
}
private ProtectionDomain constructClassDomain(URL classCodeSourceResource) {
ProtectionDomain ret = getCachedDomain(classCodeSourceResource);
if (ret == null) {
CodeSource cs = new CodeSource(classCodeSourceResource, (Certificate[]) null);
DOMAIN_CACHE.add(new WeakReference<>(ret = new ProtectionDomain(cs, getPermissions(cs), this, null)));
}
return ret;
}
private ProtectionDomain getCachedDomain(URL classCodeSourceResource) {
for (WeakReference<ProtectionDomain> domainRef : DOMAIN_CACHE) {
ProtectionDomain domain = domainRef.get();
if (domain == null) {
DOMAIN_CACHE.remove(domainRef);
}
else if (domain.getCodeSource().implies(new CodeSource(classCodeSourceResource, (Certificate[]) null))) {
return domain;
}
}
return null;
}
}
The "unsafe" code
package com.example.untrusted;
public class Test {
public static void testExitVm() {
System.out.println("May I...?!");
System.exit(-1);
}
}
The entry point
package com.example.trusted;
import java.security.AccessControlException;
import java.security.Permission;
import com.example.untrusted.Test;
public class Main {
private static final Permission EXIT_VM_PERM = new RuntimePermission("exitVM.*");
public static void main(String... args) {
System.setSecurityManager(new SecurityManager());
try {
Test.testExitVm();
}
catch (AccessControlException ace) {
Permission deniedPerm = ace.getPermission();
if (EXIT_VM_PERM.implies(deniedPerm)) {
ace.printStackTrace();
handleUnauthorizedVmExitAttempt(Integer.parseInt(deniedPerm.getName().replace("exitVM.", "")));
}
}
}
private static void handleUnauthorizedVmExitAttempt(int exitCode) {
System.out.println("here let me do it for you");
System.exit(exitCode);
}
}
Testing
Packaging
Place the loader and the main class in one JAR (let's call it trusted.jar) and the demo untrusted class in another (untrusted.jar).
Assigning privileges
The default Policy (sun.security.provider.PolicyFile) is backed by the file at <JRE>/lib/security/java.policy, as well as any of the files referenced by the policy.url.n properties in <JRE>/lib/security/java.security. Modify the former (the latter should hopefully be empty by default) as follows:
// Standard extensions get all permissions by default
grant codeBase "file:${{java.ext.dirs}}/*" {
permission java.security.AllPermission;
};
// no default permissions
grant {};
// trusted code
grant codeBase "file:///path/to/trusted.jar" {
permission java.security.AllPermission;
};
// third-party code
grant codeBase "file:///path/to/untrusted.jar" {
permission java.lang.RuntimePermission "exitVM.-1", "";
};
Note that it is virtually impossible to get components extending the security infrastructure (custom class loaders, policy providers, etc.) to work properly without granting them AllPermission.
Running
Run:
java -classpath "/path/to/trusted.jar:/path/to/untrusted.jar" -Djava.system.class.loader=com.example.trusted.ClasspathClassLoader com.example.trusted.Main
The privileged operation should succeed.
Next comment out the RuntimePermission under untrusted.jar, within the policy file, and re-run. The privileged operation should fail.
As a closing note, when debugging AccessControlExceptions, running with -Djava.security.debug=access=domain,access=failure,policy can help track down offending domains and policy configuration issues.
This question already has answers here:
How to provide an interface to JavaCompiler when compiling a source file dynamically?
(3 answers)
Closed 5 years ago.
The community reviewed whether to reopen this question 4 months ago and left it closed:
Original close reason(s) were not resolved
(This question is similar to many questions I have seen but most are not specific enough for what I am doing)
Background:
The purpose of my program is to make it easy for people who use my program to make custom "plugins" so to speak, then compile and load them into the program for use (vs having an incomplete, slow parser implemented in my program). My program allows users to input code into a predefined class extending a compiled class packaged with my program. They input the code into text panes then my program copies the code into the methods being overridden. It then saves this as a .java file (nearly) ready for the compiler. The program runs javac (java compiler) with the saved .java file as its input.
My question is, how do I get it so that the client can (using my compiled program) save this java file (which extends my InterfaceExample) anywhere on their computer, have my program compile it (without saying "cannot find symbol: InterfaceExample") then load it and call the doSomething() method?
I keep seeing Q&A's using reflection or ClassLoader and one that almost described how to compile it, but none are detailed enough for me/I do not understand them completely.
Take a look at JavaCompiler
The following is based on the example given in the JavaDocs
This will save a File in the testcompile directory (based on the package name requirements) and the compile the File to a Java class...
package inlinecompiler;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.io.Writer;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import javax.tools.Diagnostic;
import javax.tools.DiagnosticCollector;
import javax.tools.JavaCompiler;
import javax.tools.JavaFileObject;
import javax.tools.StandardJavaFileManager;
import javax.tools.ToolProvider;
public class InlineCompiler {
public static void main(String[] args) {
StringBuilder sb = new StringBuilder(64);
sb.append("package testcompile;\n");
sb.append("public class HelloWorld implements inlinecompiler.InlineCompiler.DoStuff {\n");
sb.append(" public void doStuff() {\n");
sb.append(" System.out.println(\"Hello world\");\n");
sb.append(" }\n");
sb.append("}\n");
File helloWorldJava = new File("testcompile/HelloWorld.java");
if (helloWorldJava.getParentFile().exists() || helloWorldJava.getParentFile().mkdirs()) {
try {
Writer writer = null;
try {
writer = new FileWriter(helloWorldJava);
writer.write(sb.toString());
writer.flush();
} finally {
try {
writer.close();
} catch (Exception e) {
}
}
/** Compilation Requirements *********************************************************************************************/
DiagnosticCollector<JavaFileObject> diagnostics = new DiagnosticCollector<JavaFileObject>();
JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
StandardJavaFileManager fileManager = compiler.getStandardFileManager(diagnostics, null, null);
// This sets up the class path that the compiler will use.
// I've added the .jar file that contains the DoStuff interface within in it...
List<String> optionList = new ArrayList<String>();
optionList.add("-classpath");
optionList.add(System.getProperty("java.class.path") + File.pathSeparator + "dist/InlineCompiler.jar");
Iterable<? extends JavaFileObject> compilationUnit
= fileManager.getJavaFileObjectsFromFiles(Arrays.asList(helloWorldJava));
JavaCompiler.CompilationTask task = compiler.getTask(
null,
fileManager,
diagnostics,
optionList,
null,
compilationUnit);
/********************************************************************************************* Compilation Requirements **/
if (task.call()) {
/** Load and execute *************************************************************************************************/
System.out.println("Yipe");
// Create a new custom class loader, pointing to the directory that contains the compiled
// classes, this should point to the top of the package structure!
URLClassLoader classLoader = new URLClassLoader(new URL[]{new File("./").toURI().toURL()});
// Load the class from the classloader by name....
Class<?> loadedClass = classLoader.loadClass("testcompile.HelloWorld");
// Create a new instance...
Object obj = loadedClass.newInstance();
// Santity check
if (obj instanceof DoStuff) {
// Cast to the DoStuff interface
DoStuff stuffToDo = (DoStuff)obj;
// Run it baby
stuffToDo.doStuff();
}
/************************************************************************************************* Load and execute **/
} else {
for (Diagnostic<? extends JavaFileObject> diagnostic : diagnostics.getDiagnostics()) {
System.out.format("Error on line %d in %s%n",
diagnostic.getLineNumber(),
diagnostic.getSource().toUri());
}
}
fileManager.close();
} catch (IOException | ClassNotFoundException | InstantiationException | IllegalAccessException exp) {
exp.printStackTrace();
}
}
}
public static interface DoStuff {
public void doStuff();
}
}
Now updated to include suppling a classpath for the compiler and loading and execution of the compiled class!
I suggest using the Java Runtime Compiler library. You can give it a String in memory and it will compile and load the class into the current class loader (or one of your choice) and return the Class loaded. Nested classes are also loaded. Note: this works entirely in memory by default.
e.g.
// dynamically you can call
String className = "mypackage.MyClass";
String javaCode = "package mypackage;\n" +
"public class MyClass implements Runnable {\n" +
" public void run() {\n" +
" System.out.println(\"Hello World\");\n" +
" }\n" +
"}\n";
Class aClass = CompilerUtils.CACHED_COMPILER.loadFromJava(className, javaCode);
Runnable runner = (Runnable) aClass.newInstance();
runner.run();
I'm working on a program that watches a directory and runs all tests in the directory when it sees changes in the directory.
This requires the program to dynamically load the classes, instead of getting the cached copies.
I can dynamically load the test classes. Changes to the tests get detected and used at runtime. However, this isn't the case for the classes tested by the tests.
My code for dynamically loading the classes and returning a list of test classes:
List<Class<?>> classes = new ArrayList<Class<?>>();
for (File file : classFiles) {
String fullName = file.getPath();
String name = fullName.substring(fullName.indexOf("bin")+4)
.replace('/', '.')
.replace('\\', '.');
name = name.substring(0, name.length() - 6);
tempClass = new DynamicClassLoader(Thread.currentThread().getContextClassLoader()).findClass(name) } catch (ClassNotFoundException e1) {
// TODO Decide how to handle exception
e1.printStackTrace();
}
boolean cHasTestMethods = false;
for(Method method: tempClass.getMethods()){
if(method.isAnnotationPresent(Test.class)){
cHasTestMethods = true;
break;
}
}
if (!Modifier.isAbstract(cachedClass.getModifiers()) && cHasTestMethods) {
classes.add(tempClass);
}
}
return classes;
with DynamicClassLoader being as the Reloader described here How to force Java to reload class upon instantiation?
Any idea how to fix it? I thought all classes would be dynamically loaded. Note however that I don't overwrite loadclass in my DynamicClassLoader because if I do my test classes give init
EDIT:
This doesn't work, the class gets loaded but the tests in it aren't detected...
List<Request> requests = new ArrayList<Request>();
for (File file : classFiles) {
String fullName = file.getPath();
String name = fullName.substring(fullName.indexOf("bin")+4)
.replace('/', '.')
.replace('\\', '.');
name = name.substring(0, name.length() - 6);
Class<?> cachedClass = null;
Class<?> dynamicClass = null;
try {
cachedClass = Class.forName(name);
URL[] urls={ cachedClass.getProtectionDomain().getCodeSource().getLocation() };
ClassLoader delegateParent = cachedClass .getClassLoader().getParent();
URLClassLoader cl = new URLClassLoader(urls, delegateParent) ;
dynamicClass = cl.loadClass(name);
System.out.println(dynamicClass);
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
Edit edit: i detect the test methods like this:
for(Method method: dynamicClass.getMethods()){
if(method.isAnnotationPresent(Test.class)){
requests.add(Request.method(dynamicClass, method.getName()));
}
}
If you used the custom ClassLoader exactly like in the linked answer it is not overriding the method protected Class<?> loadClass(String name, boolean resolve). This implies that when the JVM is resolving dependencies it will still delegate to the parent class loader. And, of course, when it was not delegating to the parent ClassLoader it had the risk of missing some required classes.
The easiest solution is to set up the right parent class loader. You are currently passing Thread.currentThread().getContextClassLoader() which is a bit strange as your main intention is that the delegation should not delegate to that loader but load the changed classes. You have to think about which class loaders exist and which to use and which not. E.g. if the class Foo is within the scope of your current code but you want to (re)load it with the new ClassLoader, Foo.class.getClassLoader().getParent() would be the right delegate parent for the new ClassLoader. Note that it might be null but this doesn’t matter as in this case it would use the bootstrap loader which is the correct parent then.
Note that when you set up the right parent ClassLoader matching your intentions you don’t need that custom ClassLoader anymore. The default implementation (see URLClassLoader) already does the right thing. And with current Java versions it is Closeable making it even more suitable for dynamic loading scenarios.
Here is a simple example of a class reloading:
import java.io.IOException;
import java.net.URL;
import java.net.URLClassLoader;
public class ReloadMyClass
{
public static void main(String[] args)
throws ClassNotFoundException, IOException {
Class<?> myClass=ReloadMyClass.class;
System.out.printf("my class is Class#%x%n", myClass.hashCode());
System.out.println("reloading");
URL[] urls={ myClass.getProtectionDomain().getCodeSource().getLocation() };
ClassLoader delegateParent = myClass.getClassLoader().getParent();
try(URLClassLoader cl=new URLClassLoader(urls, delegateParent)) {
Class<?> reloaded=cl.loadClass(myClass.getName());
System.out.printf("reloaded my class: Class#%x%n", reloaded.hashCode());
System.out.println("Different classes: "+(myClass!=reloaded));
}
}
}
I trying to make simple java profiler and using ClassLoader for this.
This is my implementation of ClassLoader:
import java.rmi.RemoteException;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;
public class CustomClassLoader extends ClassLoader {
private Notifier notifier;
public CustomClassLoader() {
super();
}
public CustomClassLoader(ClassLoader parent) {
super(parent);
}
private void initNotifier() {
if (notifier != null) return;
try {
System.out.println("2");
Registry registry = LocateRegistry.getRegistry(Const.registryPort);
System.out.println("3");
notifier = (Notifier) registry.lookup(Const.stubName);
System.out.println("4");
} catch (Exception e) {
e.printStackTrace();
System.exit(1);
}
}
#Override
protected synchronized Class<?> loadClass(String name, boolean resolve)
throws ClassNotFoundException {
System.out.println("0");
Class clazz = super.loadClass(name, resolve);
System.out.println("1");
initNotifier();
System.out.println("5");
try {
notifier.classLoaded(name);
System.out.println("6");
} catch (RemoteException e) {
e.printStackTrace();
System.exit(1);
}
return clazz;
}
}
When i've try to use this class loader i receive this output (I've tried to use 1.6_37 and 1.7_10 jkd):
C:\Users\Scepion1d>java -cp C:\Users\Scepion1d\Dropbox\Workspace\IntellijIDEA\pr
ofiler\out\artifacts\loader\loader.jar;C:\Users\Scepion1d\Dropbox\Workspace\Inte
llijIDEA\app\out\production\app -Djava.system.class.loader=CustomClassLoader Main
0
1
2
0
1
2
3
0
1
2
3
java.lang.IllegalArgumentException: Non-positive latency: 0
at sun.misc.GC$LatencyRequest.<init>(GC.java:190)
at sun.misc.GC$LatencyRequest.<init>(GC.java:156)
at sun.misc.GC.requestLatency(GC.java:254)
at sun.rmi.transport.DGCClient$EndpointEntry.lookup(DGCClient.java:212)
at sun.rmi.transport.DGCClient.registerRefs(DGCClient.java:120)
at sun.rmi.transport.ConnectionInputStream.registerRefs(ConnectionInputS
tream.java:80)
at sun.rmi.transport.StreamRemoteCall.releaseInputStream(StreamRemoteCal
l.java:138)
at sun.rmi.transport.StreamRemoteCall.done(StreamRemoteCall.java:292)
at sun.rmi.server.UnicastRef.done(UnicastRef.java:431)
at sun.rmi.registry.RegistryImpl_Stub.lookup(Unknown Source)
at CustomClassLoader.initNotifier(CustomClassLoader.java:22)
at CustomClassLoader.loadClass(CustomClassLoader.java:35)
at java.lang.ClassLoader.loadClass(ClassLoader.java:247)
at sun.security.jca.ProviderConfig$3.run(ProviderConfig.java:234)
at java.security.AccessController.doPrivileged(Native Method)
at sun.security.jca.ProviderConfig.doLoadProvider(ProviderConfig.java:22
5)
at sun.security.jca.ProviderConfig.getProvider(ProviderConfig.java:205)
at sun.security.jca.ProviderList.getProvider(ProviderList.java:215)
at sun.security.jca.ProviderList.getService(ProviderList.java:313)
at sun.security.jca.GetInstance.getInstance(GetInstance.java:140)
at java.security.Security.getImpl(Security.java:659)
at java.security.MessageDigest.getInstance(MessageDigest.java:129)
at java.rmi.dgc.VMID.computeAddressHash(VMID.java:140)
at java.rmi.dgc.VMID.<clinit>(VMID.java:27)
at sun.rmi.transport.DGCClient.<clinit>(DGCClient.java:66)
at sun.rmi.transport.ConnectionInputStream.registerRefs(ConnectionInputS
tream.java:80)
at sun.rmi.transport.StreamRemoteCall.releaseInputStream(StreamRemoteCal
l.java:138)
at sun.rmi.transport.StreamRemoteCall.done(StreamRemoteCall.java:292)
at sun.rmi.server.UnicastRef.done(UnicastRef.java:431)
at sun.rmi.registry.RegistryImpl_Stub.lookup(Unknown Source)
at CustomClassLoader.initNotifier(CustomClassLoader.java:22)
at CustomClassLoader.loadClass(CustomClassLoader.java:35)
at java.lang.ClassLoader.loadClass(ClassLoader.java:247)
at sun.security.jca.ProviderConfig$3.run(ProviderConfig.java:234)
at java.security.AccessController.doPrivileged(Native Method)
at sun.security.jca.ProviderConfig.doLoadProvider(ProviderConfig.java:22
5)
at sun.security.jca.ProviderConfig.getProvider(ProviderConfig.java:205)
at sun.security.jca.ProviderList.getProvider(ProviderList.java:215)
at sun.security.jca.ProviderList$3.get(ProviderList.java:130)
at sun.security.jca.ProviderList$3.get(ProviderList.java:125)
at java.util.AbstractList$Itr.next(AbstractList.java:345)
at java.security.SecureRandom.getPrngAlgorithm(SecureRandom.java:522)
at java.security.SecureRandom.getDefaultPRNG(SecureRandom.java:165)
at java.security.SecureRandom.<init>(SecureRandom.java:133)
at java.rmi.server.UID.<init>(UID.java:92)
at java.rmi.server.ObjID.<clinit>(ObjID.java:71)
at java.rmi.registry.LocateRegistry.getRegistry(LocateRegistry.java:158)
at java.rmi.registry.LocateRegistry.getRegistry(LocateRegistry.java:106)
at java.rmi.registry.LocateRegistry.getRegistry(LocateRegistry.java:73)
at CustomClassLoader.initNotifier(CustomClassLoader.java:20)
at CustomClassLoader.loadClass(CustomClassLoader.java:35)
at java.lang.ClassLoader.loadClass(ClassLoader.java:247)
I've thought that problem is in the RMI server, but i wrote another RMI client and it works good.
Does anyone know where is the problem(s) and how to solve it(them)?
TL;DR: Don't use a class loader which such heavy side effects as the root class loader.
The problem is that the const field gcInterval on class sun.rmi.transport.DGCClient is not initialized before it's used (and hence shows value 0). The reason for this is that your class loader makes the call via RMI which creates a new instance of DGCClient. During the execution of the constructor of DGCClient another class is loaded (see stack trace). This third call to the class loader triggers the RMI call again which doesn't create a new instance of DGCClient but uses the previously created one and does some call on it. That means that a call is made on a half-initialized object which leads to the use of this not-yet initialized constant field.
We can't possibly blame Sun/Oracle for this since every Java class can assume that it is loaded without such unpredictable side effects.