/**
* Copyright 2007-2015, Kaazing Corporation. All rights reserved.
*
* 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 org.kaazing.k3po.driver.internal.behavior.visitor;
import static java.lang.String.format;
import static java.util.Objects.requireNonNull;
import static org.jboss.netty.channel.Channels.pipeline;
import static org.jboss.netty.util.CharsetUtil.UTF_8;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.net.URI;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentMap;
import javax.el.ELResolver;
import javax.el.ValueExpression;
import org.jboss.netty.channel.ChannelHandler;
import org.jboss.netty.channel.ChannelPipeline;
import org.jboss.netty.channel.ChannelPipelineFactory;
import org.jboss.netty.logging.InternalLogger;
import org.jboss.netty.logging.InternalLoggerFactory;
import org.kaazing.k3po.driver.internal.RobotException;
import org.kaazing.k3po.driver.internal.behavior.Barrier;
import org.kaazing.k3po.driver.internal.behavior.Configuration;
import org.kaazing.k3po.driver.internal.behavior.handler.CompletionHandler;
import org.kaazing.k3po.driver.internal.behavior.handler.FailureHandler;
import org.kaazing.k3po.driver.internal.behavior.handler.barrier.AwaitBarrierDownstreamHandler;
import org.kaazing.k3po.driver.internal.behavior.handler.barrier.AwaitBarrierUpstreamHandler;
import org.kaazing.k3po.driver.internal.behavior.handler.barrier.NotifyBarrierHandler;
import org.kaazing.k3po.driver.internal.behavior.handler.codec.Masker;
import org.kaazing.k3po.driver.internal.behavior.handler.codec.Maskers;
import org.kaazing.k3po.driver.internal.behavior.handler.codec.MessageDecoder;
import org.kaazing.k3po.driver.internal.behavior.handler.codec.MessageEncoder;
import org.kaazing.k3po.driver.internal.behavior.handler.codec.ReadByteArrayBytesDecoder;
import org.kaazing.k3po.driver.internal.behavior.handler.codec.ReadByteLengthBytesDecoder;
import org.kaazing.k3po.driver.internal.behavior.handler.codec.ReadExactBytesDecoder;
import org.kaazing.k3po.driver.internal.behavior.handler.codec.ReadExactTextDecoder;
import org.kaazing.k3po.driver.internal.behavior.handler.codec.ReadExpressionDecoder;
import org.kaazing.k3po.driver.internal.behavior.handler.codec.ReadIntLengthBytesDecoder;
import org.kaazing.k3po.driver.internal.behavior.handler.codec.ReadLongLengthBytesDecoder;
import org.kaazing.k3po.driver.internal.behavior.handler.codec.ReadRegexDecoder;
import org.kaazing.k3po.driver.internal.behavior.handler.codec.ReadShortLengthBytesDecoder;
import org.kaazing.k3po.driver.internal.behavior.handler.codec.ReadVariableLengthBytesDecoder;
import org.kaazing.k3po.driver.internal.behavior.handler.codec.WriteBytesEncoder;
import org.kaazing.k3po.driver.internal.behavior.handler.codec.WriteExpressionEncoder;
import org.kaazing.k3po.driver.internal.behavior.handler.codec.WriteTextEncoder;
import org.kaazing.k3po.driver.internal.behavior.handler.codec.http.HttpContentLengthEncoder;
import org.kaazing.k3po.driver.internal.behavior.handler.codec.http.HttpHeaderDecoder;
import org.kaazing.k3po.driver.internal.behavior.handler.codec.http.HttpHeaderEncoder;
import org.kaazing.k3po.driver.internal.behavior.handler.codec.http.HttpHeaderMissingDecoder;
import org.kaazing.k3po.driver.internal.behavior.handler.codec.http.HttpHostEncoder;
import org.kaazing.k3po.driver.internal.behavior.handler.codec.http.HttpMethodDecoder;
import org.kaazing.k3po.driver.internal.behavior.handler.codec.http.HttpMethodEncoder;
import org.kaazing.k3po.driver.internal.behavior.handler.codec.http.HttpParameterDecoder;
import org.kaazing.k3po.driver.internal.behavior.handler.codec.http.HttpParameterEncoder;
import org.kaazing.k3po.driver.internal.behavior.handler.codec.http.HttpRequestFormEncoder;
import org.kaazing.k3po.driver.internal.behavior.handler.codec.http.HttpStatusDecoder;
import org.kaazing.k3po.driver.internal.behavior.handler.codec.http.HttpStatusEncoder;
import org.kaazing.k3po.driver.internal.behavior.handler.codec.http.HttpTrailerDecoder;
import org.kaazing.k3po.driver.internal.behavior.handler.codec.http.HttpTrailerEncoder;
import org.kaazing.k3po.driver.internal.behavior.handler.codec.http.HttpVersionDecoder;
import org.kaazing.k3po.driver.internal.behavior.handler.codec.http.HttpVersionEncoder;
import org.kaazing.k3po.driver.internal.behavior.handler.command.AbortHandler;
import org.kaazing.k3po.driver.internal.behavior.handler.command.CloseHandler;
import org.kaazing.k3po.driver.internal.behavior.handler.command.DisconnectHandler;
import org.kaazing.k3po.driver.internal.behavior.handler.command.FlushHandler;
import org.kaazing.k3po.driver.internal.behavior.handler.command.ReadConfigHandler;
import org.kaazing.k3po.driver.internal.behavior.handler.command.ReadOptionOffsetHandler;
import org.kaazing.k3po.driver.internal.behavior.handler.command.ShutdownOutputHandler;
import org.kaazing.k3po.driver.internal.behavior.handler.command.UnbindHandler;
import org.kaazing.k3po.driver.internal.behavior.handler.command.WriteConfigHandler;
import org.kaazing.k3po.driver.internal.behavior.handler.command.WriteHandler;
import org.kaazing.k3po.driver.internal.behavior.handler.command.WriteOptionOffsetHandler;
import org.kaazing.k3po.driver.internal.behavior.handler.event.AbortedHandler;
import org.kaazing.k3po.driver.internal.behavior.handler.event.BoundHandler;
import org.kaazing.k3po.driver.internal.behavior.handler.event.ChildClosedHandler;
import org.kaazing.k3po.driver.internal.behavior.handler.event.ChildOpenedHandler;
import org.kaazing.k3po.driver.internal.behavior.handler.event.ClosedHandler;
import org.kaazing.k3po.driver.internal.behavior.handler.event.ConnectedHandler;
import org.kaazing.k3po.driver.internal.behavior.handler.event.DisconnectedHandler;
import org.kaazing.k3po.driver.internal.behavior.handler.event.InputShutdownHandler;
import org.kaazing.k3po.driver.internal.behavior.handler.event.OpenedHandler;
import org.kaazing.k3po.driver.internal.behavior.handler.event.ReadHandler;
import org.kaazing.k3po.driver.internal.behavior.handler.event.ReadHttpTrailersHandler;
import org.kaazing.k3po.driver.internal.behavior.handler.event.UnboundHandler;
import org.kaazing.k3po.driver.internal.behavior.visitor.GenerateConfigurationVisitor.State;
import org.kaazing.k3po.driver.internal.netty.bootstrap.BootstrapFactory;
import org.kaazing.k3po.driver.internal.netty.channel.ChannelAddressFactory;
import org.kaazing.k3po.driver.internal.resolver.ClientBootstrapResolver;
import org.kaazing.k3po.driver.internal.resolver.LocationResolver;
import org.kaazing.k3po.driver.internal.resolver.OptionsResolver;
import org.kaazing.k3po.driver.internal.resolver.ServerBootstrapResolver;
import org.kaazing.k3po.lang.internal.RegionInfo;
import org.kaazing.k3po.lang.internal.ast.AstAbortNode;
import org.kaazing.k3po.lang.internal.ast.AstAbortedNode;
import org.kaazing.k3po.lang.internal.ast.AstAcceptNode;
import org.kaazing.k3po.lang.internal.ast.AstAcceptableNode;
import org.kaazing.k3po.lang.internal.ast.AstBoundNode;
import org.kaazing.k3po.lang.internal.ast.AstChildClosedNode;
import org.kaazing.k3po.lang.internal.ast.AstChildOpenedNode;
import org.kaazing.k3po.lang.internal.ast.AstCloseNode;
import org.kaazing.k3po.lang.internal.ast.AstClosedNode;
import org.kaazing.k3po.lang.internal.ast.AstConnectNode;
import org.kaazing.k3po.lang.internal.ast.AstConnectedNode;
import org.kaazing.k3po.lang.internal.ast.AstDisconnectNode;
import org.kaazing.k3po.lang.internal.ast.AstDisconnectedNode;
import org.kaazing.k3po.lang.internal.ast.AstNode;
import org.kaazing.k3po.lang.internal.ast.AstOpenedNode;
import org.kaazing.k3po.lang.internal.ast.AstPropertyNode;
import org.kaazing.k3po.lang.internal.ast.AstReadAwaitNode;
import org.kaazing.k3po.lang.internal.ast.AstReadClosedNode;
import org.kaazing.k3po.lang.internal.ast.AstReadConfigNode;
import org.kaazing.k3po.lang.internal.ast.AstReadNotifyNode;
import org.kaazing.k3po.lang.internal.ast.AstReadOptionNode;
import org.kaazing.k3po.lang.internal.ast.AstReadValueNode;
import org.kaazing.k3po.lang.internal.ast.AstScriptNode;
import org.kaazing.k3po.lang.internal.ast.AstStreamNode;
import org.kaazing.k3po.lang.internal.ast.AstStreamableNode;
import org.kaazing.k3po.lang.internal.ast.AstUnbindNode;
import org.kaazing.k3po.lang.internal.ast.AstUnboundNode;
import org.kaazing.k3po.lang.internal.ast.AstWriteAwaitNode;
import org.kaazing.k3po.lang.internal.ast.AstWriteCloseNode;
import org.kaazing.k3po.lang.internal.ast.AstWriteConfigNode;
import org.kaazing.k3po.lang.internal.ast.AstWriteFlushNode;
import org.kaazing.k3po.lang.internal.ast.AstWriteNotifyNode;
import org.kaazing.k3po.lang.internal.ast.AstWriteOptionNode;
import org.kaazing.k3po.lang.internal.ast.AstWriteValueNode;
import org.kaazing.k3po.lang.internal.ast.matcher.AstByteLengthBytesMatcher;
import org.kaazing.k3po.lang.internal.ast.matcher.AstExactBytesMatcher;
import org.kaazing.k3po.lang.internal.ast.matcher.AstExactTextMatcher;
import org.kaazing.k3po.lang.internal.ast.matcher.AstExpressionMatcher;
import org.kaazing.k3po.lang.internal.ast.matcher.AstFixedLengthBytesMatcher;
import org.kaazing.k3po.lang.internal.ast.matcher.AstIntLengthBytesMatcher;
import org.kaazing.k3po.lang.internal.ast.matcher.AstLongLengthBytesMatcher;
import org.kaazing.k3po.lang.internal.ast.matcher.AstRegexMatcher;
import org.kaazing.k3po.lang.internal.ast.matcher.AstShortLengthBytesMatcher;
import org.kaazing.k3po.lang.internal.ast.matcher.AstValueMatcher;
import org.kaazing.k3po.lang.internal.ast.matcher.AstVariableLengthBytesMatcher;
import org.kaazing.k3po.lang.internal.ast.value.AstExpressionValue;
import org.kaazing.k3po.lang.internal.ast.value.AstLiteralBytesValue;
import org.kaazing.k3po.lang.internal.ast.value.AstLiteralTextValue;
import org.kaazing.k3po.lang.internal.ast.value.AstValue;
import org.kaazing.k3po.lang.internal.el.ExpressionContext;
/**
* Builds the pipeline of handlers that are used to "execute" the Robot script.
*/
public class GenerateConfigurationVisitor implements AstNode.Visitor<Configuration, State> {
private static final InternalLogger LOGGER = InternalLoggerFactory.getInstance(GenerateConfigurationVisitor.class);
private final ChannelAddressFactory addressFactory;
private final BootstrapFactory bootstrapFactory;
public static final class State {
private final ConcurrentMap<String, Barrier> barriersByName;
private Configuration configuration;
// the read / write maskers are reset per stream
private Masker readUnmasker;
private Masker writeMasker;
/* The pipelineAsMap is built by each node that is visited. */
private Map<String, ChannelHandler> pipelineAsMap;
public State(ConcurrentMap<String, Barrier> barriersByName) {
this.barriersByName = barriersByName;
}
private Barrier lookupBarrier(String barrierName) {
Barrier barrier = barriersByName.get(barrierName);
if (barrier == null) {
Barrier newBarrier = new Barrier(barrierName);
barrier = barriersByName.putIfAbsent(barrierName, newBarrier);
if (barrier == null) {
barrier = newBarrier;
}
}
return barrier;
}
public class PipelineFactory {
private Map<URI, List<ChannelPipeline>> pipelines = new HashMap<>();
public List<ChannelPipeline> getPipeline(URI acceptURI) {
List<ChannelPipeline> pipeline = pipelines.get(acceptURI);
if (pipeline == null) {
pipeline = new ArrayList<>();
pipelines.put(acceptURI, pipeline);
}
return pipeline;
}
}
public Map<String, Barrier> getBarriersByName() {
return barriersByName;
}
}
public GenerateConfigurationVisitor(BootstrapFactory bootstrapFactory, ChannelAddressFactory addressFactory) {
this.bootstrapFactory = bootstrapFactory;
this.addressFactory = addressFactory;
}
@Override
public Configuration visit(AstScriptNode script, State state) throws Exception {
state.configuration = new Configuration();
for (AstPropertyNode property : script.getProperties()) {
property.accept(this, state);
}
for (AstStreamNode stream : script.getStreams()) {
stream.accept(this, state);
}
return state.configuration;
}
@Override
public Configuration visit(AstPropertyNode propertyNode, State state) throws Exception {
String propertyName = propertyNode.getPropertyName();
AstValue propertyValue = propertyNode.getPropertyValue();
ExpressionContext environment = propertyNode.getExpressionContext();
Object value = propertyValue.accept(new GeneratePropertyValueVisitor(), environment);
ELResolver resolver = environment.getELResolver();
// TODO: Remove when JUEL sync bug is fixed https://github.com/k3po/k3po/issues/147
synchronized (environment) {
resolver.setValue(environment, null, propertyName, value);
}
if (value instanceof AutoCloseable) {
state.configuration.getResources().add((AutoCloseable) value);
}
if (LOGGER.isDebugEnabled()) {
Object formatValue = (value instanceof byte[]) ? AstLiteralBytesValue.toString((byte[]) value) : value;
LOGGER.debug(format("Setting value for ${%s} to %s", propertyName, formatValue));
}
return state.configuration;
}
private static class GeneratePropertyValueVisitor implements AstValue.Visitor<Object, ExpressionContext> {
@Override
public Object visit(AstExpressionValue value, ExpressionContext environment) throws Exception {
// TODO: Remove when JUEL sync bug is fixed https://github.com/k3po/k3po/issues/147
synchronized (environment) {
return value.getValue().getValue(environment);
}
}
@Override
public Object visit(AstLiteralTextValue value, ExpressionContext environment) throws Exception {
return value.getValue();
}
@Override
public Object visit(AstLiteralBytesValue value, ExpressionContext environment) throws Exception {
return value.getValue();
}
}
@Override
public Configuration visit(AstAcceptableNode acceptedNode, State state) throws Exception {
// masking is a no-op by default for each stream
state.readUnmasker = Masker.IDENTITY_MASKER;
state.writeMasker = Masker.IDENTITY_MASKER;
state.pipelineAsMap = new LinkedHashMap<>();
for (AstStreamableNode streamable : acceptedNode.getStreamables()) {
streamable.accept(this, state);
}
Map<String, ChannelHandler> pipelineAsMap = state.pipelineAsMap;
String handlerName = String.format("completion#%d", pipelineAsMap.size() + 1);
CompletionHandler handler = new CompletionHandler();
handler.setRegionInfo(acceptedNode.getRegionInfo());
pipelineAsMap.put(handlerName, handler);
return state.configuration;
}
@Override
public Configuration visit(AstAcceptNode acceptNode, State state) throws Exception {
Map<String, ChannelHandler> savedPipelineAsMap = state.pipelineAsMap;
// masking is a no-op by default for each stream
state.readUnmasker = Masker.IDENTITY_MASKER;
state.writeMasker = Masker.IDENTITY_MASKER;
/* Create a list of pipelines, for each acceptable */
final List<ChannelPipeline> pipelines = new ArrayList<>();
state.pipelineAsMap = new LinkedHashMap<>();
for (AstAcceptableNode acceptableNode : acceptNode.getAcceptables()) {
acceptableNode.accept(this, state);
ChannelPipeline pipeline = pipelineFromMap(state.pipelineAsMap);
pipelines.add(pipeline);
}
state.pipelineAsMap = savedPipelineAsMap;
// retain pipelines for tear down
RegionInfo acceptInfo = acceptNode.getRegionInfo();
state.configuration.getServerPipelines(acceptInfo).addAll(pipelines);
state.configuration.getClientAndServerPipelines().addAll(pipelines);
/*
* As new connections are accepted we grab a pipeline line off the list. Note the pipelines map is ordered. Note
* that the final pipeline is just a Fail and Complete so that additional connect attempts will fail.
*/
ChannelPipelineFactory pipelineFactory = new ChannelPipelineFactory() {
private final Iterator<ChannelPipeline> i = pipelines.iterator();
@Override
public ChannelPipeline getPipeline() throws Exception {
return i.hasNext() ? i.next() : pipeline(new FailureHandler(), new CompletionHandler());
}
};
Map<String, Object> acceptOptions = new HashMap<>();
acceptOptions.put("regionInfo", acceptInfo);
acceptOptions.putAll(acceptNode.getOptions());
OptionsResolver optionsResolver = new OptionsResolver(acceptOptions, acceptNode.getEnvironment());
String notifyName = acceptNode.getNotifyName();
Barrier notifyBarrier = null;
if (notifyName != null) {
notifyBarrier = state.lookupBarrier(notifyName);
}
// Now that accept supports expression value, accept uri may not be available at this point.
// To defer the evaluation of accept uri and initialization of ServerBootstrap, LocationResolver and
// ServerResolver are created with information necessary to create ClientBootstrap when the
// accept uri is available.
LocationResolver locationResolver = new LocationResolver(acceptNode.getLocation(), acceptNode.getEnvironment());
ServerBootstrapResolver serverResolver = new ServerBootstrapResolver(bootstrapFactory, addressFactory,
pipelineFactory, locationResolver, optionsResolver, notifyBarrier);
state.configuration.getServerResolvers().add(serverResolver);
return state.configuration;
}
/**
* Creates the pipeline, visits all streamable nodes and the creates the ClientBootstrap with the pipeline and
* remote address,
*/
@Override
public Configuration visit(AstConnectNode connectNode, State state) throws Exception {
// masking is a no-op by default for each stream
state.readUnmasker = Masker.IDENTITY_MASKER;
state.writeMasker = Masker.IDENTITY_MASKER;
state.pipelineAsMap = new LinkedHashMap<>();
for (AstStreamableNode streamable : connectNode.getStreamables()) {
streamable.accept(this, state);
}
/* Add the completion handler */
String handlerName = String.format("completion#%d", state.pipelineAsMap.size() + 1);
CompletionHandler completionHandler = new CompletionHandler();
completionHandler.setRegionInfo(connectNode.getRegionInfo());
state.pipelineAsMap.put(handlerName, completionHandler);
String awaitName = connectNode.getAwaitName();
Barrier awaitBarrier = null;
if (awaitName != null) {
awaitBarrier = state.lookupBarrier(awaitName);
}
final ChannelPipeline pipeline = pipelineFromMap(state.pipelineAsMap);
/*
* TODO. This is weird. I will only have one pipeline per connect. But if I don't set a factory When a connect
* occurs it will create a shallow copy of the pipeline I set. This doesn't work due to the beforeAdd methods in
* ExecutionHandler. Namely when the pipeline is cloned it uses the same handler objects so the handler future
* is not null and we fail with an assertion error.
*/
ChannelPipelineFactory pipelineFactory = new ChannelPipelineFactory() {
private int numCalled;
@Override
public ChannelPipeline getPipeline() throws Exception {
if (numCalled++ != 0) {
throw new RobotException("getPipeline called more than once");
}
return pipeline;
}
};
// Now that connect supports barrier and expression value, connect uri may not be available at this point.
// To defer the evaluation of connect uri and initialization of ClientBootstrap, LocationResolver and
// ClientResolver are created with information necessary to create ClientBootstrap when the connect uri
// is available.
LocationResolver locationResolver = new LocationResolver(connectNode.getLocation(), connectNode.getEnvironment());
OptionsResolver optionsResolver = new OptionsResolver(connectNode.getOptions(), connectNode.getEnvironment());
ClientBootstrapResolver clientResolver = new ClientBootstrapResolver(bootstrapFactory, addressFactory,
pipelineFactory, locationResolver, optionsResolver, awaitBarrier, connectNode.getRegionInfo());
// retain pipelines for tear down
state.configuration.getClientAndServerPipelines().add(pipeline);
state.configuration.getClientResolvers().add(clientResolver);
return state.configuration;
}
@Override
public Configuration visit(AstReadAwaitNode node, State state) throws Exception {
RegionInfo regionInfo = node.getRegionInfo();
String barrierName = node.getBarrierName();
Barrier barrier = state.lookupBarrier(barrierName);
AwaitBarrierUpstreamHandler handler = new AwaitBarrierUpstreamHandler(barrier);
handler.setRegionInfo(regionInfo);
Map<String, ChannelHandler> pipelineAsMap = state.pipelineAsMap;
String handlerName = String.format("read.await#%d", pipelineAsMap.size() + 1);
pipelineAsMap.put(handlerName, handler);
state.configuration.getBarriers().add(barrier);
return state.configuration;
}
@Override
public Configuration visit(AstWriteAwaitNode node, State state) throws Exception {
RegionInfo regionInfo = node.getRegionInfo();
String barrierName = node.getBarrierName();
Barrier barrier = state.lookupBarrier(barrierName);
AwaitBarrierDownstreamHandler handler = new AwaitBarrierDownstreamHandler(barrier);
handler.setRegionInfo(regionInfo);
Map<String, ChannelHandler> pipelineAsMap = state.pipelineAsMap;
String handlerName = String.format("write.await#%d", pipelineAsMap.size() + 1);
pipelineAsMap.put(handlerName, handler);
state.configuration.getBarriers().add(barrier);
return state.configuration;
}
@Override
public Configuration visit(AstReadNotifyNode node, State state) throws Exception {
RegionInfo regionInfo = node.getRegionInfo();
String barrierName = node.getBarrierName();
Barrier barrier = state.lookupBarrier(barrierName);
NotifyBarrierHandler handler = new NotifyBarrierHandler(barrier);
handler.setRegionInfo(regionInfo);
Map<String, ChannelHandler> pipelineAsMap = state.pipelineAsMap;
String handlerName = String.format("read.notify#%d", pipelineAsMap.size() + 1);
pipelineAsMap.put(handlerName, handler);
state.configuration.getBarriers().add(barrier);
return state.configuration;
}
@Override
public Configuration visit(AstWriteNotifyNode node, State state) throws Exception {
RegionInfo regionInfo = node.getRegionInfo();
String barrierName = node.getBarrierName();
Barrier barrier = state.lookupBarrier(barrierName);
NotifyBarrierHandler handler = new NotifyBarrierHandler(barrier);
handler.setRegionInfo(regionInfo);
Map<String, ChannelHandler> pipelineAsMap = state.pipelineAsMap;
String handlerName = String.format("write.notify#%d", pipelineAsMap.size() + 1);
pipelineAsMap.put(handlerName, handler);
state.configuration.getBarriers().add(barrier);
return state.configuration;
}
@Override
public Configuration visit(AstWriteValueNode node, State state) throws Exception {
List<MessageEncoder> messageEncoders = new ArrayList<>();
for (AstValue val : node.getValues()) {
messageEncoders.add(val.accept(new GenerateWriteEncoderVisitor(), state.configuration));
}
WriteHandler handler = new WriteHandler(messageEncoders, state.writeMasker);
handler.setRegionInfo(node.getRegionInfo());
String handlerName = String.format("write#%d", state.pipelineAsMap.size() + 1);
state.pipelineAsMap.put(handlerName, handler);
return state.configuration;
}
private static final class GenerateWriteEncoderVisitor implements AstValue.Visitor<MessageEncoder, Configuration> {
@Override
public MessageEncoder visit(AstExpressionValue value, Configuration config) throws Exception {
ExpressionContext environment = value.getEnvironment();
return new WriteExpressionEncoder(value.getValue(), environment);
}
@Override
public MessageEncoder visit(AstLiteralTextValue value, Configuration config) throws Exception {
return new WriteTextEncoder(value.getValue(), UTF_8);
}
@Override
public MessageEncoder visit(AstLiteralBytesValue value, Configuration config) throws Exception {
return new WriteBytesEncoder(value.getValue());
}
}
@Override
public Configuration visit(AstDisconnectNode node, State state) throws Exception {
RegionInfo regionInfo = node.getRegionInfo();
DisconnectHandler handler = new DisconnectHandler();
handler.setRegionInfo(regionInfo);
Map<String, ChannelHandler> pipelineAsMap = state.pipelineAsMap;
String handlerName = String.format("disconnect#%d", pipelineAsMap.size() + 1);
pipelineAsMap.put(handlerName, handler);
return state.configuration;
}
@Override
public Configuration visit(AstUnbindNode node, State state) throws Exception {
RegionInfo regionInfo = node.getRegionInfo();
UnbindHandler handler = new UnbindHandler();
handler.setRegionInfo(regionInfo);
Map<String, ChannelHandler> pipelineAsMap = state.pipelineAsMap;
String handlerName = String.format("unbind#%d", pipelineAsMap.size() + 1);
pipelineAsMap.put(handlerName, handler);
return state.configuration;
}
@Override
public Configuration visit(AstCloseNode node, State state) throws Exception {
RegionInfo regionInfo = node.getRegionInfo();
CloseHandler handler = new CloseHandler();
handler.setRegionInfo(regionInfo);
Map<String, ChannelHandler> pipelineAsMap = state.pipelineAsMap;
String handlerName = String.format("close#%d", pipelineAsMap.size() + 1);
pipelineAsMap.put(handlerName, handler);
return state.configuration;
}
@Override
public Configuration visit(AstAbortNode node, State state) throws Exception {
RegionInfo regionInfo = node.getRegionInfo();
AbortHandler handler = new AbortHandler();
handler.setRegionInfo(regionInfo);
Map<String, ChannelHandler> pipelineAsMap = state.pipelineAsMap;
String handlerName = String.format("abort#%d", pipelineAsMap.size() + 1);
pipelineAsMap.put(handlerName, handler);
return state.configuration;
}
@Override
public Configuration visit(AstChildOpenedNode node, State state) throws Exception {
RegionInfo regionInfo = node.getRegionInfo();
ChildOpenedHandler handler = new ChildOpenedHandler();
handler.setRegionInfo(regionInfo);
Map<String, ChannelHandler> pipelineAsMap = state.pipelineAsMap;
String handlerName = String.format("childOpened#%d", pipelineAsMap.size() + 1);
pipelineAsMap.put(handlerName, handler);
return state.configuration;
}
@Override
public Configuration visit(AstChildClosedNode node, State state) throws Exception {
RegionInfo regionInfo = node.getRegionInfo();
ChildClosedHandler handler = new ChildClosedHandler();
handler.setRegionInfo(regionInfo);
Map<String, ChannelHandler> pipelineAsMap = state.pipelineAsMap;
String handlerName = String.format("childClosed#%d", pipelineAsMap.size() + 1);
pipelineAsMap.put(handlerName, handler);
return state.configuration;
}
@Override
public Configuration visit(AstOpenedNode node, State state) throws Exception {
RegionInfo regionInfo = node.getRegionInfo();
OpenedHandler handler = new OpenedHandler();
handler.setRegionInfo(regionInfo);
Map<String, ChannelHandler> pipelineAsMap = state.pipelineAsMap;
String handlerName = String.format("opened#%d", pipelineAsMap.size() + 1);
pipelineAsMap.put(handlerName, handler);
return state.configuration;
}
@Override
public Configuration visit(AstBoundNode node, State state) throws Exception {
RegionInfo regionInfo = node.getRegionInfo();
BoundHandler handler = new BoundHandler();
handler.setRegionInfo(regionInfo);
Map<String, ChannelHandler> pipelineAsMap = state.pipelineAsMap;
String handlerName = String.format("bound#%d", pipelineAsMap.size() + 1);
pipelineAsMap.put(handlerName, handler);
return state.configuration;
}
@Override
public Configuration visit(AstConnectedNode node, State state) throws Exception {
RegionInfo regionInfo = node.getRegionInfo();
ConnectedHandler handler = new ConnectedHandler();
handler.setRegionInfo(regionInfo);
Map<String, ChannelHandler> pipelineAsMap = state.pipelineAsMap;
String handlerName = String.format("connected#%d", pipelineAsMap.size() + 1);
pipelineAsMap.put(handlerName, handler);
return state.configuration;
}
@Override
public Configuration visit(AstReadValueNode node, State state) throws Exception {
List<MessageDecoder> messageDecoders = new ArrayList<>();
for (AstValueMatcher matcher : node.getMatchers()) {
messageDecoders.add(matcher.accept(new GenerateReadDecoderVisitor(), state.configuration));
}
ReadHandler handler = new ReadHandler(messageDecoders, state.readUnmasker);
handler.setRegionInfo(node.getRegionInfo());
Map<String, ChannelHandler> pipelineAsMap = state.pipelineAsMap;
String handlerName = String.format("read#%d", pipelineAsMap.size() + 1);
pipelineAsMap.put(handlerName, handler);
return state.configuration;
}
private static final class GenerateReadDecoderVisitor implements AstValueMatcher.Visitor<MessageDecoder, Configuration> {
@Override
public MessageDecoder visit(AstExpressionMatcher matcher, Configuration config) throws Exception {
ValueExpression expression = matcher.getValue();
ExpressionContext environment = matcher.getEnvironment();
return new ReadExpressionDecoder(matcher.getRegionInfo(), expression, environment);
}
@Override
public MessageDecoder visit(AstFixedLengthBytesMatcher matcher, Configuration config) throws Exception {
int length = matcher.getLength();
String captureName = matcher.getCaptureName();
ExpressionContext environment = matcher.getEnvironment();
MessageDecoder decoder =
(captureName != null) ? new ReadByteArrayBytesDecoder(matcher.getRegionInfo(), length, environment,
captureName) : new ReadByteArrayBytesDecoder(matcher.getRegionInfo(), length);
return decoder;
}
@Override
public MessageDecoder visit(AstByteLengthBytesMatcher matcher, Configuration config) throws Exception {
// String captureName = matcher.getCaptureName();
// ExpressionContext environment = state.configuration.getExpressionContext();
// state.readDecoders.add(new ReadByteLengthBytesDecoder(environment, captureName));
// return null;
return fixedLengthVisit(matcher, config, ReadByteLengthBytesDecoder.class);
}
@Override
public MessageDecoder visit(AstShortLengthBytesMatcher matcher, Configuration config) throws Exception {
// String captureName = matcher.getCaptureName();
// ExpressionContext environment = state.configuration.getExpressionContext();
// state.readDecoders.add(new ReadShortLengthBytesDecoder(environment, captureName));
// return null;
return fixedLengthVisit(matcher, config, ReadShortLengthBytesDecoder.class);
}
@Override
public MessageDecoder visit(AstIntLengthBytesMatcher matcher, Configuration config) throws Exception {
// String captureName = matcher.getCaptureName();
// ExpressionContext environment = state.configuration.getExpressionContext();
// state.readDecoders.add(new ReadIntLengthBytesDecoder(environment, captureName));
// return null;
return fixedLengthVisit(matcher, config, ReadIntLengthBytesDecoder.class);
}
@Override
public MessageDecoder visit(AstLongLengthBytesMatcher matcher, Configuration config) throws Exception {
// String captureName = matcher.getCaptureName();
// ExpressionContext environment = state.configuration.getExpressionContext();
// state.readDecoders.add(new ReadLongLengthBytesDecoder(environment, captureName));
// return null;
return fixedLengthVisit(matcher, config, ReadLongLengthBytesDecoder.class);
}
private MessageDecoder fixedLengthVisit(AstFixedLengthBytesMatcher matcher, Configuration config, Class<?> clazz)
throws NoSuchMethodException, SecurityException, InstantiationException, IllegalAccessException,
IllegalArgumentException, InvocationTargetException {
String captureName = matcher.getCaptureName();
RegionInfo regionInfo = matcher.getRegionInfo();
ExpressionContext environment = matcher.getEnvironment();
@SuppressWarnings("unchecked") Constructor<MessageDecoder> constructor =
(Constructor<MessageDecoder>) clazz.getConstructor(RegionInfo.class, ExpressionContext.class, String.class);
return constructor.newInstance(regionInfo, environment, captureName);
}
@Override
public MessageDecoder visit(AstRegexMatcher matcher, Configuration config) throws Exception {
ExpressionContext environment = matcher.getEnvironment();
MessageDecoder result;
result = new ReadRegexDecoder(matcher.getRegionInfo(), matcher.getValue(), UTF_8, environment);
return result;
}
@Override
public MessageDecoder visit(AstExactTextMatcher matcher, Configuration config) throws Exception {
return new ReadExactTextDecoder(matcher.getRegionInfo(), matcher.getValue(), UTF_8);
}
@Override
public MessageDecoder visit(AstExactBytesMatcher matcher, Configuration config) throws Exception {
return new ReadExactBytesDecoder(matcher.getRegionInfo(), matcher.getValue());
}
@Override
public MessageDecoder visit(AstVariableLengthBytesMatcher matcher, Configuration config) throws Exception {
ValueExpression length = matcher.getLength();
String captureName = matcher.getCaptureName();
ExpressionContext environment = matcher.getEnvironment();
MessageDecoder decoder =
(captureName != null) ? new ReadVariableLengthBytesDecoder(matcher.getRegionInfo(), length, environment,
captureName) : new ReadVariableLengthBytesDecoder(matcher.getRegionInfo(), length, environment);
return decoder;
}
}
@Override
public Configuration visit(AstDisconnectedNode node, State state) throws Exception {
RegionInfo regionInfo = node.getRegionInfo();
DisconnectedHandler handler = new DisconnectedHandler();
handler.setRegionInfo(regionInfo);
Map<String, ChannelHandler> pipelineAsMap = state.pipelineAsMap;
String handlerName = String.format("disconnected#%d", pipelineAsMap.size() + 1);
pipelineAsMap.put(handlerName, handler);
return state.configuration;
}
@Override
public Configuration visit(AstUnboundNode node, State state) throws Exception {
RegionInfo regionInfo = node.getRegionInfo();
UnboundHandler handler = new UnboundHandler();
handler.setRegionInfo(regionInfo);
Map<String, ChannelHandler> pipelineAsMap = state.pipelineAsMap;
String handlerName = String.format("unbound#%d", pipelineAsMap.size() + 1);
pipelineAsMap.put(handlerName, handler);
return state.configuration;
}
@Override
public Configuration visit(AstClosedNode node, State state) throws Exception {
RegionInfo regionInfo = node.getRegionInfo();
ClosedHandler handler = new ClosedHandler();
handler.setRegionInfo(regionInfo);
Map<String, ChannelHandler> pipelineAsMap = state.pipelineAsMap;
String handlerName = String.format("closed#%d", pipelineAsMap.size() + 1);
pipelineAsMap.put(handlerName, handler);
return state.configuration;
}
@Override
public Configuration visit(AstAbortedNode node, State state) throws Exception {
RegionInfo regionInfo = node.getRegionInfo();
AbortedHandler handler = new AbortedHandler();
handler.setRegionInfo(regionInfo);
Map<String, ChannelHandler> pipelineAsMap = state.pipelineAsMap;
String handlerName = String.format("aborted#%d", pipelineAsMap.size() + 1);
pipelineAsMap.put(handlerName, handler);
return state.configuration;
}
private static ChannelPipeline pipelineFromMap(Map<String, ChannelHandler> pipelineAsMap) {
ChannelPipeline pipeline = pipeline();
for (Map.Entry<String, ChannelHandler> entry : pipelineAsMap.entrySet()) {
pipeline.addLast(entry.getKey(), entry.getValue());
}
return pipeline;
}
// HTTP
@Override
public Configuration visit(AstReadConfigNode node, State state) throws Exception {
switch (node.getType()) {
case "method": {
AstValueMatcher methodName = node.getMatcher("name");
requireNonNull(methodName);
MessageDecoder methodValueDecoder = methodName.accept(new GenerateReadDecoderVisitor(), state.configuration);
// TODO: compareEqualsIgnoreCase
ReadConfigHandler handler = new ReadConfigHandler(new HttpMethodDecoder(methodValueDecoder));
handler.setRegionInfo(node.getRegionInfo());
Map<String, ChannelHandler> pipelineAsMap = state.pipelineAsMap;
String handlerName = String.format("readConfig#%d (http method)", pipelineAsMap.size() + 1);
pipelineAsMap.put(handlerName, handler);
return state.configuration;
}
case "header": {
AstLiteralTextValue name = (AstLiteralTextValue) node.getValue("name");
requireNonNull(name);
List<MessageDecoder> valueDecoders = new ArrayList<>();
for (AstValueMatcher matcher : node.getMatchers()) {
valueDecoders.add(matcher.accept(new GenerateReadDecoderVisitor(), state.configuration));
}
HttpHeaderDecoder decoder = new HttpHeaderDecoder(name.getValue(), valueDecoders);
decoder.setRegionInfo(node.getRegionInfo());
ReadConfigHandler handler = new ReadConfigHandler(decoder);
handler.setRegionInfo(node.getRegionInfo());
Map<String, ChannelHandler> pipelineAsMap = state.pipelineAsMap;
String handlerName = String.format("readConfig#%d (http header)", pipelineAsMap.size() + 1);
pipelineAsMap.put(handlerName, handler);
return state.configuration;
}
case "header missing": {
AstLiteralTextValue name = (AstLiteralTextValue) node.getValue("name");
requireNonNull(name);
HttpHeaderMissingDecoder decoder = new HttpHeaderMissingDecoder(name.getValue());
decoder.setRegionInfo(node.getRegionInfo());
ReadConfigHandler handler = new ReadConfigHandler(decoder);
handler.setRegionInfo(node.getRegionInfo());
Map<String, ChannelHandler> pipelineAsMap = state.pipelineAsMap;
String handlerName = String.format("readConfig#%d (http header missing)", pipelineAsMap.size() + 1);
pipelineAsMap.put(handlerName, handler);
return state.configuration;
}
case "parameter": {
AstLiteralTextValue name = (AstLiteralTextValue) node.getValue("name");
requireNonNull(name);
List<MessageDecoder> valueDecoders = new ArrayList<>();
for (AstValueMatcher matcher : node.getMatchers()) {
valueDecoders.add(matcher.accept(new GenerateReadDecoderVisitor(), state.configuration));
}
HttpParameterDecoder decoder = new HttpParameterDecoder(name.getValue(), valueDecoders);
decoder.setRegionInfo(node.getRegionInfo());
ReadConfigHandler handler = new ReadConfigHandler(decoder);
handler.setRegionInfo(node.getRegionInfo());
Map<String, ChannelHandler> pipelineAsMap = state.pipelineAsMap;
String handlerName = String.format("readConfig#%d (http parameter)", pipelineAsMap.size() + 1);
pipelineAsMap.put(handlerName, handler);
return state.configuration;
}
case "version": {
AstValueMatcher version = node.getMatcher("version");
MessageDecoder versionDecoder = version.accept(new GenerateReadDecoderVisitor(), state.configuration);
ReadConfigHandler handler = new ReadConfigHandler(new HttpVersionDecoder(versionDecoder));
handler.setRegionInfo(node.getRegionInfo());
Map<String, ChannelHandler> pipelineAsMap = state.pipelineAsMap;
String handlerName = String.format("readConfig#%d (http version)", pipelineAsMap.size() + 1);
pipelineAsMap.put(handlerName, handler);
return state.configuration;
}
case "status": {
AstValueMatcher code = node.getMatcher("code");
AstValueMatcher reason = node.getMatcher("reason");
MessageDecoder codeDecoder = code.accept(new GenerateReadDecoderVisitor(), state.configuration);
MessageDecoder reasonDecoder = reason.accept(new GenerateReadDecoderVisitor(), state.configuration);
ReadConfigHandler handler = new ReadConfigHandler(new HttpStatusDecoder(codeDecoder, reasonDecoder));
handler.setRegionInfo(node.getRegionInfo());
Map<String, ChannelHandler> pipelineAsMap = state.pipelineAsMap;
String handlerName = String.format("readConfig#%d (http status)", pipelineAsMap.size() + 1);
pipelineAsMap.put(handlerName, handler);
return state.configuration;
}
case "trailer": {
AstLiteralTextValue name = (AstLiteralTextValue) node.getValue("name");
List<MessageDecoder> valueDecoders = new ArrayList<>();
for (AstValueMatcher matcher : node.getMatchers()) {
valueDecoders.add(matcher.accept(new GenerateReadDecoderVisitor(), state.configuration));
}
HttpTrailerDecoder httpTrailerDecoder = new HttpTrailerDecoder(name.getValue(), valueDecoders);
ReadHttpTrailersHandler handler =
new ReadHttpTrailersHandler(httpTrailerDecoder);
// Ideally we could use a ReadConfigHandler as follows, but the trailers come insync with
// with the channel close, which completes the composite future of all handlers and checks
// the ReadConfigHandler to see if it completed.
// HttpTrailerDecoder decoder = new HttpTrailerDecoder(name.getValue(), valueDecoders);
// decoder.setRegionInfo(node.getRegionInfo());
// ReadConfigHandler handler = new ReadConfigHandler(decoder);
httpTrailerDecoder.setRegionInfo(node.getRegionInfo());
handler.setRegionInfo(node.getRegionInfo());
Map<String, ChannelHandler> pipelineAsMap = state.pipelineAsMap;
String handlerName = String.format("readConfig#%d (http status)", pipelineAsMap.size() + 1);
pipelineAsMap.put(handlerName, handler);
return state.configuration;
}
default:
throw new IllegalStateException("Unrecognized configuration type: " + node.getType());
}
}
@Override
public Configuration visit(AstWriteConfigNode node, State state) throws Exception {
switch (node.getType()) {
case "request": {
AstValue form = (AstLiteralTextValue) node.getValue("form");
MessageEncoder formEncoder = form.accept(new GenerateWriteEncoderVisitor(), state.configuration);
WriteConfigHandler handler = new WriteConfigHandler(new HttpRequestFormEncoder(formEncoder));
handler.setRegionInfo(node.getRegionInfo());
String handlerName = String.format("writeConfig#%d (http request)", state.pipelineAsMap.size() + 1);
state.pipelineAsMap.put(handlerName, handler);
return state.configuration;
}
case "header": {
AstValue name = node.getName("name");
MessageEncoder nameEncoder = name.accept(new GenerateWriteEncoderVisitor(), state.configuration);
List<MessageEncoder> valueEncoders = new ArrayList<>();
for (AstValue value : node.getValues()) {
valueEncoders.add(value.accept(new GenerateWriteEncoderVisitor(), state.configuration));
}
WriteConfigHandler handler = new WriteConfigHandler(new HttpHeaderEncoder(nameEncoder, valueEncoders));
handler.setRegionInfo(node.getRegionInfo());
String handlerName = String.format("writeConfig#%d (http header)", state.pipelineAsMap.size() + 1);
state.pipelineAsMap.put(handlerName, handler);
return state.configuration;
}
case "content-length": {
WriteConfigHandler handler = new WriteConfigHandler(new HttpContentLengthEncoder());
handler.setRegionInfo(node.getRegionInfo());
String handlerName = String.format("writeConfig#%d (http content length)", state.pipelineAsMap.size() + 1);
state.pipelineAsMap.put(handlerName, handler);
return null;
}
case "host": {
WriteConfigHandler handler = new WriteConfigHandler(new HttpHostEncoder());
handler.setRegionInfo(node.getRegionInfo());
String handlerName = String.format("writeConfig#%d (http host)", state.pipelineAsMap.size() + 1);
state.pipelineAsMap.put(handlerName, handler);
return null;
}
case "method": {
AstValue methodName = node.getValue();
requireNonNull(methodName);
MessageEncoder methodEncoder = methodName.accept(new GenerateWriteEncoderVisitor(), state.configuration);
WriteConfigHandler handler = new WriteConfigHandler(new HttpMethodEncoder(methodEncoder));
handler.setRegionInfo(node.getRegionInfo());
String handlerName = String.format("writeConfig#%d (http method)", state.pipelineAsMap.size() + 1);
state.pipelineAsMap.put(handlerName, handler);
return state.configuration;
}
case "parameter": {
AstValue name = node.getName("name");
MessageEncoder nameEncoder = name.accept(new GenerateWriteEncoderVisitor(), state.configuration);
List<MessageEncoder> valueEncoders = new ArrayList<>();
for (AstValue value : node.getValues()) {
valueEncoders.add(value.accept(new GenerateWriteEncoderVisitor(), state.configuration));
}
WriteConfigHandler handler = new WriteConfigHandler(new HttpParameterEncoder(nameEncoder, valueEncoders));
handler.setRegionInfo(node.getRegionInfo());
String handlerName = String.format("writeConfig#%d (http parameter)", state.pipelineAsMap.size() + 1);
state.pipelineAsMap.put(handlerName, handler);
return state.configuration;
}
case "version": {
AstValue version = node.getValue();
MessageEncoder versionEncoder = version.accept(new GenerateWriteEncoderVisitor(), state.configuration);
WriteConfigHandler handler = new WriteConfigHandler(new HttpVersionEncoder(versionEncoder));
handler.setRegionInfo(node.getRegionInfo());
String handlerName = String.format("writeConfig#%d (http version)", state.pipelineAsMap.size() + 1);
state.pipelineAsMap.put(handlerName, handler);
return state.configuration;
}
case "status": {
AstValue code = node.getValue("code");
AstValue reason = node.getValue("reason");
MessageEncoder codeEncoder = code.accept(new GenerateWriteEncoderVisitor(), state.configuration);
MessageEncoder reasonEncoder = reason.accept(new GenerateWriteEncoderVisitor(), state.configuration);
WriteConfigHandler handler = new WriteConfigHandler(new HttpStatusEncoder(codeEncoder, reasonEncoder));
handler.setRegionInfo(node.getRegionInfo());
String handlerName = String.format("writeConfig#%d (http status)", state.pipelineAsMap.size() + 1);
state.pipelineAsMap.put(handlerName, handler);
return state.configuration;
}
case "trailer": {
AstValue name = node.getName("name");
MessageEncoder nameEncoder = name.accept(new GenerateWriteEncoderVisitor(), state.configuration);
List<MessageEncoder> valueEncoders = new ArrayList<>();
for (AstValue value : node.getValues()) {
valueEncoders.add(value.accept(new GenerateWriteEncoderVisitor(), state.configuration));
}
WriteConfigHandler handler = new WriteConfigHandler(new HttpTrailerEncoder(nameEncoder, valueEncoders));
String handlerName = String.format("writeConfig#%d (http trailer)", state.pipelineAsMap.size() + 1);
state.pipelineAsMap.put(handlerName, handler);
return state.configuration;
}
default:
throw new IllegalStateException("Unrecognized configuration type: " + node.getType());
}
}
@Override
public Configuration visit(AstReadClosedNode node, State state) throws Exception {
InputShutdownHandler handler = new InputShutdownHandler();
handler.setRegionInfo(node.getRegionInfo());
String handlerName = String.format("readClosed#%d", state.pipelineAsMap.size() + 1);
state.pipelineAsMap.put(handlerName, handler);
return state.configuration;
}
@Override
public Configuration visit(AstWriteCloseNode node, State state) throws Exception {
ShutdownOutputHandler handler = new ShutdownOutputHandler();
handler.setRegionInfo(node.getRegionInfo());
String handlerName = String.format("writeClose#%d", state.pipelineAsMap.size() + 1);
state.pipelineAsMap.put(handlerName, handler);
return state.configuration;
}
@Override
public Configuration visit(AstWriteFlushNode node, State state) throws Exception {
FlushHandler handler = new FlushHandler();
handler.setRegionInfo(node.getRegionInfo());
String handlerName = String.format("flush#%d", state.pipelineAsMap.size() + 1);
state.pipelineAsMap.put(handlerName, handler);
return state.configuration;
}
@Override
public Configuration visit(AstReadOptionNode node, State state) throws Exception {
String optionName = node.getOptionName();
switch (optionName) {
case "mask" :
AstValue maskValue = node.getOptionValue();
state.readUnmasker = maskValue.accept(new GenerateMaskOptionValueVisitor(), state);
break;
case "offset" :
AstLiteralTextValue offsetValue = (AstLiteralTextValue) node.getOptionValue();
int offset = Integer.parseInt(offsetValue.getValue());
ReadOptionOffsetHandler handler = new ReadOptionOffsetHandler(offset);
handler.setRegionInfo(node.getRegionInfo());
String handlerName = String.format("readOption#%d (offset=%d)", state.pipelineAsMap.size() + 1, offset);
state.pipelineAsMap.put(handlerName, handler);
break;
case "chunkExtension":
throw new UnsupportedOperationException(
"HttpMessageDecoder and DefaultHttpChunk do not support chunkExtensions in Netty 3.9,"
+ " see https://github.com/k3po/k3po/issues/313, support for chunk extensions is thus not yet added");
default:
throw new IllegalArgumentException("Unrecognized read option : " + optionName);
}
return state.configuration;
}
@Override
public Configuration visit(AstWriteOptionNode node, State state) throws Exception {
String optionName = node.getOptionName();
switch (optionName) {
case "mask" :
AstValue maskValue = node.getOptionValue();
state.writeMasker = maskValue.accept(new GenerateMaskOptionValueVisitor(), state);
break;
case "offset" :
AstLiteralTextValue offsetValue = (AstLiteralTextValue) node.getOptionValue();
int offset = Integer.parseInt(offsetValue.getValue());
WriteOptionOffsetHandler handler = new WriteOptionOffsetHandler(offset);
handler.setRegionInfo(node.getRegionInfo());
String handlerName = String.format("writeOption#%d (offset=%d)", state.pipelineAsMap.size() + 1, offset);
state.pipelineAsMap.put(handlerName, handler);
break;
case "chunkExtension":
throw new UnsupportedOperationException(
"HttpMessageDecoder and DefaultHttpChunk do not support chunkExtensions in Netty 3.9,"
+ " see https://github.com/k3po/k3po/issues/313, support for chunk extensions is thus not yet added");
default:
throw new IllegalArgumentException("Unrecognized write option : " + optionName);
}
return state.configuration;
}
private static final class GenerateMaskOptionValueVisitor implements AstValue.Visitor<Masker, State> {
@Override
public Masker visit(AstExpressionValue value, State state) throws Exception {
ValueExpression expression = value.getValue();
ExpressionContext environment = value.getEnvironment();
return Maskers.newMasker(expression, environment);
}
@Override
public Masker visit(AstLiteralTextValue value, State state) throws Exception {
String literalText = value.getValue();
byte[] literalTextAsBytes = literalText.getBytes(UTF_8);
for (byte literalTextAsByte : literalTextAsBytes) {
if (literalTextAsByte != 0x00) {
return Maskers.newMasker(literalTextAsBytes);
}
}
// no need to unmask for all-zeros masking key
return Masker.IDENTITY_MASKER;
}
@Override
public Masker visit(AstLiteralBytesValue value, State state) throws Exception {
byte[] literalBytes = value.getValue();
for (byte literalByte : literalBytes) {
if (literalByte != 0x00) {
return Maskers.newMasker(literalBytes);
}
}
// no need to unmask for all-zeros masking key
return Masker.IDENTITY_MASKER;
}
}
}