/*
* Copyright 2009 JBoss, a divison 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.cdi.server;
import com.google.inject.AbstractModule;
import com.google.inject.Guice;
import com.google.inject.Injector;
import com.google.inject.Provider;
import org.jboss.errai.bus.client.api.Message;
import org.jboss.errai.bus.client.api.MessageCallback;
import org.jboss.errai.bus.client.api.ResourceProvider;
import org.jboss.errai.bus.client.api.builder.AbstractRemoteCallBuilder;
import org.jboss.errai.bus.client.framework.MessageBus;
import org.jboss.errai.bus.client.framework.ProxyProvider;
import org.jboss.errai.bus.rebind.RebindUtils;
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.io.CommandBindingsCallback;
import org.jboss.errai.bus.server.io.ConversationalEndpointCallback;
import org.jboss.errai.bus.server.io.RemoteServiceCallback;
import org.jboss.errai.bus.server.service.ErraiService;
import org.jboss.errai.cdi.server.events.ShutdownEventObserver;
import org.jboss.errai.container.ServiceFactory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.enterprise.context.ApplicationScoped;
import javax.enterprise.context.Conversation;
import javax.enterprise.event.Observes;
import javax.enterprise.inject.spi.*;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
/**
* Extension points to the CDI container.
* Makes Errai components available as CDI beans (i.e. the message bus)
* and registers CDI components as services with Errai.
*
* @author Heiko.Braun <hbraun@redhat.com>
*/
@ApplicationScoped
public class CDIExtensionPoints implements Extension {
private static final Logger log = LoggerFactory.getLogger(CDIExtensionPoints.class);
private TypeRegistry managedTypes = null;
private String uuid = null;
private ContextManager contextManager;
private ErraiService service;
public void beforeBeanDiscovery(@Observes BeforeBeanDiscovery bbd) {
this.uuid = UUID.randomUUID().toString();
this.managedTypes = new TypeRegistry();
log.info("Created Errai-CDI context: " + uuid);
}
/**
* Register managed beans as Errai services
*
* @param event
* @param <T>
*/
public <T> void observeResources(@Observes ProcessAnnotatedType<T> event) {
final AnnotatedType<T> type = event.getAnnotatedType();
// services
if (type.isAnnotationPresent(Service.class)) {
log.debug("Discovered Errai annotation on type: " + type);
boolean isRpc = false;
Class<T> javaClass = type.getJavaClass();
for (Class<?> intf : javaClass.getInterfaces()) {
isRpc = intf.isAnnotationPresent(Remote.class);
if (isRpc) {
log.debug("Identified Errai RPC interface: " + intf + " on " + type);
managedTypes.addRPCEndpoint(intf, type);
}
}
if (!isRpc) {
managedTypes.addServiceEndpoint(type);
}
// enforce application scope until we get the other scopes working
ApplicationScoped scope = type.getAnnotation(ApplicationScoped.class);
if (null == scope)
log.warn("Service implementation not @ApplicationScoped: " + type.getJavaClass());
}
// veto on client side implementations that contain CDI annotations
// (i.e. @Observes) Otherwise Weld might try to invoke on them
if (type.getJavaClass().getPackage().getName().contains("client")
&& !type.getJavaClass().isInterface()) {
event.veto();
log.info("Veto " + type);
}
/**
* Mixing JSR-299 and Errai annotation causes bean valdation problems.
* Therefore we need to provide additional meta data for the Provider implementations,
* (the Produces annotation literal)
* even though these classes are only client side implementations.
*/
/*else if(type.isAnnotationPresent(org.jboss.errai.ioc.client.api.Provider.class))
{
AnnotatedTypeBuilder<T> builder = AnnotatedTypeBuilder.newInstance(event.getAnnotatedType().getJavaClass());
builder.readAnnotationsFromUnderlyingType();
//builder.addToClass(new ApplicationScopedQualifier(){});
for(AnnotatedMethod method : type.getMethods())
{
if("provide".equals(method.getJavaMember().getName()))
{
builder.addToMethod(method.getJavaMember(), new ProducesQualifier(){});
break;
}
}
AnnotatedType<T> replacement = builder.create();
event.setAnnotatedType(replacement);
} */
}
public void afterBeanDiscovery(@Observes AfterBeanDiscovery abd, BeanManager bm) {
// Errai Service wrapper
this.service = Util.lookupErraiService();
abd.addBean(new ServiceMetaData(bm, this.service));
final MessageBus bus = service.getBus();
// context handling hooks
this.contextManager = new ContextManager(uuid, bm, bus);
// Custom Conversation
abd.addBean(new ConversationMetaData(bm, new ErraiConversation(
(Conversation) Util.lookupCallbackBean(bm, Conversation.class),
this.contextManager
)));
// event dispatcher
EventDispatcher eventDispatcher = new EventDispatcher(bm, bus, this.contextManager);
EventSubscriptionListener listener = new EventSubscriptionListener(abd, bus);
bus.addSubscribeListener(listener);
// Errai bus injection
abd.addBean(new MessageBusMetaData(bm, bus));
// Register observers
abd.addObserverMethod(new ShutdownEventObserver(managedTypes, bus, uuid));
// subscribe service and rpc endpoints
subscribeServices(bm, bus);
// subscribe event dispatcher
bus.subscribe(EventDispatcher.NAME, eventDispatcher);
}
private void subscribeServices(final BeanManager beanManager, final MessageBus bus) {
for (final AnnotatedType<?> type : managedTypes.getServiceEndpoints()) {
// Discriminate on @Command
Map<String, Method> commandPoints = new HashMap<String, Method>();
for (final AnnotatedMethod method : type.getMethods()) {
if (method.isAnnotationPresent(Command.class)) {
Command command = method.getAnnotation(Command.class);
for (String cmdName : command.value()) {
if (cmdName.equals("")) cmdName = method.getJavaMember().getName();
commandPoints.put(cmdName, method.getJavaMember());
}
}
}
log.info("Register MessageCallback: " + type);
String subjectName = Util.resolveServiceName(type.getJavaClass());
Object targetbean = Util.lookupCallbackBean(beanManager, type.getJavaClass());
final MessageCallback invocationTarget = commandPoints.isEmpty() ?
(MessageCallback) targetbean : new CommandBindingsCallback(commandPoints, targetbean);
// TODO: enable CommandBindings
bus.subscribe(subjectName, new MessageCallback() {
//private BeanLookup lookup = new BeanLookup(type,beanManager);
public void callback(final Message message) {
contextManager.activateRequestContext();
contextManager.activateConversationContext(message);
try {
if (invocationTarget == null) {
((MessageCallback) Util.lookupCallbackBean(beanManager, type.getJavaClass())).callback(message);
} else {
invocationTarget.callback(message);
}
} finally {
contextManager.deactivateRequestContext();
contextManager.deactivateConversationContext(message);
}
}
});
}
for (final Class<?> rpcIntf : managedTypes.getRpcEndpoints().keySet()) {
final AnnotatedType type = managedTypes.getRpcEndpoints().get(rpcIntf);
final Class beanClass = type.getJavaClass();
log.info("Register RPC Endpoint: " + type + "(" + rpcIntf + ")");
// TODO: Copied from errai internals, refactor at some point
createRPCScaffolding(rpcIntf, beanClass, bus, new ResourceProvider() {
public Object get() {
return Util.lookupRPCBean(beanManager, rpcIntf, beanClass);
}
});
}
}
private void createRPCScaffolding(final Class remoteIface, final Class<?> type, final MessageBus bus,
final ResourceProvider resourceProvider) {
final Injector injector = Guice.createInjector(new AbstractModule() {
@Override
protected void configure() {
bind(MessageBus.class).toInstance(bus);
//bind(RequestDispatcher.class).toInstance(context.getService().getDispatcher());
bind(type).toProvider(new Provider() {
public Object get() {
return resourceProvider.get();
}
});
}
});
Object svc = injector.getInstance(type);
Map<String, MessageCallback> epts = new HashMap<String, MessageCallback>();
// beware of classloading issues. better reflect on the actual instance
for (Class<?> intf : svc.getClass().getInterfaces()) {
for (final Method method : intf.getDeclaredMethods()) {
if (RebindUtils.isMethodInInterface(remoteIface, method)) {
epts.put(RebindUtils.createCallSignature(method),
new ConversationalEndpointCallback(svc, method, bus));
}
}
}
final RemoteServiceCallback delegate = new RemoteServiceCallback(epts);
bus.subscribe(remoteIface.getName() + ":RPC", new MessageCallback() {
public void callback(Message message) {
try {
CDIExtensionPoints.this.contextManager.activateRequestContext();
delegate.callback(message);
} finally {
CDIExtensionPoints.this.contextManager.deactivateRequestContext();
}
}
});
new ProxyProvider() {
{
AbstractRemoteCallBuilder.setProxyFactory(this);
}
public <T> T getRemoteProxy(Class<T> proxyType) {
throw new RuntimeException("This API is not supported in the server-side environment.");
}
};
}
class BeanLookup {
private BeanManager beanManager;
private AnnotatedType<?> type;
private Object invocationTarget;
BeanLookup(AnnotatedType<?> type, BeanManager bm) {
this.type = type;
this.beanManager = bm;
}
public Object getInvocationTarget() {
if (null == invocationTarget) {
invocationTarget =
Util.lookupCallbackBean(beanManager, type.getJavaClass());
}
return invocationTarget;
}
}
}