/*
* Copyright 2012 JBoss, by Red Hat, 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 org.jboss.errai.bus.server.service;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import org.jboss.errai.bus.client.api.builder.DefaultRemoteCallBuilder;
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.bus.client.api.messaging.RequestDispatcher;
import org.jboss.errai.bus.server.annotations.Remote;
import org.jboss.errai.bus.server.annotations.Service;
import org.jboss.errai.bus.server.io.RPCEndpointFactory;
import org.jboss.errai.bus.server.io.RemoteServiceCallback;
import org.jboss.errai.bus.server.io.ServiceInstanceProvider;
import org.jboss.errai.bus.server.service.bootstrap.BootstrapContext;
import org.jboss.errai.bus.server.service.bootstrap.GuiceProviderProxy;
import org.jboss.errai.bus.server.util.NotAService;
import org.jboss.errai.bus.server.util.ServiceMethodParser;
import org.jboss.errai.bus.server.util.ServiceParser;
import org.jboss.errai.bus.server.util.ServiceTypeParser;
import org.jboss.errai.codegen.util.ProxyUtil;
import org.jboss.errai.common.client.api.Assert;
import org.jboss.errai.common.client.api.ResourceProvider;
import org.jboss.errai.common.client.api.tasks.TaskManager;
import org.jboss.errai.common.client.api.tasks.TaskManagerFactory;
import org.jboss.errai.common.client.framework.ProxyFactory;
import org.jboss.errai.common.metadata.MetaDataProcessor;
import org.jboss.errai.common.metadata.MetaDataScanner;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.google.inject.AbstractModule;
import com.google.inject.Guice;
import com.google.inject.Injector;
/**
* @author Heiko Braun <hbraun@redhat.com>
* @author Christian Sadilek <csadilek@redhat.com>
* @author Max Barkley <mbarkley@redhat.com>
*/
public class ServiceProcessor implements MetaDataProcessor<BootstrapContext> {
private Logger log = LoggerFactory.getLogger(ServiceProcessor.class);
// TODO need to exclude client classes based on GWT module definition
private static final String CLIENT_PKG_REGEX = ".*(\\.client\\.).*";
@Override
public void process(final BootstrapContext context, MetaDataScanner reflections) {
final ErraiServiceConfiguratorImpl config = (ErraiServiceConfiguratorImpl) context.getConfig();
final Set<Class<?>> services = reflections.getTypesAnnotatedWithExcluding(Service.class, CLIENT_PKG_REGEX);
final Set<Method> methodServices = reflections.getMethodsAnnotatedWithExcluding(Service.class, CLIENT_PKG_REGEX);
for (Class<?> loadClass : services) {
processServiceClass(loadClass, context, config);
}
for (Method loadMethod : methodServices) {
processServiceMethod(loadMethod, context, config);
}
}
private void processServiceClass(final Class<?> loadClass, final BootstrapContext context,
final ErraiServiceConfiguratorImpl config) {
ServiceParser svcParser;
try {
svcParser = new ServiceTypeParser(loadClass);
}
catch (NotAService ex) {
// Diagnose Errai-111
StringBuilder sb = new StringBuilder();
sb.append("Service annotation cannot be loaded. (See https://jira.jboss.org/browse/ERRAI-111)\n");
sb.append(loadClass.getSimpleName()).append(" loader: ").append(loadClass.getClassLoader()).append("\n");
sb.append("@Service loader:").append(Service.class.getClassLoader()).append("\n");
log.warn(sb.toString());
return;
}
Class<?> remoteImpl = ((ServiceTypeParser) svcParser).getRemoteImplementation();
if (remoteImpl != null) {
createRPCScaffolding(remoteImpl, loadClass, context);
}
processService(svcParser, context);
}
private void processServiceMethod(final Method loadMethod, final BootstrapContext context,
final ErraiServiceConfiguratorImpl config) {
ServiceParser svcParser;
try {
svcParser = new ServiceMethodParser(loadMethod);
} catch (NotAService ex) {
// Diagnose Errai-111
StringBuilder sb = new StringBuilder();
sb.append("Service annotation cannot be loaded. (See https://jira.jboss.org/browse/ERRAI-111)\n");
sb.append(loadMethod.getName()).append(" class: ").append(loadMethod.getClass().getSimpleName());
sb.append(" loader: ").append(loadMethod.getClass().getClassLoader()).append("\n");
sb.append("@Service loader:").append(Service.class.getClassLoader()).append("\n");
log.warn(sb.toString());
return;
}
processService(svcParser, context);
}
private void processService(ServiceParser svcParser, BootstrapContext context) {
Object svc = null;
ErraiServiceConfiguratorImpl config = (ErraiServiceConfiguratorImpl) context.getConfig();
Class<?> loadClass = svcParser.getDelegateClass();
// Now try and get the appropriate injector
// Will never return true for service methods
if (svcParser.isCallback()) {
final Class<? extends MessageCallback> clazz = loadClass.asSubclass(MessageCallback.class);
log.debug("discovered service: " + clazz.getName());
try {
svc = createServiceInjector(clazz, context, config, true);
} catch (Throwable t) {
t.printStackTrace();
}
}
if (svc == null) {
svc = createServiceInjector(loadClass, context, config, false);
}
// If we have created an injector, get a callback and register it
if (svc != null) {
MessageCallback callback = svcParser.getCallback(svc, context.getBus());
if (callback != null) {
if (svcParser.isLocal()) {
context.getBus().subscribeLocal(svcParser.getServiceName(), callback);
}
else {
context.getBus().subscribe(svcParser.getServiceName(), callback);
}
}
}
}
/**
* Creates an injector for a service. isCallback should be true iff clazz can safely be cast to
* {@link MessageCallback MessageCallback}.
*/
private Object createServiceInjector(final Class<?> clazz, final BootstrapContext context,
final ErraiServiceConfiguratorImpl config, boolean isCallback) {
Object retVal;
if (isCallback) {
retVal = Guice.createInjector(new AbstractModule() {
@SuppressWarnings("unchecked")
@Override
protected void configure() {
bind(MessageCallback.class).to((Class<? extends MessageCallback>) clazz);
bind(MessageBus.class).toInstance(context.getBus());
bind(RequestDispatcher.class).toInstance(context.getService().getDispatcher());
bind(TaskManager.class).toInstance(TaskManagerFactory.get());
// Add any extension bindings.
for (Map.Entry<Class<?>, ResourceProvider> entry : config.getExtensionBindings().entrySet()) {
bind(entry.getKey()).toProvider(new GuiceProviderProxy(entry.getValue()));
}
}
}).getInstance(MessageCallback.class);
}
else {
retVal = Guice.createInjector(new AbstractModule() {
@SuppressWarnings("unchecked")
@Override
protected void configure() {
bind(MessageBus.class).toInstance(context.getBus());
bind(RequestDispatcher.class).toInstance(context.getService().getDispatcher());
bind(TaskManager.class).toInstance(TaskManagerFactory.get());
// Add any extension bindings.
for (Map.Entry<Class<?>, ResourceProvider> entry : config.getExtensionBindings().entrySet()) {
bind(entry.getKey()).toProvider(new GuiceProviderProxy(entry.getValue()));
}
}
}).getInstance(clazz);
}
return retVal;
}
private static Class getRemoteImplementation(Class type) {
for (Class iface : type.getInterfaces()) {
if (iface.isAnnotationPresent(Remote.class)) {
return iface;
}
else if (iface.getInterfaces().length != 0 && ((iface = getRemoteImplementation(iface)) != null)) {
return iface;
}
}
return null;
}
private static Object createRPCScaffolding(final Class remoteIface, final Class<?> type,
final BootstrapContext context) {
final ErraiServiceConfiguratorImpl config = (ErraiServiceConfiguratorImpl) context.getConfig();
final Injector injector = Guice.createInjector(new AbstractModule() {
@Override
protected void configure() {
bind(MessageBus.class).toInstance(context.getBus());
bind(RequestDispatcher.class).toInstance(context.getService().getDispatcher());
bind(TaskManager.class).toInstance(TaskManagerFactory.get());
// Add any extension bindings.
for (Map.Entry<Class<?>, ResourceProvider> entry : config.getExtensionBindings().entrySet()) {
bind(entry.getKey()).toProvider(new GuiceProviderProxy(entry.getValue()));
}
}
});
final Object svc = injector.getInstance(type);
final Map<String, MessageCallback> epts = new HashMap<String, MessageCallback>();
final ServiceInstanceProvider genericSvc = new ServiceInstanceProvider() {
@Override
public Object get(Message message) {
return svc;
}
};
// beware of classloading issues. better reflect on the actual instance
for (Class<?> intf : svc.getClass().getInterfaces()) {
for (final Method method : intf.getMethods()) {
if (ProxyUtil.isMethodInInterface(remoteIface, method)) {
epts.put(ProxyUtil.createCallSignature(intf, method),
RPCEndpointFactory.createEndpointFor(genericSvc, method, context.getBus()));
}
}
}
context.getBus().subscribe(remoteIface.getName() + ":RPC", new RemoteServiceCallback(epts));
// note: this method just exists because we want AbstractRemoteCallBuilder to be package
// private.
DefaultRemoteCallBuilder.setProxyFactory(Assert.notNull(new ProxyFactory() {
@Override
public <T> T getRemoteProxy(Class<T> proxyType) {
throw new RuntimeException(
"There is not yet an available Errai RPC implementation for the server-side environment.");
}
}));
return svc;
}
}