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

SmallRye GraphQL 1.2.7 #18297

Merged
merged 3 commits into from
Jul 4, 2021
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
2 changes: 1 addition & 1 deletion bom/application/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@
<smallrye-health.version>3.1.1</smallrye-health.version>
<smallrye-metrics.version>3.0.1</smallrye-metrics.version>
<smallrye-open-api.version>2.1.7</smallrye-open-api.version>
<smallrye-graphql.version>1.2.5</smallrye-graphql.version>
<smallrye-graphql.version>1.2.7</smallrye-graphql.version>
<smallrye-opentracing.version>2.0.0</smallrye-opentracing.version>
<smallrye-fault-tolerance.version>5.1.0</smallrye-fault-tolerance.version>
<smallrye-jwt.version>3.2.0</smallrye-jwt.version>
Expand Down
6 changes: 3 additions & 3 deletions docs/src/main/asciidoc/smallrye-graphql-client.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -235,7 +235,7 @@ specify that within the `@GraphQLClientApi` annotation (by setting the `endpoint
or move this over to the configuration file, `application.properties`:

----
star-wars-typesafe/mp-graphql/url=https://swapi-graphql.netlify.app/.netlify/functions/index
quarkus.smallrye-graphql-client.star-wars-typesafe.url=https://swapi-graphql.netlify.app/.netlify/functions/index
----

`star-wars-typesafe` is the name of the configured client instance, and corresponds to the `configKey`
Expand Down Expand Up @@ -274,7 +274,7 @@ representations of the GraphQL types and documents. The client API interface is

We still need to configure the URL for the client, so let's put this into `application.properties`:
----
star-wars-dynamic/mp-graphql/url=https://swapi-graphql.netlify.app/.netlify/functions/index
quarkus.smallrye-graphql-client.star-wars-dynamic.url=https://swapi-graphql.netlify.app/.netlify/functions/index
----

We decided to name the client `star-wars-dynamic`. We will use this name when injecting a dynamic client
Expand All @@ -283,7 +283,7 @@ to properly qualify the injection point.
If you need to add an authorization header, or any other custom HTTP header (in our case
it's not required), this can be done by:
----
star-wars-dynamic/mp-graphql/header/HEADER-KEY=HEADER-VALUE"
quarkus.smallrye-graphql-client.star-wars-dynamic.header.HEADER-KEY=HEADER-VALUE"
----

Add this to the `StarWarsResource` created earlier:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@
import static io.quarkus.deployment.annotations.ExecutionTime.RUNTIME_INIT;
import static io.quarkus.deployment.annotations.ExecutionTime.STATIC_INIT;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.inject.Singleton;
Expand All @@ -17,6 +19,7 @@
import io.quarkus.arc.deployment.AdditionalBeanBuildItem;
import io.quarkus.arc.deployment.AutoInjectAnnotationBuildItem;
import io.quarkus.arc.deployment.BeanArchiveIndexBuildItem;
import io.quarkus.arc.deployment.BeanContainerBuildItem;
import io.quarkus.arc.deployment.SyntheticBeanBuildItem;
import io.quarkus.deployment.Feature;
import io.quarkus.deployment.annotations.BuildProducer;
Expand All @@ -27,6 +30,9 @@
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 @@ -109,21 +115,67 @@ void initializeTypesafeClient(BeanArchiveIndexBuildItem index,
reflectiveClass.produce(ReflectiveClassBuildItem.builder("java.util.Collection").methods(true).build());
}

/**
* io.smallrye.graphql.client.GraphQLClientsConfiguration bean requires knowledge of all interfaces annotated
* with `@GraphQLClientApi`
*/
@BuildStep
@Record(STATIC_INIT)
void setTypesafeApiClasses(BeanArchiveIndexBuildItem index,
BeanContainerBuildItem beanContainerBuildItem,
SmallRyeGraphQLClientRecorder recorder) {
List<String> apiClassNames = new ArrayList<>();
for (AnnotationInstance annotation : index.getIndex().getAnnotations(GRAPHQL_CLIENT_API)) {
ClassInfo apiClassInfo = annotation.target().asClass();
apiClassNames.add(apiClassInfo.name().toString());
}
recorder.setTypesafeApiClasses(apiClassNames);
}

/**
* Allows the optional usage of short class names in GraphQL client configuration rather than
* fully qualified names. This method computes a mapping between short names and qualified names,
* and the configuration merger bean will take it into account when merging Quarkus configuration
* with SmallRye-side configuration.
*/
@BuildStep
@Record(RUNTIME_INIT)
void translateClientConfiguration(BeanArchiveIndexBuildItem index,
void shortNamesToQualifiedNames(BuildProducer<SyntheticBeanBuildItem> syntheticBeans,
SmallRyeGraphQLClientRecorder recorder,
GraphQLClientsConfig config) {
// Map with all classes annotated with @GraphQLApi, the key is its short name,
// value is the fully qualified name. The reason is being able to match short name
// used in the configuration to a class
GraphQLClientsConfig quarkusConfig,
BeanArchiveIndexBuildItem index) {
Map<String, String> shortNamesToQualifiedNames = new HashMap<>();
for (AnnotationInstance annotation : index.getIndex().getAnnotations(GRAPHQL_CLIENT_API)) {
ClassInfo clazz = annotation.target().asClass();
shortNamesToQualifiedNames.put(clazz.name().withoutPackagePrefix(), clazz.name().toString());
}

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

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);
}

// 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();
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -27,10 +27,8 @@ public class TypesafeGraphQLClientInjectionTest {
static QuarkusUnitTest test = new QuarkusUnitTest()
.setArchiveProducer(() -> ShrinkWrap.create(JavaArchive.class)
.addClasses(TestingGraphQLApi.class, TestingGraphQLClientApi.class, Person.class)
.addAsResource(new StringAsset("typesafeclient/mp-graphql/url=" + url),
// TODO: adding headers via config is not supported by typesafe client yet
// + "\n" +
// "typesafeclient/mp-graphql/header/My-Header=My-Value"),
.addAsResource(new StringAsset("typesafeclient/mp-graphql/url=" + url + "\n" +
"typesafeclient/mp-graphql/header/My-Header=My-Value"),
"application.properties")
.addAsManifestResource(EmptyAsset.INSTANCE, "beans.xml"));

Expand All @@ -44,10 +42,14 @@ public void performQuery() {
assertEquals("Arthur", people.get(1).getFirstName());
}

// TODO: adding headers via config is not supported by typesafe client yet
// @Test
// public void checkHeaders() throws ExecutionException, InterruptedException {
// assertEquals("My-Value", client.returnHeader("My-Header"));
// }
/**
* Verify that configured HTTP headers are applied by the client.
* We do this by asking the server side to read the header received from the client and send
* its value back to the client.
*/
@Test
public void checkHeaders() {
assertEquals("My-Value", client.returnHeader("My-Header"));
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -27,10 +27,8 @@ public class TypesafeGraphQLClientInjectionWithQuarkusConfigTest {
static QuarkusUnitTest test = new QuarkusUnitTest()
.setArchiveProducer(() -> ShrinkWrap.create(JavaArchive.class)
.addClasses(TestingGraphQLApi.class, TestingGraphQLClientApi.class, Person.class)
.addAsResource(new StringAsset("quarkus.smallrye-graphql-client.typesafeclient.url=" + url),
// TODO: adding headers via config is not supported by typesafe client yet
// + "\n" +
// "quarkus.smallrye-graphql-client.typesafeclient.header.My-Header=My-Value"),
.addAsResource(new StringAsset("quarkus.smallrye-graphql-client.typesafeclient.url=" + url + "\n" +
"quarkus.smallrye-graphql-client.typesafeclient.header.My-Header=My-Value"),
"application.properties")
.addAsManifestResource(EmptyAsset.INSTANCE, "beans.xml"));

Expand All @@ -44,10 +42,14 @@ public void performQuery() {
assertEquals("Arthur", people.get(1).getFirstName());
}

// TODO: adding headers via config is not supported by typesafe client yet
// @Test
// public void checkHeaders() throws ExecutionException, InterruptedException {
// assertEquals("My-Value", client.returnHeader("My-Header"));
// }
/**
* Verify that configured HTTP headers are applied by the client.
* We do this by asking the server side to read the header received from the client and send
* its value back to the client.
*/
@Test
public void checkHeaders() {
assertEquals("My-Value", client.returnHeader("My-Header"));
}

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

import java.util.Map;

import javax.annotation.PostConstruct;
import javax.inject.Inject;
import javax.inject.Singleton;

import io.smallrye.graphql.client.GraphQLClientConfiguration;
import io.smallrye.graphql.client.GraphQLClientsConfiguration;

/**
* On startup, this beans takes Quarkus-specific configuration of GraphQL clients (quarkus.* properties)
* and merges this configuration with the configuration parsed by SmallRye GraphQL itself (CLIENT/mp-graphql/* properties)
*
* The resulting merged configuration resides in the application-scoped `io.smallrye.graphql.client.GraphQLClientConfiguration`
*
* Quarkus configuration overrides SmallRye configuration where applicable.
*/
@Singleton
public class GraphQLClientConfigurationMergerBean {

@Inject
GraphQLClientsConfiguration upstreamConfiguration;

@Inject
GraphQLClientsConfig quarkusConfiguration;

@Inject
GraphQLClientSupport support;

@PostConstruct
void enhanceGraphQLConfiguration() {
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;

GraphQLClientConfig quarkusConfig = client.getValue();
// if SmallRye configuration does not contain this client, simply use it
if (!upstreamConfiguration.getClients().containsKey(configKey)) {
GraphQLClientConfiguration transformed = new GraphQLClientConfiguration();
transformed.setHeaders(quarkusConfig.headers);
transformed.setUrl(quarkusConfig.url);
upstreamConfiguration.getClients().put(configKey, transformed);
} else {
// if SmallRye configuration already contains this client, override it with the Quarkus configuration
GraphQLClientConfiguration upstreamConfig = upstreamConfiguration.getClients().get(configKey);
if (quarkusConfig.url != null) {
upstreamConfig.setUrl(quarkusConfig.url);
}
// merge the headers
if (quarkusConfig.headers != null) {
upstreamConfig.getHeaders().putAll(quarkusConfig.headers);
}
}
}

}

public void nothing() {
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package io.quarkus.smallrye.graphql.client.runtime;

import java.util.Map;

/**
* Items from build time needed to make available at runtime.
*/
public class GraphQLClientSupport {

/**
* Allows the optional usage of short class names in GraphQL client configuration rather than
* fully qualified names. The configuration merger bean will take it into account
* when merging Quarkus configuration with SmallRye-side configuration.
*/
private Map<String, String> shortNamesToQualifiedNamesMapping;

public Map<String, String> getShortNamesToQualifiedNamesMapping() {
return shortNamesToQualifiedNamesMapping;
}

public void setShortNamesToQualifiedNamesMapping(Map<String, String> shortNamesToQualifiedNamesMapping) {
this.shortNamesToQualifiedNamesMapping = shortNamesToQualifiedNamesMapping;
}
}
Original file line number Diff line number Diff line change
@@ -1,9 +1,14 @@
package io.quarkus.smallrye.graphql.client.runtime;

import java.util.List;
import java.util.Map;
import java.util.function.Supplier;
import java.util.stream.Collectors;

import io.quarkus.arc.Arc;
import io.quarkus.runtime.RuntimeValue;
import io.quarkus.runtime.annotations.Recorder;
import io.smallrye.graphql.client.GraphQLClientsConfiguration;
import io.smallrye.graphql.client.typesafe.api.TypesafeGraphQLClientBuilder;

@Recorder
Expand All @@ -16,21 +21,28 @@ public <T> Supplier<T> typesafeClientSupplier(Class<T> targetClassName) {
};
}

/**
* Translates quarkus.* configuration properties to system properties understood by SmallRye GraphQL.
*/
public void translateClientConfiguration(GraphQLClientsConfig clientsConfig,
Map<String, String> shortNamesToQualifiedNames) {
for (Map.Entry<String, GraphQLClientConfig> client : clientsConfig.clients.entrySet()) {
String configKey = client.getKey();
// the config key can be a short class name, in which case we try to translate it to a fully qualified name
// and use the FQ name in the set property, because that's what SmallRye GraphQL understands
String clientName = shortNamesToQualifiedNames.getOrDefault(configKey, configKey);
GraphQLClientConfig config = client.getValue();
System.setProperty(clientName + "/mp-graphql/url", config.url);
for (Map.Entry<String, String> header : config.headers.entrySet()) {
System.setProperty(clientName + "/mp-graphql/header/" + header.getKey(), header.getValue());
public void setTypesafeApiClasses(List<String> apiClassNames) {
GraphQLClientsConfiguration configBean = Arc.container().instance(GraphQLClientsConfiguration.class).get();
List<Class<?>> classes = apiClassNames.stream().map(className -> {
try {
return Class.forName(className, true, Thread.currentThread().getContextClassLoader());
} catch (ClassNotFoundException e) {
throw new RuntimeException(e);
}
}
}).collect(Collectors.toList());
configBean.apiClasses(classes);
}

public RuntimeValue<GraphQLClientSupport> clientSupport(Map<String, String> shortNamesToQualifiedNames) {
GraphQLClientSupport support = new GraphQLClientSupport();
support.setShortNamesToQualifiedNamesMapping(shortNamesToQualifiedNames);
return new RuntimeValue<>(support);
}

public void initializeConfigurationMergerBean() {
GraphQLClientConfigurationMergerBean merger = Arc.container()
.instance(GraphQLClientConfigurationMergerBean.class).get();
merger.nothing();
}

}
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
package io.quarkus.io.smallrye.graphql.client;

import java.util.List;

import org.eclipse.microprofile.graphql.Mutation;
import org.eclipse.microprofile.graphql.NonNull;
import org.eclipse.microprofile.graphql.Query;

import io.smallrye.graphql.client.typesafe.api.GraphQLClientApi;
Expand All @@ -13,4 +16,7 @@ public interface LuckyNumbersClientApi {

@Mutation(value = "set")
Integer setLuckyNumber(Integer newLuckyNumber);

@Query(value = "echoList")
List<Integer> echoList(@NonNull List<Integer> list);
}
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
package io.quarkus.io.smallrye.graphql.client;

import java.util.List;

import javax.enterprise.context.ApplicationScoped;

import org.eclipse.microprofile.graphql.GraphQLApi;
import org.eclipse.microprofile.graphql.Mutation;
import org.eclipse.microprofile.graphql.NonNull;
import org.eclipse.microprofile.graphql.Query;

import io.smallrye.graphql.api.Subscription;
Expand Down Expand Up @@ -31,4 +34,9 @@ public Multi<Integer> primeNumbers() {
return Multi.createFrom().items(2, 3, 5, 7, 11, 13);
}

@Query(value = "echoList")
public List<Integer> echoList(@NonNull List<Integer> list) {
return list;
}

}
Loading