rbeckman-nextgen / test-mc3

0 stars 0 forks source link

Simplify access to custom JARs in isolated classloader #4304

Open rbeckman-nextgen opened 4 years ago

rbeckman-nextgen commented 4 years ago

I originally posted this content as a comment to MIRTH-3835 but want to make sure it gets surfaced/tracked as a bug. I'm trying to use the Isolated ClassLoader and am having issues using a constructor that requires arguments. I'm reasonably sure I'm doing it right, but can't rule out operator error. At minimum, it should be easy to make a test case -- e.g. a glorified String class that takes its text as a constructor argument.

Just in case it matters, I'm still using custom-lib, but have set the import property to False. (We use the same settings file on a bunch of single-tenant Mirth instances as an inbound interface to our product. I'm deploying the JARs in two folders now and will cut-over to the new folder once we replace all of the custom-lib-only instances.)

I originally got around the HAPI name collision issue by (manually) changing the namespace for all of HAPI. It was painful, but it made imports unambiguous. I'm continuing to use the namespaced version of the HAPI JAR in this example to stay unambiguous:

^^ These last few steps demonstrate that we're referencing the right class since it knows the class has the Map constructor but not the HashMap constructor.

Now I try to call constructor.newInstance(<args>) and...

ERROR (com.mirth.connect.server.channel.ErrorTaskHandler:25): com.mirth.connect.donkey.server.DeployException: Failed to deploy channel 7533537f-89d6-427a-9693-ea711d52f92b. ... Caused by: java.lang.NoClassDefFoundError: Could not initialize class com.ambassador.hl7v2.parser.CustomModelClassFactory at java.base/jdk.internal.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method) at java.base/jdk.internal.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:62) at java.base/jdk.internal.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45) at java.base/java.lang.reflect.Constructor.newInstance(Constructor.java:490) 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 org.mozilla.javascript.MemberBox.invoke(MemberBox.java:126) at org.mozilla.javascript.NativeJavaMethod.call(NativeJavaMethod.java:225) at org.mozilla.javascript.Interpreter.interpretLoop(Interpreter.java:1479) at org.mozilla.javascript.Interpreter.interpret(Interpreter.java:815) at org.mozilla.javascript.InterpretedFunction.call(InterpretedFunction.java:109) at org.mozilla.javascript.ContextFactory.doTopCall(ContextFactory.java:405) at org.mozilla.javascript.ScriptRuntime.doTopCall(ScriptRuntime.java:3508) at org.mozilla.javascript.InterpretedFunction.exec(InterpretedFunction.java:120) at com.mirth.connect.server.util.javascript.JavaScriptTask.executeScript(JavaScriptTask.java:150) at com.mirth.connect.server.util.javascript.JavaScriptUtil.executeScript(JavaScriptUtil.java:542) at com.mirth.connect.server.util.javascript.JavaScriptUtil$2.doCall(JavaScriptUtil.java:379) at com.mirth.connect.server.util.javascript.JavaScriptTask.call(JavaScriptTask.java:113) ... 4 more

Even though I've definitely found and worked with the target class, it seems like I'm back at square one. I hope I'm doing something wrong. There's a huge, circuitous process to even get the right constructor and then it can't find the right class anyway.

Imported Issue. Original Details: Jira Issue Key: MIRTH-4449 Reporter: ambsw Created: 2019-07-22T08:31:34.000-0700

rbeckman-nextgen commented 4 years ago

OK. Per this SO answer it sounds like the most likely explanation is that a dependency is not available to the isolated class loader. This makes sense due to the isolation. So I suspect my suggested "String" test case will pass.

I'm going to try and hunt down the missing library, but (in my case at least) the suggested classloader prioritization would have avoided the issue. If I understand the suggestion, it would also eliminate the circuitous code required to obtain the constructor for the class.

P.S. While investigating this issue, I also discovered that the simpler clazz.newInstance() is [not recommended](https://docs.oracle.com/javase/6/docs/api/java/lang/Class.html#newInstance()) so everyone should really use the getConstructor (or getDeclaredConstructor) overhead.

Imported Comment. Original Details: Author: ambsw Created: 2019-07-23T07:54:56.000-0700

rbeckman-nextgen commented 4 years ago

OK. I confirmed that my issue was the absence of several dependencies (i.e. log4j, slf4j, and the plugin linking the two) in the isolated class loader. I'm going to rename the issue to emphasize the complexity of the current approach:

My original hope was that you could wrap this in more automation -- like a newIsolatedClass() call that could get the right constructor and provide intelligent errors. Unfortunately, this may not be practical since the constructor needs the right signature e.g. Map vs. HashMap. I'm not a Java guy but that looks hard to automate.

A hierarchy of classloaders was also suggested. This would have solved my dependency problem. However, it could create hard-to-diagnose issues if e.g. the newer version of HAPI required a newer version of log4j. It would seem to import but still break at odd times.

Imported Comment. Original Details: Author: ambsw Created: 2019-07-30T10:43:53.000-0700

rbeckman-nextgen commented 4 years ago

Apparently I can't rename this. Perhaps a dev can change this to "simplify access to custom JARs in isolated classloader"

Imported Comment. Original Details: Author: ambsw Created: 2019-07-30T10:45:05.000-0700