Skip to content

Commit

Permalink
Merge branch 'main' into federation-batch-tests
Browse files Browse the repository at this point in the history
  • Loading branch information
robp94 authored Sep 8, 2023
2 parents 76208c2 + bf87cc3 commit 25dd0f1
Show file tree
Hide file tree
Showing 19 changed files with 347 additions and 98 deletions.
8 changes: 4 additions & 4 deletions docs/src/main/asciidoc/qute-reference.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -1143,10 +1143,10 @@ Then, we can call the tag like this:
<1> `item` is resolved to an iteration element and can be referenced using the `it` key in the tag template.
<2> Tag content injected using the `nested-content` key in the tag template.

By default, the tag template can reference data from the parent context.
For example, the tag above could use the following expression `{items.size}`.
However, sometimes it might be useful to disable this behavior and execute the tag as an _isolated_ template, i.e. without access to the context of the template that calls the tag.
In this case, just add `_isolated` or `_isolated=true` argument to the call site, e.g. `{#itemDetail item showImage=true _isolated /}`.
By default, a tag template cannot reference the data from the parent context.
Qute executes the tag as an _isolated_ template, i.e. without access to the context of the template that calls the tag.
However, sometimes it might be useful to change the default behavior and disable the isolation.
In this case, just add `_isolated=false` or `_unisolated` argument to the call site, for example `{#itemDetail item showImage=true _isolated=false /}` or `{#itemDetail item showImage=true _unisolated /}`.

User tags can also make use of the template inheritance in the same way as regular `{#include}` sections do.

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package io.quarkus.cache.deployment.spi;

import io.quarkus.builder.item.SimpleBuildItem;

