quarkiverse / quarkus-cxf

Quarkus CXF Extension to support SOAP based web services.
Apache License 2.0
79 stars 60 forks source link

Can't use XSLT feature in native mode #501

Open Ballsigno opened 2 years ago

Ballsigno commented 2 years ago

Basic Info

Java: java 17.0.4 2022-07-19 LTS (oracle)
Mandrel: openjdk 17.0.3 2022-04-19
※ I know the doc says Native mode is currently supported for Java 11.

Hi. I have a question about XSLT feature.

// get my client
Client client = ClientProxy.getClient(myService);

// create XSLT InInterceptor (from ~/resources/test.xsl)
XSLTInInterceptor inInterceptor = new XSLTInInterceptor("test.xsl");

// add to my client settings
client.getInInterceptors().add(inInterceptor);

↑ It works fine in dev mode, but when I tried in native mode, it was failed.

java.lang.IllegalArgumentException: Cannot create XSLT template from path: test.xsl
        at org.apache.cxf.feature.transform.AbstractXSLTInterceptor.<init>(AbstractXSLTInterceptor.java:74)
        at org.apache.cxf.feature.transform.XSLTInInterceptor.<init>(XSLTInInterceptor.java:51)
        at org.acme.GreetingResource.test1(GreetingResource.java:47)
        at org.acme.GreetingResource$quarkusrestinvoker$test1_7ef637907f0bfe03310c5187ae0e45fdc512a9ab.invoke(Unknown Source)
        at org.jboss.resteasy.reactive.server.handlers.InvocationHandler.handle(InvocationHandler.java:29)
        at io.quarkus.resteasy.reactive.server.runtime.QuarkusResteasyReactiveRequestContext.invokeHandler(QuarkusResteasyReactiveRequestContext.java:108)
        at org.jboss.resteasy.reactive.common.core.AbstractResteasyReactiveContext.run(AbstractResteasyReactiveContext.java:140)
        at io.quarkus.vertx.core.runtime.VertxCoreRecorder$14.runWith(VertxCoreRecorder.java:555)
        at org.jboss.threads.EnhancedQueueExecutor$Task.run(EnhancedQueueExecutor.java:2449)
        at org.jboss.threads.EnhancedQueueExecutor$ThreadBody.run(EnhancedQueueExecutor.java:1478)
        at org.jboss.threads.DelegatingRunnable.run(DelegatingRunnable.java:29)
        at org.jboss.threads.ThreadLocalResettingRunnable.run(ThreadLocalResettingRunnable.java:29)
        at io.netty.util.concurrent.FastThreadLocalRunnable.run(FastThreadLocalRunnable.java:30)
        at java.lang.Thread.run(Thread.java:833)
        at com.oracle.svm.core.thread.PlatformThreads.threadStartRoutine(PlatformThreads.java:704)
        at com.oracle.svm.core.posix.thread.PosixPlatformThreads.pthreadStartRoutine(PosixPlatformThreads.java:202)
Caused by: javax.xml.transform.TransformerConfigurationException: java.lang.NullPointerException
        at com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl.newTemplates(TransformerFactoryImpl.java:1077)
        at org.apache.cxf.feature.transform.AbstractXSLTInterceptor.<init>(AbstractXSLTInterceptor.java:71)
        ... 15 more
Caused by: java.lang.NullPointerException
        at com.sun.org.apache.xalan.internal.xsltc.compiler.util.ErrorMsg.getFileName(ErrorMsg.java:264)
        at com.sun.org.apache.xalan.internal.xsltc.compiler.util.ErrorMsg.<init>(ErrorMsg.java:237)
        at com.sun.org.apache.xalan.internal.xsltc.compiler.Parser.makeInstance(Parser.java:1000)
        at com.sun.org.apache.xalan.internal.xsltc.compiler.Parser.startElement(Parser.java:1312)
        at com.sun.org.apache.xalan.internal.xsltc.trax.DOM2SAX.parse(DOM2SAX.java:269)
        at com.sun.org.apache.xalan.internal.xsltc.trax.DOM2SAX.parse(DOM2SAX.java:192)
        at com.sun.org.apache.xalan.internal.xsltc.trax.DOM2SAX.parse(DOM2SAX.java:130)
        at com.sun.org.apache.xalan.internal.xsltc.compiler.Parser.parse(Parser.java:439)
        at com.sun.org.apache.xalan.internal.xsltc.compiler.XSLTC.compile(XSLTC.java:478)
        at com.sun.org.apache.xalan.internal.xsltc.compiler.XSLTC.compile(XSLTC.java:572)
        at com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl.newTemplates(TransformerFactoryImpl.java:1036)
        ... 16 more

If the xsl file doesn't exist, I'm sure I will get 'Cannot load XSLT from path:" error. Therefore I passed the right argument but failed. Is this a bug or am I missing something?

shumonsharif commented 2 years ago

Hi @Ballsigno Did you include the test.xsl resource in your native image? https://quarkus.io/guides/writing-native-applications-tips#including-resources

Ballsigno commented 2 years ago

@shumonsharif
Thank you for your reply!
Yes, I have pom.xml that has include setting.

