Skip to content

Commit

Permalink
Clients with a class with postponed initialization in their method
Browse files Browse the repository at this point in the history
signatures cannot be compiled to native quarkiverse#580
  • Loading branch information
ppalaga committed Mar 23, 2023
1 parent 5856d70 commit 6eb8b74
Show file tree
Hide file tree
Showing 15 changed files with 327 additions and 13 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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
18 changes: 18 additions & 0 deletions docs/modules/ROOT/pages/includes/quarkus-cxf.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -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<some-number>.$Proxy<some-number> caused initialization of this class`. `jdk.proxy<some-number>.$Proxy<some-number>` 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]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}

}
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -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;
Expand All @@ -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;
Expand All @@ -63,10 +75,21 @@ public class CxfClientProcessor {
void collectClients(
CxfFixedConfig config,
CombinedIndexBuildItem combinedIndexBuildItem,
List<RuntimeInitializedClassBuildItem> runtimeInitializedClasses,
List<RuntimeInitializedPackageBuildItem> runtimeInitializedPackages,
BuildProducer<NativeImageFeatureBuildItem> nativeImageFeatures,
BuildProducer<NativeImageProxyDefinitionBuildItem> proxies,
BuildProducer<CxfClientBuildItem> clients) {
IndexView index = combinedIndexBuildItem.getIndex();

final Set<String> rtInitClasses = runtimeInitializedClasses.stream()
.map(RuntimeInitializedClassBuildItem::getClassName)
.collect(Collectors.toSet());
final Set<String> rtInitPackages = runtimeInitializedPackages.stream()
.map(RuntimeInitializedPackageBuildItem::getPackageName)
.collect(Collectors.toSet());

final AtomicBoolean hasRuntimeInitializedProxy = new AtomicBoolean(false);
final Map<String, ClientFixedConfig> clientSEIsInUse = findClientSEIsInUse(index, config);
CxfDeploymentUtils.webServiceAnnotations(index)
.forEach(annotation -> {
Expand All @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -242,13 +279,46 @@ static ClientFixedConfig findClientConfig(CxfFixedConfig config, String key, Str
void generateClientProducers(
List<CxfClientBuildItem> clients,
BuildProducer<GeneratedBeanBuildItem> generatedBeans,
BuildProducer<UnremovableBeanBuildItem> unremovableBeans) {
BuildProducer<UnremovableBeanBuildItem> unremovableBeans,
BuildProducer<ReflectiveClassBuildItem> 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 <a href="https://github.com/quarkiverse/quarkus-cxf/issues/580">#580</a>.
*
* @param generatedBeans
*/
private void copyMarkerInterfaceToApplication(BuildProducer<GeneratedBeanBuildItem> 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(
Expand Down Expand Up @@ -396,4 +466,84 @@ private void produceUnremovableBean(
.forEach(unremovables::produce);
}

private static class ProxyInfo {

public static ProxyInfo of(
boolean refersToRuntimeInitializedClasses,
ClassInfo wsClassInfo,
Set<String> rtInitClasses,
Set<String> rtInitPackages,
IndexView index) {
final List<String> 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<String> 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<String> 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<String> interfaces, boolean isRuntimeInitialized) {
super();
this.interfaces = interfaces;
this.isRuntimeInitialized = isRuntimeInitialized;
}

private final List<String> interfaces;
private final boolean isRuntimeInitialized;

}

}
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ public class CXFClientData implements Serializable {
private String sei;
private String wsName;
private String wsNamespace;
private boolean proxyClassRuntimeInitialized;

public CXFClientData() {
}
Expand All @@ -27,12 +28,14 @@ public CXFClientData(
String sei,
String wsName,
String wsNamespace,
List<String> classNames) {
List<String> 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<String> getClassNames() {
Expand All @@ -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<String> classNames) {
this.classNames = Collections.unmodifiableList(classNames);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ public class CXFClientInfo {
private String epName;
private String username;
private String password;
private boolean proxyClassRuntimeInitialized;
private final List<String> inInterceptors = new ArrayList<>();
private final List<String> outInterceptors = new ArrayList<>();
private final List<String> outFaultInterceptors = new ArrayList<>();
Expand All @@ -36,7 +37,8 @@ public CXFClientInfo(
String soapBinding,
String wsNamespace,
String wsName,
List<String> classNames) {
List<String> classNames,
boolean proxyClassRuntimeInitialized) {
this.classNames.addAll(classNames);
this.endpointAddress = endpointAddress;
this.epName = null;
Expand All @@ -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;
Expand Down Expand Up @@ -128,6 +132,10 @@ public List<String> getClassNames() {
return classNames;
}

public boolean isProxyClassRuntimeInitialized() {
return proxyClassRuntimeInitialized;
}

public List<String> getFeatures() {
return features;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,8 @@ public RuntimeValue<CXFClientInfo> cxfClientInfoSupplier(CXFClientData cxfClient
cxfClientData.getSoapBinding(),
cxfClientData.getWsNamespace(),
cxfClientData.getWsName(),
cxfClientData.getClassNames()));
cxfClientData.getClassNames(),
cxfClientData.isProxyClassRuntimeInitialized()));
}

private static class ServletConfig {
Expand Down
Loading

0 comments on commit 6eb8b74

Please sign in to comment.