Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix OIDC/GraphQL client initialization #36623

Merged
merged 1 commit into from
Oct 23, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
import io.quarkus.deployment.builditem.FeatureBuildItem;
import io.quarkus.oidc.client.graphql.runtime.OidcClientGraphQLConfig;
import io.quarkus.oidc.client.graphql.runtime.OidcGraphQLClientIntegrationRecorder;
import io.quarkus.smallrye.graphql.client.deployment.GraphQLClientConfigInitializedBuildItem;

public class OidcGraphQLClientIntegrationProcessor {

Expand All @@ -37,7 +38,8 @@ void feature(BuildProducer<FeatureBuildItem> featureProducer) {
void initialize(BeanContainerBuildItem containerBuildItem,
OidcGraphQLClientIntegrationRecorder recorder,
OidcClientGraphQLConfig config,
BeanArchiveIndexBuildItem index) {
BeanArchiveIndexBuildItem index,
GraphQLClientConfigInitializedBuildItem configInitialized) {
Map<String, String> configKeysToOidcClients = new HashMap<>();
for (AnnotationInstance annotation : index.getIndex().getAnnotations(GRAPHQL_CLIENT_API)) {
ClassInfo clazz = annotation.target().asClass();
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package io.quarkus.smallrye.graphql.client.deployment;

import io.quarkus.builder.item.SimpleBuildItem;

/**
* This marks that the SmallRye GraphQL Client configurations have been processed and initialized.
*/
public final class GraphQLClientConfigInitializedBuildItem extends SimpleBuildItem {
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@
import java.util.Map;

import jakarta.enterprise.context.ApplicationScoped;
import jakarta.inject.Singleton;

import org.jboss.jandex.AnnotationInstance;
import org.jboss.jandex.AnnotationValue;
Expand All @@ -33,8 +32,6 @@
import io.quarkus.deployment.builditem.nativeimage.ReflectiveClassBuildItem;
import io.quarkus.deployment.builditem.nativeimage.ReflectiveHierarchyBuildItem;
import io.quarkus.deployment.builditem.nativeimage.ServiceProviderBuildItem;
import io.quarkus.runtime.RuntimeValue;
import io.quarkus.smallrye.graphql.client.runtime.GraphQLClientConfigurationMergerBean;
import io.quarkus.smallrye.graphql.client.runtime.GraphQLClientSupport;
import io.quarkus.smallrye.graphql.client.runtime.GraphQLClientsConfig;
import io.quarkus.smallrye.graphql.client.runtime.SmallRyeGraphQLClientRecorder;
Expand Down Expand Up @@ -157,7 +154,7 @@ void setTypesafeApiClasses(BeanArchiveIndexBuildItem index,
*/
@BuildStep
@Record(RUNTIME_INIT)
void initializeClientSupport(BuildProducer<SyntheticBeanBuildItem> syntheticBeans,
GraphQLClientConfigInitializedBuildItem mergeClientConfigurations(BuildProducer<SyntheticBeanBuildItem> syntheticBeans,
SmallRyeGraphQLClientRecorder recorder,
GraphQLClientsConfig quarkusConfig,
BeanArchiveIndexBuildItem index) {
Expand All @@ -182,33 +179,12 @@ void initializeClientSupport(BuildProducer<SyntheticBeanBuildItem> syntheticBean
knownConfigKeys.add(configKey);
}

RuntimeValue<GraphQLClientSupport> support = recorder.clientSupport(shortNamesToQualifiedNames,
knownConfigKeys);

DotName supportClassName = DotName.createSimple(GraphQLClientSupport.class.getName());
SyntheticBeanBuildItem bean = SyntheticBeanBuildItem
.configure(supportClassName)
.addType(supportClassName)
.scope(Singleton.class)
.runtimeValue(support)
.setRuntimeInit()
.unremovable()
.done();
syntheticBeans.produce(bean);
}

@BuildStep
AdditionalBeanBuildItem configurationMergerBean() {
return AdditionalBeanBuildItem.unremovableOf(GraphQLClientConfigurationMergerBean.class);
}
GraphQLClientSupport support = new GraphQLClientSupport();
support.setShortNamesToQualifiedNamesMapping(shortNamesToQualifiedNames);
support.setKnownConfigKeys(knownConfigKeys);

// FIXME: this seems unnecessary, but is needed to make sure that the GraphQLClientConfigurationMergerBean
// gets initialized, can this be done differently?
@BuildStep
@Record(RUNTIME_INIT)
void initializeConfigMergerBean(BeanContainerBuildItem containerBuildItem,
SmallRyeGraphQLClientRecorder recorder) {
recorder.initializeConfigurationMergerBean();
recorder.mergeClientConfigurations(support, quarkusConfig);
return new GraphQLClientConfigInitializedBuildItem();
}

@BuildStep
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,13 +1,20 @@
package io.quarkus.smallrye.graphql.client.runtime;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.function.Supplier;
import java.util.stream.Collectors;

import io.quarkus.arc.Arc;
import io.quarkus.runtime.RuntimeValue;
import org.eclipse.microprofile.config.Config;
import org.eclipse.microprofile.config.ConfigProvider;
import org.jboss.logging.Logger;

import io.quarkus.runtime.LaunchMode;
import io.quarkus.runtime.annotations.Recorder;
import io.smallrye.graphql.client.impl.GraphQLClientConfiguration;
import io.smallrye.graphql.client.impl.GraphQLClientsConfiguration;
import io.smallrye.graphql.client.typesafe.api.TypesafeGraphQLClientBuilder;
import io.smallrye.graphql.client.vertx.VertxManager;
Expand All @@ -16,6 +23,8 @@
@Recorder
public class SmallRyeGraphQLClientRecorder {

private final Logger logger = Logger.getLogger(SmallRyeGraphQLClientRecorder.class);

public <T> Supplier<T> typesafeClientSupplier(Class<T> targetClassName) {
return () -> {
TypesafeGraphQLClientBuilder builder = TypesafeGraphQLClientBuilder.newBuilder();
Expand All @@ -36,22 +45,95 @@ public void setTypesafeApiClasses(List<String> apiClassNames) {
configBean.addTypesafeClientApis(classes);
}

public RuntimeValue<GraphQLClientSupport> clientSupport(Map<String, String> shortNamesToQualifiedNames,
List<String> knownConfigKeys) {
GraphQLClientSupport support = new GraphQLClientSupport();
support.setShortNamesToQualifiedNamesMapping(shortNamesToQualifiedNames);
support.setKnownConfigKeys(knownConfigKeys);
return new RuntimeValue<>(support);
}
public void mergeClientConfigurations(GraphQLClientSupport support, GraphQLClientsConfig quarkusConfiguration) {
GraphQLClientsConfiguration upstreamConfigs = GraphQLClientsConfiguration.getInstance();
for (Map.Entry<String, GraphQLClientConfig> client : quarkusConfiguration.clients.entrySet()) {
// the raw config key provided in the config, this might be a short class name,
// so translate that into the fully qualified name if applicable
String rawConfigKey = client.getKey();
Map<String, String> shortNamesToQualifiedNamesMapping = support.getShortNamesToQualifiedNamesMapping();
String configKey = shortNamesToQualifiedNamesMapping != null &&
shortNamesToQualifiedNamesMapping.containsKey(rawConfigKey)
? shortNamesToQualifiedNamesMapping.get(rawConfigKey)
: rawConfigKey;

public void initializeConfigurationMergerBean() {
GraphQLClientConfigurationMergerBean merger = Arc.container()
.instance(GraphQLClientConfigurationMergerBean.class).get();
merger.nothing();
GraphQLClientConfig quarkusConfig = client.getValue();
// if SmallRye configuration does not contain this client, simply use it
GraphQLClientConfiguration upstreamConfig = upstreamConfigs.getClient(configKey);
if (upstreamConfig == null) {
GraphQLClientConfiguration transformed = toSmallRyeNativeConfiguration(quarkusConfig);
upstreamConfigs.addClient(configKey, transformed);
} else {
// if SmallRye configuration already contains this client, enhance it with the Quarkus configuration
upstreamConfig.merge(toSmallRyeNativeConfiguration(quarkusConfig));
}
}

// allow automatically wiring client to the local server instance in test mode
if (LaunchMode.current() == LaunchMode.TEST) {
String testUrl = null;
for (String configKey : support.getKnownConfigKeys()) {
GraphQLClientConfiguration config = upstreamConfigs.getClient(configKey);
if (config.getUrl() == null) {
if (testUrl == null) {
testUrl = getTestingServerUrl();
}
logger.info("Automatically wiring the URL of GraphQL client named " + configKey + " to " + testUrl
+ ". If this is incorrect, " +
"please set it manually using the quarkus.smallrye-graphql-client." + maybeWithQuotes(configKey)
+ ".url property. Also note that" +
" this autowiring is only supported during tests.");
config.setUrl(testUrl);
}
}
}
}

public void setGlobalVertxInstance(Supplier<Vertx> vertx) {
VertxManager.setFromGlobal(vertx.get());
}

private String maybeWithQuotes(String key) {
if (key.contains(".")) {
return "\"" + key + "\"";
} else {
return key;
}
}

// translates a Quarkus `GraphQLClientConfig` configuration object to `GraphQLClientConfiguration` which is understood
// by SmallRye GraphQL
private GraphQLClientConfiguration toSmallRyeNativeConfiguration(GraphQLClientConfig quarkusConfig) {
GraphQLClientConfiguration transformed = new GraphQLClientConfiguration();
transformed.setHeaders(quarkusConfig.headers);
transformed.setInitPayload(Optional.ofNullable(quarkusConfig.initPayload)
.map(m -> new HashMap<String, Object>(m)).orElse(null));
quarkusConfig.url.ifPresent(transformed::setUrl);
transformed.setWebsocketSubprotocols(quarkusConfig.subprotocols.orElse(new ArrayList<>()));
quarkusConfig.keyStore.ifPresent(transformed::setKeyStore);
quarkusConfig.keyStoreType.ifPresent(transformed::setKeyStoreType);
quarkusConfig.keyStorePassword.ifPresent(transformed::setKeyStorePassword);
quarkusConfig.trustStore.ifPresent(transformed::setTrustStore);
quarkusConfig.trustStoreType.ifPresent(transformed::setTrustStoreType);
quarkusConfig.trustStorePassword.ifPresent(transformed::setTrustStorePassword);
quarkusConfig.proxyHost.ifPresent(transformed::setProxyHost);
quarkusConfig.proxyPort.ifPresent(transformed::setProxyPort);
quarkusConfig.proxyUsername.ifPresent(transformed::setProxyUsername);
quarkusConfig.proxyPassword.ifPresent(transformed::setProxyPassword);
quarkusConfig.maxRedirects.ifPresent(transformed::setMaxRedirects);
quarkusConfig.executeSingleResultOperationsOverWebsocket
.ifPresent(transformed::setExecuteSingleOperationsOverWebsocket);
quarkusConfig.websocketInitializationTimeout.ifPresent(transformed::setWebsocketInitializationTimeout);
quarkusConfig.allowUnexpectedResponseFields.ifPresent(transformed::setAllowUnexpectedResponseFields);
transformed.setDynamicHeaders(new HashMap<>());
return transformed;
}

private String getTestingServerUrl() {
Config config = ConfigProvider.getConfig();
// the client extension doesn't have dependencies on neither the server extension nor quarkus-vertx-http, so guessing
// is somewhat limited
return "http://localhost:" + config.getOptionalValue("quarkus.http.test-port", int.class).orElse(8081) + "/graphql";
}

}