Skip to content

Commit

Permalink
feat: make the policy compatible with V4 API (Proxy & Message)
Browse files Browse the repository at this point in the history
  BREAKING CHANGE: this policy is now using the V4 interfaces

fix APIM-1622
  • Loading branch information
leleueri committed Jun 23, 2023
1 parent b6acea9 commit 33fba04
Show file tree
Hide file tree
Showing 18 changed files with 829 additions and 150 deletions.
3 changes: 2 additions & 1 deletion README.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,8 @@ attributes and more.
|===
|Plugin version | APIM version

| 1.7.x and upper | 3.10.x to latest
| 2.x | 4.0.x to latest
| 1.7.x | 3.10.x to 3.20.x
| Up to 1.6.x | Up to 3.9.x
|===

Expand Down
41 changes: 40 additions & 1 deletion pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -37,11 +37,15 @@
<gravitee-apim-gateway-tests-sdk.version>4.0.0-SNAPSHOT</gravitee-apim-gateway-tests-sdk.version>
<gravitee-apim-gateway.version>4.0.0-SNAPSHOT</gravitee-apim-gateway.version>
<gravitee-bom.version>5.0.0</gravitee-bom.version>
<gravitee-gateway-api.version>3.0.0-alpha.6</gravitee-gateway-api.version>
<gravitee-gateway-api.version>3.0.0-alpha.7</gravitee-gateway-api.version>
<gravitee-policy-api.version>1.11.0</gravitee-policy-api.version>
<freemarker.version>2.3.32</freemarker.version>
<guava.version>30.1.1-jre</guava.version>

<gravitee-entrypoint-sse.version>3.1.0-alpha.2</gravitee-entrypoint-sse.version>
<gravitee-entrypoint-http-post.version>1.0.0-alpha.2</gravitee-entrypoint-http-post.version>
<gravitee-reactor-message.version>1.0.0-alpha.7</gravitee-reactor-message.version>

<maven-assembly-plugin.version>3.6.0</maven-assembly-plugin.version>
<!-- Property used by the publication job in CI-->
<publish-folder-path>graviteeio-apim/plugins/policies</publish-folder-path>
Expand Down Expand Up @@ -149,6 +153,41 @@
<version>${gravitee-apim-gateway-tests-sdk.version}</version>
<scope>test</scope>
</dependency>

<dependency>
<groupId>io.gravitee.apim.plugin.entrypoint</groupId>
<artifactId>gravitee-apim-plugin-entrypoint-http-proxy</artifactId>
<version>${gravitee-apim-gateway-tests-sdk.version}</version>
<scope>test</scope>
</dependency>

<dependency>
<groupId>com.graviteesource.entrypoint</groupId>
<artifactId>gravitee-entrypoint-sse</artifactId>
<version>${gravitee-entrypoint-sse.version}</version>
<scope>test</scope>
</dependency>

<dependency>
<groupId>io.gravitee.apim.plugin.endpoint</groupId>
<artifactId>gravitee-apim-plugin-endpoint-http-proxy</artifactId>
<version>${gravitee-apim-gateway-tests-sdk.version}</version>
<scope>test</scope>
</dependency>

<dependency>
<groupId>com.graviteesource.entrypoint</groupId>
<artifactId>gravitee-entrypoint-http-post</artifactId>
<version>${gravitee-entrypoint-http-post.version}</version>
<scope>test</scope>
</dependency>

<dependency>
<groupId>com.graviteesource.reactor</groupId>
<artifactId>gravitee-reactor-message</artifactId>
<version>${gravitee-reactor-message.version}</version>
<scope>test</scope>
</dependency>
</dependencies>

<build>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,127 +15,118 @@
*/
package io.gravitee.policy.assigncontent;

