From 4d3336f757b96d2d66302daff3856c11c1765f8b Mon Sep 17 00:00:00 2001 From: Peter Palaga Date: Mon, 18 Nov 2024 15:46:51 +0100 Subject: [PATCH] Temporary workaround for https://github.com/quarkiverse/quarkus-cxf/issues/1608 --- .../cxf/deployment/CxfClientProcessor.java | 21 ++ .../java/io/quarkiverse/cxf/CXFRecorder.java | 9 + .../cxf/wsdl/QuarkusWSDLManager.java | 291 ++++++++++++++++++ 3 files changed, 321 insertions(+) create mode 100644 extensions/core/runtime/src/main/java/io/quarkiverse/cxf/wsdl/QuarkusWSDLManager.java diff --git a/extensions/core/deployment/src/main/java/io/quarkiverse/cxf/deployment/CxfClientProcessor.java b/extensions/core/deployment/src/main/java/io/quarkiverse/cxf/deployment/CxfClientProcessor.java index 05597b34b..368e06107 100644 --- a/extensions/core/deployment/src/main/java/io/quarkiverse/cxf/deployment/CxfClientProcessor.java +++ b/extensions/core/deployment/src/main/java/io/quarkiverse/cxf/deployment/CxfClientProcessor.java @@ -23,6 +23,8 @@ import org.apache.cxf.endpoint.Client; import org.apache.cxf.transport.http.HTTPTransportFactory; +import org.apache.cxf.wsdl11.CatalogWSDLLocator; +import org.apache.cxf.wsdl11.WSDLManagerImpl; import org.jboss.jandex.AnnotationInstance; import org.jboss.jandex.AnnotationTarget; import org.jboss.jandex.AnnotationValue; @@ -507,6 +509,25 @@ void customizers( } } + /** + * Temporary workaround for https://github.com/quarkiverse/quarkus-cxf/issues/1608 + */ + @BuildStep + @Record(ExecutionTime.STATIC_INIT) + void workaroundAsyncWsdlInit( + CXFRecorder recorder, + BuildProducer customizers) { + customizers.produce(new RuntimeBusCustomizerBuildItem(recorder.setQuarkusWSDLManager())); + } + + /** + * Temporary workaround for https://github.com/quarkiverse/quarkus-cxf/issues/1608 + */ + @BuildStep + ReflectiveClassBuildItem workaroundAsyncWsdlInit() { + return ReflectiveClassBuildItem.builder(WSDLManagerImpl.class, CatalogWSDLLocator.class).fields().build(); + } + @BuildStep @Record(ExecutionTime.RUNTIME_INIT) void workaroundBadForceURLConnectionInit(CXFRecorder recorder) { diff --git a/extensions/core/runtime/src/main/java/io/quarkiverse/cxf/CXFRecorder.java b/extensions/core/runtime/src/main/java/io/quarkiverse/cxf/CXFRecorder.java index a481ee763..d7ca65385 100644 --- a/extensions/core/runtime/src/main/java/io/quarkiverse/cxf/CXFRecorder.java +++ b/extensions/core/runtime/src/main/java/io/quarkiverse/cxf/CXFRecorder.java @@ -10,12 +10,14 @@ import org.apache.cxf.Bus; import org.apache.cxf.transport.http.HTTPTransportFactory; +import org.apache.cxf.wsdl.WSDLManager; import org.jboss.logging.Logger; import io.netty.util.internal.shaded.org.jctools.queues.MessagePassingQueue.Supplier; import io.quarkiverse.cxf.annotation.CXFEndpoint; import io.quarkiverse.cxf.transport.CxfHandler; import io.quarkiverse.cxf.transport.VertxDestinationFactory; +import io.quarkiverse.cxf.wsdl.QuarkusWSDLManager; import io.quarkus.arc.Arc; import io.quarkus.arc.runtime.BeanContainer; import io.quarkus.runtime.RuntimeValue; @@ -285,6 +287,13 @@ public RuntimeValue> setBusHTTPConduitFactory(HTTPConduitImpl fact return new RuntimeValue<>(bus -> bus.setExtension(factory, HTTPConduitSpec.class)); } + /** + * Temporary workaround for https://github.com/quarkiverse/quarkus-cxf/issues/1608 + */ + public RuntimeValue> setQuarkusWSDLManager() { + return new RuntimeValue<>(bus -> bus.setExtension(QuarkusWSDLManager.newInstance(bus), WSDLManager.class)); + } + public void workaroundBadForceURLConnectionInit() { // A workaround for the bad initialization of HTTPTransportFactory.forceURLConnectionConduit // in the downstream CXF 4.0.5.fuse-redhat-00012: diff --git a/extensions/core/runtime/src/main/java/io/quarkiverse/cxf/wsdl/QuarkusWSDLManager.java b/extensions/core/runtime/src/main/java/io/quarkiverse/cxf/wsdl/QuarkusWSDLManager.java new file mode 100644 index 000000000..8b58ce261 --- /dev/null +++ b/extensions/core/runtime/src/main/java/io/quarkiverse/cxf/wsdl/QuarkusWSDLManager.java @@ -0,0 +1,291 @@ +package io.quarkiverse.cxf.wsdl; + +import java.io.IOException; +import java.io.InputStream; +import java.lang.reflect.Field; +import java.net.URI; +import java.net.URISyntaxException; +import java.security.AccessController; +import java.security.PrivilegedActionException; +import java.security.PrivilegedExceptionAction; +import java.util.HashSet; +import java.util.Set; +import java.util.logging.Level; +import java.util.logging.Logger; + +import javax.wsdl.Definition; +import javax.wsdl.WSDLException; +import javax.wsdl.extensions.ExtensionRegistry; +import javax.wsdl.xml.WSDLReader; +import javax.xml.namespace.QName; +import javax.xml.stream.XMLStreamException; +import javax.xml.stream.XMLStreamReader; + +import org.apache.cxf.Bus; +import org.apache.cxf.BusException; +import org.apache.cxf.common.logging.LogUtils; +import org.apache.cxf.helpers.IOUtils; +import org.apache.cxf.helpers.LoadingByteArrayOutputStream; +import org.apache.cxf.message.Exchange; +import org.apache.cxf.message.ExchangeImpl; +import org.apache.cxf.message.Message; +import org.apache.cxf.message.MessageImpl; +import org.apache.cxf.service.model.EndpointInfo; +import org.apache.cxf.staxutils.StaxUtils; +import org.apache.cxf.staxutils.XMLStreamReaderWrapper; +import org.apache.cxf.transport.Conduit; +import org.apache.cxf.transport.ConduitInitiator; +import org.apache.cxf.transport.ConduitInitiatorManager; +import org.apache.cxf.transport.MessageObserver; +import org.apache.cxf.transport.TransportURIResolver; +import org.apache.cxf.wsdl11.CatalogWSDLLocator; +import org.apache.cxf.wsdl11.ResourceManagerWSDLLocator; +import org.apache.cxf.wsdl11.WSDLManagerImpl; +import org.w3c.dom.Document; +import org.xml.sax.InputSource; + +/** + * Temporary workaround for https://github.com/quarkiverse/quarkus-cxf/issues/1608 + */ +public class QuarkusWSDLManager extends WSDLManagerImpl { + + private final ExtensionRegistry registry; + private XMLStreamReaderWrapper xmlStreamReaderWrapper; + + public static QuarkusWSDLManager newInstance(Bus b) { + try { + return new QuarkusWSDLManager(b); + } catch (BusException e) { + throw new RuntimeException(e); + } + } + + private QuarkusWSDLManager(Bus b) throws BusException { + super(); + this.registry = getField(WSDLManagerImpl.class, this, "registry"); + setBus(b); + } + + @SuppressWarnings("unchecked") + private static T getField(Class cl, Object inst, String fieldName) { + try { + final Field fld = cl.getDeclaredField(fieldName); + fld.setAccessible(true); + return (T) fld.get(inst); + } catch (IllegalArgumentException | IllegalAccessException | NoSuchFieldException | SecurityException e) { + throw new RuntimeException(e); + } + } + + private static void setField(Class cl, Object inst, String fieldName, Object value) { + try { + final Field fld = cl.getDeclaredField(fieldName); + fld.setAccessible(true); + fld.set(inst, value); + } catch (IllegalArgumentException | IllegalAccessException | NoSuchFieldException | SecurityException e) { + throw new RuntimeException(e); + } + } + + @SuppressWarnings({ "removal", "deprecation" }) + @Override + protected Definition loadDefinition(String url) throws WSDLException { + final Bus bus = getBus(); + final WSDLReader reader = getWSDLFactory().newWSDLReader(); + reader.setFeature("javax.wsdl.verbose", false); + reader.setFeature("javax.wsdl.importDocuments", true); + reader.setExtensionRegistry(registry); + + //we'll create a new String here to make sure the passed in key is not referenced in the loading of + //the wsdl and thus would be held onto from the cached map from both the weak reference (key) and + //from the strong reference (Definition). For example, the Definition sometimes keeps the original + //string as the documentBaseLocation which would result in it being held onto strongly + //from the definition. With this, the String the definition holds onto would be unique + url = new String(url); + CatalogWSDLLocator catLocator = new CatalogWSDLLocator(url, bus); + setField(CatalogWSDLLocator.class, catLocator, "resolver", new QuarkusTransportURIResolver(bus)); + + final ResourceManagerWSDLLocator wsdlLocator = new ResourceManagerWSDLLocator(url, + catLocator, + bus); + InputSource src = wsdlLocator.getBaseInputSource(); + final Definition def; + if (src.getByteStream() != null || src.getCharacterStream() != null) { + final Document doc; + XMLStreamReader xmlReader = null; + try { + xmlReader = StaxUtils.createXMLStreamReader(src); + if (xmlStreamReaderWrapper != null) { + xmlReader = xmlStreamReaderWrapper.wrap(xmlReader); + } + doc = StaxUtils.read(xmlReader, true); + if (src.getSystemId() != null) { + try { + doc.setDocumentURI(new String(src.getSystemId())); + } catch (Exception e) { + //ignore - probably not DOM level 3 + } + } + } catch (Exception e) { + throw new WSDLException(WSDLException.PARSER_ERROR, e.getMessage(), e); + } finally { + try { + StaxUtils.close(xmlReader); + } catch (XMLStreamException ex) { + throw new WSDLException(WSDLException.PARSER_ERROR, ex.getMessage(), ex); + } + } + + // This is needed to avoid security exceptions when running with a security manager + if (System.getSecurityManager() == null) { + def = reader.readWSDL(wsdlLocator, doc.getDocumentElement()); + } else { + try { + def = AccessController.doPrivileged((PrivilegedExceptionAction) () -> reader + .readWSDL(wsdlLocator, doc.getDocumentElement())); + } catch (PrivilegedActionException paex) { + throw new WSDLException(WSDLException.PARSER_ERROR, paex.getMessage(), paex); + } + } + } else { + if (System.getSecurityManager() == null) { + def = reader.readWSDL(wsdlLocator); + } else { + try { + def = AccessController + .doPrivileged((PrivilegedExceptionAction) () -> reader.readWSDL(wsdlLocator)); + } catch (PrivilegedActionException paex) { + throw new WSDLException(WSDLException.PARSER_ERROR, paex.getMessage(), paex); + } + } + } + + return def; + } + + @Override + public void setXMLStreamReaderWrapper(XMLStreamReaderWrapper wrapper) { + super.setXMLStreamReaderWrapper(wrapper); + this.xmlStreamReaderWrapper = wrapper; + } + + static class QuarkusTransportURIResolver extends TransportURIResolver { + static final Logger LOG = LogUtils.getL7dLogger(TransportURIResolver.class); + private static final Set DEFAULT_URI_RESOLVER_HANDLES = new HashSet<>(); + static { + //bunch we really don't want to have the conduits checked for + //as we know the conduits don't handle. No point + //wasting the time checking/loading conduits and such + DEFAULT_URI_RESOLVER_HANDLES.add("file"); + DEFAULT_URI_RESOLVER_HANDLES.add("classpath"); + DEFAULT_URI_RESOLVER_HANDLES.add("wsjar"); + DEFAULT_URI_RESOLVER_HANDLES.add("jar"); + DEFAULT_URI_RESOLVER_HANDLES.add("zip"); + } + + public QuarkusTransportURIResolver(Bus b) { + super(b); + } + + @Override + public InputSource resolve(String curUri, String baseUri) { + // Spaces must be encoded or URI.resolve() will choke + curUri = curUri.replace(" ", "%20"); + + InputSource is = null; + URI base; + try { + if (baseUri == null) { + base = new URI(curUri); + } else { + base = new URI(baseUri); + base = base.resolve(curUri); + } + } catch (URISyntaxException use) { + //ignore + base = null; + LOG.log(Level.FINEST, "Could not resolve curUri " + curUri, use); + } + try { + if (base == null + || DEFAULT_URI_RESOLVER_HANDLES.contains(base.getScheme())) { + is = super.resolve(curUri, baseUri); + } + } catch (Exception ex) { + //nothing + LOG.log(Level.FINEST, "Default URI handlers could not resolve " + baseUri + " " + curUri, ex); + } + if (is == null && base != null + && base.getScheme() != null + && !DEFAULT_URI_RESOLVER_HANDLES.contains(base.getScheme())) { + try { + ConduitInitiatorManager mgr = bus.getExtension(ConduitInitiatorManager.class); + ConduitInitiator ci = null; + if ("http".equals(base.getScheme()) || "https".equals(base.getScheme())) { + //common case, don't "search" + ci = mgr.getConduitInitiator("http://cxf.apache.org/transports/http"); + } + if (ci == null) { + ci = mgr.getConduitInitiatorForUri(base.toString()); + } + if (ci != null) { + EndpointInfo info = new EndpointInfo(); + // set the endpointInfo name which could be used for configuration + info.setName(new QName("http://cxf.apache.org", "TransportURIResolver")); + info.setAddress(base.toString()); + Conduit c = ci.getConduit(info, bus); + Message message = new MessageImpl(); + Exchange exch = new ExchangeImpl(); + exch.put(Bus.class, bus); + message.setExchange(exch); + + message.put(Message.HTTP_REQUEST_METHOD, "GET"); + c.setMessageObserver(new MessageObserver() { + @Override + public void onMessage(Message message) { + LoadingByteArrayOutputStream bout = new LoadingByteArrayOutputStream(); + try { + IOUtils.copyAndCloseInput(message.getContent(InputStream.class), bout); + message.getExchange().put(InputStream.class, bout.createInputStream()); + } catch (IOException e) { + //ignore + } + } + }); + c.prepare(message); + c.close(message); + if (exch.getInMessage() != null) { + c.close(exch.getInMessage()); + } + if (exch.getInFaultMessage() != null) { + c.close(exch.getInFaultMessage()); + } + c.close(); + InputStream ins = exch.get(InputStream.class); + resourceOpened.add(ins); + InputSource src = new InputSource(ins); + String u = (String) message.get("transport.retransmit.url"); + if (u == null) { + u = base.toString(); + } + src.setPublicId(u); + src.setSystemId(u); + lastestImportUri = u; + currentResolver.unresolve(); + return src; + } + } catch (Exception e) { + throw new RuntimeException("Conduit initiator could not resolve " + baseUri + " " + curUri, e); + } + } + if (is == null + && (base == null + || base.getScheme() == null + || !DEFAULT_URI_RESOLVER_HANDLES.contains(base.getScheme()))) { + is = super.resolve(curUri, baseUri); + } + return is; + } + } + +}