Bnd, ServiceLoader and javax.xml.stream.FactoryFinder

Hi,

I am trying to use javax.xml.stream.XMLInputFactory and javax.xml.stream.XMLOutputFactory inside an OSGi framework. These use ServiceLoader internally to locate concrete XMLInputFactory and XMLOutputFactory classes, i.e. by invoking javax.xml.stream.FactoryFinder. However, FactoryFinder is part of the JDK and so has no osgi.serviceloader metadata.

The only way I know to add extra requirements to Bnd is:

-runrequires: \
    osgi.serviceloader;filter:='(osgi.serviceloader=javax.xml.stream.XMLInputFactory)';osgi.serviceloader='javax.xml.stream.XMLInputFactory',\
    osgi.serviceloader;filter:='(osgi.serviceloader=javax.xml.stream.XMLOutputFactory)';osgi.serviceloader='javax.xml.stream.XMLOutputFactory',\

However, I still get this exception at runtime:

    => javax.xml.stream.FactoryConfigurationError: Provider for javax.xml.stream.XMLInputFactory cannot be found
       java.xml/javax.xml.stream.FactoryFinder.find(FactoryFinder.java:320)
       java.xml/javax.xml.stream.XMLInputFactory.newFactory(XMLInputFactory.java:323)
       com.fasterxml.jackson.dataformat.xml.XmlFactory.<init>(XmlFactory.java:120)
       com.fasterxml.jackson.dataformat.xml.XmlFactory.<init>(XmlFactory.java:106)
       com.fasterxml.jackson.dataformat.xml.XmlFactory.<init>(XmlFactory.java:90)
       com.fasterxml.jackson.dataformat.xml.XmlMapper.<init>(XmlMapper.java:129)
       com.fasterxml.jackson.dataformat.xml.XmlMapper.builder(XmlMapper.java:220)
       ...

FWIW, this is happening with a locally-patched 2.14.0-SNAPSHOT version of Jackson where I have added these Bnd annotations:

package com.fasterxml.jackson.dataformat.xml;

@ServiceProvider(JsonFactory.class)
@ServiceConsumer(XMLInputFactory.class)
@ServiceConsumer(XMLOutputFactory.class)
public class XmlFactory extends JsonFactory

However, it occurs to me that it is the system bundle which is the actual “consumer” here, and so maybe that is where the requirement should be. But there is no obvious equivalent of -runsystemcapabilities for requirements other than -runrequires.

For reference, my -runbundles does contain:

org.apache.aries.spifly.dynamic.bundle;version='[1.3.2,1.3.3)',\

Can anyone help me sort my .bndrun file out please?
Thanks,
Chris

Edit: From the POV of a TestOSGi Gradle task, this test fails:

@ServiceConsumer(XMLInputFactory.class)
class ServiceLoaderTest {
    private static final Logger logger = LoggerFactory.getLogger(ServiceLoader.class);

    @Test
    void loadXMLInputFactoryByClassLoader() {
        final XMLInputFactory inputFactory = XMLInputFactory.newFactory(XMLInputFactory.class.getName(), getClass().getClassLoader());
        assertNotNull(inputFactory);
        logger.info("FOUND: {}", inputFactory);
    }
}

but this succeeds:

@ServiceConsumer(XMLInputFactory.class)
class ServiceLoaderTest {
    private static final Logger logger = LoggerFactory.getLogger(ServiceLoader.class);

    @Test
    void serviceLoadXMLInputFactory() {
        final ServiceLoader<XMLInputFactory> inputFactoriesLoader = ServiceLoader.load(XMLInputFactory.class, getClass().getClassLoader());
        final List<XMLInputFactory> inputFactories = inputFactoriesLoader.stream().map(ServiceLoader.Provider::get).collect(toList());
        assertThat(inputFactories).isNotEmpty();
        inputFactories.forEach(input -> logger.info("FOUND: {}", input));
    }
}

Oh, I had thought @ServiceConsumer was instructing Aries SpiFly to register a handler on the bundle which would enable BundleClassLoader.getResources() to find the correct META-INF/services files. I see now that the Aries SpiFly service is actually rewriting the bundle’s bytecode to replace invocations of

java.util.ServiceLoader.load(<service>, <classloader>)

with

org.apache.aries.spifly.Util.serviceLoaderLoad(<service>, <caller>)

