jpmml / jpmml-transpiler

Java Transpiler (Translator + Compiler) API for PMML
GNU Affero General Public License v3.0
28 stars 2 forks source link

Obscure XML dependency #7

Closed ahmed-shariff closed 4 years ago

ahmed-shariff commented 4 years ago

When I try to load a transpiled model from android, I get the following error:

E/AndroidRuntime: FATAL EXCEPTION: main
    Process: com.umanitoba.hci.sdetection, PID: 13054
    java.util.ServiceConfigurationError: org.dmg.pmml.PMML: Provider PMML$1438030319 not found
        at java.util.ServiceLoader.fail(ServiceLoader.java:233)
        at java.util.ServiceLoader.access$100(ServiceLoader.java:183)
        at java.util.ServiceLoader$LazyIterator.nextService(ServiceLoader.java:373)
        at java.util.ServiceLoader$LazyIterator.next(ServiceLoader.java:416)
        at java.util.ServiceLoader$1.next(ServiceLoader.java:494)
        at com.umanitoba.hci.sdetection.DetectIMU.FlattenedChunkedDataModel.<init>(FlattenedChunkedDataModel.java:92)
        at com.umanitoba.hci.sdetection.DetectIMU.SingleSensorClassificationConsumer.<init>(SingleSensorClassificationConsumer.java:22)
        at com.umanitoba.hci.sdetection.WearDetector.<init>(WearDetector.java:21)
        at com.umanitoba.hci.sdetection.SensorService.onCreate(SensorService.java:114)
        at android.app.ActivityThread.leCreateService(ActivityThread.java:3533)
        at android.app.ActivityThread.access$1300(ActivityThread.java:200)
        at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1667)
        at android.os.Handler.dispatchMessage(Handler.java:106)
        at android.os.Looper.loop(Looper.java:193)
        at android.app.ActivityThread.main(ActivityThread.java:6680)
        at java.lang.reflect.Method.invoke(Native Method)
        at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:493)
        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:858)
     Caused by: java.lang.ClassNotFoundException: PMML$1438030319
        at java.lang.Class.classForName(Native Method)
        at java.lang.Class.forName(Class.java:453)
        at java.util.ServiceLoader$LazyIterator.nextService(ServiceLoader.java:371)
        at java.util.ServiceLoader$LazyIterator.next(ServiceLoader.java:416) 
        at java.util.ServiceLoader$1.next(ServiceLoader.java:494) 
        at com.umanitoba.hci.sdetection.DetectIMU.FlattenedChunkedDataModel.<init>(FlattenedChunkedDataModel.java:92) 
        at com.umanitoba.hci.sdetection.DetectIMU.SingleSensorClassificationConsumer.<init>(SingleSensorClassificationConsumer.java:22) 
        at com.umanitoba.hci.sdetection.WearDetector.<init>(WearDetector.java:21) 
        at com.umanitoba.hci.sdetection.SensorService.onCreate(SensorService.java:114) 
        at android.app.ActivityThread.handleCreateService(ActivityThread.java:3533) 
        at android.app.ActivityThread.access$1300(ActivityThread.java:200) 
        at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1667) 
        at android.os.Handler.dispatchMessage(Handler.java:106) 
        at android.os.Looper.loop(Looper.java:193) 
        at android.app.ActivityThread.main(ActivityThread.java:6680) 
        at java.lang.reflect.Method.invoke(Native Method) 
        at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:493) 
        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:858) 
     Caused by: java.lang.UnsupportedOperationException: can't load this type of class file
        at java.lang.ClassLoader.defineClass(ClassLoader.java:591)
        at java.security.SecureClassLoader.defineClass(SecureClassLoader.java:142)
        at java.net.URLClassLoader.defineClass(URLClassLoader.java:469)
        at java.net.URLClassLoader.access$100(URLClassLoader.java:73)
        at java.net.URLClassLoader$1.run(URLClassLoader.java:368)
        at java.net.URLClassLoader$1.run(URLClassLoader.java:362)
        at java.security.AccessController.doPrivileged(AccessController.java:69)
        at java.security.AccessController.doPrivileged(AccessController.java:94)
        at java.net.URLClassLoader.findClass(URLClassLoader.java:361)
        at java.lang.ClassLoader.loadClass(ClassLoader.java:379)
        at java.lang.ClassLoader.loadClass(ClassLoader.java:312)
        at java.lang.Class.classForName(Native Method) 
        at java.lang.Class.forName(Class.java:453) 
        at java.util.ServiceLoader$LazyIterator.nextService(ServiceLoader.java:371) 
        at java.util.ServiceLoader$LazyIterator.next(ServiceLoader.java:416) 
        at java.util.ServiceLoader$1.next(ServiceLoader.java:494) 
        at com.umanitoba.hci.sdetection.DetectIMU.FlattenedChunkedDataModel.<init>(FlattenedChunkedDataModel.java:92) 
        at com.umanitoba.hci.sdetection.DetectIMU.SingleSensorClassificationConsumer.<init>(SingleSensorClassificationConsumer.java:22) 
        at com.umanitoba.hci.sdetection.WearDetector.<init>(WearDetector.java:21) 
        at com.umanitoba.hci.sdetection.SensorService.onCreate(SensorService.java:114) 
        at android.app.ActivityThread.handleCreateService(ActivityThread.java:3533) 
        at android.app.ActivityThread.access$1300(ActivityThread.java:200) 
        at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1667) 
        at android.os.Handler.dispatchMessage(Handler.java:106) 
        at android.os.Looper.loop(Looper.java:193) 
        at android.app.ActivityThread.main(ActivityThread.java:6680) 
        at java.lang.reflect.Method.invoke(Native Method) 
        at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:493) 
        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:858) 

