/*
* Copyright (C) 2011 Red Hat, Inc. and/or its affiliates.
*
* 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 org.jboss.errai.bus.client.api.builder;
import java.lang.annotation.Annotation;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import org.jboss.errai.bus.client.api.BusErrorCallback;
import org.jboss.errai.bus.client.api.base.DefaultErrorCallback;
import org.jboss.errai.bus.client.api.base.MessageBuilder;
import org.jboss.errai.bus.client.api.messaging.Message;
import org.jboss.errai.bus.client.api.messaging.MessageBus;
import org.jboss.errai.bus.client.api.messaging.MessageCallback;
import org.jboss.errai.common.client.api.Assert;
import org.jboss.errai.common.client.api.ErrorCallback;
import org.jboss.errai.common.client.api.RemoteCallback;
import org.jboss.errai.common.client.framework.ProxyFactory;
import org.jboss.errai.common.client.framework.RemoteServiceProxyFactory;
import org.jboss.errai.common.client.framework.RpcStub;
import org.jboss.errai.common.client.protocols.MessageParts;
/**
* Facilitates the building of a remote call. Ensures that the remote call is constructed properly.
* <p/>
* Part of the fluent API centered around {@link MessageBuilder}.
*/
public class DefaultRemoteCallBuilder {
private static ProxyFactory proxyFactory = new RemoteServiceProxyFactory();
/* Used to generate a unique number */
private volatile static int callCounter = 0;
private final Message message;
private RemoteCallback<Object> remoteCallback;
/* The type of response that is expected by the callback */
private Class<Object> responseType = Object.class;
public DefaultRemoteCallBuilder(Message message) {
this.message = message;
}
public <T, R> T call(final RemoteCallback<R> callback, final Class<T> remoteService) {
return call(callback, null, remoteService);
}
public <T, R> T call(final RemoteCallback<R> callback, final BusErrorCallback errorCallback, final Class<T> remoteService) {
final T svc = proxyFactory.getRemoteProxy(remoteService);
((RpcStub) svc).setRemoteCallback(callback);
((RpcStub) svc).setErrorCallback(errorCallback);
return svc;
}
/**
* Only intended for use by generated code. Use {@link #call(RemoteCallback, Class)} or
* {@link #call(RemoteCallback, BusErrorCallback, Class)} from handwritten code.
* <p/>
* Creates, implements and returns an instance of <tt>RemoteCallEndpointDef</tt> and all applicable arguments, which
* should be instantiated after this call to <tt>serviceName</tt>. The endpoint allows a function from a service to be
* called directly, rather than waiting for a response to a message.
*
* @param serviceName
* the service to call, and create a remote call endpoint for
*
* @return the remote call endpoint.
*/
public RemoteCallEndpointDef call(final String serviceName) {
message.toSubject(serviceName + ":RPC");
final RemoteCallSendable sendable = new RemoteCallSendable() {
@Override
public void sendNowWith(final MessageBus bus) {
final Integer id;
final String replyTo =
message.getSubject() + "." + message.getCommandType() + ":" + (id = uniqueNumber()) + ":RespondTo:RPC";
final String errorTo =
message.getSubject() + "." + message.getCommandType() + ":" + id + ":Errors:RPC";
if (remoteCallback != null) {
bus.subscribe(replyTo,
new MessageCallback() {
@Override
public void callback(Message message) {
bus.unsubscribeAll(replyTo);
if (DefaultRemoteCallBuilder.this.message.getErrorCallback() != null) {
bus.unsubscribeAll(errorTo);
}
remoteCallback.callback(message.get(responseType, "MethodReply"));
}
}
);
message.set(MessageParts.ReplyTo, replyTo);
}
if (message.getErrorCallback() != null) {
bus.subscribe(errorTo,
new MessageCallback() {
@Override
public void callback(Message m) {
bus.unsubscribeAll(errorTo);
if (remoteCallback != null) {
bus.unsubscribeAll(replyTo);
}
message.set(MessageParts.AdditionalDetails, m.get(String.class, MessageParts.AdditionalDetails));
final Throwable throwable = m.get(Throwable.class, MessageParts.Throwable);
final boolean defaultErrorHandling = message.getErrorCallback().error(message,
throwable);
if (defaultErrorHandling) {
DefaultErrorCallback.INSTANCE.error(message, throwable);
}
}
}
);
message.set(MessageParts.ErrorTo, errorTo);
}
message.sendNowWith(bus);
}
};
final RemoteCallErrorDef errorDef = new RemoteCallErrorDef() {
@Override
public RemoteCallSendable errorsHandledBy(@SuppressWarnings("rawtypes") ErrorCallback errorCallback) {
if (errorCallback != null) {
message.errorsCall(errorCallback);
}
return sendable;
}
@Override
public RemoteCallSendable defaultErrorHandling() {
return sendable;
}
};
final RemoteCallResponseDef respondDef = new RemoteCallResponseDef() {
@Override
@SuppressWarnings("unchecked")
public <T> RemoteCallErrorDef respondTo(Class<T> returnType, RemoteCallback<T> callback) {
responseType = (Class<Object>) returnType;
remoteCallback = (RemoteCallback<Object>) callback;
return errorDef;
}
};
return new RemoteCallEndpointDef() {
@Override
public RemoteCallResponseDef endpoint(String endPointName) {
message.command(endPointName);
return respondDef;
}
@Override
public RemoteCallResponseDef endpoint(String endPointName, Annotation[] qualifiers, Object[] args) {
message.command(endPointName);
if (qualifiers != null) {
final List<String> qualNames = new ArrayList<String>(qualifiers.length);
for (final Annotation a : qualifiers) {
qualNames.add(a.annotationType().getName());
}
message.set("Qualifiers", qualNames);
}
if (args != null)
message.set("MethodParms", Arrays.asList(args));
return respondDef;
}
@Override
public RemoteCallResponseDef endpoint(String endPointName, Object[] args) {
message.command(endPointName);
if (args != null)
message.set("MethodParms", Arrays.asList(args));
return respondDef;
}
};
}
private static int uniqueNumber() {
return ++callCounter > 1000000 ? callCounter = 0 : callCounter;
}
/**
* Sets the proxy provider factory that is used by MessageBuilder and friends for creating remote proxies. Unless you
* are creating an Errai extension that provides an alternative remoting mechanism, there is never a need to call this
* method.
*
* @param provider
* The ProxyProvider that provides RPC proxies to message builders. Not null.
*/
public static void setProxyFactory(ProxyFactory provider) {
proxyFactory = Assert.notNull(provider);
}
public static void destroyProxyFactory() {
proxyFactory = null;
}
}