/*
* Copyright 2016 Netflix, 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.reactivex.netty.protocol.tcp.server;
import io.netty.buffer.ByteBuf;
import io.reactivex.netty.channel.Connection;
import rx.annotations.Beta;
/**
* A utility to create an interceptor chain to be used with a {@link TcpServer} to modify behavior of connections
* accepted by that server.
*
* <h2>What are interceptors?</h2>
*
* Interceptors can be used to achieve use-cases that involve instrumentation related to any behavior of the connection,
* they can even be used to short-circuit the rest of the chain or to provide canned responses. In order to achieve such
* widely different use-cases, an interceptor is modelled as a simple function that takes one {@link ConnectionHandler}
* and returns another {@link ConnectionHandler} instance. With this low level abstraction, any use-case pertaining to
* connection instrumentation can be achieved.
*
* <h2>Interceptor chain anatomy</h2>
*
<PRE>
------- ------- ------- ------- -------
| | | | | | | | | |
| | ---> | | ---> | | ---> | | ---> | |
| | | | | | | | | |
------- ------- ------- ------- -------
Interceptor Interceptor Interceptor Interceptor Connection
1 2 3 4 Handler
</PRE>
*
* An interceptor chain always starts with an interceptor and ends with a {@link ConnectionHandler} and any number of
* other interceptors can exist between the start and end. <p>
*
* A chain can be created by using the various {@code start*()} methods available in this class, eg:
* {@link #start(Interceptor)}, {@link #startRaw(Interceptor)}.<p>
*
* After starting a chain, any number of other interceptors can be added by using the various {@code next*()} methods
* available in this class, eg: {@link #next(Interceptor)}, {@link #nextWithTransform(TransformingInterceptor)},
* {@link #nextWithReadTransform(TransformingInterceptor)} and {@link #nextWithWriteTransform(TransformingInterceptor)}<p>
*
* After adding the required interceptors, by providing a {@link ConnectionHandler} via the
* {@link #end(ConnectionHandler)} method, the chain can be ended and the returned {@link ConnectionHandler} can be used
* with any {@link TcpServer}<p>
*
* So, a typical interaction with this class would look like: <p>
*
* {@code
* TcpServer.newServer().start(TcpServerInterceptorChain.start(first).next(second).next(third).end(handler))
* }
*
* <h2>Simple Interceptor</h2>
*
* For interceptors that do not change the types of objects read or written to the underlying connection, the interface
* {@link TcpServerInterceptorChain.Interceptor} defines the interceptor contract.
*
* <h2>Modifying the type of data read/written to the {@link Connection}</h2>
*
* Sometimes, it is required to change the type of objects read or written to a {@link Connection} instance handled by
* a {@link TcpServer}. For such cases, the interface {@link TcpServerInterceptorChain.TransformingInterceptor} defines
* the interceptor contract. Since, this included 4 generic arguments to the interceptor, this is not the base type for
* all interceptors and should be used only when the types of the {@link Connection} are actually to be changed.
*
* <h2>Execution order</h2>
*
<PRE>
------- ------- ------- ------- ------- ------- -------
| | | | | | | | | | | | | |
| | ---> | | ---> | | ---> | | ---> | | ---> | | ---> | |
| | | | | | | | | | | | | |
------- ------- ------- ------- ------- ------- -------
Tcp Connection Interceptor Interceptor Interceptor Interceptor Connection
Server Handler 1 2 3 4 Handler
(Internal) (User)
</PRE>
*
* The above diagram depicts the execution order of interceptors. The first connection handler (internal) is created by
* this class and as is returned by {@link #end(ConnectionHandler)} method by providing a {@link ConnectionHandler} that
* does the actual processing of the connection. {@link TcpServer} with which this interceptor chain is used, will
* invoke the internal connection handler provided by this class. <p>
*
* The interceptors are invoked in the order that they are added to this chain.
*
* @param <R> The type of objects read from a connection to {@link TcpServer} with which this interceptor chain will be
* used.
* @param <W> The type of objects written to a connection to {@link TcpServer} with which this interceptor chain will be
* used.
* @param <RR> The type of objects read from a connection to {@link TcpServer} after applying this interceptor chain.
* @param <WW> The type of objects written to a connection to {@link TcpServer} after applying this interceptor chain.
*/
@Beta
public final class TcpServerInterceptorChain<R, W, RR, WW> {
private final TransformingInterceptor<R, W, RR, WW> interceptor;
private TcpServerInterceptorChain(TransformingInterceptor<R, W, RR, WW> interceptor) {
this.interceptor = interceptor;
}
/**
* Add the next interceptor to this chain.
*
* @param next Next interceptor to add.
*
* @return A new interceptor chain with the interceptors current existing and the passed interceptor added to the
* end.
*/
public TcpServerInterceptorChain<R, W, RR, WW> next(final Interceptor<RR, WW> next) {
return new TcpServerInterceptorChain<>(new TransformingInterceptor<R, W, RR, WW>() {
@Override
public ConnectionHandler<R, W> intercept(ConnectionHandler<RR, WW> handler) {
return interceptor.intercept(next.intercept(handler));
}
});
}
/**
* Add the next interceptor to this chain, which changes the type of objects read from the connections processed by
* the associated {@link TcpServer}.
*
* @param next Next interceptor to add.
*
* @return A new interceptor chain with the interceptors current existing and the passed interceptor added to the
* end.
*/
public <RRR> TcpServerInterceptorChain<R, W, RRR, WW> nextWithReadTransform(final TransformingInterceptor<RR, WW, RRR, WW> next) {
return new TcpServerInterceptorChain<>(new TransformingInterceptor<R, W, RRR, WW>() {
@Override
public ConnectionHandler<R, W> intercept(ConnectionHandler<RRR, WW> handler) {
return interceptor.intercept(next.intercept(handler));
}
});
}
/**
* Add the next interceptor to this chain, which changes the type of objects written to the connections processed by
* the associated {@link TcpServer}.
*
* @param next Next interceptor to add.
*
* @return A new interceptor chain with the interceptors current existing and the passed interceptor added to the
* end.
*/
public <WWW> TcpServerInterceptorChain<R, W, RR, WWW> nextWithWriteTransform(final TransformingInterceptor<RR, WW, RR, WWW> next) {
return new TcpServerInterceptorChain<>(new TransformingInterceptor<R, W, RR, WWW>() {
@Override
public ConnectionHandler<R, W> intercept(ConnectionHandler<RR, WWW> handler) {
return interceptor.intercept(next.intercept(handler));
}
});
}
/**
* Add the next interceptor to this chain, which changes the type of objects read and written from/to the
* connections processed by the associated {@link TcpServer}.
*
* @param next Next interceptor to add.
*
* @return A new interceptor chain with the interceptors current existing and the passed interceptor added to the
* end.
*/
public <RRR, WWW> TcpServerInterceptorChain<R, W, RRR, WWW> nextWithTransform(final TransformingInterceptor<RR, WW, RRR, WWW> next) {
return new TcpServerInterceptorChain<>(new TransformingInterceptor<R, W, RRR, WWW>() {
@Override
public ConnectionHandler<R, W> intercept(ConnectionHandler<RRR, WWW> handler) {
return interceptor.intercept(next.intercept(handler));
}
});
}
/**
* Terminates this chain with the passed {@link ConnectionHandler} and returns a {@link ConnectionHandler} to be
* used by a {@link TcpServer}
*
* @param handler Connection handler to use.
*
* @return A connection handler that wires the interceptor chain, to be used with {@link TcpServer} instead of
* directly using the passed {@code handler}
*/
public ConnectionHandler<R, W> end(ConnectionHandler<RR, WW> handler) {
return interceptor.intercept(handler);
}
/**
* One of the methods to start creating the interceptor chain. The other start methods can be used for starting with
* interceptors that modify the type of Objects read/written from/to the connections processed by the associated
* {@link TcpServer}.
*
* @param start The starting interceptor for this chain.
*
* @param <R> The type of objects read from a connection to {@link TcpServer} with which this interceptor chain will
* be used.
* @param <W> The type of objects written to a connection to {@link TcpServer} with which this interceptor chain
* will be used.
*
* @return A new interceptor chain.
*/
public static <R, W> TcpServerInterceptorChain<R, W, R, W> start(final Interceptor<R, W> start) {
return new TcpServerInterceptorChain<>(new TransformingInterceptor<R, W, R, W>() {
@Override
public ConnectionHandler<R, W> intercept(ConnectionHandler<R, W> handler) {
return start.intercept(handler);
}
});
}
/**
* One of the methods to start creating the interceptor chain. The other start methods can be used for starting with
* interceptors that modify the type of Objects read/written from/to the connections processed by the associated
* {@link TcpServer}.
*
* @param start The starting interceptor for this chain.
*
* @return A new interceptor chain.
*/
public static TcpServerInterceptorChain<ByteBuf, ByteBuf, ByteBuf, ByteBuf> startRaw(final Interceptor<ByteBuf, ByteBuf> start) {
return new TcpServerInterceptorChain<>(new TransformingInterceptor<ByteBuf, ByteBuf, ByteBuf, ByteBuf>() {
@Override
public ConnectionHandler<ByteBuf, ByteBuf> intercept(ConnectionHandler<ByteBuf, ByteBuf> handler) {
return start.intercept(handler);
}
});
}
/**
* An interceptor that preserves the type of objects read and written to the connection.
*
* @param <R> Type of objects read from the connection handled by this interceptor.
* @param <W> Type of objects written to the connection handled by this interceptor.
*/
public interface Interceptor<R, W> {
/**
* Intercepts and optionally changes the passed {@code ConnectionHandler}.
*
* @param handler Handler to intercept.
*
* @return Handler to use after this transformation.
*/
ConnectionHandler<R, W> intercept(ConnectionHandler<R, W> handler);
}
/**
* An interceptor that changes the type of objects read and written to the connection.
*
* @param <R> Type of objects read from the connection before applying this interceptor.
* @param <W> Type of objects written to the connection before applying this interceptor.
* @param <RR> Type of objects read from the connection after applying this interceptor.
* @param <WW> Type of objects written to the connection after applying this interceptor.
*/
public interface TransformingInterceptor<R, W, RR, WW> {
/**
* Intercepts and changes the passed {@code ConnectionHandler}.
*
* @param handler Handler to intercept.
*
* @return Handler to use after this transformation.
*/
ConnectionHandler<R, W> intercept(ConnectionHandler<RR, WW> handler);
}
}