/*
* 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 com.google.inject.AbstractModule;
import com.google.inject.Guice;
import com.google.inject.Injector;
import org.jboss.errai.bus.client.api.Local;
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.Command;
import org.jboss.errai.bus.server.annotations.Remote;
import org.jboss.errai.bus.server.annotations.Service;
import org.jboss.errai.bus.server.annotations.security.RequireAuthentication;
import org.jboss.errai.bus.server.annotations.security.RequireRoles;
import org.jboss.errai.bus.server.io.CommandBindingsCallback;
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.security.auth.rules.RolesRequiredRule;
import org.jboss.errai.bus.server.service.bootstrap.BootstrapContext;
import org.jboss.errai.bus.server.service.bootstrap.GuiceProviderProxy;
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.jboss.errai.config.rebind.ProxyUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
/**
* @author Heiko Braun <hbraun@redhat.com>
* @author Christian Sadilek <csadilek@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);
for (Class<?> loadClass : services) {
Object svc = null;
Service svcAnnotation = loadClass.getAnnotation(Service.class);
if (null == svcAnnotation) {
// 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());
continue;
}
boolean local = loadClass.isAnnotationPresent(Local.class);
String svcName = svcAnnotation.value();
// If no name is specified, just use the class name as the service by default.
if ("".equals(svcName)) {
svcName = loadClass.getSimpleName();
}
Map<String, Method> commandPoints = new HashMap<String, Method>();
for (final Method method : loadClass.getDeclaredMethods()) {
if (method.isAnnotationPresent(Command.class)) {
Command command = method.getAnnotation(Command.class);
for (String cmdName : command.value()) {
if (cmdName.equals("")) cmdName = method.getName();
commandPoints.put(cmdName, method);
}
}
}
Class remoteImpl = getRemoteImplementation(loadClass);
if (remoteImpl != null) {
svc = createRPCScaffolding(remoteImpl, loadClass, context);
}
if (MessageCallback.class.isAssignableFrom(loadClass)) {
final Class<? extends MessageCallback> clazz = loadClass.asSubclass(MessageCallback.class);
log.debug("discovered service: " + clazz.getName());
try {
svc = Guice.createInjector(new AbstractModule() {
@Override
protected void configure() {
bind(MessageCallback.class).to(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);
}
catch (Throwable t) {
t.printStackTrace();
}
if (commandPoints.isEmpty()) {
// Subscribe the service to the bus.
if (local) {
context.getBus().subscribeLocal(svcName, (MessageCallback) svc);
}
else {
context.getBus().subscribe(svcName, (MessageCallback) svc);
}
}
}
if (svc == null) {
svc = 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()));
}
}
}).getInstance(loadClass);
}
RolesRequiredRule rule = null;
if (loadClass.isAnnotationPresent(RequireRoles.class)) {
rule = new RolesRequiredRule(loadClass.getAnnotation(RequireRoles.class).value(), context.getBus());
}
else if (loadClass.isAnnotationPresent(RequireAuthentication.class)) {
rule = new RolesRequiredRule(new HashSet<Object>(), context.getBus());
}
if (!commandPoints.isEmpty()) {
if (local) {
context.getBus().subscribeLocal(svcName, new CommandBindingsCallback(commandPoints, svc, context.getBus()));
}
else {
context.getBus().subscribe(svcName, new CommandBindingsCallback(commandPoints, svc, context.getBus()));
}
}
if (rule != null) {
context.getBus().addRule(svcName, rule);
}
}
}
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;
}
}