/*
* Copyright 2015 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.channel;
import io.netty.channel.Channel;
import io.netty.channel.ChannelHandler;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelPipeline;
import io.netty.util.concurrent.EventExecutorGroup;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import rx.functions.Action1;
import rx.functions.Func0;
import java.util.LinkedList;
import java.util.Map.Entry;
import java.util.NoSuchElementException;
/**
* An implementation of {@link ChannelPipeline} which is detached from a channel and provides a
* {@link #addToChannel(Channel)} method to be invoked when this pipeline handlers are to be added to an actual channel
* pipeline.
*
* This must NOT be used on an actual channel, it does not support any channel operations. It only supports pipeline
* modification operations.
*/
public class DetachedChannelPipeline {
private static final Logger logger = LoggerFactory.getLogger(DetachedChannelPipeline.class);
private final LinkedList<HandlerHolder> holdersInOrder;
private final Action1<ChannelPipeline> nullableTail;
public DetachedChannelPipeline() {
this(null);
}
public DetachedChannelPipeline(final Action1<ChannelPipeline> nullableTail) {
this.nullableTail = nullableTail;
holdersInOrder = new LinkedList<>();
}
private DetachedChannelPipeline(final DetachedChannelPipeline copyFrom,
final Action1<ChannelPipeline> nullableTail) {
this.nullableTail = nullableTail;
holdersInOrder = new LinkedList<>();
synchronized (copyFrom.holdersInOrder) {
for (HandlerHolder handlerHolder : copyFrom.holdersInOrder) {
holdersInOrder.addLast(handlerHolder);
}
}
}
public ChannelInitializer<Channel> getChannelInitializer() {
return new ChannelInitializer<Channel>() {
@Override
protected void initChannel(Channel ch) throws Exception {
final ChannelPipeline pipeline = ch.pipeline();
synchronized (holdersInOrder) {
unguardedCopyToPipeline(pipeline);
}
}
};
}
public void addToChannel(Channel channel) {
final ChannelPipeline pipeline = channel.pipeline();
synchronized (holdersInOrder) {
unguardedCopyToPipeline(pipeline);
}
}
public DetachedChannelPipeline copy() {
return copy(null);
}
public DetachedChannelPipeline copy(Action1<ChannelPipeline> newTail) {
return new DetachedChannelPipeline(this, newTail);
}
public DetachedChannelPipeline addFirst(String name, Func0<ChannelHandler> handlerFactory) {
return _guardedAddFirst(new HandlerHolder(name, handlerFactory));
}
public DetachedChannelPipeline addFirst(EventExecutorGroup group,
String name, Func0<ChannelHandler> handlerFactory) {
return _guardedAddFirst(new HandlerHolder(name, handlerFactory, group));
}
public DetachedChannelPipeline addLast(String name, Func0<ChannelHandler> handlerFactory) {
return _guardedAddLast(new HandlerHolder(name, handlerFactory));
}
public DetachedChannelPipeline addLast(EventExecutorGroup group, String name, Func0<ChannelHandler> handlerFactory) {
return _guardedAddLast(new HandlerHolder(name, handlerFactory, group));
}
public DetachedChannelPipeline addBefore(String baseName, String name, Func0<ChannelHandler> handlerFactory) {
return _guardedAddBefore(baseName, new HandlerHolder(name, handlerFactory));
}
public DetachedChannelPipeline addBefore(EventExecutorGroup group, String baseName, String name, Func0<ChannelHandler> handlerFactory) {
return _guardedAddBefore(baseName, new HandlerHolder(name, handlerFactory, group));
}
public DetachedChannelPipeline addAfter(String baseName, String name, Func0<ChannelHandler> handlerFactory) {
return _guardedAddAfter(baseName, new HandlerHolder(name, handlerFactory));
}
public DetachedChannelPipeline addAfter(EventExecutorGroup group, String baseName, String name, Func0<ChannelHandler> handlerFactory) {
return _guardedAddAfter(baseName, new HandlerHolder(name, handlerFactory, group));
}
@SafeVarargs
public final DetachedChannelPipeline addFirst(Func0<ChannelHandler>... handlerFactories) {
synchronized (holdersInOrder) {
for (int i = handlerFactories.length - 1; i >= 0; i--) {
Func0<ChannelHandler> handlerFactory = handlerFactories[i];
holdersInOrder.addFirst(new HandlerHolder(handlerFactory));
}
}
return this;
}
@SafeVarargs
public final DetachedChannelPipeline addFirst(EventExecutorGroup group, Func0<ChannelHandler>... handlerFactories) {
synchronized (holdersInOrder) {
for (int i = handlerFactories.length - 1; i >= 0; i--) {
Func0<ChannelHandler> handlerFactory = handlerFactories[i];
holdersInOrder.addFirst(new HandlerHolder(null, handlerFactory, group));
}
}
return this;
}
@SafeVarargs
public final DetachedChannelPipeline addLast(Func0<ChannelHandler>... handlerFactories) {
for (Func0<ChannelHandler> handlerFactory : handlerFactories) {
_guardedAddLast(new HandlerHolder(handlerFactory));
}
return this;
}
@SafeVarargs
public final DetachedChannelPipeline addLast(EventExecutorGroup group, Func0<ChannelHandler>... handlerFactories) {
for (Func0<ChannelHandler> handlerFactory : handlerFactories) {
_guardedAddLast(new HandlerHolder(null, handlerFactory, group));
}
return this;
}
public DetachedChannelPipeline configure(Action1<ChannelPipeline> configurator) {
_guardedAddLast(new HandlerHolder(configurator));
return this;
}
public void copyTo(ChannelPipeline pipeline) {
synchronized (holdersInOrder) {
unguardedCopyToPipeline(pipeline);
}
}
/*Visible for testing*/ LinkedList<HandlerHolder> getHoldersInOrder() {
return holdersInOrder;
}
private void unguardedCopyToPipeline(ChannelPipeline pipeline) { /*To be guarded by lock on holders*/
for (HandlerHolder holder : holdersInOrder) {
if (holder.hasPipelineConfigurator()) {
holder.getPipelineConfigurator().call(pipeline);
continue;
}
if (holder.hasGroup()) {
if (holder.hasName()) {
pipeline.addLast(holder.getGroupIfConfigured(), holder.getNameIfConfigured(),
holder.getHandlerFactoryIfConfigured().call());
} else {
pipeline.addLast(holder.getGroupIfConfigured(), holder.getHandlerFactoryIfConfigured().call());
}
} else if (holder.hasName()) {
pipeline.addLast(holder.getNameIfConfigured(), holder.getHandlerFactoryIfConfigured().call());
} else {
pipeline.addLast(holder.getHandlerFactoryIfConfigured().call());
}
}
if (null != nullableTail) {
nullableTail.call(pipeline); // This is the last handler to be added to the pipeline always.
}
if (logger.isDebugEnabled()) {
logger.debug("Channel pipeline in initializer: " + pipelineToString(pipeline));
}
}
private HandlerHolder unguardedFindHandlerByName(String baseName, boolean leniant) {
for (HandlerHolder handlerHolder : holdersInOrder) {
if (handlerHolder.hasName() && handlerHolder.getNameIfConfigured().equals(baseName)) {
return handlerHolder;
}
}
if (leniant) {
return null;
} else {
throw new NoSuchElementException("No handler with name: " + baseName + " configured in the pipeline.");
}
}
private DetachedChannelPipeline _guardedAddFirst(HandlerHolder toAdd) {
synchronized (holdersInOrder) {
holdersInOrder.addFirst(toAdd);
}
return this;
}
private DetachedChannelPipeline _guardedAddLast(HandlerHolder toAdd) {
synchronized (holdersInOrder) {
holdersInOrder.addLast(toAdd);
}
return this;
}
private DetachedChannelPipeline _guardedAddBefore(String baseName, HandlerHolder toAdd) {
synchronized (holdersInOrder) {
HandlerHolder before = unguardedFindHandlerByName(baseName, false);
final int indexOfBefore = holdersInOrder.indexOf(before);
holdersInOrder.add(indexOfBefore, toAdd);
}
return this;
}
private DetachedChannelPipeline _guardedAddAfter(String baseName, HandlerHolder toAdd) {
synchronized (holdersInOrder) {
HandlerHolder after = unguardedFindHandlerByName(baseName, false);
final int indexOfAfter = holdersInOrder.indexOf(after);
holdersInOrder.add(indexOfAfter + 1, toAdd);
}
return this;
}
private static String pipelineToString(ChannelPipeline pipeline) {
StringBuilder builder = new StringBuilder();
for (Entry<String, ChannelHandler> handlerEntry : pipeline) {
if (builder.length() == 0) {
builder.append("[\n");
} else {
builder.append(" ==> ");
}
builder.append("{ name =>")
.append(handlerEntry.getKey())
.append(", handler => ")
.append(handlerEntry.getValue())
.append("}\n")
;
}
if (builder.length() > 0) {
builder.append("}\n");
}
return builder.toString();
}
/**
* A holder class for holding handler information, required to add handlers to the actual pipeline.
*/
/*Visible for testing*/ static class HandlerHolder {
private final String nameIfConfigured;
private final Func0<ChannelHandler> handlerFactoryIfConfigured;
private final Action1<ChannelPipeline> pipelineConfigurator;
private final EventExecutorGroup groupIfConfigured;
HandlerHolder(Action1<ChannelPipeline> pipelineConfigurator) {
this.pipelineConfigurator = pipelineConfigurator;
nameIfConfigured = null;
handlerFactoryIfConfigured = null;
groupIfConfigured = null;
}
HandlerHolder(Func0<ChannelHandler> handlerFactory) {
this(null, handlerFactory);
}
HandlerHolder(String name, Func0<ChannelHandler> handlerFactory) {
this(name, handlerFactory, null);
}
HandlerHolder(String name, Func0<ChannelHandler> handlerFactory, EventExecutorGroup group) {
nameIfConfigured = name;
handlerFactoryIfConfigured = handlerFactory;
groupIfConfigured = group;
pipelineConfigurator = null;
}
public String getNameIfConfigured() {
return nameIfConfigured;
}
public boolean hasName() {
return null != nameIfConfigured;
}
public Func0<ChannelHandler> getHandlerFactoryIfConfigured() {
return handlerFactoryIfConfigured;
}
public EventExecutorGroup getGroupIfConfigured() {
return groupIfConfigured;
}
public boolean hasGroup() {
return null != groupIfConfigured;
}
public Action1<ChannelPipeline> getPipelineConfigurator() {
return pipelineConfigurator;
}
public boolean hasPipelineConfigurator() {
return null != pipelineConfigurator;
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (!(o instanceof HandlerHolder)) {
return false;
}
HandlerHolder that = (HandlerHolder) o;
if (groupIfConfigured != null? !groupIfConfigured.equals(that.groupIfConfigured) :
that.groupIfConfigured != null) {
return false;
}
if (handlerFactoryIfConfigured != null?
!handlerFactoryIfConfigured.equals(that.handlerFactoryIfConfigured) :
that.handlerFactoryIfConfigured != null) {
return false;
}
if (nameIfConfigured != null? !nameIfConfigured.equals(that.nameIfConfigured) :
that.nameIfConfigured != null) {
return false;
}
if (pipelineConfigurator != null? !pipelineConfigurator.equals(that.pipelineConfigurator) :
that.pipelineConfigurator != null) {
return false;
}
return true;
}
@Override
public int hashCode() {
int result = nameIfConfigured != null? nameIfConfigured.hashCode() : 0;
result = 31 * result + (handlerFactoryIfConfigured != null? handlerFactoryIfConfigured.hashCode() : 0);
result = 31 * result + (pipelineConfigurator != null? pipelineConfigurator.hashCode() : 0);
result = 31 * result + (groupIfConfigured != null? groupIfConfigured.hashCode() : 0);
return result;
}
@Override
public String toString() {
return "HandlerHolder{" + "nameIfConfigured='" + nameIfConfigured + '\'' + ", handlerFactoryIfConfigured=" +
handlerFactoryIfConfigured + ", pipelineConfigurator=" + pipelineConfigurator
+ ", groupIfConfigured=" + groupIfConfigured + '}';
}
}
}