I pushed the model file to android and ran the same code I used to execute it on the pc. The loading process I used:

        File file = new File(Environment.getExternalStorageDirectory() + File.separator + "temp_model.jar");

        URL[] classpath = new URL[1];
        try{
            classpath[0] = file.toURI().toURL();
        } catch (MalformedURLException e1) {
            e1.printStackTrace();
        }

        try(URLClassLoader clazzLoader = new URLClassLoader(classpath, PMML.class.getClassLoader())){
            PMML javaPmml = null;
            javaPmml = PMMLUtil.load(clazzLoader);
vruusmann commented 4 years ago

Caused by: java.lang.UnsupportedOperationException: can't load this type of class file

This is the root cause - for some reason your Android runtime doesn't like this class file. Perhaps you'd need to tweak some compiler options, such as explicitly stating that the generated class files must conform to Java 1.8/Java 8.

Alternatively, don't use the class loader mechanism, and see if your Android runtime will throw a more meaningful exception in such a case:

  1. During transpilation, explicitly set the name of the generated class. For example, com.mycompany.MyPMML.
  2. In your application, explicitly invoke the constructor of the above class:
PMML javaPmml = new com.mycompany.MyPMML();
ahmed-shariff commented 4 years ago

Thank you for the support. I appreciate it alot.

So the problem seems to have been coming from the xercesImpl library. Following this, I added

compile group: 'xerces', name: 'xercesImpl', version: '2.12.0'

to gradle, and now it loads.

vruusmann commented 4 years ago

So the problem seems to have been coming from the xercesImpl library.

That's seriously odd - the transpiled PMML class model object should be completely independent of the XML parsing/JAXB unmarshalling stack (for example, take a look at the generated Java source code - there are no XML imports anywhere).

If you have time, then you could invest some of it into investigating how exactly the Xerces library "contributes" to solving the original UnsupportedOperationException. If there's an actual dependency hidden somewhere, then I'd sure like to eliminate it.

atlantis0 commented 3 years ago

Caused by: java.lang.UnsupportedOperationException: can't load this type of class file

This is the root cause - for some reason your Android runtime doesn't like this class file. Perhaps you'd need to tweak some compiler options, such as explicitly stating that the generated class files must conform to Java 1.8/Java 8.

Alternatively, don't use the class loader mechanism, and see if your Android runtime will throw a more meaningful exception in such a case:

1. During transpilation, explicitly set the name of the generated class. For example, `com.mycompany.MyPMML`.

2. In your application, explicitly invoke the constructor of the above class:
PMML javaPmml = new com.mycompany.MyPMML();

@vruusmann Thank you

This has been an effective method for me. I was able to import the generated .jar file in my android project and run it successfully. One problem though. The app requires API level 26 and above because of this method, https://developer.android.com/reference/java/lang/ThreadLocal#withInitial(java.util.function.Supplier%3C?%20extends%20S%3E), used by jpmml-evaluator/model. Any workround for this? Need to suppport lower level API versions.

vruusmann commented 3 years ago

@atlantis0 I've extracted your comment into a separate issue (because it's not related to the original issue in any way).

Now, please can you go there, and provide a detailed Android SDK(?) complaint regarding this API incompatibility? Specifically, does it fail compile-time (the project doesn't build, because there's a chance of hitting the ThreadLocal#withInitial(Supplier) method), or during run-time (the project builds fine, but the application fails when executed).

Can you "disable" the invocation of the default ThreadLocal#withInitial(Supplier) method by setting a custom ThreadLocal value manually?

Tell me more! Otherwise, my fix might be insufficient.

vruusmann commented 3 years ago

@atlantis0 Add your clarifications to https://github.com/jpmml/jpmml-model/issues/27