feat(gateway-engine): Allow policies to process failures.
ADD
gateway/engine/core/src/main/java/io/apiman/gateway/engine/Procedure.java
+/*
+ * Copyright 2018 JBoss Inc
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.apiman.gateway.engine;
+
+/**
+ * Models a procedure that accepts no parameters and returns nothing.
+ *
+ * @author Marc Savy {@literal <marc@rhymewithgravy.com>}
+ */
+@FunctionalInterface
+public interface Procedure {
+
+ /**
+ * The procedure to execute
+ */
+ void execute();
+}

MODIFY
gateway/engine/core/src/main/java/io/apiman/gateway/engine/impl/ApiRequestExecutorImpl.java
gateway/engine/core/src/main/java/io/apiman/gateway/engine/impl/ApiRequestExecutorImpl.java
@@ -282,7 +282,6 @@ public class ApiRequestExecutorImpl implements IApiRequestExecutor {
// of the implementation as to how they should cope with the chunks.
handleStream();
});
- requestChain.policyFailureHandler(policyFailureHandler);
requestChain.doApply(request);
};
@@ -806,7 +805,15 @@ public class ApiRequestExecutorImpl implements IApiRequestExecutor {
private Chain<ApiRequest> createRequestChain(IAsyncHandler<ApiRequest> requestHandler) {
RequestChain chain = new RequestChain(policyImpls, context);
chain.headHandler(requestHandler);
- chain.policyFailureHandler(policyFailureHandler);
+ chain.policyFailureHandler(failure -> {
+ // Jump straight to the response leg.
+ // It will likely not have been initialised, so create one.
+ if (responseChain == null) {
+ // Its response will not be used as we take the failure path only, so we just use an empty lambda.
+ responseChain = createResponseChain((ignored) -> {});
+ }
+ responseChain.doFailure(failure);
+ });
chain.policyErrorHandler(policyErrorHandler);
return chain;
}
@@ -818,11 +825,15 @@ public class ApiRequestExecutorImpl implements IApiRequestExecutor {
ResponseChain chain = new ResponseChain(policyImpls, context);
chain.headHandler(responseHandler);
chain.policyFailureHandler(result -> {
+ if (apiConnectionResponse != null) {
apiConnectionResponse.abort();
+ }
policyFailureHandler.handle(result);
});
chain.policyErrorHandler(result -> {
+ if (apiConnectionResponse != null) {
apiConnectionResponse.abort();
+ }
policyErrorHandler.handle(result);
});
return chain;

MODIFY
gateway/engine/core/src/main/java/io/apiman/gateway/engine/policy/Chain.java
gateway/engine/core/src/main/java/io/apiman/gateway/engine/policy/Chain.java
*/
package io.apiman.gateway.engine.policy;
+import io.apiman.gateway.engine.Procedure;
import io.apiman.gateway.engine.async.IAsyncHandler;
import io.apiman.gateway.engine.beans.PolicyFailure;
import io.apiman.gateway.engine.io.AbstractStream;
@@ -50,13 +51,14 @@ public abstract class Chain<H> extends AbstractStream<H> implements IAbortable,
private final IPolicyContext context;
private IReadWriteStream<H> headPolicyHandler;
- private IAsyncHandler<PolicyFailure> policyFailureHandler;
private IAsyncHandler<Throwable> policyErrorHandler;
+ private IAsyncHandler<PolicyFailure> policyFailureHandler;
private Iterator<PolicyWithConfiguration> policyIterator;
private H apiObject;
private boolean firstElem = true;
+ private PolicyFailure failure;
/**
* Constructor.
@@ -154,6 +156,31 @@ public abstract class Chain<H> extends AbstractStream<H> implements IAbortable,
}
}
+ protected void doApplyFailure(PolicyFailure failure) {
+ try {
+ this.failure = failure;
+
+ if(firstElem) {
+ chainPolicyHandlers();
+ firstElem = false;
+ }
+
+ if (policyIterator.hasNext()) {
+ applyFailure(policyIterator.next(), getContext());
+ } else {
+ policyFailureHandler.handle(failure);
+ }
+ } catch (Throwable error) {
+ throwError(error);
+ }
+ }
+
+ private void applyFailure(PolicyWithConfiguration policy, IPolicyContext context) {
+ executeInPolicyContextLoader(policy.getPolicy().getClass().getClassLoader(), () -> {
+ policy.getPolicy().processFailure(failure, context, policy.getConfiguration(), this);
+ });
+ }
+
/**
* @see io.apiman.gateway.engine.policy.IPolicyChain#doSkip(java.lang.Object)
*/
@@ -211,6 +238,15 @@ public abstract class Chain<H> extends AbstractStream<H> implements IAbortable,
headHandler.handle(api);
}
+ /**
+ * Get policy failure handler
+ *
+ * @return the policy failure handler
+ */
+ protected IAsyncHandler<PolicyFailure> getPolicyFailureHandler() {
+ return policyFailureHandler;
+ }
+
/**
* Sets the policy failure handler.
* @param failureHandler the failure handler
@@ -225,10 +261,7 @@ public abstract class Chain<H> extends AbstractStream<H> implements IAbortable,
* @param failure the policy failure
*/
@Override
- public void doFailure(PolicyFailure failure) {
- abort(null);
- policyFailureHandler.handle(failure);
- }
+ public abstract void doFailure(PolicyFailure failure);
/**
* Sets the policy error handler.
@@ -259,6 +292,17 @@ public abstract class Chain<H> extends AbstractStream<H> implements IAbortable,
// }
}
+ protected void executeInPolicyContextLoader(ClassLoader classloader, Procedure procedure) {
+ ClassLoader oldCtxLoader = Thread.currentThread().getContextClassLoader();
+ try {
+ Thread.currentThread().setContextClassLoader(classloader);
+ procedure.execute();
+ } finally {
+ Thread.currentThread().setContextClassLoader(oldCtxLoader);
+ }
+ }
+
+
/**
* Gets the API handler for the policy.
* @param policy

MODIFY
gateway/engine/core/src/main/java/io/apiman/gateway/engine/policy/IPolicy.java
gateway/engine/core/src/main/java/io/apiman/gateway/engine/policy/IPolicy.java
@@ -17,6 +17,7 @@ package io.apiman.gateway.engine.policy;
import io.apiman.gateway.engine.beans.ApiRequest;
import io.apiman.gateway.engine.beans.ApiResponse;
+import io.apiman.gateway.engine.beans.PolicyFailure;
import io.apiman.gateway.engine.beans.exceptions.ConfigurationParseException;
/**
@@ -58,4 +59,22 @@ public interface IPolicy {
*/
public void apply(ApiResponse response, IPolicyContext context, Object config, IPolicyChain<ApiResponse> chain);
+ /**
+ * <p>
+ * Process any failure emitted by a subsequent policy in the chain.
+ * <p>
+ * For example, implementors may wish to add or remove certain headers, even in
+ * the case of failures.
+ * <p>
+ * By default, this is a no-op and providing an implementation is not required.
+ *
+ * @param failure the policy failure
+ * @param context contextual information
+ * @param config the policy's configuration information
+ * @param chain the chain
+ */
+ default void processFailure(PolicyFailure failure, IPolicyContext context, Object config, IPolicyFailureChain chain) {
+ chain.doFailure(failure);
+ }
+
}

