/* * Copyright (C) 2014 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.cdi.server.events; import static org.jboss.errai.enterprise.client.cdi.api.CDI.getSubjectNameByType; import java.lang.annotation.Annotation; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import javax.enterprise.inject.spi.BeanManager; import javax.enterprise.inject.spi.EventMetadata; import javax.enterprise.inject.spi.ObserverMethod; import org.jboss.errai.bus.client.api.QueueSession; import org.jboss.errai.bus.client.api.RoutingFlag; import org.jboss.errai.bus.client.api.base.CommandMessage; 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.bus.server.api.RpcContext; import org.jboss.errai.bus.server.util.LocalContext; import org.jboss.errai.common.client.protocols.MessageParts; import org.jboss.errai.config.rebind.EnvUtil; import org.jboss.errai.enterprise.client.cdi.CDICommands; import org.jboss.errai.enterprise.client.cdi.CDIProtocol; import org.jboss.errai.enterprise.client.cdi.api.CDI; import org.jboss.errai.enterprise.client.cdi.api.Conversational; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * Acts as a bridge between Errai Bus and the CDI event system. * * @author Mike Brock * @author Christian Sadilek <csadilek@redhat.com> * @author Jonathan Fuerth <jfuerth@redhat.com> */ public class EventDispatcher implements MessageCallback { private static final Logger log = LoggerFactory.getLogger("EventDispatcher"); private static final String CDI_EVENT_CHANNEL_OPEN = "cdi.event.channel.open"; private final BeanManager beanManager; private final EventRoutingTable eventRoutingTable; private final MessageBus messagebus; private final Set<String> observedEvents; private final Map<String, Annotation> eventQualifiers; private final Set<ClientObserverMetadata> clientObservers = Collections .newSetFromMap(new ConcurrentHashMap<ClientObserverMetadata, Boolean>()); public EventDispatcher(final BeanManager beanManager, final EventRoutingTable eventRoutingTable, final MessageBus messageBus, final Set<String> observedEvents, final Map<String, Annotation> eventQualifiers) { this.beanManager = beanManager; this.eventRoutingTable = eventRoutingTable; this.messagebus = messageBus; this.observedEvents = observedEvents; this.eventQualifiers = eventQualifiers; } @Override @SuppressWarnings("unchecked") public void callback(final Message message) { if (!message.isFlagSet(RoutingFlag.FromRemote)) return; try { final LocalContext localContext = LocalContext.get(message); final String typeName = message.get(String.class, CDIProtocol.BeanType); final Set<String> annotationTypes = message.get(Set.class, CDIProtocol.Qualifiers); switch (CDICommands.valueOf(message.getCommandType())) { case RemoteSubscribe: final Class<?> type = Thread.currentThread().getContextClassLoader().loadClass(typeName); final ClientObserverMetadata clientObserver = new ClientObserverMetadata(type, annotationTypes); if (!clientObservers.contains(clientObserver)) { if (type == null || !EnvUtil.isPortableType(type)) { log.warn("client tried to register a non-portable type: " + type); return; } clientObservers.add(clientObserver); } eventRoutingTable.activateRoute(typeName, annotationTypes, message.getResource(QueueSession.class, "Session")); break; case RemoteUnsubscribe: eventRoutingTable.deactivateRoute(typeName, annotationTypes, message.getResource(QueueSession.class, "Session")); break; case CDIEvent: if (!isRoutable(localContext, message)) { return; } final Object o = message.get(Object.class, CDIProtocol.BeanReference); try { RpcContext.set(message); final Set<String> qualifierNames = message.get(Set.class, CDIProtocol.Qualifiers); final List<Annotation> qualifiers = new ArrayList<Annotation>(); if (qualifierNames != null) { for (final String serializedQualifier : qualifierNames) { final Annotation qualifier = eventQualifiers.get(serializedQualifier); if (qualifier != null) { qualifiers.add(qualifier); } } } // Fire event to all local observers final Annotation[] qualArray = qualifiers.toArray(new Annotation[qualifiers.size()]); final Set<ObserverMethod<? super Object>> observerMethods = beanManager.resolveObserverMethods(o, qualArray); for (final ObserverMethod<? super Object> observer : observerMethods) { // Don't mirror the event back to the clients if (!(AnyEventObserver.class.equals(observer.getBeanClass()))) { observer.notify(o); } } } finally { RpcContext.remove(); } break; case AttachRemote: if (observedEvents.size() > 0) { MessageBuilder.createConversation(message).toSubject(CDI.CLIENT_DISPATCHER_SUBJECT) .command(CDICommands.AttachRemote).with(MessageParts.RemoteServices, getEventTypes()).done().reply(); } else { MessageBuilder.createConversation(message).toSubject(CDI.CLIENT_DISPATCHER_SUBJECT) .command(CDICommands.AttachRemote).with(MessageParts.RemoteServices, "").done().reply(); } localContext.setAttribute(CDI_EVENT_CHANNEL_OPEN, "1"); break; default: throw new IllegalArgumentException("Unknown command type " + message.getCommandType()); } } catch (final Exception e) { throw new RuntimeException("Failed to dispatch CDI Event", e); } } private String getEventTypes() { final StringBuilder stringBuilder = new StringBuilder(); for (final String s : observedEvents) { if (stringBuilder.length() != 0) { stringBuilder.append(","); } stringBuilder.append(s); } return stringBuilder.toString(); } public boolean isRoutable(final LocalContext localContext, final Message message) { return "1".equals(localContext.getAttribute(String.class, CDI_EVENT_CHANNEL_OPEN)) && observedEvents.contains(message.get(String.class, CDIProtocol.BeanType)); } public void sendEventToClients(Object event, EventMetadata emd) { for (final ClientObserverMetadata clientObserver : clientObservers) { if (clientObserver.matches(event, emd)) { sendEventToClient(event, clientObserver.getQualifiers()); } } } private void sendEventToClient(Object event, Set<String> qualifierTypes) { final Class<? extends Object> eventType = event.getClass(); final String sessionId = getConversationalSessionId(eventType); final Map<String, Object> messageParts = new HashMap<String, Object>(10); messageParts.put(MessageParts.ToSubject.name(), getSubjectNameByType(eventType.getName())); messageParts.put(MessageParts.CommandType.name(), CDICommands.CDIEvent.name()); messageParts.put(CDIProtocol.BeanType.name(), eventType.getName()); messageParts.put(CDIProtocol.BeanReference.name(), event); if (!qualifierTypes.isEmpty()) { messageParts.put(CDIProtocol.Qualifiers.name(), qualifierTypes); } if (sessionId != null) { messageParts.put(MessageParts.SessionID.name(), sessionId); messagebus.send(CommandMessage.createWithParts(messageParts)); } else { for (final String id : eventRoutingTable.getQueueIdsForRoute(eventType.getName(), qualifierTypes)) { messagebus.send(CommandMessage.createWithParts(new RoutingMap(messageParts, id))); } } } static String getConversationalSessionId(Class<? extends Object> eventType) { final QueueSession queueSession = RpcContext.getQueueSession(); if (eventType.isAnnotationPresent(Conversational.class) && queueSession != null && queueSession.getSessionId() != null) { return queueSession.getSessionId(); } return null; } }