Project

Profile

Help

Extension Function Integration with Saxon EE embedded in Oxygen 17 / Java

Added by David Lee over 8 years ago

I am working on improving an existing extension function to work properly when embedded in Oxygen 17.

If any or all of these questions seem more appropriate to Oxygen support let me know. I'm working from 'bottom up' and Saxon is the current roof.

The function uses the ExtensionFunctionDefinition and ExtensionFunctionCall interfaces. I Added the net.sf.saxon.lib.Initializer interface and initialize(Configuration config) method to the main class. Following the Oxygen docs I first added the main jar then the 20+ dependent jars to the Transformation Scenario, XSLT, Extensions, Libraries page. This opened up a brand new world of additional pleasure debugging class loading and method calls through a stack now 4 turtles deep and control inversion and thread usage previously unpredicted. I ultimately got this to work both standalone (calling Oxygen from the shell) and in Eclipse but could use some suggestions on an appropriate strategy for least effort, bugs and pain, and maximum integration, flexibility and useability. The following are the areas I am running into problems, I don't know if these are Saxon EE, Saxon, or Oxygen or calling environment specific as the existing code is largely used as its own app/main with Saxon HE.

  1. Initialization / Classloading When I start a transformation the main class is constructed and the initialize() method called very quickly across about threads. I found the source in Saxon for calling initialize() in the command line case but only guessing in Oxygen.
    The classloader and environment does not appear to be quite the same as is Documented in Oxygen. I havent figured it out entirely, but it is acting as if the inital class load is using a different classloader then when ExtensionFunctionCall.call is invoked. The debugger is showing them to be the same object ID but expressions like this fail on new dynamically loaded classes from the the main .jar Class<?> cls = forName( "name" , classloader ); if( Interface.class.isAssignableFrom( cls ) ) ...

( where cls is an instance of a class extending Interface )

If I put all the jar files in the Oxygen library directory or if I run in Eclipse and include them in the classpath this works correctly.

  1. Configuration object Oxygen is calling initialize() on multiple threads using different Configuration objects. The app is coded to handle a different configuration per class loader, but not per thread.
    That was simply to change, but my question is if this is expected and if so any recomendations on managing the configuration appreciated. Since the extension function accepts and returns Saxon Items, I presume as documented they should all come from the same Configuration (my code is using S9API). Since I dont know what to expect of the calling code from Oxygen I am creating a thread-local Processor object from the passed in Configuration in the static context in each ExtensionFunctionCall.call ( if the current TLS refers to a different Configuration). This works fine, if I restrict objects in and out to only the current call chain. If I knew what the range of expected behaviour would be then I can code for that appropriately. E.g. if I know the Configuration is per thread indefinitely or if the same thread that called initialize() will be used to call all functions registered during the initialize I can associate the scope of the dependant objects accordingly. I suspect that this is the case, since it seems unnecessary to be using different Configurations per thread for the same transformation call, I would hope that they would atleast be shared within the same threads for the duration. Any advice appreciated.

  2. Co-Existing with a pre-loaded SaxonEE/PE/HE
    An obvious first problem is that my app has a copy of SaxonHE and if I include that in the CLASSPATH along with the currently loaded Saxon(EE/PE/HE) from Oxygen predictably bad things happen. Removing my copy of saxon from the classpath largely resolves this, but not completely as the set of dependant jars overlap. Hunt and Peck style I eliminated offending jars as class errors occured until it worked, for one test.
    A production solution should be a bit better then trial & error. Suggestions welcome. I'm considering the following approaches (and the affect #1,2 as well)

A) selectively remove my supplied jar files by comparing the jars in oxygen lib of the same MVN coordinates. Trial & error determine if different versions of the same artifact should be included or not. Include the entire list in whatever classpath-location is needed to solve #1

B) Create a builder (probably a simple gradle builder) that one can run after installation or upgrade that makes use of the dependency resolution logic of gradle/maven to create the list in #A based on what is found at the time.

C) Create a minimum 'stub' jar that exposes only the initialize() call and uses a private self-first resolving classloader for everything. Likely this will require marshalling all saxon objects in and out of the extension method. ( doesnt sound fun ) C2) do this in a sub process serializing all data

D) Like A&B but instead of a directory of jars (which is painful to add in the GUI and also leads to more classloader confusion ) create an "Uber" jar of all classes. I generally avoid this for various reasons including licensing, packaging, signing etc. But I believe if its done by the end user at install time (#B) then I can reasonably defer the licencing issue to the user. I haven't tried this but I don't believe the jar signing/packing will be an issue in Oxygen, I have only seen it enforced in some JEE Web containers. Alternatively a Zip or JarOfJars with my own classloader.

Any suggestions on these issues appreciated. I suspect you're team has integrated Saxon into more environments than I have. Any lessons from that I would love to learn the easy way :)

Thanks -David


Please register to reply