import freemarker.cache.StringTemplateLoader;
import freemarker.core.TemplateClassResolver;
import freemarker.template.Configuration;
import freemarker.template.DefaultObjectWrapperBuilder;
import freemarker.template.Template;
import freemarker.template.TemplateExceptionHandler;
import io.gravitee.gateway.api.ExecutionContext;
import io.gravitee.gateway.api.Request;
import io.gravitee.gateway.api.Response;
import freemarker.template.TemplateException;
import io.gravitee.common.http.HttpStatusCode;
import io.gravitee.gateway.api.buffer.Buffer;
import io.gravitee.gateway.api.http.stream.TransformableRequestStreamBuilder;
import io.gravitee.gateway.api.http.stream.TransformableResponseStreamBuilder;
import io.gravitee.gateway.api.stream.ReadWriteStream;
import io.gravitee.gateway.api.stream.exception.TransformationException;
import io.gravitee.policy.api.PolicyChain;
import io.gravitee.policy.api.annotations.OnRequestContent;
import io.gravitee.policy.api.annotations.OnResponseContent;
import io.gravitee.gateway.reactive.api.ExecutionFailure;
import io.gravitee.gateway.reactive.api.context.HttpExecutionContext;
import io.gravitee.gateway.reactive.api.context.MessageExecutionContext;
import io.gravitee.gateway.reactive.api.el.EvaluableMessage;
import io.gravitee.gateway.reactive.api.el.EvaluableRequest;
import io.gravitee.gateway.reactive.api.el.EvaluableResponse;
import io.gravitee.gateway.reactive.api.message.Message;
import io.gravitee.gateway.reactive.api.policy.Policy;
import io.gravitee.policy.assigncontent.configuration.AssignContentPolicyConfiguration;
import io.gravitee.policy.assigncontent.configuration.PolicyScope;
import io.gravitee.policy.assigncontent.freemarker.CustomTemplateLoader;
import io.gravitee.policy.assigncontent.freemarker.LegacyDefaultMemberAccessPolicy;
import io.gravitee.policy.assigncontent.utils.AttributesBasedExecutionContext;
import io.gravitee.policy.assigncontent.utils.ContentAwareRequest;
import io.gravitee.policy.assigncontent.utils.ContentAwareResponse;
import io.gravitee.policy.assigncontent.utils.Sha1;
import io.gravitee.policy.v3.assigncontent.AssignContentPolicyV3;
import io.reactivex.rxjava3.core.Completable;
import io.reactivex.rxjava3.core.Maybe;
import java.io.IOException;
import java.io.StringWriter;
import java.nio.charset.Charset;
import java.util.HashMap;
import java.util.Map;
import lombok.extern.slf4j.Slf4j;

