From 6eb8b74b533fcb17aa423160d5cd3a40a9fbbb49 Mon Sep 17 00:00:00 2001 From: Peter Palaga Date: Fri, 17 Mar 2023 12:13:44 +0100 Subject: [PATCH] Clients with a class with postponed initialization in their method signatures cannot be compiled to native #580 --- .../calculator-client/application.properties | 1 + .../ROOT/pages/includes/quarkus-cxf.adoc | 18 ++ .../cxf/deployment/CxfClientBuildItem.java | 8 +- .../cxf/deployment/CxfClientProcessor.java | 162 +++++++++++++++++- .../io/quarkiverse/cxf/CXFClientData.java | 13 +- .../io/quarkiverse/cxf/CXFClientInfo.java | 12 +- .../java/io/quarkiverse/cxf/CXFRecorder.java | 3 +- .../io/quarkiverse/cxf/CxfClientProducer.java | 27 ++- .../io/quarkiverse/cxf/CxfFixedConfig.java | 29 ++++ .../cxf/QuarkusJaxWsProxyFactoryBean.java | 24 +++ .../cxf/graal/QuarkusCxfFeature.java | 20 +++ .../proxy/RuntimeInitializedProxyMarker.java | 4 + .../cxf/client/it/rtinit/Operands.java | 9 + .../cxf/client/it/rtinit/Result.java | 9 + .../src/main/resources/application.properties | 1 + 15 files changed, 327 insertions(+), 13 deletions(-) create mode 100644 extensions/core/runtime/src/main/java/io/quarkiverse/cxf/QuarkusJaxWsProxyFactoryBean.java create mode 100644 extensions/core/runtime/src/main/java/io/quarkiverse/cxf/graal/QuarkusCxfFeature.java create mode 100644 extensions/core/runtime/src/main/java/io/quarkiverse/cxf/runtime/proxy/RuntimeInitializedProxyMarker.java diff --git a/docs/modules/ROOT/examples/calculator-client/application.properties b/docs/modules/ROOT/examples/calculator-client/application.properties index eb8f47230..b7d52e1a3 100644 --- a/docs/modules/ROOT/examples/calculator-client/application.properties +++ b/docs/modules/ROOT/examples/calculator-client/application.properties @@ -25,4 +25,5 @@ quarkus.cxf.client.clientWithRuntimeInitializedPayload.client-endpoint-url=${cxf quarkus.cxf.client.clientWithRuntimeInitializedPayload.service-interface=io.quarkiverse.cxf.client.it.rtinit.ClientWithRuntimeInitializedPayload quarkus.cxf.client.clientWithRuntimeInitializedPayload.endpoint-namespace=http://www.jboss.org/eap/quickstarts/wscalculator/Calculator quarkus.cxf.client.clientWithRuntimeInitializedPayload.endpoint-name=CalculatorService +quarkus.cxf.client.clientWithRuntimeInitializedPayload.native.runtime-initialized = true quarkus.native.additional-build-args=--initialize-at-run-time=io.quarkiverse.cxf.client.it.rtinit.Operands\\,io.quarkiverse.cxf.client.it.rtinit.Result diff --git a/docs/modules/ROOT/pages/includes/quarkus-cxf.adoc b/docs/modules/ROOT/pages/includes/quarkus-cxf.adoc index 67ef7ea82..bd1ff299e 100644 --- a/docs/modules/ROOT/pages/includes/quarkus-cxf.adoc +++ b/docs/modules/ROOT/pages/includes/quarkus-cxf.adoc @@ -186,6 +186,24 @@ endif::add-copy-button-to-env-var[] |`false` +a|icon:lock[title=Fixed at build time] [[quarkus-cxf_quarkus.cxf.client.-clients-.native.runtime-initialized]]`link:#quarkus-cxf_quarkus.cxf.client.-clients-.native.runtime-initialized[quarkus.cxf.client."clients".native.runtime-initialized]` + +[.description] +-- +If `true`, the client dynamic proxy class generated by native compiler will be initialized at runtime; otherwise the proxy class will be initialized at build time. +Setting this to `true` makes sense if your service endpoint interface references some class initialized at runtime in its method signatures. E.g. Say, your service interface has method @code++{++int add(Operands o)++}++ and the `Operands` class was requested to be initialized at runtime. Then, without setting this configuration parameter to `true`, the native compiler will throw an exception saying something like `Classes that should be initialized at run time got initialized during image building: org.acme.Operands ... jdk.proxy.$Proxy caused initialization of this class`. `jdk.proxy.$Proxy` is the proxy class generated by the native compiler. +While `quarkus-cxf` can auto-detect the proper setting in some cases, the auto-detection is not perfect. This is because runtime initialization of classes can be requested in many ways out of which only the ones done via Quarkus `RuntimeInitializedClassBuildItem` and `RuntimeInitializedPackageBuildItem` can safely be observed by `quarkus-cxf`. In other cases, you'll have to set this manually. + +ifdef::add-copy-button-to-env-var[] +Environment variable: env_var_with_copy_button:+++QUARKUS_CXF_CLIENT__CLIENTS__NATIVE_RUNTIME_INITIALIZED+++[] +endif::add-copy-button-to-env-var[] +ifndef::add-copy-button-to-env-var[] +Environment variable: `+++QUARKUS_CXF_CLIENT__CLIENTS__NATIVE_RUNTIME_INITIALIZED+++` +endif::add-copy-button-to-env-var[] +--|boolean +|`false` + + a| [[quarkus-cxf_quarkus.cxf.endpoint.-endpoints-.implementor]]`link:#quarkus-cxf_quarkus.cxf.endpoint.-endpoints-.implementor[quarkus.cxf.endpoint."endpoints".implementor]` [.description] diff --git a/extensions/core/deployment/src/main/java/io/quarkiverse/cxf/deployment/CxfClientBuildItem.java b/extensions/core/deployment/src/main/java/io/quarkiverse/cxf/deployment/CxfClientBuildItem.java index 7ab8cfa7a..8df55c866 100644 --- a/extensions/core/deployment/src/main/java/io/quarkiverse/cxf/deployment/CxfClientBuildItem.java +++ b/extensions/core/deployment/src/main/java/io/quarkiverse/cxf/deployment/CxfClientBuildItem.java @@ -5,15 +5,21 @@ */ public final class CxfClientBuildItem extends AbstractEndpointBuildItem { private final String sei; + private final boolean proxyClassRuntimeInitialized; public CxfClientBuildItem(String sei, String soapBinding, String wsNamespace, - String wsName) { + String wsName, boolean runtimeInitialized) { super(soapBinding, wsNamespace, wsName); this.sei = sei; + this.proxyClassRuntimeInitialized = runtimeInitialized; } public String getSei() { return sei; } + public boolean isProxyClassRuntimeInitialized() { + return proxyClassRuntimeInitialized; + } + } 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 0ccb5b0c1..c296650c0 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 @@ -1,21 +1,27 @@ package io.quarkiverse.cxf.deployment; +import java.io.IOException; import java.lang.reflect.Modifier; +import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.List; import java.util.Map; -import java.util.Map.Entry; import java.util.Optional; +import java.util.Set; import java.util.TreeMap; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.function.Predicate; import java.util.stream.Collectors; import jakarta.enterprise.context.ApplicationScoped; import jakarta.enterprise.inject.Produces; import jakarta.enterprise.inject.spi.InjectionPoint; import jakarta.inject.Inject; +import jakarta.xml.ws.BindingProvider; import jakarta.xml.ws.soap.SOAPBinding; +import org.apache.cxf.endpoint.Client; import org.jboss.jandex.AnnotationInstance; import org.jboss.jandex.AnnotationTarget; import org.jboss.jandex.AnnotationValue; @@ -34,6 +40,7 @@ import io.quarkiverse.cxf.CxfFixedConfig; import io.quarkiverse.cxf.CxfFixedConfig.ClientFixedConfig; import io.quarkiverse.cxf.annotation.CXFClient; +import io.quarkiverse.cxf.graal.QuarkusCxfFeature; import io.quarkus.arc.deployment.GeneratedBeanBuildItem; import io.quarkus.arc.deployment.GeneratedBeanGizmoAdaptor; import io.quarkus.arc.deployment.SyntheticBeanBuildItem; @@ -44,7 +51,12 @@ import io.quarkus.deployment.annotations.ExecutionTime; import io.quarkus.deployment.annotations.Record; import io.quarkus.deployment.builditem.CombinedIndexBuildItem; +import io.quarkus.deployment.builditem.NativeImageFeatureBuildItem; import io.quarkus.deployment.builditem.nativeimage.NativeImageProxyDefinitionBuildItem; +import io.quarkus.deployment.builditem.nativeimage.ReflectiveClassBuildItem; +import io.quarkus.deployment.builditem.nativeimage.RuntimeInitializedClassBuildItem; +import io.quarkus.deployment.builditem.nativeimage.RuntimeInitializedPackageBuildItem; +import io.quarkus.deployment.util.IoUtil; import io.quarkus.gizmo.ClassCreator; import io.quarkus.gizmo.ClassOutput; import io.quarkus.gizmo.FieldCreator; @@ -63,10 +75,21 @@ public class CxfClientProcessor { void collectClients( CxfFixedConfig config, CombinedIndexBuildItem combinedIndexBuildItem, + List runtimeInitializedClasses, + List runtimeInitializedPackages, + BuildProducer nativeImageFeatures, BuildProducer proxies, BuildProducer clients) { IndexView index = combinedIndexBuildItem.getIndex(); + final Set rtInitClasses = runtimeInitializedClasses.stream() + .map(RuntimeInitializedClassBuildItem::getClassName) + .collect(Collectors.toSet()); + final Set rtInitPackages = runtimeInitializedPackages.stream() + .map(RuntimeInitializedPackageBuildItem::getPackageName) + .collect(Collectors.toSet()); + + final AtomicBoolean hasRuntimeInitializedProxy = new AtomicBoolean(false); final Map clientSEIsInUse = findClientSEIsInUse(index, config); CxfDeploymentUtils.webServiceAnnotations(index) .forEach(annotation -> { @@ -93,14 +116,27 @@ void collectClients( .map(bindingType -> bindingType.value().asString()) .orElse(SOAPBinding.SOAP11HTTP_BINDING); + final ProxyInfo proxyInfo = ProxyInfo.of( + Optional.ofNullable(clientConfig.native_).map(native_ -> native_.runtimeInitialized) + .orElse(false), + wsClassInfo, + rtInitClasses, + rtInitPackages, + index); + proxies.produce(new NativeImageProxyDefinitionBuildItem(proxyInfo.interfaces)); + clients.produce( - new CxfClientBuildItem(sei, soapBinding, wsNamespace, wsName)); - proxies.produce(new NativeImageProxyDefinitionBuildItem(wsClassInfo.name().toString(), - "jakarta.xml.ws.BindingProvider", "java.io.Closeable", "org.apache.cxf.endpoint.Client")); + new CxfClientBuildItem(sei, soapBinding, wsNamespace, wsName, proxyInfo.isRuntimeInitialized)); + + hasRuntimeInitializedProxy.set(hasRuntimeInitializedProxy.get() || proxyInfo.isRuntimeInitialized); } }); + if (hasRuntimeInitializedProxy.get()) { + nativeImageFeatures.produce(new NativeImageFeatureBuildItem(QuarkusCxfFeature.class)); + } + } @BuildStep @@ -124,7 +160,8 @@ void startClient( client.getSei(), client.getWsName(), client.getWsNamespace(), - wrapperClassNames.get(client.getSei()))) + wrapperClassNames.get(client.getSei()), + client.isProxyClassRuntimeInitialized())) .map(cxf -> { LOGGER.debugf("producing dedicated CXFClientInfo bean named '%s' for SEI %s", cxf.getSei(), cxf.getSei()); return SyntheticBeanBuildItem @@ -242,13 +279,46 @@ static ClientFixedConfig findClientConfig(CxfFixedConfig config, String key, Str void generateClientProducers( List clients, BuildProducer generatedBeans, - BuildProducer unremovableBeans) { + BuildProducer unremovableBeans, + BuildProducer reflectiveClasses) { clients .stream() .map(CxfClientBuildItem::getSei) .forEach(sei -> { generateCxfClientProducer(sei, generatedBeans, unremovableBeans); }); + + if (clients.stream().anyMatch(CxfClientBuildItem::isProxyClassRuntimeInitialized)) { + reflectiveClasses + .produce(ReflectiveClassBuildItem.builder(CxfClientProducer.RUNTIME_INITIALIZED_PROXY_MARKER_INTERFACE_NAME) + .build()); + copyMarkerInterfaceToApplication(generatedBeans); + } + } + + /** + * Copies the {@value CxfClientProducer#RUNTIME_INITIALIZED_PROXY_MARKER_INTERFACE_NAME} from the current + * classloader + * to the user application. Why we have do that: First, the interface is package-visible so that adding it to + * the client proxy definition forces GraalVM to generate the proxy class in + * {@value CxfClientProducer#RUNTIME_INITIALIZED_PROXY_MARKER_INTERFACE_PACKAGE} package rather than under a random + * package/class name. Thanks to that we can request the postponed initialization of the generated proxy class by package + * name. + * More details in #580. + * + * @param generatedBeans + */ + private void copyMarkerInterfaceToApplication(BuildProducer generatedBeans) { + byte[] bytes; + try { + bytes = IoUtil.readClassAsBytes(getClass().getClassLoader(), + CxfClientProducer.RUNTIME_INITIALIZED_PROXY_MARKER_INTERFACE_NAME); + } catch (IOException e) { + throw new RuntimeException("Could not read " + CxfClientProducer.RUNTIME_INITIALIZED_PROXY_MARKER_INTERFACE_NAME + + ".class from quarkus-cxf-deployment jar"); + } + String className = CxfClientProducer.RUNTIME_INITIALIZED_PROXY_MARKER_INTERFACE_NAME.replace('.', '/'); + generatedBeans.produce(new GeneratedBeanBuildItem(className, bytes)); } private void generateCxfClientProducer( @@ -396,4 +466,84 @@ private void produceUnremovableBean( .forEach(unremovables::produce); } + private static class ProxyInfo { + + public static ProxyInfo of( + boolean refersToRuntimeInitializedClasses, + ClassInfo wsClassInfo, + Set rtInitClasses, + Set rtInitPackages, + IndexView index) { + final List result = new ArrayList<>(); + result.add(wsClassInfo.name().toString()); + result.add(BindingProvider.class.getName()); + result.add("java.io.Closeable"); + result.add(Client.class.getName()); + + if (!refersToRuntimeInitializedClasses) { + /* Try to auto-detect unless the user decided himself */ + Predicate isRuntimeInitializedClass = className -> rtInitClasses.contains(className) + || rtInitPackages.contains(getPackage(className)); + refersToRuntimeInitializedClasses = refersToRuntimeInitializedClasses( + wsClassInfo, + isRuntimeInitializedClass, + index); + } + + if (refersToRuntimeInitializedClasses) { + result.add(io.quarkiverse.cxf.CxfClientProducer.RUNTIME_INITIALIZED_PROXY_MARKER_INTERFACE_NAME); + } + return new ProxyInfo(result, refersToRuntimeInitializedClasses); + } + + static String getPackage(String className) { + int lastDot = className.lastIndexOf('.'); + if (lastDot < 0) { + return ""; + } + return className.substring(0, lastDot); + } + + private static boolean refersToRuntimeInitializedClasses(ClassInfo wsClassInfo, + Predicate isRuntimeInitializedClass, IndexView index) { + if (isRuntimeInitializedClass.test(wsClassInfo.name().toString())) { + return true; + } + boolean ownMethods = wsClassInfo.methods().stream() + .filter(m -> (m.flags() & java.lang.reflect.Modifier.STATIC) == 0) // only non-static methods + .anyMatch(m -> isRuntimeInitializedClass.test(m.returnType().name().toString()) + || m.parameterTypes().stream() + .map(Type::name) + .map(DotName::toString) + .anyMatch(isRuntimeInitializedClass)); + if (ownMethods) { + return true; + } + + /* Do the same recursively for all interfaces */ + return wsClassInfo.interfaceNames().stream() + .map(intf -> { + final ClassInfo cl = index.getClassByName(intf); + if (cl == null) { + LOGGER.warnf( + "Could not check whether %s refers to runtime initialized classes because it was not found in Jandex", + intf); + } + return cl; + }) + .filter(cl -> cl != null) + .anyMatch(cl -> refersToRuntimeInitializedClasses(cl, isRuntimeInitializedClass, index)); + } + + private ProxyInfo(List interfaces, boolean isRuntimeInitialized) { + super(); + this.interfaces = interfaces; + this.isRuntimeInitialized = isRuntimeInitialized; + } + + private final List interfaces; + private final boolean isRuntimeInitialized; + + } + } diff --git a/extensions/core/runtime/src/main/java/io/quarkiverse/cxf/CXFClientData.java b/extensions/core/runtime/src/main/java/io/quarkiverse/cxf/CXFClientData.java index 8a7673937..4e0846f4f 100644 --- a/extensions/core/runtime/src/main/java/io/quarkiverse/cxf/CXFClientData.java +++ b/extensions/core/runtime/src/main/java/io/quarkiverse/cxf/CXFClientData.java @@ -18,6 +18,7 @@ public class CXFClientData implements Serializable { private String sei; private String wsName; private String wsNamespace; + private boolean proxyClassRuntimeInitialized; public CXFClientData() { } @@ -27,12 +28,14 @@ public CXFClientData( String sei, String wsName, String wsNamespace, - List classNames) { + List classNames, + boolean proxyClassRuntimeInitialized) { this.soapBinding = soapBinding; this.sei = sei; this.wsName = wsName; this.wsNamespace = wsNamespace; this.classNames = Collections.unmodifiableList(classNames); + this.proxyClassRuntimeInitialized = proxyClassRuntimeInitialized; } public List getClassNames() { @@ -55,6 +58,14 @@ public String getWsNamespace() { return wsNamespace; } + public boolean isProxyClassRuntimeInitialized() { + return proxyClassRuntimeInitialized; + } + + public void setProxyClassRuntimeInitialized(boolean proxyClassRuntimeInitialized) { + this.proxyClassRuntimeInitialized = proxyClassRuntimeInitialized; + } + public void setClassNames(List classNames) { this.classNames = Collections.unmodifiableList(classNames); } diff --git a/extensions/core/runtime/src/main/java/io/quarkiverse/cxf/CXFClientInfo.java b/extensions/core/runtime/src/main/java/io/quarkiverse/cxf/CXFClientInfo.java index abd77f054..3c2577d3a 100644 --- a/extensions/core/runtime/src/main/java/io/quarkiverse/cxf/CXFClientInfo.java +++ b/extensions/core/runtime/src/main/java/io/quarkiverse/cxf/CXFClientInfo.java @@ -19,6 +19,7 @@ public class CXFClientInfo { private String epName; private String username; private String password; + private boolean proxyClassRuntimeInitialized; private final List inInterceptors = new ArrayList<>(); private final List outInterceptors = new ArrayList<>(); private final List outFaultInterceptors = new ArrayList<>(); @@ -36,7 +37,8 @@ public CXFClientInfo( String soapBinding, String wsNamespace, String wsName, - List classNames) { + List classNames, + boolean proxyClassRuntimeInitialized) { this.classNames.addAll(classNames); this.endpointAddress = endpointAddress; this.epName = null; @@ -48,10 +50,12 @@ public CXFClientInfo( this.wsName = wsName; this.wsNamespace = wsNamespace; this.wsdlUrl = null; + this.proxyClassRuntimeInitialized = proxyClassRuntimeInitialized; } public CXFClientInfo(CXFClientInfo other) { - this(other.sei, other.endpointAddress, other.soapBinding, other.wsNamespace, other.wsName, other.classNames); + this(other.sei, other.endpointAddress, other.soapBinding, other.wsNamespace, other.wsName, other.classNames, + other.proxyClassRuntimeInitialized); this.wsdlUrl = other.wsdlUrl; this.epNamespace = other.epNamespace; this.epName = other.epName; @@ -128,6 +132,10 @@ public List getClassNames() { return classNames; } + public boolean isProxyClassRuntimeInitialized() { + return proxyClassRuntimeInitialized; + } + public List getFeatures() { return features; } 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 4b45796f1..66cff7293 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 @@ -38,7 +38,8 @@ public RuntimeValue cxfClientInfoSupplier(CXFClientData cxfClient cxfClientData.getSoapBinding(), cxfClientData.getWsNamespace(), cxfClientData.getWsName(), - cxfClientData.getClassNames())); + cxfClientData.getClassNames(), + cxfClientData.isProxyClassRuntimeInitialized())); } private static class ServletConfig { diff --git a/extensions/core/runtime/src/main/java/io/quarkiverse/cxf/CxfClientProducer.java b/extensions/core/runtime/src/main/java/io/quarkiverse/cxf/CxfClientProducer.java index 21a9500c8..14db8b618 100644 --- a/extensions/core/runtime/src/main/java/io/quarkiverse/cxf/CxfClientProducer.java +++ b/extensions/core/runtime/src/main/java/io/quarkiverse/cxf/CxfClientProducer.java @@ -2,6 +2,7 @@ import static java.util.stream.Collectors.toList; +import java.io.Closeable; import java.util.List; import java.util.Map; import java.util.stream.Collectors; @@ -12,11 +13,13 @@ import jakarta.enterprise.inject.spi.CDI; import jakarta.enterprise.inject.spi.InjectionPoint; import jakarta.inject.Inject; +import jakarta.xml.ws.BindingProvider; import jakarta.xml.ws.handler.Handler; import org.apache.cxf.Bus; import org.apache.cxf.common.spi.GeneratedNamespaceClassLoader; import org.apache.cxf.common.spi.NamespaceClassCreator; +import org.apache.cxf.endpoint.Client; import org.apache.cxf.endpoint.dynamic.ExceptionClassCreator; import org.apache.cxf.endpoint.dynamic.ExceptionClassLoader; import org.apache.cxf.feature.Feature; @@ -26,7 +29,6 @@ import org.apache.cxf.jaxb.FactoryClassLoader; import org.apache.cxf.jaxb.WrapperHelperClassLoader; import org.apache.cxf.jaxb.WrapperHelperCreator; -import org.apache.cxf.jaxws.JaxWsProxyFactoryBean; import org.apache.cxf.jaxws.spi.WrapperClassCreator; import org.apache.cxf.jaxws.spi.WrapperClassLoader; import org.apache.cxf.message.Message; @@ -49,6 +51,9 @@ public abstract class CxfClientProducer { private static final Logger LOGGER = Logger.getLogger(CxfClientProducer.class); + public static final String RUNTIME_INITIALIZED_PROXY_MARKER_INTERFACE_PACKAGE = "io.quarkiverse.cxf.runtime.proxy"; + public static final String RUNTIME_INITIALIZED_PROXY_MARKER_INTERFACE_NAME = "io.quarkiverse.cxf.runtime.proxy.RuntimeInitializedProxyMarker"; + @Inject CxfConfig config; @@ -76,8 +81,26 @@ private Object produceCxfClient(CXFClientInfo cxfClientInfo) { LOGGER.errorf("WebService interface (client) class %s not found", cxfClientInfo.getSei()); return null; } + Class[] interfaces; + try { + interfaces = cxfClientInfo.isProxyClassRuntimeInitialized() + ? new Class[] { + BindingProvider.class, + Closeable.class, + Client.class, + Class.forName(RUNTIME_INITIALIZED_PROXY_MARKER_INTERFACE_NAME, true, + Thread.currentThread().getContextClassLoader()) + } + : new Class[] { + BindingProvider.class, + Closeable.class, + Client.class + }; + } catch (ClassNotFoundException e) { + throw new RuntimeException("Could not load " + RUNTIME_INITIALIZED_PROXY_MARKER_INTERFACE_NAME, e); + } QuarkusClientFactoryBean quarkusClientFactoryBean = new QuarkusClientFactoryBean(cxfClientInfo.getClassNames()); - JaxWsProxyFactoryBean factory = new JaxWsProxyFactoryBean(quarkusClientFactoryBean); + QuarkusJaxWsProxyFactoryBean factory = new QuarkusJaxWsProxyFactoryBean(quarkusClientFactoryBean, interfaces); Bus bus = quarkusClientFactoryBean.getBus(true); bus.setExtension(new WrapperHelperClassLoader(bus), WrapperHelperCreator.class); bus.setExtension(new ExtensionClassLoader(bus), ExtensionClassCreator.class); diff --git a/extensions/core/runtime/src/main/java/io/quarkiverse/cxf/CxfFixedConfig.java b/extensions/core/runtime/src/main/java/io/quarkiverse/cxf/CxfFixedConfig.java index 25abf8a93..b68b9dcf9 100644 --- a/extensions/core/runtime/src/main/java/io/quarkiverse/cxf/CxfFixedConfig.java +++ b/extensions/core/runtime/src/main/java/io/quarkiverse/cxf/CxfFixedConfig.java @@ -37,10 +37,39 @@ public static class ClientFixedConfig { @ConfigItem(defaultValue = "false") public boolean alternative; + /** Configuration options related to native mode */ + @ConfigItem(name = "native") + public NativeClientFixedConfig native_; + public static ClientFixedConfig createDefault() { ClientFixedConfig result = new ClientFixedConfig(); result.serviceInterface = Optional.empty(); return result; } } + + @ConfigGroup + public static class NativeClientFixedConfig { + + /** + * If {@code true}, the client dynamic proxy class generated by native compiler will be initialized at runtime; + * otherwise the proxy class will be initialized at build time. + *