/**
* A build item that can be used by extensions to determine what kind of cache backend is configured.
* This is useful for cases where caching extensions specific data does not make sense for remote cache backends
*/
public final class CacheTypeBuildItem extends SimpleBuildItem {

private final Type type;

public CacheTypeBuildItem(Type type) {
this.type = type;
}

public Type getType() {
return type;
}

public enum Type {
LOCAL,
REMOTE
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
import static io.quarkus.cache.deployment.CacheDeploymentConstants.INTERCEPTOR_BINDING_CONTAINERS;
import static io.quarkus.cache.deployment.CacheDeploymentConstants.MULTI;
import static io.quarkus.cache.deployment.CacheDeploymentConstants.REGISTER_REST_CLIENT;
import static io.quarkus.cache.runtime.CacheBuildConfig.CAFFEINE_CACHE_TYPE;
import static io.quarkus.deployment.annotations.ExecutionTime.RUNTIME_INIT;
import static io.quarkus.runtime.metrics.MetricsFactory.MICROMETER;
import static java.util.stream.Collectors.toList;
Expand Down Expand Up @@ -54,6 +55,8 @@
import io.quarkus.cache.deployment.exception.VoidReturnTypeTargetException;
import io.quarkus.cache.deployment.spi.AdditionalCacheNameBuildItem;
import io.quarkus.cache.deployment.spi.CacheManagerInfoBuildItem;
import io.quarkus.cache.deployment.spi.CacheTypeBuildItem;
import io.quarkus.cache.runtime.CacheBuildConfig;
import io.quarkus.cache.runtime.CacheInvalidateAllInterceptor;
import io.quarkus.cache.runtime.CacheInvalidateInterceptor;
import io.quarkus.cache.runtime.CacheManagerRecorder;
Expand Down Expand Up @@ -92,6 +95,12 @@ RestClientAnnotationsTransformerBuildItem restClientAnnotationsTransformer() {
return new RestClientAnnotationsTransformerBuildItem(new RestClientCacheAnnotationsTransformer());
}

@BuildStep
CacheTypeBuildItem type(CacheBuildConfig config) {
return new CacheTypeBuildItem(
CAFFEINE_CACHE_TYPE.equals(config.type()) ? CacheTypeBuildItem.Type.LOCAL : CacheTypeBuildItem.Type.REMOTE);
}

@BuildStep
void validateCacheAnnotationsAndProduceCacheNames(CombinedIndexBuildItem combinedIndex,
List<AdditionalCacheNameBuildItem> additionalCacheNames,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,21 +1,40 @@
package io.quarkus.qute.deployment;

import java.util.Optional;

import io.quarkus.arc.deployment.AdditionalBeanBuildItem;
import io.quarkus.cache.deployment.spi.AdditionalCacheNameBuildItem;
import io.quarkus.deployment.Capabilities;
import io.quarkus.deployment.Capability;
import io.quarkus.cache.deployment.spi.CacheTypeBuildItem;
import io.quarkus.deployment.annotations.BuildProducer;
import io.quarkus.deployment.annotations.BuildStep;
import io.quarkus.qute.cache.QuteCache;
import io.quarkus.qute.runtime.cache.CacheConfigurator;
import io.quarkus.qute.runtime.cache.MissingCacheConfigurator;
import io.quarkus.qute.runtime.cache.UnsupportedRemoteCacheConfigurator;

public class CacheProcessor {

@BuildStep
void initialize(Capabilities capabilities, BuildProducer<AdditionalBeanBuildItem> beans,
void initialize(Optional<CacheTypeBuildItem> cacheTypeBuildItem,
BuildProducer<AdditionalBeanBuildItem> beans,
BuildProducer<AdditionalCacheNameBuildItem> cacheNames) {
if (capabilities.isPresent(Capability.CACHE)) {
beans.produce(new AdditionalBeanBuildItem("io.quarkus.qute.runtime.cache.CacheConfigurator"));
// We need to produce additional cache name because quarkus-cache only considers the CombinedIndexBuildItem and not the bean archive index
Class configuratorClass;
boolean supported = false;
if (cacheTypeBuildItem.isEmpty()) { // no caching enabled
configuratorClass = MissingCacheConfigurator.class;
} else {
CacheTypeBuildItem.Type type = cacheTypeBuildItem.get().getType();
if (type != CacheTypeBuildItem.Type.LOCAL) { // it does not make sense to use a remote cache for Qute
configuratorClass = UnsupportedRemoteCacheConfigurator.class;
} else {
configuratorClass = CacheConfigurator.class;
supported = true;
}
}

beans.produce(new AdditionalBeanBuildItem(configuratorClass.getName()));
// We need to produce additional cache name because quarkus-cache only considers the CombinedIndexBuildItem and not the bean archive index
if (supported) {
cacheNames.produce(new AdditionalCacheNameBuildItem(QuteCache.NAME));
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package io.quarkus.qute.runtime.cache;

import java.util.concurrent.CompletionStage;
import java.util.function.Function;

import jakarta.enterprise.event.Observes;

import io.quarkus.qute.CacheSectionHelper;
import io.quarkus.qute.EngineBuilder;
import io.quarkus.qute.ResultNode;

public class MissingCacheConfigurator {

void configureEngine(@Observes EngineBuilder builder) {
builder.addSectionHelper(new CacheSectionHelper.Factory(new CacheSectionHelper.Cache() {

@Override
public CompletionStage<ResultNode> getValue(String key, Function<String, CompletionStage<ResultNode>> loader) {
throw new IllegalStateException("#cache cannot be used without the 'quarkus-cache' extension");
}
}));
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package io.quarkus.qute.runtime.cache;

import java.util.concurrent.CompletionStage;
import java.util.function.Function;

import jakarta.enterprise.event.Observes;

import io.quarkus.qute.CacheSectionHelper;
import io.quarkus.qute.EngineBuilder;
import io.quarkus.qute.ResultNode;

public class UnsupportedRemoteCacheConfigurator {

void configureEngine(@Observes EngineBuilder builder) {
builder.addSectionHelper(new CacheSectionHelper.Factory(new CacheSectionHelper.Cache() {

@Override
public CompletionStage<ResultNode> getValue(String key, Function<String, CompletionStage<ResultNode>> loader) {
throw new IllegalStateException("#cache is not supported for remote caches");
}
}));
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
import java.util.Set;

import io.quarkus.vertx.http.runtime.HttpCompression;
import io.vertx.core.AsyncResult;
import io.vertx.core.Handler;
import io.vertx.core.MultiMap;
import io.vertx.core.http.HttpHeaders;
Expand All @@ -24,33 +23,31 @@ public HttpCompressionHandler(Handler<RoutingContext> routeHandler, HttpCompress

@Override
public void handle(RoutingContext context) {
context.addEndHandler(new Handler<AsyncResult<Void>>() {
context.addHeadersEndHandler(new Handler<Void>() {
@Override
public void handle(AsyncResult<Void> result) {
if (result.succeeded()) {
MultiMap headers = context.response().headers();
String contentEncoding = headers.get(HttpHeaders.CONTENT_ENCODING);
if (contentEncoding != null && HttpHeaders.IDENTITY.toString().equals(contentEncoding)) {
switch (compression) {
case ON:
headers.remove(HttpHeaders.CONTENT_ENCODING);
break;
case UNDEFINED:
String contentType = headers.get(HttpHeaders.CONTENT_TYPE);
if (contentType != null) {
int paramIndex = contentType.indexOf(';');
if (paramIndex > -1) {
contentType = contentType.substring(0, paramIndex);
}
if (compressedMediaTypes.contains(contentType)) {
headers.remove(HttpHeaders.CONTENT_ENCODING);
}
public void handle(Void result) {
MultiMap headers = context.response().headers();
String contentEncoding = headers.get(HttpHeaders.CONTENT_ENCODING);
if (contentEncoding != null && HttpHeaders.IDENTITY.toString().equals(contentEncoding)) {
switch (compression) {
case ON:
headers.remove(HttpHeaders.CONTENT_ENCODING);
break;
case UNDEFINED:
String contentType = headers.get(HttpHeaders.CONTENT_TYPE);
if (contentType != null) {
int paramIndex = contentType.indexOf(';');
if (paramIndex > -1) {
contentType = contentType.substring(0, paramIndex);
}
break;
default:
// OFF - no action is needed because the "Content-Encoding: identity" header is set
break;
}
if (compressedMediaTypes.contains(contentType)) {
headers.remove(HttpHeaders.CONTENT_ENCODING);
}
}
break;
default:
// OFF - no action is needed because the "Content-Encoding: identity" header is set
break;
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@
import io.quarkus.vertx.http.deployment.webjar.WebJarBuildItem;
import io.quarkus.vertx.http.deployment.webjar.WebJarResourcesFilter;
import io.quarkus.vertx.http.deployment.webjar.WebJarResultsBuildItem;
import io.quarkus.vertx.http.runtime.HttpBuildTimeConfig;
import io.smallrye.graphql.api.AdaptWith;
import io.smallrye.graphql.api.Deprecated;
import io.smallrye.graphql.api.Entry;
Expand Down Expand Up @@ -148,6 +149,8 @@ public class SmallRyeGraphQLProcessor {

private static final int GRAPHQL_WEBSOCKET_HANDLER_ORDER = -10000;

private static final String GRAPHQL_MEDIA_TYPE = "application/graphql+json";

@BuildStep
void feature(BuildProducer<FeatureBuildItem> featureProducer) {
featureProducer.produce(new FeatureBuildItem(Feature.SMALLRYE_GRAPHQL));
Expand Down Expand Up @@ -349,7 +352,8 @@ void buildExecutionEndpoint(
BodyHandlerBuildItem bodyHandlerBuildItem,
SmallRyeGraphQLConfig graphQLConfig,
BeanContainerBuildItem beanContainer,
BuildProducer<WebsocketSubProtocolsBuildItem> webSocketSubProtocols) {
BuildProducer<WebsocketSubProtocolsBuildItem> webSocketSubProtocols,
HttpBuildTimeConfig httpBuildTimeConfig) {

/*
* <em>Ugly Hack</em>
Expand Down Expand Up @@ -395,8 +399,11 @@ void buildExecutionEndpoint(
// Queries and Mutations
boolean allowGet = getBooleanConfigValue(ConfigKey.ALLOW_GET, false);
boolean allowQueryParametersOnPost = getBooleanConfigValue(ConfigKey.ALLOW_POST_WITH_QUERY_PARAMETERS, false);
boolean allowCompression = httpBuildTimeConfig.enableCompression && httpBuildTimeConfig.compressMediaTypes
.map(mediaTypes -> mediaTypes.contains(GRAPHQL_MEDIA_TYPE))
.orElse(false);
Handler<RoutingContext> executionHandler = recorder.executionHandler(graphQLInitializedBuildItem.getInitialized(),
allowGet, allowQueryParametersOnPost, runBlocking);
allowGet, allowQueryParametersOnPost, runBlocking, allowCompression);

HttpRootPathBuildItem.Builder requestBuilder = httpRootPathBuildItem.routeBuilder()
.routeFunction(graphQLConfig.rootPath, recorder.routeFunction(bodyHandlerBuildItem.getHandler()))
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
package io.quarkus.smallrye.graphql.deployment;

import java.util.Arrays;

import org.eclipse.microprofile.graphql.GraphQLApi;
import org.eclipse.microprofile.graphql.Query;
import org.hamcrest.CoreMatchers;
import org.hamcrest.Matchers;
import org.jboss.shrinkwrap.api.asset.EmptyAsset;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension;

import io.quarkus.test.QuarkusUnitTest;
import io.restassured.RestAssured;

public class CompressionTest extends AbstractGraphQLTest {

private static final String PI = "3.141592653589793238462643383279502884197169399375105820";
private static final String TAU = "6.28";

@RegisterExtension
static final QuarkusUnitTest config = new QuarkusUnitTest()
.withApplicationRoot((jar) -> jar
.addAsManifestResource(EmptyAsset.INSTANCE, "beans.xml"))
.overrideConfigKey("quarkus.http.enable-compression", "true");

@Test
public void singleValidQueryCompressedResponseTest() {
String body = getPayload("{ validPiQuery }");
assertCompressed(body, PI);
}

@Test
public void multipleValidQueriesCompressedResponseTest() {
String body = getPayload("{ validPiQuery validTauQuery }");
assertCompressed(body, PI, TAU);
}

@Test
public void singleInvalidQueryCompressedResponseTest() {
String body = getPayload("{ invalidQuery }");
assertCompressed(body, "errors");
}

private void assertCompressed(String body, String... expectedOutput) {
org.hamcrest.Matcher messageMatcher = Arrays.stream(expectedOutput)
.map(CoreMatchers::containsString)
.reduce(Matchers.allOf(), (a, b) -> Matchers.allOf(a, b));

RestAssured.given()
.body(body)
.contentType(MEDIATYPE_JSON)
.post("/graphql")
.prettyPeek()
.then()
.assertThat()
.statusCode(200)
.header("Content-Encoding", "gzip")
.body(messageMatcher);
}

@GraphQLApi
public static class Schema {
@Query
public String validPiQuery() {
return PI;
}

@Query
public String validTauQuery() {
return TAU;
}

@Query
public String invalidQuery() {
throw new RuntimeException();
}
}

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

import io.vertx.core.Handler;
import io.vertx.core.http.HttpHeaders;
import io.vertx.ext.web.RoutingContext;

public class SmallRyeGraphQLCompressionHandler implements Handler<RoutingContext> {

private final Handler<RoutingContext> routeHandler;

public SmallRyeGraphQLCompressionHandler(Handler<RoutingContext> routeHandler) {
this.routeHandler = routeHandler;
}

@Override
public void handle(RoutingContext context) {
context.addHeadersEndHandler(new Handler<Void>() {
@Override
public void handle(Void event) {
context.response().headers().remove(HttpHeaders.CONTENT_ENCODING);
}
});
routeHandler.handle(context);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -32,11 +32,16 @@ public RuntimeValue<Boolean> createExecutionService(BeanContainer beanContainer,
}

public Handler<RoutingContext> executionHandler(RuntimeValue<Boolean> initialized, boolean allowGet,
boolean allowPostWithQueryParameters, boolean runBlocking) {
boolean allowPostWithQueryParameters, boolean runBlocking, boolean allowCompression) {
if (initialized.getValue()) {
return new SmallRyeGraphQLExecutionHandler(allowGet, allowPostWithQueryParameters, runBlocking,
Handler<RoutingContext> handler = new SmallRyeGraphQLExecutionHandler(allowGet,
allowPostWithQueryParameters, runBlocking,
getCurrentIdentityAssociation(),
Arc.container().instance(CurrentVertxRequest.class).get());
if (allowCompression) {
return new SmallRyeGraphQLCompressionHandler(handler);
}
return handler;
} else {
return new SmallRyeGraphQLNoEndpointHandler();
}
Expand Down
Loading

0 comments on commit 25dd0f1

Please sign in to comment.