/**
* @author David BRASSELY (david.brassely at graviteesource.com)
* @author GraviteeSource Team
*/
public class AssignContentPolicy {
@Slf4j
public class AssignContentPolicy extends AssignContentPolicyV3 implements Policy {

private static final CustomTemplateLoader templateLoader = new CustomTemplateLoader();
private static final Configuration templateConfiguration = loadConfiguration();

/**
* SOAP transformer templateConfiguration
*/
private final AssignContentPolicyConfiguration configuration;
public static final String PLUGIN_ID = "policy-assign-content";

public AssignContentPolicy(AssignContentPolicyConfiguration configuration) {
this.configuration = configuration;
super(configuration);
}

@OnRequestContent
public ReadWriteStream<Buffer> onRequestContent(Request request, ExecutionContext executionContext, PolicyChain policyChain) {
if (configuration.getScope() == PolicyScope.REQUEST) {
return TransformableRequestStreamBuilder
.on(request)
.chain(policyChain)
.transform(buffer -> {
try {
Template template = getTemplate(configuration.getBody());
StringWriter writer = new StringWriter();
Map<String, Object> model = new HashMap<>();
model.put("request", new ContentAwareRequest(request, buffer.toString()));
model.put("context", new AttributesBasedExecutionContext(executionContext));
template.process(model, writer);
return Buffer.buffer(writer.toString());
} catch (Exception ioe) {
throw new TransformationException("Unable to assign body content: " + ioe.getMessage(), ioe);
}
})
.build();
}

return null;
@Override
public String id() {
return PLUGIN_ID;
}

@OnResponseContent
public ReadWriteStream<Buffer> onResponseContent(Response response, ExecutionContext executionContext, PolicyChain policyChain) {
if (configuration.getScope() == PolicyScope.RESPONSE) {
return TransformableResponseStreamBuilder
.on(response)
.chain(policyChain)
.transform(buffer -> {
try {
Template template = getTemplate(configuration.getBody());
StringWriter writer = new StringWriter();
Map<String, Object> model = new HashMap<>();
model.put("response", new ContentAwareResponse(response, buffer.toString()));
model.put("context", new AttributesBasedExecutionContext(executionContext));
template.process(model, writer);
return Buffer.buffer(writer.toString());
} catch (Exception ioe) {
throw new TransformationException("Unable to assign body content: " + ioe.getMessage(), ioe);
}
})
.build();
}

return null;
@Override
public Completable onRequest(HttpExecutionContext ctx) {
return ctx.request().onBody(body -> assignBodyContent(ctx, body, true));
}

private static Configuration loadConfiguration() {
Configuration configuration = new Configuration(Configuration.VERSION_2_3_30);
configuration.setDefaultEncoding(Charset.defaultCharset().name());
configuration.setNewBuiltinClassResolver(TemplateClassResolver.ALLOWS_NOTHING_RESOLVER);
configuration.setTemplateExceptionHandler(TemplateExceptionHandler.RETHROW_HANDLER);
@Override
public Completable onResponse(HttpExecutionContext ctx) {
return ctx.response().onBody(body -> assignBodyContent(ctx, body, false));
}

// Ensure default value is false
configuration.setAPIBuiltinEnabled(false);
private Maybe<Buffer> assignBodyContent(HttpExecutionContext ctx, Maybe<Buffer> body, boolean isRequest) {
return body
.flatMap(content -> {
var writer = replaceContent(isRequest, ctx, content.toString());
return Maybe.just(Buffer.buffer(writer.toString()));
})
.switchIfEmpty(
Maybe.fromCallable(() -> {
// For method like GET where body is missing, we have to handle the case where the maybe is empty.
// It can make sens if in the Flow we have an override method policy that replace the GET by a POST
var writer = replaceContent(isRequest, ctx, "");
return Buffer.buffer(writer.toString());
})
)
.onErrorResumeNext(ioe -> {
log.debug("Unable to assign body content", ioe);
return ctx.interruptBodyWith(
new ExecutionFailure(HttpStatusCode.INTERNAL_SERVER_ERROR_500)
.message("Unable to assign body content: " + ioe.getMessage())
);
});
}

final DefaultObjectWrapperBuilder objectWrapperBuilder = new DefaultObjectWrapperBuilder(Configuration.VERSION_2_3_30);
objectWrapperBuilder.setMemberAccessPolicy(LegacyDefaultMemberAccessPolicy.INSTANCE);
configuration.setObjectWrapper(objectWrapperBuilder.build());
private StringWriter replaceContent(boolean isRequest, HttpExecutionContext ctx, String content) throws IOException, TemplateException {
Template template = getTemplate(configuration.getBody());
StringWriter writer = new StringWriter();
Map<String, Object> model = new HashMap<>();
if (isRequest) {
model.put("request", new EvaluableRequest(ctx.request(), content));
} else {
model.put("request", new EvaluableRequest(ctx.request()));
model.put("response", new EvaluableResponse(ctx.response(), content));
}
model.put("context", new AttributesBasedExecutionContext(ctx));
template.process(model, writer);
return writer;
}

// Load inline templates
configuration.setTemplateLoader(templateLoader);
@Override
public Completable onMessageRequest(MessageExecutionContext ctx) {
return ctx.request().onMessage(msg -> assignMessageContent(ctx, msg));
}

return configuration;
@Override
public Completable onMessageResponse(MessageExecutionContext ctx) {
return ctx.response().onMessage(msg -> assignMessageContent(ctx, msg));
}

Template getTemplate(String template) throws IOException {
String hash = Sha1.sha1(template);
templateLoader.putIfAbsent(hash, template);
return templateConfiguration.getTemplate(hash);
private Maybe<Message> assignMessageContent(MessageExecutionContext ctx, Message msg) {
return Maybe
.fromCallable(() -> {
Template template = getTemplate(configuration.getBody());
StringWriter writer = new StringWriter();
Map<String, Object> model = new HashMap<>();
model.put("message", new EvaluableMessage(msg));
model.put("context", new AttributesBasedExecutionContext(ctx));
template.process(model, writer);
return msg.content(Buffer.buffer(writer.toString()));
})
.onErrorResumeNext(err -> {
log.debug("Unable to assign message content", err);
return ctx.interruptMessageWith(
new ExecutionFailure(HttpStatusCode.INTERNAL_SERVER_ERROR_500)
.message("Unable to assign message content: " + err.getMessage())
);
});
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,21 +15,21 @@
*/
package io.gravitee.policy.assigncontent.utils;

import io.gravitee.gateway.api.ExecutionContext;
import io.gravitee.gateway.reactive.api.context.GenericExecutionContext;
import java.util.HashMap;
import java.util.Map;

/**
* @author David BRASSELY (david.brassely at graviteesource.com)
* @author Eric LELEU (eric.leleu at graviteesource.com)
* @author GraviteeSource Team
*/
public class AttributesBasedExecutionContext {

private static final String CONTEXT_DICTIONARIES_VARIABLE = "dictionaries";
private final ExecutionContext context;
private final GenericExecutionContext context;
private final Map<String, Object> attributes = new AttributeMap();

public AttributesBasedExecutionContext(final ExecutionContext context) {
public AttributesBasedExecutionContext(final GenericExecutionContext context) {
this.context = context;
}

Expand Down
Loading

0 comments on commit 33fba04

Please sign in to comment.