+ * Setting this to {@code true} makes sense if your service endpoint interface references some class initialized + * at runtime in its method signatures. E.g. Say, your service interface has method @code{int add(Operands o)} + * and the {@code Operands} class was requested to be initialized at runtime. Then, without setting this + * configuration parameter to {@code true}, the native compiler will throw an exception saying something like + * {@code Classes that should be initialized at run time got initialized during image building: org.acme.Operands ... jdk.proxy.$Proxy caused initialization of this class}. + * {@code jdk.proxy.$Proxy} is the proxy class generated by the native compiler. + *

+ * While {@code quarkus-cxf} can auto-detect the proper setting in some cases, the auto-detection is not perfect. + * This is because runtime initialization of classes can be requested in many ways out of which only the ones + * done via Quarkus {@code RuntimeInitializedClassBuildItem} and {@code RuntimeInitializedPackageBuildItem} + * can safely be observed by {@code quarkus-cxf}. In other cases, you'll have to set this manually. + */ + @ConfigItem(defaultValue = "false") + public boolean runtimeInitialized; + + } + } diff --git a/extensions/core/runtime/src/main/java/io/quarkiverse/cxf/QuarkusJaxWsProxyFactoryBean.java b/extensions/core/runtime/src/main/java/io/quarkiverse/cxf/QuarkusJaxWsProxyFactoryBean.java new file mode 100644 index 000000000..cb61c9cb4 --- /dev/null +++ b/extensions/core/runtime/src/main/java/io/quarkiverse/cxf/QuarkusJaxWsProxyFactoryBean.java @@ -0,0 +1,24 @@ +package io.quarkiverse.cxf; + +import org.apache.cxf.frontend.ClientFactoryBean; +import org.apache.cxf.jaxws.JaxWsProxyFactoryBean; + +public class QuarkusJaxWsProxyFactoryBean extends JaxWsProxyFactoryBean { + + private final Class[] additionalImplementingClasses; + + public QuarkusJaxWsProxyFactoryBean(ClientFactoryBean fact, Class... additionalImplementingClasses) { + super(fact); + this.additionalImplementingClasses = additionalImplementingClasses; + } + + @Override + protected Class[] getImplementingClasses() { + Class cls = getClientFactoryBean().getServiceClass(); + Class[] result = new Class[additionalImplementingClasses.length + 1]; + result[0] = cls; + System.arraycopy(additionalImplementingClasses, 0, result, 1, additionalImplementingClasses.length); + return result; + } + +} diff --git a/extensions/core/runtime/src/main/java/io/quarkiverse/cxf/graal/QuarkusCxfFeature.java b/extensions/core/runtime/src/main/java/io/quarkiverse/cxf/graal/QuarkusCxfFeature.java new file mode 100644 index 000000000..b589774db --- /dev/null +++ b/extensions/core/runtime/src/main/java/io/quarkiverse/cxf/graal/QuarkusCxfFeature.java @@ -0,0 +1,20 @@ +package io.quarkiverse.cxf.graal; + +import org.graalvm.nativeimage.hosted.Feature; +import org.graalvm.nativeimage.hosted.RuntimeClassInitialization; + +import io.quarkiverse.cxf.CxfClientProducer; + +/** + * + */ +public class QuarkusCxfFeature implements Feature { + @Override + public void afterRegistration(AfterRegistrationAccess access) { + /* + * We cannot do this using a RuntimeInitializedPackageBuildItem because it would cause a dependency cycle in + * io.quarkiverse.cxf.deployment.CxfClientProcessor.collectClients() + */ + RuntimeClassInitialization.initializeAtRunTime(CxfClientProducer.RUNTIME_INITIALIZED_PROXY_MARKER_INTERFACE_PACKAGE); + } +} diff --git a/extensions/core/runtime/src/main/java/io/quarkiverse/cxf/runtime/proxy/RuntimeInitializedProxyMarker.java b/extensions/core/runtime/src/main/java/io/quarkiverse/cxf/runtime/proxy/RuntimeInitializedProxyMarker.java new file mode 100644 index 000000000..6f1addd8f --- /dev/null +++ b/extensions/core/runtime/src/main/java/io/quarkiverse/cxf/runtime/proxy/RuntimeInitializedProxyMarker.java @@ -0,0 +1,4 @@ +package io.quarkiverse.cxf.runtime.proxy; + +interface RuntimeInitializedProxyMarker { +} \ No newline at end of file diff --git a/integration-tests/client/src/main/java/io/quarkiverse/cxf/client/it/rtinit/Operands.java b/integration-tests/client/src/main/java/io/quarkiverse/cxf/client/it/rtinit/Operands.java index a2b72048a..9834d53a3 100644 --- a/integration-tests/client/src/main/java/io/quarkiverse/cxf/client/it/rtinit/Operands.java +++ b/integration-tests/client/src/main/java/io/quarkiverse/cxf/client/it/rtinit/Operands.java @@ -3,6 +3,15 @@ import java.util.Objects; +import jakarta.xml.bind.annotation.XmlAccessType; +import jakarta.xml.bind.annotation.XmlAccessorType; +import jakarta.xml.bind.annotation.XmlType; + +@XmlAccessorType(XmlAccessType.FIELD) +@XmlType(name = "operands", propOrder = { + "a", + "b" +}) public class Operands { private int a; private int b; diff --git a/integration-tests/client/src/main/java/io/quarkiverse/cxf/client/it/rtinit/Result.java b/integration-tests/client/src/main/java/io/quarkiverse/cxf/client/it/rtinit/Result.java index 4ea8bad6a..ecc6cee79 100644 --- a/integration-tests/client/src/main/java/io/quarkiverse/cxf/client/it/rtinit/Result.java +++ b/integration-tests/client/src/main/java/io/quarkiverse/cxf/client/it/rtinit/Result.java @@ -3,6 +3,15 @@ import java.util.Objects; +import jakarta.xml.bind.annotation.XmlAccessType; +import jakarta.xml.bind.annotation.XmlAccessorType; +import jakarta.xml.bind.annotation.XmlType; + +@XmlAccessorType(XmlAccessType.FIELD) +@XmlType(name = "result", propOrder = { + "operands", + "result" +}) public class Result { private int result; diff --git a/integration-tests/client/src/main/resources/application.properties b/integration-tests/client/src/main/resources/application.properties index eb8f47230..b7d52e1a3 100644 --- a/integration-tests/client/src/main/resources/application.properties +++ b/integration-tests/client/src/main/resources/application.properties @@ -25,4 +25,5 @@ quarkus.cxf.client.clientWithRuntimeInitializedPayload.client-endpoint-url=${cxf quarkus.cxf.client.clientWithRuntimeInitializedPayload.service-interface=io.quarkiverse.cxf.client.it.rtinit.ClientWithRuntimeInitializedPayload quarkus.cxf.client.clientWithRuntimeInitializedPayload.endpoint-namespace=http://www.jboss.org/eap/quickstarts/wscalculator/Calculator quarkus.cxf.client.clientWithRuntimeInitializedPayload.endpoint-name=CalculatorService +quarkus.cxf.client.clientWithRuntimeInitializedPayload.native.runtime-initialized = true quarkus.native.additional-build-args=--initialize-at-run-time=io.quarkiverse.cxf.client.it.rtinit.Operands\\,io.quarkiverse.cxf.client.it.rtinit.Result