MODIFY
gateway/engine/core/src/main/java/io/apiman/gateway/engine/policy/IPolicyChain.java
gateway/engine/core/src/main/java/io/apiman/gateway/engine/policy/IPolicyChain.java
@@ -24,7 +24,7 @@ import io.apiman.gateway.engine.beans.PolicyFailure;
* @author eric.wittmann@redhat.com
* @param <T> Api object type
*/
-public interface IPolicyChain<T> {
+public interface IPolicyChain<T> extends IPolicyFailureChain {
/**
* Called by a policy when it has successfully completed applying itself. This

ADD
gateway/engine/core/src/main/java/io/apiman/gateway/engine/policy/IPolicyFailureChain.java
+/*
+ * Copyright 2018 JBoss Inc
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.apiman.gateway.engine.policy;
+
+import io.apiman.gateway.engine.beans.PolicyFailure;
+
+/**
+ * For exposing only failure elements of policy chain.
+ *
+ * @author Marc Savy {@literal <marc@rhymewithgravy.com>}
+ */
+public interface IPolicyFailureChain {
+ /**
+ * Called by a policy when it has detected a violation or failure in the policy. This
+ * will stop the processing of policies and immediately return an failure response to
+ * the originating client.
+ * @param failure the policy failure
+ */
+ public void doFailure(PolicyFailure failure);
+}

MODIFY
gateway/engine/core/src/main/java/io/apiman/gateway/engine/policy/PolicyFactoryImpl.java
gateway/engine/core/src/main/java/io/apiman/gateway/engine/policy/PolicyFactoryImpl.java
@@ -40,7 +40,7 @@ public class PolicyFactoryImpl implements IPolicyFactory {
/**
* Constructor.
- * @param config
+ * @param config the configuration
*/
public PolicyFactoryImpl(Map<String, String> config) {
}

MODIFY
gateway/engine/core/src/main/java/io/apiman/gateway/engine/policy/RequestChain.java
gateway/engine/core/src/main/java/io/apiman/gateway/engine/policy/RequestChain.java
package io.apiman.gateway.engine.policy;
import io.apiman.gateway.engine.beans.ApiRequest;
+import io.apiman.gateway.engine.beans.PolicyFailure;
import io.apiman.gateway.engine.io.IReadWriteStream;
import java.util.Iterator;
@@ -62,13 +63,9 @@ public class RequestChain extends Chain<ApiRequest> {
*/
@Override
protected void applyPolicy(PolicyWithConfiguration policy, IPolicyContext context) {
- ClassLoader oldCtxLoader = Thread.currentThread().getContextClassLoader();
- try {
- Thread.currentThread().setContextClassLoader(policy.getPolicy().getClass().getClassLoader());
+ executeInPolicyContextLoader(policy.getPolicy().getClass().getClassLoader(), () -> {
policy.getPolicy().apply(getHead(), context, policy.getConfiguration(), this);
- } finally {
- Thread.currentThread().setContextClassLoader(oldCtxLoader);
- }
+ });
}
/**
@@ -112,6 +109,15 @@ public class RequestChain extends Chain<ApiRequest> {
public boolean hasNext() {
return index < policies.size();
}
- };
+ }
+
+ /**
+ * For a request-side error, just kick out immediately; we want the
+ * response chain to deal with this.
+ */
+ @Override
+ public void doFailure(PolicyFailure failure) {
+ getPolicyFailureHandler().handle(failure);
+ }
}

MODIFY
gateway/engine/core/src/main/java/io/apiman/gateway/engine/policy/ResponseChain.java
gateway/engine/core/src/main/java/io/apiman/gateway/engine/policy/ResponseChain.java
package io.apiman.gateway.engine.policy;
import io.apiman.gateway.engine.beans.ApiResponse;
+import io.apiman.gateway.engine.beans.PolicyFailure;
import io.apiman.gateway.engine.io.IReadWriteStream;
import java.util.Iterator;
@@ -62,15 +63,10 @@ public class ResponseChain extends Chain<ApiResponse> {
*/
@Override
protected void applyPolicy(PolicyWithConfiguration policy, IPolicyContext context) {
- ClassLoader oldCtxLoader = Thread.currentThread().getContextClassLoader();
- try {
- Thread.currentThread().setContextClassLoader(policy.getPolicy().getClass().getClassLoader());
+ executeInPolicyContextLoader(policy.getPolicy().getClass().getClassLoader(), () -> {
policy.getPolicy().apply(getHead(), context, policy.getConfiguration(), this);
- } finally {
- Thread.currentThread().setContextClassLoader(oldCtxLoader);
+ });
}
- }
-
/**
* An iterator over a list of policies - iterates through the policies from
* back to front (in reverse), which is the proper order when applying the
@@ -112,5 +108,10 @@ public class ResponseChain extends Chain<ApiResponse> {
public boolean hasNext() {
return index >= 0;
}
- };
+ }
+
+ @Override
+ public void doFailure(PolicyFailure failure) {
+ doApplyFailure(failure);
+ }
}

MODIFY
gateway/engine/policies/src/main/java/io/apiman/gateway/engine/policies/AbstractMappedDataPolicy.java
gateway/engine/policies/src/main/java/io/apiman/gateway/engine/policies/AbstractMappedDataPolicy.java
@@ -25,6 +25,7 @@ import io.apiman.gateway.engine.policy.IPolicyContext;
* Base class for data policies that want to use jackson to map their config.
*
* @author eric.wittmann@redhat.com
+ * @param <C> Policy configuration type
*/
public abstract class AbstractMappedDataPolicy<C> extends AbstractMappedPolicy<C> implements IDataPolicy {

MODIFY
gateway/engine/policies/src/main/java/io/apiman/gateway/engine/policies/AbstractMappedPolicy.java
gateway/engine/policies/src/main/java/io/apiman/gateway/engine/policies/AbstractMappedPolicy.java
*/
package io.apiman.gateway.engine.policies;
-import com.fasterxml.jackson.databind.DeserializationFeature;
-import com.fasterxml.jackson.databind.ObjectMapper;
import io.apiman.gateway.engine.beans.ApiRequest;
import io.apiman.gateway.engine.beans.ApiResponse;
+import io.apiman.gateway.engine.beans.PolicyFailure;
import io.apiman.gateway.engine.beans.exceptions.ConfigurationParseException;
import io.apiman.gateway.engine.policy.IPolicy;
import io.apiman.gateway.engine.policy.IPolicyChain;
import io.apiman.gateway.engine.policy.IPolicyContext;
+import io.apiman.gateway.engine.policy.IPolicyFailureChain;
+
+import com.fasterxml.jackson.databind.DeserializationFeature;
+import com.fasterxml.jackson.databind.ObjectMapper;
/**
* A base class for policy impls that use jackson to parse configuration info.
@@ -49,7 +52,7 @@ public abstract class AbstractMappedPolicy<C> implements IPolicy {
@Override
public C parseConfiguration(String jsonConfiguration) throws ConfigurationParseException {
try {
- return mapper.reader(getConfigurationClass()).readValue(jsonConfiguration);
+ return mapper.readerFor(getConfigurationClass()).readValue(jsonConfiguration);
} catch (Exception e) {
throw new ConfigurationParseException(e);
}
@@ -99,4 +102,19 @@ public abstract class AbstractMappedPolicy<C> implements IPolicy {
chain.doApply(response);
}
+ @SuppressWarnings("unchecked")
+ @Override
+ public void processFailure(PolicyFailure failure, IPolicyContext context, Object config, IPolicyFailureChain chain) {
+ doProcessFailure(failure, context, (C) config, chain);
+ }
+
+ /**
+ * Override if you wish to modify a failure.
+ *
+ * @see IPolicy#processFailure(PolicyFailure, IPolicyContext, Object)
+ */
+ protected void doProcessFailure(PolicyFailure failure, IPolicyContext context, C config, IPolicyFailureChain chain) {
+ chain.doFailure(failure);
+ }
+
}

feat(gateway-engine): Allow policies to process failures.

Signed-off-by: Marc Savy <marc@rhymewithgravy.com>