/**********************************************************\
| |
| hprose |
| |
| Official WebSite: http://www.hprose.com/ |
| http://www.hprose.org/ |
| |
\**********************************************************/
/**********************************************************\
* *
* HproseClient.java *
* *
* hprose client class for Java. *
* *
* LastModified: Nov 14, 2016 *
* Author: Ma Bingyao <andot@hprose.com> *
* *
\**********************************************************/
package hprose.client;
import hprose.common.HandlerManager;
import hprose.common.HproseCallback;
import hprose.common.HproseCallback1;
import hprose.common.HproseContext;
import hprose.common.HproseErrorEvent;
import hprose.common.HproseException;
import hprose.common.HproseFilter;
import hprose.common.HproseResultMode;
import hprose.common.InvokeSettings;
import hprose.io.ByteBufferStream;
import hprose.io.HproseMode;
import static hprose.io.HproseTags.TagArgument;
import static hprose.io.HproseTags.TagCall;
import static hprose.io.HproseTags.TagEnd;
import static hprose.io.HproseTags.TagError;
import static hprose.io.HproseTags.TagResult;
import hprose.io.serialize.Writer;
import hprose.io.unserialize.Reader;
import hprose.util.ClassUtil;
import hprose.util.StrUtil;
import hprose.util.concurrent.Action;
import hprose.util.concurrent.AsyncCall;
import hprose.util.concurrent.AsyncFunc;
import hprose.util.concurrent.Func;
import hprose.util.concurrent.Promise;
import hprose.util.concurrent.Threads;
import java.io.IOException;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Proxy;
import java.lang.reflect.Type;
import java.net.URI;
import java.net.URISyntaxException;
import java.nio.Buffer;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
public abstract class HproseClient extends HandlerManager {
private final static Object[] nullArgs = new Object[0];
private final ArrayList<HproseFilter> filters = new ArrayList<HproseFilter>();
private final ArrayList<String> uriList = new ArrayList<String>();
private final AtomicInteger index = new AtomicInteger(-1);
private volatile HproseMode mode;
private volatile int timeout = 30000;
private volatile int retry = 10;
private volatile boolean idempotent = false;
private volatile boolean failswitch = false;
private volatile int failround = 0;
private volatile boolean byref = false;
private volatile boolean simple = false;
protected String uri;
public HproseErrorEvent onError = null;
public Action<HproseClient> onFailswitch = null;
protected HproseClient() {
this((String[])null, HproseMode.MemberMode);
}
protected HproseClient(HproseMode mode) {
this((String[])null, mode);
}
protected HproseClient(String uri) {
this(uri, HproseMode.MemberMode);
}
protected HproseClient(String uri, HproseMode mode) {
this(uri == null ? (String[])null : new String[] {uri}, mode);
}
protected HproseClient(String[] uriList) {
this(uriList, HproseMode.MemberMode);
}
protected HproseClient(String[] uriList, HproseMode mode) {
this.mode = mode;
if (uriList != null) {
setUriList(uriList);
}
Threads.registerShutdownHandler(new Runnable() {
public void run() {
close();
}
});
}
public void close() {}
private final static HashMap<String, Class<? extends HproseClient>> clientFactories = new HashMap<String, Class<? extends HproseClient>>();
public static void registerClientFactory(String scheme, Class<? extends HproseClient> clientClass) {
synchronized (clientFactories) {
clientFactories.put(scheme, clientClass);
}
}
static {
registerClientFactory("tcp", HproseTcpClient.class);
registerClientFactory("tcp4", HproseTcpClient.class);
registerClientFactory("tcp6", HproseTcpClient.class);
registerClientFactory("http", HproseHttpClient.class);
registerClientFactory("https", HproseHttpClient.class);
}
public static HproseClient create(String uri) throws IOException, URISyntaxException {
return create(new String[] { uri }, HproseMode.MemberMode);
}
public static HproseClient create(String uri, HproseMode mode) throws IOException, URISyntaxException {
return create(new String[] { uri }, mode);
}
public static HproseClient create(String[] uriList, HproseMode mode) throws IOException, URISyntaxException {
String scheme = (new URI(uriList[0])).getScheme().toLowerCase();
for (int i = 1, n = uriList.length; i < n; ++i) {
if (!(new URI(uriList[i])).getScheme().toLowerCase().equalsIgnoreCase(scheme)) {
throw new HproseException("Not support multiple protocol.");
}
}
Class<? extends HproseClient> clientClass = clientFactories.get(scheme);
if (clientClass != null) {
try {
HproseClient client = clientClass.newInstance();
client.mode = mode;
client.useService(uriList);
return client;
}
catch (Exception ex) {
throw new HproseException("This client doesn't support " + scheme + " scheme.");
}
}
throw new HproseException("This client doesn't support " + scheme + " scheme.");
}
public final List<String> getUriList() {
return uriList;
}
private void initUriList() {
Collections.shuffle(this.uriList);
this.index.set(0);
this.failround = 0;
this.uri = this.uriList.get(0);
}
public final void setUriList(List<String> uriList) {
this.uriList.clear();
int n = uriList.size();
for (int i = 0; i < n; i++) {
this.uriList.add(uriList.get(i));
}
this.initUriList();
}
public final void setUriList(String[] uriList) {
this.uriList.clear();
int n = uriList.length;
for (int i = 0; i < n; i++) {
this.uriList.add(uriList[i]);
}
this.initUriList();
}
public final int getTimeout() {
return timeout;
}
public final void setTimeout(int timeout) {
if (timeout < 1) throw new IllegalArgumentException("timeout must be great than 0");
this.timeout = timeout;
}
public final int getRetry() {
return retry;
}
public final void setRetry(int retry) {
this.retry = retry;
}
public final boolean isIdempotent() {
return idempotent;
}
public final void setIdempotent(boolean idempotent) {
this.idempotent = idempotent;
}
public final boolean isFailswitch() {
return failswitch;
}
public final void setFailswitch(boolean failswitch) {
this.failswitch = failswitch;
}
public final int getFailround() {
return failround;
}
public final boolean isByref() {
return byref;
}
public final void setByref(boolean byref) {
this.byref = byref;
}
public final boolean isSimple() {
return simple;
}
public final void setSimple(boolean simple) {
this.simple = simple;
}
public final HproseFilter getFilter() {
if (filters.isEmpty()) {
return null;
}
return filters.get(0);
}
public final void setFilter(HproseFilter filter) {
if (!filters.isEmpty()) {
filters.clear();
}
if (filter != null) {
filters.add(filter);
}
}
public final void addFilter(HproseFilter filter) {
if (filter != null) {
filters.add(filter);
}
}
public final boolean removeFilter(HproseFilter filter) {
return filters.remove(filter);
}
public final void useService(String uri) {
setUriList(new String[] { uri });
}
public final void useService(String[] uriList) {
setUriList(uriList);
}
public final <T> T useService(Class<T> type) {
return useService(type, null);
}
public final <T> T useService(String uri, Class<T> type) {
return useService(uri, type, null);
}
public final <T> T useService(String[] uriList, Class<T> type) {
return useService(uriList, type, null);
}
@SuppressWarnings("unchecked")
public final <T> T useService(Class<T> type, String ns) {
HproseInvocationHandler handler = new HproseInvocationHandler(this, ns);
if (type.isInterface()) {
return (T) Proxy.newProxyInstance(type.getClassLoader(), new Class<?>[]{type}, handler);
}
else {
return (T) Proxy.newProxyInstance(type.getClassLoader(), type.getInterfaces(), handler);
}
}
public final <T> T useService(String uri, Class<T> type, String ns) {
useService(uri);
return useService(type, ns);
}
public final <T> T useService(String[] uriList, Class<T> type, String ns) {
setUriList(uriList);
return useService(type, ns);
}
private ByteBuffer outputFilter(ByteBuffer request, ClientContext context) {
if (request.position() != 0) {
request.flip();
}
for (int i = 0, n = filters.size(); i < n; ++i) {
request = filters.get(i).outputFilter(request, context);
if (request.position() != 0) {
request.flip();
}
}
return request;
}
private ByteBuffer inputFilter(ByteBuffer response, ClientContext context) {
if (response.position() != 0) {
response.flip();
}
for (int i = filters.size() - 1; i >= 0; --i) {
response = filters.get(i).inputFilter(response, context);
if (response.position() != 0) {
response.flip();
}
}
return response;
}
private Promise<ByteBuffer> beforeFilterHandler(ByteBuffer request, final ClientContext context) {
request = outputFilter(request, context);
if (context.getSettings().isOneway()) {
afterFilterHandler.handle(request, context);
return Promise.value(null);
}
return afterFilterHandler.handle(request, context).then(new Func<ByteBuffer, ByteBuffer>() {
public ByteBuffer call(ByteBuffer response) throws Throwable {
response = inputFilter(response, context);
return response;
}
});
}
private Promise<ByteBuffer> afterFilterHandler(final ByteBuffer request, final ClientContext context) {
return sendAndReceive(request, context).catchError(
new AsyncFunc<ByteBuffer, Throwable>() {
public Promise<ByteBuffer> call(Throwable e) throws Throwable {
Promise<ByteBuffer> response = retry(request, context);
if (response != null) {
return response;
}
throw e;
}
}
);
}
private Promise<ByteBuffer> retry(final ByteBuffer request, final ClientContext context) throws Throwable {
InvokeSettings settings = context.getSettings();
if (settings.isFailswitch()) {
failswitch();
}
if (settings.isIdempotent() && context.retried < settings.getRetry()) {
int interval = ++context.retried * 500;
if (settings.isFailswitch()) {
interval -= (uriList.size() - 1) * 500;
}
if (interval > 5000) {
interval = 5000;
}
if (interval > 0) {
return Promise.delayed(interval, new AsyncCall<ByteBuffer>() {
public Promise<ByteBuffer> call() throws Throwable {
return afterFilterHandler(request, context);
}
});
}
else {
return afterFilterHandler(request, context);
}
}
return null;
}
private void failswitch() throws Throwable {
int n = uriList.size();
if (n > 1) {
if (index.compareAndSet(n - 1, 0)) {
uri = uriList.get(0);
failround++;
}
else {
uri = uriList.get(index.incrementAndGet());
}
}
else {
failround++;
}
if (onFailswitch != null) {
onFailswitch.call(this);
}
}
private ClientContext getContext(InvokeSettings settings) {
ClientContext context = new ClientContext(this);
context.getSettings().copyFrom(settings);
if (settings.getUserData() != null) {
context.getUserData().putAll(settings.getUserData());
}
return context;
}
private ByteBufferStream encode(String name, Object[] args, ClientContext context) throws Throwable {
ByteBufferStream stream = new ByteBufferStream();
InvokeSettings settings = context.getSettings();
Writer writer = new Writer(stream.getOutputStream(), mode, settings.isSimple());
stream.write(TagCall);
writer.writeString(name);
if ((args != null) && (args.length > 0 || settings.isByref())) {
writer.reset();
for (int i = 0, n = args.length; i < n; ++i) {
if (args[i] instanceof Future) {
args[i] = ((Future)args[i]).get(settings.getTimeout(), TimeUnit.MILLISECONDS);
}
}
writer.writeArray(args);
if (settings.isByref()) {
writer.writeBoolean(true);
}
}
stream.write(TagEnd);
return stream;
}
private Object getRaw(ByteBufferStream stream, Type returnType) throws HproseException {
stream.flip();
if (returnType == null ||
returnType == Object.class ||
returnType == ByteBuffer.class ||
returnType == Buffer.class) {
return stream.buffer;
}
else if (returnType == ByteBufferStream.class) {
return stream;
}
else if (returnType == byte[].class) {
byte[] bytes = stream.toArray();
stream.close();
return bytes;
}
throw new HproseException("Can't Convert ByteBuffer to Type: " + returnType.toString());
}
private Object decode(ByteBufferStream stream, Object[] args, ClientContext context) throws IOException, HproseException {
InvokeSettings settings = context.getSettings();
if (settings.isOneway()) {
return null;
}
if (stream.available() == 0) throw new HproseException("EOF");
int tag = stream.buffer.get(stream.buffer.limit() - 1);
if (tag != TagEnd) {
throw new HproseException("Wrong Response: \r\n" + StrUtil.toString(stream));
}
HproseResultMode resultMode = settings.getMode();
Type returnType = settings.getReturnType();
if (resultMode == HproseResultMode.RawWithEndTag) {
return getRaw(stream, returnType);
}
else if (resultMode == HproseResultMode.Raw) {
stream.buffer.limit(stream.buffer.limit() - 1);
return getRaw(stream, returnType);
}
Object result = null;
Reader reader = new Reader(stream.getInputStream(), mode);
tag = stream.read();
if (tag == TagResult) {
if (resultMode == HproseResultMode.Normal) {
result = reader.unserialize(returnType);
}
else if (resultMode == HproseResultMode.Serialized) {
result = getRaw(reader.readRaw(), returnType);
}
tag = stream.read();
if (tag == TagArgument) {
reader.reset();
Object[] arguments = reader.readObjectArray();
int length = args.length;
if (length > arguments.length) {
length = arguments.length;
}
System.arraycopy(arguments, 0, args, 0, length);
tag = stream.read();
}
}
else if (tag == TagError) {
throw new HproseException(reader.readString());
}
if (tag != TagEnd) {
stream.rewind();
throw new HproseException("Wrong Response: \r\n" + StrUtil.toString(stream));
}
return result;
}
@Override
protected Promise<Object> invokeHandler(String name, Object[] args, HproseContext context) {
return invokeHandler(name, args, (ClientContext)context);
}
@Override
protected Promise<ByteBuffer> beforeFilterHandler(ByteBuffer request, HproseContext context) {
return beforeFilterHandler(request, (ClientContext)context);
}
@Override
protected Promise<ByteBuffer> afterFilterHandler(ByteBuffer request, HproseContext context) {
return afterFilterHandler(request, (ClientContext)context);
}
@SuppressWarnings("unchecked")
private Promise<Object> invokeHandler(String name, final Object[] args, final ClientContext context) {
final ByteBufferStream stream;
try {
stream = encode(name, args, context);
}
catch (Throwable e) {
return Promise.error(e);
}
final InvokeSettings settings = context.getSettings();
return beforeFilterHandler.handle(stream.buffer, context).then(
new Func<Object, ByteBuffer>() {
public Object call(ByteBuffer value) throws Throwable {
stream.buffer = value;
try {
return decode(stream, args, context);
}
finally {
if (settings.getMode() == HproseResultMode.Normal ||
settings.getMode() == HproseResultMode.Serialized ||
settings.getReturnType() == byte[].class) {
stream.close();
}
}
}
});
}
protected abstract Promise<ByteBuffer> sendAndReceive(ByteBuffer request, ClientContext context);
public final void invoke(String name, HproseCallback1<?> callback) {
invoke(name, nullArgs, callback, null, null, null);
}
public final void invoke(String name, HproseCallback1<?> callback, HproseErrorEvent errorEvent) {
invoke(name, nullArgs, callback, errorEvent, null, null);
}
public final void invoke(String name, HproseCallback1<?> callback, InvokeSettings settings) {
invoke(name, nullArgs, callback, null, null, settings);
}
public final void invoke(String name, HproseCallback1<?> callback, HproseErrorEvent errorEvent, InvokeSettings settings) {
invoke(name, nullArgs, callback, errorEvent, null, settings);
}
public final void invoke(String name, Object[] args, HproseCallback1<?> callback) {
invoke(name, args, callback, null, null, null);
}
public final void invoke(String name, Object[] args, HproseCallback1<?> callback, HproseErrorEvent errorEvent) {
invoke(name, args, callback, errorEvent, null, null);
}
public final void invoke(String name, Object[] args, HproseCallback1<?> callback, InvokeSettings settings) {
invoke(name, args, callback, null, null, settings);
}
public final void invoke(String name, Object[] args, HproseCallback1<?> callback, HproseErrorEvent errorEvent, InvokeSettings settings) {
invoke(name, args, callback, errorEvent, null, settings);
}
public final <T> void invoke(String name, HproseCallback1<T> callback, Class<T> returnType) {
invoke(name, nullArgs, callback, null, returnType, null);
}
public final <T> void invoke(String name, HproseCallback1<T> callback, HproseErrorEvent errorEvent, Class<T> returnType) {
invoke(name, nullArgs, callback, errorEvent, returnType, null);
}
public final <T> void invoke(String name, HproseCallback1<T> callback, Class<T> returnType, InvokeSettings settings) {
invoke(name, nullArgs, callback, null, returnType, settings);
}
public final <T> void invoke(String name, HproseCallback1<T> callback, HproseErrorEvent errorEvent, Class<T> returnType, InvokeSettings settings) {
invoke(name, nullArgs, callback, errorEvent, returnType, settings);
}
public final <T> void invoke(String name, Object[] args, HproseCallback1<T> callback, Class<T> returnType) {
invoke(name, args, callback, null, returnType, null);
}
public final <T> void invoke(String name, Object[] args, HproseCallback1<T> callback, HproseErrorEvent errorEvent, Class<T> returnType) {
invoke(name, args, callback, errorEvent, returnType, null);
}
public final <T> void invoke(String name, Object[] args, HproseCallback1<T> callback, Class<T> returnType, InvokeSettings settings) {
invoke(name, args, callback, null, returnType, settings);
}
public final <T> void invoke(final String name, Object[] args, final HproseCallback1<T> callback, final HproseErrorEvent errorEvent, Class<T> returnType, InvokeSettings settings) {
if (settings == null) settings = new InvokeSettings();
if (returnType != null) settings.setReturnType(returnType);
final HproseErrorEvent errEvent = (errorEvent == null) ? onError : errorEvent;
settings.setAsync(true);
final HproseContext context = getContext(settings);
Promise.all(args).then(new Action<Object[]>() {
public void call(Object[] args) throws Throwable {
invokeHandler.handle(name, args, context).then(
new Action<Object>() {
@SuppressWarnings("unchecked")
public void call(Object value) throws Throwable {
callback.handler((T)value);
}
},
new Action<Throwable>() {
public void call(Throwable e) throws Throwable {
if (errEvent != null) {
errEvent.handler(name, e);
}
}
}
);
}
});
}
public final void invoke(String name, Object[] args, HproseCallback<?> callback) {
invoke(name, args, callback, null, null, null);
}
public final void invoke(String name, Object[] args, HproseCallback<?> callback, HproseErrorEvent errorEvent) {
invoke(name, args, callback, errorEvent, null, null);
}
public final void invoke(String name, Object[] args, HproseCallback<?> callback, InvokeSettings settings) {
invoke(name, args, callback, null, null, settings);
}
public final void invoke(String name, Object[] args, HproseCallback<?> callback, HproseErrorEvent errorEvent, InvokeSettings settings) {
invoke(name, args, callback, errorEvent, null, settings);
}
public final <T> void invoke(String name, Object[] args, HproseCallback<T> callback, Class<T> returnType) {
invoke(name, args, callback, null, returnType, null);
}
public final <T> void invoke(String name, Object[] args, HproseCallback<T> callback, HproseErrorEvent errorEvent, Class<T> returnType) {
invoke(name, args, callback, errorEvent, returnType, null);
}
public final <T> void invoke(String name, Object[] args, HproseCallback<T> callback, Class<T> returnType, InvokeSettings settings) {
invoke(name, args, callback, null, returnType, settings);
}
public final <T> void invoke(final String name, final Object[] args, final HproseCallback<T> callback, final HproseErrorEvent errorEvent, Class<T> returnType, InvokeSettings settings) {
if (settings == null) settings = new InvokeSettings();
if (returnType != null) settings.setReturnType(returnType);
final HproseErrorEvent errEvent = (errorEvent == null) ? onError : errorEvent;
settings.setAsync(true);
final HproseContext context = getContext(settings);
Promise.all(args).then(new Action<Object[]>() {
public void call(final Object[] args) throws Throwable {
invokeHandler.handle(name, args, context).then(
new Action<Object>() {
@SuppressWarnings("unchecked")
public void call(Object value) throws Throwable {
callback.handler((T)value, args);
}
},
new Action<Throwable>() {
public void call(Throwable e) throws Throwable {
if (errEvent != null) {
errEvent.handler(name, e);
}
}
}
);
}
});
}
public final Object invoke(String name) throws Throwable {
return invoke(name, nullArgs, (Class<?>)null, null);
}
public final Object invoke(String name, InvokeSettings settings) throws Throwable {
return invoke(name, nullArgs, (Class<?>)null, settings);
}
public final Object invoke(String name, Object[] args) throws Throwable {
return invoke(name, args, (Class<?>)null, null);
}
public final Object invoke(String name, Object[] args, InvokeSettings settings) throws Throwable {
return invoke(name, args, (Class<?>)null, settings);
}
public final <T> T invoke(String name, Class<T> returnType) throws Throwable {
return invoke(name, nullArgs, returnType, null);
}
public final <T> T invoke(String name, Class<T> returnType, InvokeSettings settings) throws Throwable {
return invoke(name, nullArgs, returnType, settings);
}
public final <T> T invoke(String name, Object[] args, Class<T> returnType) throws Throwable {
return invoke(name, args, returnType, null);
}
@SuppressWarnings("unchecked")
public final <T> T invoke(String name, Object[] args, Class<T> returnType, InvokeSettings settings) throws Throwable {
if (settings == null) settings = new InvokeSettings();
if (returnType != null) settings.setReturnType(returnType);
Type type = settings.getReturnType();
Class<?> cls = ClassUtil.toClass(type);
if (Promise.class.equals(cls)) {
return (T)asyncInvoke(name, args, type, settings);
}
if (Future.class.equals(cls)) {
return (T)asyncInvoke(name, args, type, settings).toFuture();
}
if (settings.isAsync()) {
return (T)asyncInvoke(name, args, type, settings);
}
if (args != null) {
for (int i = 0, n = args.length; i < n; ++i) {
if (args[i] instanceof Promise) {
args[i] = ((Promise)args[i]).toFuture();
}
}
}
ClientContext context = getContext(settings);
return ((Promise<T>)invokeHandler.handle(name, args, context)).toFuture().get(context.getSettings().getTimeout(), TimeUnit.MILLISECONDS);
}
@SuppressWarnings("unchecked")
private Promise<?> asyncInvoke(final String name, Object[] args, Type type, InvokeSettings settings) {
settings.setAsync(true);
if (type instanceof ParameterizedType) {
type = ((ParameterizedType)type).getActualTypeArguments()[0];
if (void.class.equals(type) || Void.class.equals(type)) {
type = null;
}
}
else {
Class<?> cls = ClassUtil.toClass(type);
if (Promise.class.equals(cls) || Future.class.equals(cls)) {
type = null;
}
}
settings.setReturnType(type);
final HproseContext context = getContext(settings);
return Promise.all(args).then(new AsyncFunc<Object, Object[]>() {
public Promise<Object> call(Object[] args) throws Throwable {
return invokeHandler.handle(name, args, context);
}
});
}
private static class Topic<T> {
Action<T> handler;
final ConcurrentLinkedQueue<Action<T>> callbacks = new ConcurrentLinkedQueue<Action<T>>();
}
private final ConcurrentHashMap<String, ConcurrentHashMap<String, Topic<?>>> allTopics = new ConcurrentHashMap<String, ConcurrentHashMap<String, Topic<?>>>();
private Topic<?> getTopic(String name, String id) {
ConcurrentHashMap<String, Topic<?>> topics = allTopics.get(name);
if (topics != null) {
return topics.get(id);
}
return null;
}
private static final InvokeSettings autoIdSettings = new InvokeSettings();
private volatile String autoId = null;
static {
autoIdSettings.setReturnType(String.class);
autoIdSettings.setSimple(true);
autoIdSettings.setIdempotent(true);
autoIdSettings.setFailswitch(true);
}
@SuppressWarnings("unchecked")
public synchronized String autoId() {
if (autoId == null) {
try {
autoId = (String)this.invoke("#", autoIdSettings);
}
catch (Throwable e) {
if (onError != null) {
onError.handler("autoId", e);
}
}
}
return autoId;
}
public final void subscribe(String name, Action<Object> callback) {
subscribe(name, callback, Object.class, timeout);
}
public final void subscribe(String name, Action<Object> callback, int timeout) {
subscribe(name, callback, Object.class, timeout);
}
public final void subscribe(String name, String id, Action<Object> callback) {
subscribe(name, id, callback, Object.class, timeout);
}
public final void subscribe(String name, String id, Action<Object> callback, int timeout) {
subscribe(name, id, callback, Object.class, timeout);
}
public final <T> void subscribe(String name, Action<T> callback, Type type) {
subscribe(name, callback, type, timeout);
}
public final <T> void subscribe(String name, Action<T> callback, Type type, int timeout) {
subscribe(name, autoId(), callback, type, timeout);
}
public final <T> void subscribe(String name, String id, Action<T> callback, Type type) {
subscribe(name, id, callback, type, timeout);
}
public final <T> void subscribe(final String name, final String id, Action<T> callback, final Type type, final int timeout) {
subscribe(name, id, callback, type, timeout, false);
}
public final void subscribe(String name, Action<Object> callback, boolean failswitch) {
subscribe(name, callback, Object.class, timeout, failswitch);
}
public final void subscribe(String name, Action<Object> callback, int timeout, boolean failswitch) {
subscribe(name, callback, Object.class, timeout, failswitch);
}
public final void subscribe(String name, String id, Action<Object> callback, boolean failswitch) {
subscribe(name, id, callback, Object.class, timeout, failswitch);
}
public final void subscribe(String name, String id, Action<Object> callback, int timeout, boolean failswitch) {
subscribe(name, id, callback, Object.class, timeout, failswitch);
}
public final <T> void subscribe(String name, Action<T> callback, Type type, boolean failswitch) {
subscribe(name, callback, type, timeout, failswitch);
}
public final <T> void subscribe(String name, Action<T> callback, Type type, int timeout, boolean failswitch) {
subscribe(name, autoId(), callback, type, timeout, failswitch);
}
public final <T> void subscribe(String name, String id, Action<T> callback, Type type, boolean failswitch) {
subscribe(name, id, callback, type, timeout, failswitch);
}
@SuppressWarnings("unchecked")
public final <T> void subscribe(final String name, final String id, Action<T> callback, final Type type, final int timeout, final boolean failswitch) {
allTopics.putIfAbsent(name, new ConcurrentHashMap<String, Topic<?>>());
Topic<T> topic = (Topic<T>)getTopic(name, id);
if (topic == null) {
final Action<Throwable> cb = new Action<Throwable>() {
public void call(Throwable e) throws Throwable {
Topic<T> topic = (Topic<T>)getTopic(name, id);
if (topic != null) {
InvokeSettings settings = new InvokeSettings();
settings.setIdempotent(true);
settings.setFailswitch(failswitch);
settings.setReturnType(type);
settings.setTimeout(timeout);
settings.setAsync(true);
Promise<T> result = (Promise<T>)invokeHandler.handle(name, new Object[] {id}, getContext(settings));
result.then(topic.handler, this);
}
}
};
topic = new Topic<T>();
topic.handler = new Action<T>() {
public void call(T result) throws Throwable {
Topic topic = getTopic(name, id);
if (topic != null) {
if (result != null) {
ConcurrentLinkedQueue<Action<T>> callbacks = topic.callbacks;
for (Action<T> callback: callbacks) {
try {
callback.call(result);
}
catch (Throwable e) {}
}
}
cb.call(null);
}
}
};
topic.callbacks.offer(callback);
allTopics.get(name).put(id, topic);
try {
cb.call(null);
}
catch (Throwable e) {
if (onError != null) {
onError.handler(name, e);
}
}
}
else if (!topic.callbacks.contains(callback)) {
topic.callbacks.offer(callback);
}
}
@SuppressWarnings("unchecked")
private <T> void delTopic(ConcurrentHashMap<String, Topic<?>> topics, String id, Action<T> callback) {
if (topics != null && topics.size() > 0) {
if (callback != null) {
Topic<T> topic = (Topic<T>)topics.get(id);
if (topic != null) {
ConcurrentLinkedQueue<Action<T>> callbacks = topic.callbacks;
callbacks.remove(callback);
if (callbacks.isEmpty()) {
topics.remove(id);
}
}
}
else {
topics.remove(id);
}
}
}
public void unsubscribe(String name) {
unsubscribe(name, null, null);
}
public <T> void unsubscribe(String name, Action<T> callback) {
unsubscribe(name, null, callback);
}
public void unsubscribe(String name, String id) {
unsubscribe(name, id, null);
}
public <T> void unsubscribe(String name, String id, final Action<T> callback) {
final ConcurrentHashMap<String, Topic<?>> topics = (ConcurrentHashMap<String, Topic<?>>)allTopics.get(name);
if (topics != null) {
if (id == null) {
if (autoId == null) {
for (String i: topics.keySet()) {
delTopic(topics, i, callback);
}
}
else {
delTopic(topics, autoId, callback);
}
}
else {
delTopic(topics, id, callback);
}
if (topics.isEmpty()) {
allTopics.remove(name, topics);
}
}
}
public String getId() {
return autoId;
}
public boolean isSubscribed(String name) {
return allTopics.containsKey(name);
}
public String[] subscribedList() {
return allTopics.keySet().toArray(new String[0]);
}
}