/*
* Copyright (c) 2011-2014 Pivotal Software, 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 reactor.net;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import reactor.core.Environment;
import reactor.core.Reactor;
import reactor.core.support.NotifyConsumer;
import reactor.event.Event;
import reactor.event.dispatch.Dispatcher;
import reactor.event.registry.Registration;
import reactor.event.selector.Selector;
import reactor.event.selector.Selectors;
import reactor.event.support.EventConsumer;
import reactor.function.Consumer;
import reactor.function.Function;
import reactor.function.batch.BatchConsumer;
import reactor.io.Buffer;
import reactor.io.encoding.Codec;
import reactor.queue.BlockingQueueFactory;
import reactor.rx.Promise;
import reactor.rx.Promises;
import reactor.rx.Stream;
import reactor.rx.Streams;
import reactor.rx.stream.HotStream;
import reactor.util.Assert;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.nio.ByteBuffer;
import java.util.NoSuchElementException;
import java.util.Queue;
import static reactor.event.selector.Selectors.$;
/**
* An abstract {@link reactor.net.NetChannel} implementation that handles the basic interaction and {@link
* reactor.rx.Stream} and {@link reactor.function.Consumer} handling.
*
* @author Jon Brisbin
* @author Stephane Maldini
*/
public abstract class AbstractNetChannel<IN, OUT> implements NetChannel<IN, OUT> {
protected final Logger log = LoggerFactory.getLogger(getClass());
private final Selector read = $();
private final Environment env;
private final Reactor ioReactor;
private final Reactor eventsReactor;
private final Codec<Buffer, IN, OUT> codec;
private final Function<Buffer, IN> decoder;
private final Function<OUT, Buffer> encoder;
private final Queue<Object> replyToKeys;
protected AbstractNetChannel(@Nonnull Environment env,
@Nullable Codec<Buffer, IN, OUT> codec,
@Nonnull Dispatcher ioDispatcher,
@Nonnull Reactor eventsReactor) {
Assert.notNull(env, "IO Dispatcher cannot be null");
Assert.notNull(env, "Events Reactor cannot be null");
this.env = env;
this.ioReactor = new Reactor(ioDispatcher,
null,
eventsReactor.getDispatchErrorHandler(),
eventsReactor.getUncaughtErrorHandler());
this.eventsReactor = new Reactor(eventsReactor.getDispatcher(),
null,
eventsReactor.getDispatchErrorHandler(),
eventsReactor.getUncaughtErrorHandler());
this.eventsReactor.getConsumerRegistry().clear();
for (Registration<? extends Consumer<?>> reg : eventsReactor.getConsumerRegistry()) {
this.eventsReactor.getConsumerRegistry().register(reg.getSelector(), reg.getObject());
}
this.codec = codec;
if (null != codec) {
this.decoder = codec.decoder(new NotifyConsumer<IN>(read.getObject(), this.eventsReactor));
this.encoder = codec.encoder();
} else {
this.decoder = null;
this.encoder = null;
}
this.replyToKeys = BlockingQueueFactory.createQueue();
consume(new Consumer<IN>() {
@Override
public void accept(IN in) {
try {
if (!replyToKeys.isEmpty()) {
AbstractNetChannel.this.eventsReactor.notify(replyToKeys.remove(), Event.wrap(in));
}
} catch (NoSuchElementException ignored) {
}
}
});
}
public Function<Buffer, IN> getDecoder() {
return decoder;
}
public Function<OUT, Buffer> getEncoder() {
return encoder;
}
@Override
public Stream<IN> in() {
final HotStream<IN> d = Streams.<IN>defer(env, eventsReactor.getDispatcher());
consume(new Consumer<IN>() {
@Override
public void accept(IN in) {
d.broadcastNext(in);
}
});
return d;
}
@Override
public BatchConsumer<OUT> out() {
return new WriteConsumer(null);
}
@Override
public <T extends Throwable> NetChannel<IN, OUT> when(Class<T> errorType, Consumer<T> errorConsumer) {
eventsReactor.on(Selectors.T(errorType), new EventConsumer<T>(errorConsumer));
return this;
}
@Override
public NetChannel<IN, OUT> consume(final Consumer<IN> consumer) {
eventsReactor.on(read, new Consumer<Event<IN>>() {
@Override
public void accept(Event<IN> ev) {
consumer.accept(ev.getData());
}
});
return this;
}
@Override
public NetChannel<IN, OUT> receive(final Function<IN, OUT> fn) {
consume(new Consumer<IN>() {
@Override
public void accept(IN in) {
send(fn.apply(in));
}
});
return this;
}
@Override
public NetChannel<IN, OUT> send(Stream<OUT> data) {
data.consume(new Consumer<OUT>() {
@Override
public void accept(OUT out) {
send(out, null);
}
});
return this;
}
@Override
public Promise<Void> send(OUT data) {
Promise<Void> d = Promises.defer(env, eventsReactor.getDispatcher());
send(data, d);
return d;
}
@Override
public NetChannel<IN, OUT> sendAndForget(OUT data) {
send(data, null);
return this;
}
@Override
public Promise<IN> sendAndReceive(OUT data) {
final Promise<IN> d = Promises.defer(env, eventsReactor.getDispatcher());
Selector sel = $();
eventsReactor.on(sel, new EventConsumer<IN>(d)).cancelAfterUse();
replyToKeys.add(sel.getObject());
send(data, null);
return d;
}
@Override
public Promise<Boolean> close() {
Promise<Boolean> d = Promises.defer(getEnvironment(), eventsReactor.getDispatcher());
eventsReactor.getConsumerRegistry().unregister(read.getObject());
close(d);
return d;
}
/**
* Send data on this connection. The current codec (if any) will be used to encode the data to a {@link
* reactor.io.Buffer}. The given callback will be invoked when the write has completed.
*
* @param data
* The outgoing data.
* @param onComplete
* The callback to invoke when the write is complete.
*/
protected void send(OUT data, final Promise<Void> onComplete) {
ioReactor.schedule(new WriteConsumer(onComplete), data);
}
/**
* Performing necessary decoding on the data and notify the internal {@link Reactor} of any results.
*
* @param data
* The data to decode.
*
* @return {@literal true} if any more data is remaining to be consumed in the given {@link Buffer}, {@literal false}
* otherwise.
*/
public boolean read(Buffer data) {
if (null != decoder && null != data.byteBuffer()) {
decoder.apply(data);
} else {
eventsReactor.notify(read.getObject(), Event.wrap(data));
}
return data.remaining() > 0;
}
public void notifyRead(Object obj) {
eventsReactor.notify(read.getObject(), (Event.class.isInstance(obj) ? (Event) obj : Event.wrap(obj)));
}
public void notifyError(Throwable throwable) {
eventsReactor.notify(throwable.getClass(), Event.wrap(throwable));
}
/**
* Subclasses must implement this method to perform the actual IO of writing data to the connection.
*
* @param data
* The data to write, as a {@link Buffer}.
* @param onComplete
* The callback to invoke when the write is complete.
*/
protected void write(Buffer data, Promise<Void> onComplete, boolean flush) {
write(data.byteBuffer(), onComplete, flush);
}
/**
* Subclasses must implement this method to perform the actual IO of writing data to the connection.
*
* @param data
* The data to write.
* @param onComplete
* The callback to invoke when the write is complete.
* @param flush
* whether to flush the underlying IO channel
*/
protected abstract void write(ByteBuffer data, Promise<Void> onComplete, boolean flush);
/**
* Subclasses must implement this method to perform the actual IO of writing data to the connection.
*
* @param data
* The data to write.
* @param onComplete
* The callback to invoke when the write is complete.
* @param flush
* whether to flush the underlying IO channel
*/
protected abstract void write(Object data, Promise<Void> onComplete, boolean flush);
/**
* Subclasses must implement this method to perform IO flushes.
*/
protected abstract void flush();
protected Environment getEnvironment() {
return env;
}
protected Reactor getEventsReactor() {
return eventsReactor;
}
protected Reactor getIoReactor() {
return ioReactor;
}
private final class WriteConsumer implements BatchConsumer<OUT> {
private final Promise<Void> onComplete;
private volatile boolean autoflush = true;
private WriteConsumer(Promise<Void> onComplete) {
this.onComplete = onComplete;
}
@Override
public void start() {
autoflush = false;
}
@Override
public void end() {
flush();
autoflush = true;
}
@Override
public void accept(OUT data) {
try {
if (null != encoder) {
Buffer bytes = encoder.apply(data);
if (bytes.remaining() > 0) {
write(bytes, onComplete, autoflush);
}
} else {
if (Buffer.class.isInstance(data)) {
write((Buffer) data, onComplete, autoflush);
} else {
write(data, onComplete, autoflush);
}
}
} catch (Throwable t) {
eventsReactor.notify(t.getClass(), Event.wrap(t));
if (null != onComplete) {
onComplete.onError(t);
}
}
}
}
}