This rather scuppers my plans for World Domination… :man_facepalming:

Thanks anyway,
Cheers,
Chris

It’s not difficult to extend the Aries SPI-Fly also to weave for these methods:

javax.xml.stream.XMLInputFactory.newFactory(String, ClassLoader)
javax.xml.stream.XMLOutputFactory.newFactory(String, ClassLoader)
javax.xml.stream.XMLEventFactory.newFactory(String, ClassLoader)

By which I mean I have created a 1.4.0-SNAPSHOT version locally which seems to work.

But I suppose the more interesting question here is “Why didn’t someone implement this already?”. Does this go against the OSGi Spec somehow?

Cheers,
Chris

what comes to mind is http://docs.osgi.org/specification/osgi.cmpn/7.0.0/util.xml.html and IIRC that there’s some of this built into Equinox (not sure about Apache Felix).

Just wanted to let you know that for all the jax-stuff if you want to make it aviable you can simply use https://mvnrepository.com/artifact/org.glassfish.hk2/hk2-locator

Hi, thanks for replying.

I am actually trying to use a library which has jackson-dataformat-xml as one of its dependencies, so I don’t believe OSGi’s util.xml will help me here. Jackson’s bundles are all missing the @ServiceProvider annotations for their META-INF/services/* files, which I can fix. However, I cannot reasonably add @ServiceConsumer annotations for their XML newFactory() method invocations if this isn’t covered by the OSGi spec :cry:.

Perhaps the best solution would be to create a SPI-Fly-like bundle to weave for just the XML factories, targeting (say) bundles that require the javax.xml.streams package? Or just targeting a hard-coded set of bundle symbolic names, if that’s not possible.

Cheers,
Chris

Hi,

I think there’s a problem with Apache Aries spi-fly, but its GitHub repo isn’t allowing me to raise an issue for it :man_facepalming:.

Suppose a class wishes to “consume” TypeA but produce TypeB, and so is annotated as

@ServiceProvider(TypeB.class)
@ServiceConsumer(TypeA.class)
public class MyClass implements TypeB {
    // etc
}

If the OSGi framework executes the ProviderBundleTrackerCustomizer for this bundle before executing the ConsumerBundleTrackerCustomizer then the ProviderBundleTrackerCustomizer will load MyClass before the ConsumerBundleTrackerCustomizer has had an opportunity to register its “weaving data”. This means that MyClass will not be woven, of course.

I fixed this locally by copying

try {
    activator.addConsumerWeavingData(bundle, consumerHeaderName);
} catch (Exception e) {
     throw new RuntimeException(e.getMessage(), e);
}

to the top of ProviderBundleTrackerCustomizer#addBundles, but ideally I’d prefer for ProviderBundleTrackerCustomizer to defer loading MyClass until every WeavingHook is “ready”. (I have no idea how to achieve that, however… :slightly_frowning_face:).

Cheers,
Chris

Chris, Apache Aries aries, like every other Apache project uses JIRA for issue management/bug tracking → ARIES JIRA

Also before you go down the path of writing any code did you try this feature of Aries SPI Fly: https://aries.apache.org/documentation/modules/spi-fly.html#_dynamic_weaving_by_auto_properties

Finally, to avoid all startup ordering issues, please use


org.apache.aries.spifly org.apache.aries.spifly.dynamic.framework.extension ${spifly.version} ```

Instead of the other dependencies, because that one ensures that bundles are handled only when the framework extension has made the capabilities for SLM (Service Loader Mediator) available which means the weaver is available, and means it's impossible for other bundles that use the capability to resolve (and therefore start) before it.

Thanks, I was about to investigate the mysteries of WovenClassListener, but switching from org.apache.aries.spifly.dynamic.bundle to org.apache.aries.spifly.dynamic.framework.extension seems to resolved the ordering issue with the weaving.

I doubt spi-fly’s “auto properties” feature will help me because I really need to weave for consumers of XMLInputFactory.newFactory etc rather than ServiceLoader.load. I’ve been hacking on spi-fly locally just to prove that I can get something to work, but ultimately I would need for my own custom WeavingHook to execute before spi-fly registers the providers with the framework.

I am assuming here that spi-fly would never support weaving for consumers of XML(Input|Output|Event)Factory.newFactory via Bnd’s @ServiceConsumer annotation.

Cheers,
Chris

Raised as ARIES-2077.