<profile>
~ omit ~
  <properties>
    <skipITs>false</skipITs>
    <quarkus.package.type>native</quarkus.package.type>
    <quarkus.native.additional-build-args>-H:IncludeResources=test.xsl</quarkus.native.additional-build-args>
  </properties>

Additionally, I have tried this in native mode, and I successfully got the input stream. (But somehow, XSLTInInterceptor doesn't work.)

InputStream testStream = ClassLoaderUtils.getResourceAsStream("test.xsl", this.getClass());
Log.info(testStream.available()); // has value
shumonsharif commented 2 years ago

Likely a bug; thanks for opening the issue! Would you be able to provide a small reproducer?

Ballsigno commented 2 years ago

Alright, I created the small one.
(sorry, I didn't know the message is displayed if I put a link at my push comment.)

I really want to know whether this can be fixed in a few days or not. Let me know if you need anything.

https://github.com/Ballsigno/quarkus-cxf-xslt-test/tree/master/quarkus-cxf-xslt-test

shumonsharif commented 2 years ago

Thank you so much @Ballsigno ! This helps tremendously.

Unfortunately, debugging native issues always takes quite a bit of time. Not sure at this stage if this will be a quick thing or not, though I'm doubtful it'll be fixed and released in a few days. Will certainly prioritize this issue for you, and will keep you posted on progress.

Ballsigno commented 2 years ago

@shumonsharif Thank you for your support! It's understandable. I'm researching whether I can use this for my purpose these days, and now I can see the silver lining. If it turned out to be complicated, I will wait patiently🙏

ppalaga commented 2 years ago

Making XSLT work in native mode is indeed tricky. Camel Quarkus supports it via its camel-quarkus-xslt extension. It is tested there only in Camel related scenarios, but I think it might work for you as well. What I think you should do is the following:

  1. Go to https://code.quarkus.io/?e=org.apache.camel.quarkus%3Acamel-quarkus-xslt&extension-search=origin:platform%20camel-quarkus-xslt
  2. Generate the project zip, unpack locally, take the properties, dependencyManagement and dependencies from there and add them into your project.
  3. Make sure that Quarkus version from code.quarkus.io is compatible with quarkus-cxf version you use. If you wait a week or so, the versions that should work well with each other are Quarkus 2.12.0.Final, Camel Quarkus 2.12.0 (managed in Quarkus Platform BOM 2.12.0.Final, so you actually do not need to care for the Camel Quarkus version) and quarkus-cxf 1.15.0 Starting with 1.15.0, Quarkus CXF will also have a BOM io.quarkiverse.cxf:quarkus-cxf-bom that you should import into your project
  4. Configure quarkus.camel.xslt.sources in your application.properties - see https://camel.apache.org/camel-quarkus/2.11.x/reference/extensions/xslt.html
  5. Report whether it worked for you
Ballsigno commented 2 years ago

Thanks for clear steps instructions🙇🏻‍♂️ I’m not familiar with those things. Therefore I’m not so sure how is that related to my problem. Anyway, I'll give it a try tomorrow or the day after tomorrow.

ppalaga commented 2 years ago

not so sure how is that related to my problem

To make Java code work in native mode, you need to provide some (sometimes a lot of) GraalVM configuration. That's what Quarkus extensions do (among other things). camel-quarkus-xslt does it for XSLT quite generally, although its main aim is to support Camel integration pipelines using XSLT.

Ballsigno commented 2 years ago

Thank you for your explanation. I tried it Java 11 and 17, but very unfortunately, It didn't work for me. (I created a branch based on the reproducer)

Indeed it seems it affects XSLT feature because the error message is changed. (But my xsl file is valid format.)

java.lang.IllegalArgumentException: Cannot create XSLT template from path: test.xsl
        at org.apache.cxf.feature.transform.AbstractXSLTInterceptor.<init>(AbstractXSLTInterceptor.java:74)
        at org.apache.cxf.feature.transform.XSLTInInterceptor.<init>(XSLTInInterceptor.java:51)
        at org.acme.GreetingResource.test1(GreetingResource.java:47)
        at org.acme.GreetingResource$quarkusrestinvoker$test1_7ef637907f0bfe03310c5187ae0e45fdc512a9ab.invoke(Unknown Source)
        at org.jboss.resteasy.reactive.server.handlers.InvocationHandler.handle(InvocationHandler.java:29)
        at io.quarkus.resteasy.reactive.server.runtime.QuarkusResteasyReactiveRequestContext.invokeHandler(QuarkusResteasyReactiveRequestContext.java:108)
        at org.jboss.resteasy.reactive.common.core.AbstractResteasyReactiveContext.run(AbstractResteasyReactiveContext.java:140)
        at io.quarkus.vertx.core.runtime.VertxCoreRecorder$14.runWith(VertxCoreRecorder.java:555)
        at org.jboss.threads.EnhancedQueueExecutor$Task.run(EnhancedQueueExecutor.java:2449)
        at org.jboss.threads.EnhancedQueueExecutor$ThreadBody.run(EnhancedQueueExecutor.java:1478)
        at org.jboss.threads.DelegatingRunnable.run(DelegatingRunnable.java:29)
        at org.jboss.threads.ThreadLocalResettingRunnable.run(ThreadLocalResettingRunnable.java:29)
        at io.netty.util.concurrent.FastThreadLocalRunnable.run(FastThreadLocalRunnable.java:30)
        at java.lang.Thread.run(Thread.java:833)
        at com.oracle.svm.core.thread.PlatformThreads.threadStartRoutine(PlatformThreads.java:704)
        at com.oracle.svm.core.posix.thread.PosixPlatformThreads.pthreadStartRoutine(PosixPlatformThreads.java:202)
Caused by: javax.xml.transform.TransformerConfigurationException: Could not compile stylesheet
        at org.apache.xalan.xsltc.trax.TransformerFactoryImpl.newTemplates(TransformerFactoryImpl.java:832)
        at org.apache.camel.quarkus.support.xalan.XalanTransformerFactory.newTemplates(XalanTransformerFactory.java:70)
        at org.apache.cxf.feature.transform.AbstractXSLTInterceptor.<init>(AbstractXSLTInterceptor.java:71)
        ... 15 more

(add) The real problem is ↓

java.lang.NoClassDefFoundError: org.apache.xalan.xsltc.compiler.ObjectFactory
    at org.apache.xalan.xsltc.compiler.ObjectFactory.class$(ObjectFactory.java:292)
    at org.apache.xalan.xsltc.compiler.ObjectFactory.findClassLoader(ObjectFactory.java:410)
    at org.apache.xalan.xsltc.compiler.Parser.makeInstance(Parser.java:925)
    at org.apache.xalan.xsltc.compiler.Parser.startElement(Parser.java:1250)
    at org.apache.xalan.xsltc.trax.DOM2SAX.parse(DOM2SAX.java:285)
    at org.apache.xalan.xsltc.trax.DOM2SAX.parse(DOM2SAX.java:212)
    at org.apache.xalan.xsltc.trax.DOM2SAX.parse(DOM2SAX.java:149)
    at org.apache.xalan.xsltc.compiler.Parser.parse(Parser.java:421)
    at org.apache.xalan.xsltc.compiler.XSLTC.compile(XSLTC.java:347)
    at org.apache.xalan.xsltc.compiler.XSLTC.compile(XSLTC.java:446)
Ballsigno commented 2 years ago

Hmm. I'm totally stuck on this.

Apache Camel seems create a factory and compile a xsl file in initialization process, and XsltBuilder retains the result(template). On the other hand, CXF does similar things after starting application.(When I create XSLTInInterceptor.) I don't know why it doesn't work though. Since there is AbstractPhaseInterceptor, I can create own XSLTinterceptor, but I could not take the template which is created by Apache Camel.

Is there any other way to fix this? I would really appreciate any information.

Ballsigno commented 2 years ago

Finally, I found a workaround! Thank you @ppalaga, I wouldn't have found this approach by myself. I don't know what's going on but, using quarkus camel resources work. I created own interceptor, and avoid a default action that caused the problem.

Instated of doing this...

// AbstractXSLTInterceptor.java
try {
    InputStream xsltStream = ClassLoaderUtils.getResourceAsStream(xsltPath, this.getClass());
    if (xsltStream == null) {
        throw new IllegalArgumentException("Cannot load XSLT from path: " + xsltPath);
    }
    Document doc = StaxUtils.read(xsltStream);
    TransformerFactory transformerFactory = TransformerFactory.newInstance();
    try {
        transformerFactory.setFeature(javax.xml.XMLConstants.FEATURE_SECURE_PROCESSING, true);
    } catch (javax.xml.transform.TransformerConfigurationException ex) {
      //
    }
    xsltTemplate = transformerFactory.newTemplates(new DOMSource(doc)); // Somehow failed!!
} catch (TransformerConfigurationException | XMLStreamException e) {
    throw new IllegalArgumentException(
        String.format("Cannot create XSLT template from path: %s", xsltPath), e);
}

// Inject and pass component as argument from caller.
@Inject
XsltComponent xslt;

// CustomInterceptor
try (XsltEndpoint endPoint = (XsltEndpoint) xslt.createEndpoint("xslt://" + xsltPath)) {
    endPoint.init();
    xsltTemplate = endPoint.getXslt().getTemplate();
} catch (Exception e) {
    throw new IllegalArgumentException(
        String.format("Cannot create XSLT template from path: %s", xsltPath), e);
}

@shumonsharif I think I'll close this issue for now. is it okay? (I assume this is low priority...) (I'll be happy if the XSLT feature is officially supported though.)

ppalaga commented 2 years ago

I think I'll close this issue for now.

I'd vote for keeping this issue open, as it still does not work flawlessly. This issue also contains a lot of useful information (thanks, @Ballsigno !).

I think our general aim should be to have a general purpose Quarkus XSLT extension that could be leveraged for both Camel and CXF use cases. I need to speak with Quarkus team whether they'd be ready to host it within quarkusio/quarkus repo.

dufoli commented 1 year ago

According error it mean that we need a ReflectiveClassBuildItem for class org.apache.xalan.xsltc.compiler.ObjectFactory