Please wait. This can take some minutes ...
Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance.
Project price only 1 $
You can buy this project and download/modify it how often you want.
io.crossbar.autobahn.wamp.Session Maven / Gradle / Ivy
///////////////////////////////////////////////////////////////////////////////
//
// AutobahnJava - http://crossbar.io/autobahn
//
// Copyright (c) Crossbar.io Technologies GmbH and contributors
//
// Licensed under the MIT License.
// http://www.opensource.org/licenses/mit-license.php
//
///////////////////////////////////////////////////////////////////////////////
package io.crossbar.autobahn.wamp;
import com.fasterxml.jackson.core.type.TypeReference;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CompletionException;
import java.util.concurrent.Executor;
import java.util.function.BiConsumer;
import java.util.function.BiFunction;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Supplier;
import io.crossbar.autobahn.utils.ABLogger;
import io.crossbar.autobahn.utils.IABLogger;
import io.crossbar.autobahn.wamp.auth.ChallengeResponseAuth;
import io.crossbar.autobahn.wamp.auth.CryptosignAuth;
import io.crossbar.autobahn.wamp.auth.TicketAuth;
import io.crossbar.autobahn.wamp.exceptions.ApplicationError;
import io.crossbar.autobahn.wamp.exceptions.ProtocolError;
import io.crossbar.autobahn.wamp.interfaces.IAuthenticator;
import io.crossbar.autobahn.wamp.interfaces.IInvocationHandler;
import io.crossbar.autobahn.wamp.interfaces.IMessage;
import io.crossbar.autobahn.wamp.interfaces.ISerializer;
import io.crossbar.autobahn.wamp.interfaces.ISession;
import io.crossbar.autobahn.wamp.interfaces.ITransport;
import io.crossbar.autobahn.wamp.interfaces.ITransportHandler;
import io.crossbar.autobahn.wamp.interfaces.TriConsumer;
import io.crossbar.autobahn.wamp.interfaces.TriFunction;
import io.crossbar.autobahn.wamp.messages.Abort;
import io.crossbar.autobahn.wamp.messages.Authenticate;
import io.crossbar.autobahn.wamp.messages.Call;
import io.crossbar.autobahn.wamp.messages.Challenge;
import io.crossbar.autobahn.wamp.messages.Error;
import io.crossbar.autobahn.wamp.messages.Event;
import io.crossbar.autobahn.wamp.messages.Goodbye;
import io.crossbar.autobahn.wamp.messages.Hello;
import io.crossbar.autobahn.wamp.messages.Invocation;
import io.crossbar.autobahn.wamp.messages.Publish;
import io.crossbar.autobahn.wamp.messages.Published;
import io.crossbar.autobahn.wamp.messages.Register;
import io.crossbar.autobahn.wamp.messages.Registered;
import io.crossbar.autobahn.wamp.messages.Result;
import io.crossbar.autobahn.wamp.messages.Subscribe;
import io.crossbar.autobahn.wamp.messages.Subscribed;
import io.crossbar.autobahn.wamp.messages.Unregister;
import io.crossbar.autobahn.wamp.messages.Unregistered;
import io.crossbar.autobahn.wamp.messages.Unsubscribe;
import io.crossbar.autobahn.wamp.messages.Unsubscribed;
import io.crossbar.autobahn.wamp.messages.Welcome;
import io.crossbar.autobahn.wamp.messages.Yield;
import io.crossbar.autobahn.wamp.reflectionRoles.ReflectionServices;
import io.crossbar.autobahn.wamp.reflectionRoles.WampException;
import io.crossbar.autobahn.wamp.requests.CallRequest;
import io.crossbar.autobahn.wamp.requests.PublishRequest;
import io.crossbar.autobahn.wamp.requests.RegisterRequest;
import io.crossbar.autobahn.wamp.requests.SubscribeRequest;
import io.crossbar.autobahn.wamp.requests.UnregisterRequest;
import io.crossbar.autobahn.wamp.requests.UnsubscribeRequest;
import io.crossbar.autobahn.wamp.types.CallOptions;
import io.crossbar.autobahn.wamp.types.CallResult;
import io.crossbar.autobahn.wamp.types.CloseDetails;
import io.crossbar.autobahn.wamp.types.EventDetails;
import io.crossbar.autobahn.wamp.types.InvocationDetails;
import io.crossbar.autobahn.wamp.types.InvocationResult;
import io.crossbar.autobahn.wamp.types.Publication;
import io.crossbar.autobahn.wamp.types.PublishOptions;
import io.crossbar.autobahn.wamp.types.ReceptionResult;
import io.crossbar.autobahn.wamp.types.RegisterOptions;
import io.crossbar.autobahn.wamp.types.Registration;
import io.crossbar.autobahn.wamp.types.SessionDetails;
import io.crossbar.autobahn.wamp.types.SubscribeOptions;
import io.crossbar.autobahn.wamp.types.Subscription;
import io.crossbar.autobahn.wamp.utils.IDGenerator;
import io.crossbar.autobahn.wamp.utils.Platform;
import static io.crossbar.autobahn.wamp.messages.MessageMap.MESSAGE_TYPE_MAP;
import static io.crossbar.autobahn.wamp.utils.Shortcuts.getOrDefault;
import static java.util.concurrent.CompletableFuture.runAsync;
public class Session implements ISession, ITransportHandler {
public static final IABLogger LOGGER = ABLogger.getLogger(Session.class.getName());
private final int STATE_DISCONNECTED = 1;
private final int STATE_HELLO_SENT = 2;
private final int STATE_AUTHENTICATE_SENT = 3;
private final int STATE_JOINED = 4;
private final int STATE_READY = 5;
private final int STATE_GOODBYE_SENT = 6;
private final int STATE_ABORT_SENT = 7;
private ITransport mTransport;
private ISerializer mSerializer;
private Executor mExecutor;
private CompletableFuture mJoinFuture;
private List mAuthenticators;
private final ArrayList mOnJoinListeners;
private final ArrayList mOnReadyListeners;
private final ArrayList mOnLeaveListeners;
private final ArrayList mOnConnectListeners;
private final ArrayList mOnDisconnectListeners;
private final ArrayList mOnUserErrorListeners;
private final IDGenerator mIDGenerator;
private final Map mCallRequests;
private final Map mSubscribeRequests;
private final Map mPublishRequests;
private final Map mRegisterRequest;
private final Map> mSubscriptions;
private final Map mRegistrations;
private final Map mUnsubscribeRequests;
private final Map mUnregisterRequests;
private int mState = STATE_DISCONNECTED;
private long mSessionID;
private boolean mGoodbyeSent;
private String mRealm;
private ReflectionServices mReflectionServices;
public Session() {
this(Platform.autoSelectExecutor());
}
public Session(Executor executor) {
mOnJoinListeners = new ArrayList<>();
mOnReadyListeners = new ArrayList<>();
mOnLeaveListeners = new ArrayList<>();
mOnConnectListeners = new ArrayList<>();
mOnDisconnectListeners = new ArrayList<>();
mOnUserErrorListeners = new ArrayList<>();
mIDGenerator = new IDGenerator();
mCallRequests = new HashMap<>();
mSubscribeRequests = new HashMap<>();
mPublishRequests = new HashMap<>();
mRegisterRequest = new HashMap<>();
mSubscriptions = new HashMap<>();
mRegistrations = new HashMap<>();
mUnsubscribeRequests = new HashMap<>();
mUnregisterRequests = new HashMap<>();
if (executor == null) {
throw new IllegalArgumentException("executor must not be null");
}
mExecutor = executor;
}
/**
* Returns the ID of the current session, 0 otherwise.
* @return The session ID
*/
public long getID() {
return mSessionID;
}
private void throwIfNotConnected() {
if (!isConnected()) {
throw new IllegalStateException("The transport must be connected first");
}
}
@Override
public void onConnect(ITransport transport, ISerializer serializer) throws Exception {
LOGGER.d("onConnect()");
if (mTransport != null) {
throw new Exception("already connected");
}
mTransport = transport;
mSerializer = serializer;
mReflectionServices = new ReflectionServices(this, mSerializer);
runAsync(() -> {
for (OnConnectListener listener: mOnConnectListeners) {
listener.onConnect(this);
}
}, mExecutor);
}
private void send(IMessage message) {
if (!isConnected()) {
throw new IllegalStateException("no transport");
}
LOGGER.d(" >>> TX : " + message);
mTransport.send(mSerializer.serialize(message.marshal()), mSerializer.isBinary());
}
@Override
public void onMessage(byte[] payload, boolean isBinary) throws Exception {
// transform bytes to raw message:
List rawMessage = mSerializer.unserialize(payload, isBinary);
// transform raw message to typed message:
try {
int messageType = (int) rawMessage.get(0);
Class extends IMessage> messageKlass = MESSAGE_TYPE_MAP.get(messageType);
IMessage message = (IMessage) messageKlass.getMethod(
"parse", List.class).invoke(null, rawMessage);
LOGGER.d(" <<< RX : " + message);
if (mSessionID == 0) {
onPreSessionMessage(message);
} else {
onMessage(message);
}
} catch (Exception e) {
LOGGER.d("mapping received message bytes to IMessage failed: " + e.getMessage());
}
}
@Override
public void onLeave(CloseDetails details) {
if (mState == STATE_DISCONNECTED) {
return;
}
LOGGER.d("onLeave(), reason=" + details.reason);
List> futures = new ArrayList<>();
for (OnLeaveListener listener: mOnLeaveListeners) {
futures.add(runAsync(() -> listener.onLeave(this, details), mExecutor));
}
CompletableFuture d = combineFutures(futures);
d.thenRunAsync(() -> {
LOGGER.d("Notified all Session.onLeave listeners.");
}, mExecutor);
}
private void onPreSessionMessage(IMessage message) throws Exception {
if (message instanceof Welcome) {
Welcome msg = (Welcome) message;
mState = STATE_JOINED;
mSessionID = msg.session;
SessionDetails details = new SessionDetails(msg.realm, msg.session, msg.authid,
msg.authrole, msg.authmethod);
mJoinFuture.complete(details);
List> futures = new ArrayList<>();
for (OnJoinListener listener: mOnJoinListeners) {
futures.add(runAsync(() -> listener.onJoin(this, details), mExecutor));
}
CompletableFuture d = combineFutures(futures);
d.thenRunAsync(() -> {
mState = STATE_READY;
for (OnReadyListener listener: mOnReadyListeners) {
listener.onReady(this);
}
}, mExecutor);
} else if (message instanceof Abort) {
Abort abortMessage = (Abort) message;
CloseDetails details = new CloseDetails(abortMessage.reason, abortMessage.message);
List> futures = new ArrayList<>();
for (OnLeaveListener listener: mOnLeaveListeners) {
futures.add(runAsync(() -> listener.onLeave(this, details), mExecutor));
}
CompletableFuture d = combineFutures(futures);
d.thenRunAsync(() -> {
LOGGER.d("Notified Session.onLeave listeners, now closing transport");
mState = STATE_DISCONNECTED;
if (mTransport != null && mTransport.isOpen()) {
try {
mTransport.close();
} catch (Exception e) {
throw new CompletionException(e);
}
}
}, mExecutor);
} else if (message instanceof Challenge) {
Challenge msg = (Challenge) message;
io.crossbar.autobahn.wamp.types.Challenge challenge =
new io.crossbar.autobahn.wamp.types.Challenge(msg.method, msg.extra);
if (mAuthenticators != null) {
if (msg.method.equals(TicketAuth.authmethod)) {
for (IAuthenticator authenticator: mAuthenticators) {
if (authenticator.getAuthMethod().equals(TicketAuth.authmethod)) {
TicketAuth auth = (TicketAuth) authenticator;
auth.onChallenge(this, challenge).whenCompleteAsync(
(response, throwable) -> send(new Authenticate(
response.signature, response.extra)), mExecutor);
break;
}
}
} else if (msg.method.equals(ChallengeResponseAuth.authmethod)) {
for (IAuthenticator authenticator: mAuthenticators) {
if (authenticator.getAuthMethod().equals(
ChallengeResponseAuth.authmethod)) {
ChallengeResponseAuth auth = (ChallengeResponseAuth) authenticator;
auth.onChallenge(this, challenge).whenCompleteAsync(
(response, throwable) -> send(new Authenticate(
response.signature, response.extra)), mExecutor);
break;
}
}
} else if (msg.method.equals(CryptosignAuth.authmethod)) {
for (IAuthenticator authenticator: mAuthenticators) {
if (authenticator.getAuthMethod().equals(CryptosignAuth.authmethod)) {
CryptosignAuth auth = (CryptosignAuth) authenticator;
auth.onChallenge(this, challenge).whenCompleteAsync(
(response, throwable) -> send(new Authenticate(
response.signature, response.extra)), mExecutor);
break;
}
}
}
}
}
}
private void onMessage(IMessage message) throws Exception {
if (message instanceof Result) {
Result msg = (Result) message;
CallRequest request = getOrDefault(mCallRequests, msg.request, null);
if (request == null) {
throw new ProtocolError(String.format(
"RESULT received for non-pending request ID %s", msg.request));
}
mCallRequests.remove(msg.request);
if (request.resultTypeRef != null) {
// FIXME: check args length > 1 and == 0, and kwargs != null
// we cannot currently POJO automap these cases!
request.onReply.complete(mSerializer.convertValue(
msg.args.get(0), request.resultTypeRef));
} else if (request.resultTypeClass != null) {
request.onReply.complete(mSerializer.convertValue(
msg.args.get(0), request.resultTypeClass));
} else {
request.onReply.complete(new CallResult(msg.args, msg.kwargs));
}
} else if (message instanceof Subscribed) {
Subscribed msg = (Subscribed) message;
SubscribeRequest request = getOrDefault(mSubscribeRequests, msg.request, null);
if (request == null) {
throw new ProtocolError(String.format(
"SUBSCRIBED received for non-pending request ID %s", msg.request));
}
mSubscribeRequests.remove(msg.request);
if (!mSubscriptions.containsKey(msg.subscription)) {
mSubscriptions.put(msg.subscription, new ArrayList<>());
}
Subscription subscription = new Subscription(msg.subscription, request.topic,
request.resultTypeRef, request.resultTypeClass, request.handler, this);
mSubscriptions.get(msg.subscription).add(subscription);
request.onReply.complete(subscription);
} else if (message instanceof Event) {
Event msg = (Event) message;
List subscriptions = getOrDefault(mSubscriptions, msg.subscription, null);
if (subscriptions == null) {
throw new ProtocolError(String.format(
"EVENT received for non-subscribed subscription ID %s", msg.subscription));
}
List> futures = new ArrayList<>();
for (Subscription subscription: subscriptions) {
EventDetails details = new EventDetails(
subscription, msg.publication,
msg.topic != null ? msg.topic : subscription.topic,
msg.retained, -1, null,
null, this);
CompletableFuture future = null;
// Check if we expect a POJO.
Object arg;
if (subscription.resultTypeRef != null) {
arg = mSerializer.convertValue(msg.args.get(0), subscription.resultTypeRef);
} else if (subscription.resultTypeClass != null) {
arg = mSerializer.convertValue(msg.args.get(0), subscription.resultTypeClass);
} else {
arg = msg.args;
}
if (subscription.handler instanceof Runnable) {
Runnable handler = (Runnable) subscription.handler;
future = runAsync(handler::run, mExecutor);
} else if (subscription.handler instanceof Consumer) {
Consumer handler = (Consumer) subscription.handler;
future = runAsync(() -> handler.accept(arg), mExecutor);
} else if (subscription.handler instanceof Function) {
Function handler = (Function) subscription.handler;
future = runAsync(() -> handler.apply(arg), mExecutor);
} else if (subscription.handler instanceof BiConsumer) {
BiConsumer handler = (BiConsumer) subscription.handler;
future = runAsync(() -> handler.accept(arg, details), mExecutor);
} else if (subscription.handler instanceof BiFunction) {
BiFunction handler = (BiFunction) subscription.handler;
future = runAsync(() -> handler.apply(arg, details), mExecutor);
} else if (subscription.handler instanceof TriConsumer) {
TriConsumer handler = (TriConsumer) subscription.handler;
future = runAsync(
() -> handler.accept(arg, msg.kwargs, details), mExecutor);
} else if (subscription.handler instanceof TriFunction) {
TriFunction handler = (TriFunction) subscription.handler;
future = runAsync(() -> handler.apply(arg, msg.kwargs, details), mExecutor);
} else {
// FIXME: never going to reach here, though would be better to throw.
}
futures.add(future);
}
// Not really doing anything with the combined futures.
combineFutures(futures);
} else if (message instanceof Published) {
Published msg = (Published) message;
PublishRequest request = getOrDefault(mPublishRequests, msg.request, null);
if (request == null) {
throw new ProtocolError(String.format(
"PUBLISHED received for non-pending request ID %s", msg.request));
}
mPublishRequests.remove(msg.request);
Publication publication = new Publication(msg.publication);
request.onReply.complete(publication);
} else if (message instanceof Registered) {
Registered msg = (Registered) message;
RegisterRequest request = getOrDefault(mRegisterRequest, msg.request, null);
if (request == null) {
throw new ProtocolError(String.format(
"REGISTERED received for already existing registration ID %s",
msg.request));
}
mRegisterRequest.remove(msg.request);
Registration registration = new Registration(
msg.registration, request.procedure, request.endpoint, this);
mRegistrations.put(msg.registration, registration);
request.onReply.complete(registration);
} else if (message instanceof Invocation) {
Invocation msg = (Invocation) message;
Registration registration = getOrDefault(mRegistrations, msg.registration, null);
if (registration == null) {
throw new ProtocolError(String.format(
"INVOCATION received for non-registered registration ID %s",
msg.registration));
}
long callerSessionID = getOrDefault(msg.details, "caller", -1L);
String callerAuthID = getOrDefault(msg.details, "caller_authid", null);
String callerAuthRole = getOrDefault(msg.details, "caller_authrole", null);
InvocationDetails details = new InvocationDetails(
registration, registration.procedure, callerSessionID, callerAuthID, callerAuthRole, this);
runAsync(() -> {
Object result;
if (registration.endpoint instanceof Supplier) {
Supplier endpoint = (Supplier) registration.endpoint;
result = endpoint.get();
} else if (registration.endpoint instanceof Function) {
Function endpoint = (Function) registration.endpoint;
result = endpoint.apply(msg.args);
} else if (registration.endpoint instanceof BiFunction) {
BiFunction endpoint = (BiFunction) registration.endpoint;
result = endpoint.apply(msg.args, details);
} else if (registration.endpoint instanceof TriFunction) {
TriFunction endpoint = (TriFunction) registration.endpoint;
result = endpoint.apply(msg.args, msg.kwargs, details);
} else {
IInvocationHandler endpoint = (IInvocationHandler) registration.endpoint;
result = endpoint.apply(msg.args, msg.kwargs, details);
}
if (result instanceof CompletableFuture) {
CompletableFuture fResult =
(CompletableFuture) result;
fResult.whenCompleteAsync((invocRes, throwable) -> {
if (throwable != null) {
if (throwable instanceof WampException){
WampException casted = (WampException) throwable;
send(new Error(Invocation.MESSAGE_TYPE, msg.request,
casted.getErrorUri(), casted.getArguments(), casted.getKwArguments()));
}
else{
List args = new ArrayList<>();
args.add(throwable.getMessage());
send(new Error(Invocation.MESSAGE_TYPE, msg.request,
"wamp.error.runtime_error", args, null));
}
} else {
send(new Yield(msg.request, invocRes.results, invocRes.kwresults));
}
}, mExecutor);
} else if (result instanceof InvocationResult) {
InvocationResult res = (InvocationResult) result;
send(new Yield(msg.request, res.results, res.kwresults));
} else if (result instanceof List) {
send(new Yield(msg.request, (List) result, null));
} else if (result instanceof Map) {
send(new Yield(msg.request, null, (Map) result));
} else if (result instanceof Void) {
send(new Yield(msg.request, null, null));
} else {
List item = new ArrayList<>();
item.add(result);
send(new Yield(msg.request, item, null));
}
}, mExecutor).whenCompleteAsync((aVoid, throwable) -> {
// FIXME: implement better errors
if (throwable != null) {
if (throwable instanceof WampException){
WampException casted = (WampException) throwable;
send(new Error(Invocation.MESSAGE_TYPE, msg.request,
casted.getErrorUri(), casted.getArguments(), casted.getKwArguments()));
}
else
{
List args = new ArrayList<>();
args.add(throwable.getMessage());
send(new Error(Invocation.MESSAGE_TYPE, msg.request, "wamp.error.runtime_error",
args, null));
}
}
});
} else if (message instanceof Goodbye) {
Goodbye msg = (Goodbye) message;
CloseDetails details = new CloseDetails(msg.reason, msg.message);
List> futures = new ArrayList<>();
for (OnLeaveListener listener: mOnLeaveListeners) {
futures.add(runAsync(() -> listener.onLeave(this, details), mExecutor));
}
CompletableFuture d = combineFutures(futures);
d.thenRunAsync(() -> {
LOGGER.d("Notified Session.onLeave listeners, now closing transport");
if (mTransport != null && mTransport.isOpen()) {
try {
mTransport.close();
} catch (Exception e) {
throw new CompletionException(e);
}
}
mState = STATE_DISCONNECTED;
}, mExecutor);
} else if (message instanceof Unregistered) {
Unregistered msg = (Unregistered) message;
UnregisterRequest request = getOrDefault(mUnregisterRequests, msg.request, null);
if (request == null) {
throw new ProtocolError(String.format(
"UNREGISTERED received for already unregistered registration ID %s",
msg.registration));
}
if (mRegistrations.containsKey(request.registrationID)) {
mRegistrations.remove(request.registrationID);
}
request.onReply.complete(0);
} else if (message instanceof Unsubscribed) {
Unsubscribed msg = (Unsubscribed) message;
UnsubscribeRequest request = getOrDefault(mUnsubscribeRequests, msg.request, null);
List subscriptions = mSubscriptions.get(request.subscriptionID);
request.onReply.complete(subscriptions.size());
} else if (message instanceof Error) {
Error msg = (Error) message;
CompletableFuture> onReply = null;
if (msg.requestType == Call.MESSAGE_TYPE && mCallRequests.containsKey(msg.request)) {
onReply = mCallRequests.get(msg.request).onReply;
mCallRequests.remove(msg.request);
} else if (msg.requestType == Publish.MESSAGE_TYPE
&& mPublishRequests.containsKey(msg.request)) {
onReply = mPublishRequests.get(msg.request).onReply;
mPublishRequests.remove(msg.request);
} else if (msg.requestType == Subscribe.MESSAGE_TYPE
&& mSubscribeRequests.containsKey(msg.request)) {
onReply = mSubscribeRequests.get(msg.request).onReply;
mSubscribeRequests.remove(msg.request);
} else if (msg.requestType == Register.MESSAGE_TYPE
&& mRegisterRequest.containsKey(msg.request)) {
onReply = mRegisterRequest.get(msg.request).onReply;
mRegisterRequest.remove(msg.request);
}
if (onReply != null) {
onReply.completeExceptionally(new ApplicationError(
msg.error, msg.args, msg.kwargs));
} else {
throw new ProtocolError(String.format(
"ERROR received for non-pending request_type: %s and request ID %s",
msg.requestType, msg.request));
}
} else {
throw new ProtocolError(String.format("Unexpected message %s",
message.getClass().getName()));
}
}
@Override
public void onDisconnect(boolean wasClean) {
LOGGER.d("onDisconnect(), wasClean=" + wasClean);
List> futures = new ArrayList<>();
for (OnDisconnectListener listener: mOnDisconnectListeners) {
futures.add(runAsync(() -> listener.onDisconnect(this, wasClean), mExecutor));
}
CompletableFuture d = combineFutures(futures);
d.thenRunAsync(() -> {
LOGGER.d("Notified all Session.onDisconnect listeners.");
mTransport = null;
mSerializer = null;
mState = STATE_DISCONNECTED;
}, mExecutor);
}
@Override
public boolean isConnected() {
return mTransport != null;
}
private CompletableFuture combineFutures(List> futures) {
return CompletableFuture.allOf(futures.toArray(new CompletableFuture[futures.size()]));
}
private CompletableFuture reallySubscribe(
String topic,
Object handler,
SubscribeOptions options,
TypeReference resultTypeRef,
Class resultTypeClass) {
throwIfNotConnected();
CompletableFuture future = new CompletableFuture<>();
long requestID = mIDGenerator.next();
mSubscribeRequests.put(requestID, new SubscribeRequest(requestID, topic, future,
resultTypeRef, resultTypeClass, handler));
send(new Subscribe(requestID, options, topic));
return future;
}
@Override
public CompletableFuture subscribe(String topic, Runnable handler) {
return reallySubscribe(topic, handler, null, null, null);
}
@Override
public CompletableFuture subscribe(
String topic,
Runnable handler,
SubscribeOptions options) {
return reallySubscribe(topic, handler, options, null, null);
}
@Override
public CompletableFuture subscribe(String topic, Consumer> handler) {
return reallySubscribe(topic, handler, null, null, null);
}
@Override
public CompletableFuture subscribe(
String topic,
Consumer> handler,
SubscribeOptions options) {
return reallySubscribe(topic, handler, options, null, null);
}
@Override
public CompletableFuture subscribe(
String topic,
Consumer handler,
TypeReference resultType) {
return reallySubscribe(topic, handler, null, resultType, null);
}
@Override
public CompletableFuture subscribe(
String topic,
Consumer handler,
Class resultType) {
return reallySubscribe(topic, handler, null, null, resultType);
}
@Override
public CompletableFuture subscribe(
String topic,
Consumer handler,
TypeReference resultType,
SubscribeOptions options) {
return reallySubscribe(topic, handler, options, resultType, null);
}
@Override
public CompletableFuture subscribe(
String topic,
Consumer handler,
Class resultType,
SubscribeOptions options) {
return reallySubscribe(topic, handler, options, null, resultType);
}
@Override
public CompletableFuture subscribe(
String topic,
Function, CompletableFuture> handler) {
return reallySubscribe(topic, handler, null, null, null);
}
@Override
public CompletableFuture subscribe(
String topic,
Function, CompletableFuture> handler,
SubscribeOptions options) {
return reallySubscribe(topic, handler, options, null, null);
}
@Override
public CompletableFuture subscribe(
String topic,
Function> handler,
TypeReference resultType) {
return reallySubscribe(topic, handler, null, resultType, null);
}
@Override
public CompletableFuture subscribe(
String topic,
Function> handler,
Class resultType) {
return reallySubscribe(topic, handler, null, null, resultType);
}
@Override
public CompletableFuture subscribe(
String topic,
Function> handler,
TypeReference resultType,
SubscribeOptions options) {
return reallySubscribe(topic, handler, options, resultType, null);
}
@Override
public CompletableFuture subscribe(
String topic,
Function> handler,
Class resultType,
SubscribeOptions options) {
return reallySubscribe(topic, handler, options, null, resultType);
}
@Override
public CompletableFuture subscribe(
String topic,
BiConsumer, EventDetails> handler) {
return reallySubscribe(topic, handler, null, null, null);
}
@Override
public CompletableFuture subscribe(
String topic,
BiConsumer, EventDetails> handler, SubscribeOptions options) {
return reallySubscribe(topic, handler, options, null, null);
}
@Override
public CompletableFuture subscribe(
String topic,
BiConsumer handler,
TypeReference resultType) {
return reallySubscribe(topic, handler, null, resultType, null);
}
@Override
public CompletableFuture subscribe(
String topic,
BiConsumer handler,
Class resultType) {
return reallySubscribe(topic, handler, null, null, resultType);
}
@Override
public CompletableFuture subscribe(
String topic,
BiConsumer handler,
TypeReference resultType,
SubscribeOptions options) {
return reallySubscribe(topic, handler, options, resultType, null);
}
@Override
public CompletableFuture subscribe(
String topic,
BiConsumer handler,
Class resultType,
SubscribeOptions options) {
return reallySubscribe(topic, handler, options, null, resultType);
}
@Override
public CompletableFuture subscribe(
String topic,
BiFunction, EventDetails, CompletableFuture> handler) {
return reallySubscribe(topic, handler, null, null, null);
}
@Override
public CompletableFuture subscribe(
String topic,
BiFunction, EventDetails, CompletableFuture> handler,
SubscribeOptions options) {
return reallySubscribe(topic, handler, options, null, null);
}
@Override
public CompletableFuture subscribe(
String topic,
BiFunction> handler,
TypeReference resultType) {
return reallySubscribe(topic, handler, null, resultType, null);
}
@Override
public CompletableFuture subscribe(
String topic,
BiFunction> handler,
Class resultType) {
return reallySubscribe(topic, handler, null, null, resultType);
}
@Override
public CompletableFuture subscribe(
String topic,
BiFunction> handler,
TypeReference resultType,
SubscribeOptions options) {
return reallySubscribe(topic, handler, options, resultType, null);
}
@Override
public CompletableFuture subscribe(
String topic,
BiFunction> handler,
Class resultType,
SubscribeOptions options) {
return reallySubscribe(topic, handler, options, null, resultType);
}
@Override
public CompletableFuture subscribe(
String topic,
TriConsumer, Map, EventDetails> handler) {
return reallySubscribe(topic, handler, null, null, null);
}
@Override
public CompletableFuture subscribe(
String topic,
TriConsumer, Map, EventDetails> handler,
SubscribeOptions options) {
return reallySubscribe(topic, handler, options, null, null);
}
@Override
public CompletableFuture subscribe(
String topic,
TriFunction, Map, EventDetails,
CompletableFuture> handler) {
return reallySubscribe(topic, handler, null, null, null);
}
@Override
public CompletableFuture subscribe(
String topic,
TriFunction, Map, EventDetails,
CompletableFuture> handler,
SubscribeOptions options) {
return reallySubscribe(topic, handler, options, null, null);
}
@Override
public CompletableFuture unsubscribe(Subscription subscription) {
if (!subscription.isActive()) {
throw new IllegalStateException("Subscription is already inactive");
}
List subscriptions = getOrDefault(mSubscriptions, subscription.subscription,
null);
if (subscriptions == null || !subscriptions.contains(subscription)) {
throw new IllegalStateException("Subscription is already inactive");
}
subscriptions.remove(subscription);
subscription.setInactive();
int remainingCount = subscriptions.size();
CompletableFuture unsubFuture = new CompletableFuture<>();
if (remainingCount == 0) {
long requestID = mIDGenerator.next();
mUnsubscribeRequests.put(requestID, new UnsubscribeRequest(requestID, unsubFuture,
subscription.subscription));
send(new Unsubscribe(requestID, subscription.subscription));
} else {
unsubFuture.complete(remainingCount);
}
return unsubFuture;
}
private CompletableFuture reallyPublish(String topic, List args,
Map kwargs,
PublishOptions options) {
throwIfNotConnected();
CompletableFuture future = new CompletableFuture<>();
long requestID = mIDGenerator.next();
mPublishRequests.put(requestID, new PublishRequest(requestID, future));
if (options != null) {
send(new Publish(requestID, topic, args, kwargs, options.acknowledge, options.excludeMe,
options.retain));
} else {
send(new Publish(requestID, topic, args, kwargs, true, true, false));
}
return future;
}
@Override
public CompletableFuture publish(
String topic,
List args,
Map kwargs,
PublishOptions options) {
return reallyPublish(topic, args, kwargs, options);
}
@Override
public CompletableFuture publish(
String topic,
Object arg,
PublishOptions options) {
List args = new ArrayList<>();
args.add(arg);
return reallyPublish(topic, args, null, options);
}
@Override
public CompletableFuture publish(
String topic,
PublishOptions options,
Object... args) {
return reallyPublish(topic, Arrays.asList(args), null, options);
}
@Override
public CompletableFuture publish(String topic, Object... args) {
return reallyPublish(topic, Arrays.asList(args), null, null);
}
@Override
public CompletableFuture publish(String topic, PublishOptions options) {
return reallyPublish(topic, null, null, options);
}
@Override
public CompletableFuture publish(String topic) {
return reallyPublish(topic, null, null, null);
}
private CompletableFuture reallyRegister(
String procedure,
Object endpoint,
RegisterOptions options) {
throwIfNotConnected();
CompletableFuture future = new CompletableFuture<>();
long requestID = mIDGenerator.next();
mRegisterRequest.put(requestID,
new RegisterRequest(requestID, future, procedure, endpoint));
if (options != null) {
send(new Register(requestID, procedure, options.match, options.invoke));
} else {
send(new Register(requestID, procedure, null, null));
}
return future;
}
@Override
public CompletableFuture register(String procedure, Supplier endpoint) {
return reallyRegister(procedure, endpoint, null);
}
@Override
public CompletableFuture register(
String procedure,
Supplier endpoint,
RegisterOptions options) {
return reallyRegister(procedure, endpoint, options);
}
@Override
public CompletableFuture register(String procedure, IInvocationHandler endpoint) {
return reallyRegister(procedure, endpoint, null);
}
@Override
public CompletableFuture register(
String procedure,
IInvocationHandler endpoint,
RegisterOptions options) {
return reallyRegister(procedure, endpoint, options);
}
@Override
public CompletableFuture register(
String procedure,
Function endpoint) {
return reallyRegister(procedure, endpoint, null);
}
@Override
public CompletableFuture register(
String procedure,
Function endpoint,
RegisterOptions options) {
return reallyRegister(procedure, endpoint, options);
}
@Override
public CompletableFuture register(
String procedure,
BiFunction endpoint) {
return reallyRegister(procedure, endpoint, null);
}
@Override
public CompletableFuture register(
String procedure,
BiFunction endpoint,
RegisterOptions options) {
return reallyRegister(procedure, endpoint, options);
}
@Override
public CompletableFuture register(
String procedure,
TriFunction endpoint) {
return reallyRegister(procedure, endpoint, null);
}
@Override
public CompletableFuture register(
String procedure,
TriFunction endpoint,
RegisterOptions options) {
return reallyRegister(procedure, endpoint, options);
}
@Override
public CompletableFuture unregister(Registration registration) {
if (!registration.isActive()) {
throw new IllegalStateException("Registration is already inactive");
}
if (!mRegistrations.containsKey(registration.registration)) {
throw new IllegalStateException("Not registered");
}
CompletableFuture unregFuture = new CompletableFuture<>();
long requestID = mIDGenerator.next();
mUnregisterRequests.put(requestID, new UnregisterRequest(requestID, unregFuture,
registration.registration));
send(new Unregister(requestID, registration.registration));
return unregFuture;
}
private CompletableFuture reallyCall(
String procedure,
List args, Map kwargs,
CallOptions options,
TypeReference resultTypeReference,
Class resultTypeClass) {
throwIfNotConnected();
CompletableFuture future = new CompletableFuture<>();
long requestID = mIDGenerator.next();
mCallRequests.put(requestID, new CallRequest(requestID, procedure, future, options,
resultTypeReference, resultTypeClass));
if (options == null) {
send(new Call(requestID, procedure, args, kwargs, 0));
} else {
send(new Call(requestID, procedure, args, kwargs, options.timeout));
}
return future;
}
@Override
public CompletableFuture call(String procedure) {
return reallyCall(procedure, null, null, null, null, null);
}
@Override
public CompletableFuture call(String procedure, Object... args) {
return reallyCall(procedure, Arrays.asList(args), null, null, null, null);
}
@Override
public CompletableFuture call(String procedure, TypeReference resultType) {
return reallyCall(procedure, null, null, null, resultType, null);
}
@Override
public CompletableFuture call(String procedure, Class resultType) {
return reallyCall(procedure, null, null, null, null, resultType);
}
@Override
public CompletableFuture call(
String procedure,
CallOptions options,
Object... args) {
return reallyCall(procedure, Arrays.asList(args), null, options, null, null);
}
@Override
public CompletableFuture call(
String procedure,
TypeReference resultType,
CallOptions options) {
return reallyCall(procedure, null, null, options, resultType, null);
}
@Override
public CompletableFuture call(
String procedure,
Class resultType,
CallOptions options) {
return reallyCall(procedure, null, null, options, null, resultType);
}
@Override
public CompletableFuture call(
String procedure,
List args,
TypeReference resultType) {
return reallyCall(procedure, args, null, null, resultType, null);
}
@Override
public CompletableFuture call(String procedure, List args, Class resultType) {
return reallyCall(procedure, args, null, null, null, resultType);
}
@Override
public CompletableFuture call(
String procedure,
List args,
TypeReference resultType,
CallOptions options) {
return reallyCall(procedure, args, null, options, resultType, null);
}
@Override
public CompletableFuture call(
String procedure,
List args,
Class resultType,
CallOptions options) {
return reallyCall(procedure, args, null, options, null, resultType);
}
@Override
public CompletableFuture call(String procedure, Map kwargs) {
return reallyCall(procedure, null, kwargs, null, null, null);
}
@Override
public CompletableFuture call(
String procedure,
Map kwargs,
TypeReference resultType) {
return reallyCall(procedure, null, kwargs, null, resultType, null);
}
@Override
public CompletableFuture call(
String procedure,
Map kwargs,
Class resultType) {
return reallyCall(procedure, null, kwargs, null, null, resultType);
}
@Override
public CompletableFuture call(
String procedure,
Map kwargs,
TypeReference resultType,
CallOptions options) {
return reallyCall(procedure, null, kwargs, options, resultType, null);
}
@Override
public CompletableFuture call(
String procedure,
Map kwargs,
Class resultType,
CallOptions options) {
return reallyCall(procedure, null, kwargs, options, null, resultType);
}
@Override
public CompletableFuture call(
String procedure,
Map kwargs,
CallOptions options) {
return reallyCall(procedure, null, kwargs, options, null, null);
}
@Override
public CompletableFuture call(
String procedure,
List args,
Map kwargs,
CallOptions options) {
return reallyCall(procedure, args, kwargs, options, null, null);
}
@Override
public CompletableFuture call(
String procedure,
List args,
Map kwargs,
TypeReference resultType) {
return reallyCall(procedure, args, kwargs, null, resultType, null);
}
@Override
public CompletableFuture call(
String procedure,
List