/*
* Copyright 2011 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.enterprise.client.cdi.api;
import org.jboss.errai.bus.client.ErraiBus;
import org.jboss.errai.bus.client.api.Message;
import org.jboss.errai.bus.client.api.MessageCallback;
import org.jboss.errai.bus.client.api.base.CommandMessage;
import org.jboss.errai.bus.client.api.base.MessageBuilder;
import org.jboss.errai.bus.client.framework.ClientMessageBus;
import org.jboss.errai.bus.client.framework.ClientMessageBusImpl;
import org.jboss.errai.bus.client.framework.Subscription;
import org.jboss.errai.common.client.api.extension.InitVotes;
import org.jboss.errai.common.client.protocols.MessageParts;
import org.jboss.errai.common.client.util.LogUtil;
import org.jboss.errai.enterprise.client.cdi.AbstractCDIEventCallback;
import org.jboss.errai.enterprise.client.cdi.CDICommands;
import org.jboss.errai.enterprise.client.cdi.CDIEventTypeLookup;
import org.jboss.errai.enterprise.client.cdi.CDIProtocol;
import java.lang.annotation.Annotation;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.IdentityHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
/**
* CDI client interface.
*
* @author Heiko Braun <hbraun@redhat.com>
* @author Christian Sadilek <csadilek@redhat.com>
* @author Mike Brock <cbrock@redhat.com>
*/
public class CDI {
public static final String CDI_SUBJECT_PREFIX = "cdi.event:";
public static final String SERVER_DISPATCHER_SUBJECT = CDI_SUBJECT_PREFIX + "Dispatcher";
public static final String CLIENT_DISPATCHER_SUBJECT = CDI_SUBJECT_PREFIX + "ClientDispatcher";
private static final String CLIENT_ALREADY_FIRED_RESOURCE = CDI_SUBJECT_PREFIX + "AlreadyFired";
private static final Set<String> remoteEvents = new HashSet<String>();
private static boolean active = false;
private static final List<DeferredEvent> deferredEvents = new ArrayList<DeferredEvent>();
private static final List<Runnable> postInitTasks = new ArrayList<Runnable>();
private static Map<String, List<MessageCallback>> eventObservers = new HashMap<String, List<MessageCallback>>();
private static Map<String, Collection<String>> lookupTable = Collections.emptyMap();
public static final MessageCallback ROUTING_CALLBACK = new MessageCallback() {
@Override
public void callback(final Message message) {
consumeEventFromMessage(message);
}
};
public static String getSubjectNameByType(final String typeName) {
return CDI_SUBJECT_PREFIX + typeName;
}
/**
* Should only be called by bootstrapper for testing purposes.
*/
public void __resetSubsystem() {
for (final String eventType : new HashSet<String>(((ClientMessageBus) ErraiBus.get()).getAllRegisteredSubjects())) {
if (eventType.startsWith(CDI_SUBJECT_PREFIX)) {
ErraiBus.get().unsubscribeAll(eventType);
}
}
remoteEvents.clear();
active = false;
deferredEvents.clear();
postInitTasks.clear();
eventObservers.clear();
lookupTable = Collections.emptyMap();
}
public void initLookupTable(final CDIEventTypeLookup lookup) {
lookupTable = lookup.getTypeLookupMap();
}
/**
* Return a list of string representations for the qualifiers.
*
* @param qualifiers
*
* @return
*/
public static Set<String> getQualifiersPart(final Annotation[] qualifiers) {
Set<String> qualifiersPart = null;
if (qualifiers != null) {
for (final Annotation qualifier : qualifiers) {
if (qualifiersPart == null)
qualifiersPart = new HashSet<String>(qualifiers.length);
qualifiersPart.add(qualifier.annotationType().getName());
}
}
return qualifiersPart == null ? Collections.<String>emptySet() : qualifiersPart;
}
public static void fireEvent(final Object payload, final Annotation... qualifiers) {
fireEvent(false, payload, qualifiers);
}
public static void fireEvent(final boolean local,
final Object payload,
final Annotation... qualifiers) {
if (payload == null) return;
if (!local && !active) {
deferredEvents.add(new DeferredEvent(payload, qualifiers));
return;
}
final Map<String, Object> messageMap = new HashMap<String, Object>();
messageMap.put(MessageParts.CommandType.name(), CDICommands.CDIEvent.name());
messageMap.put(CDIProtocol.BeanType.name(), payload.getClass().getName());
messageMap.put(CDIProtocol.BeanReference.name(), payload);
messageMap.put(CDIProtocol.FromClient.name(), "1");
if (qualifiers != null && qualifiers.length > 0) {
messageMap.put(CDIProtocol.Qualifiers.name(), getQualifiersPart(qualifiers));
}
consumeEventFromMessage(CommandMessage.createWithParts(messageMap));
if (isRemoteCommunicationEnabled() && remoteEvents.contains(payload.getClass().getName())) {
messageMap.put(MessageParts.ToSubject.name(), SERVER_DISPATCHER_SUBJECT);
ErraiBus.get().send(CommandMessage.createWithParts(messageMap));
}
}
public static Subscription subscribe(final String eventType, final AbstractCDIEventCallback callback) {
if (!eventObservers.containsKey(eventType)) {
eventObservers.put(eventType, new ArrayList<MessageCallback>());
}
eventObservers.get(eventType).add(callback);
if (isRemoteCommunicationEnabled()) {
MessageBuilder.createMessage()
.toSubject(CDI.SERVER_DISPATCHER_SUBJECT)
.command(CDICommands.RemoteSubscribe)
.with(CDIProtocol.BeanType, eventType)
.with(CDIProtocol.Qualifiers, callback.getQualifiers())
.noErrorHandling().sendNowWith(ErraiBus.get());
}
return new Subscription() {
@Override
public void remove() {
unsubscribe(eventType, callback);
}
};
}
private static void unsubscribe(final String eventType, final AbstractCDIEventCallback callback) {
if (eventObservers.containsKey(eventType)) {
eventObservers.get(eventType).remove(callback);
if (eventObservers.get(eventType).isEmpty()) {
eventObservers.remove(eventType);
}
if (isRemoteCommunicationEnabled()) {
MessageBuilder.createMessage()
.toSubject(CDI.SERVER_DISPATCHER_SUBJECT)
.command(CDICommands.RemoteUnsubscribe)
.with(CDIProtocol.BeanType, eventType)
.with(CDIProtocol.Qualifiers, callback.getQualifiers())
.noErrorHandling().sendNowWith(ErraiBus.get());
}
}
}
public static void consumeEventFromMessage(final Message message) {
final String beanType = message.get(String.class, CDIProtocol.BeanType);
_fireEvent(beanType, message);
if (lookupTable.containsKey(beanType)) {
for (final String superType : lookupTable.get(beanType)) {
_fireEvent(superType, message);
}
}
}
private static void _fireEvent(final String beanType, final Message message) {
if (eventObservers.containsKey(beanType)) {
for (final MessageCallback callback : eventObservers.get(beanType)) {
fireIfNotFired(callback, message);
}
}
}
@SuppressWarnings("unchecked")
private static void fireIfNotFired(final MessageCallback callback, final Message message) {
if (!message.hasResource(CLIENT_ALREADY_FIRED_RESOURCE)) {
message.setResource(CLIENT_ALREADY_FIRED_RESOURCE, new IdentityHashMap<Object, Object>());
}
if (!message.getResource(Map.class, CLIENT_ALREADY_FIRED_RESOURCE).containsKey(callback)) {
callback.callback(message);
message.getResource(Map.class, CLIENT_ALREADY_FIRED_RESOURCE).put(callback, "");
}
}
public static void addRemoteEventType(final String remoteEvent) {
remoteEvents.add(remoteEvent);
}
public static void addRemoteEventTypes(final String[] remoteEvent) {
for (final String s : remoteEvent) {
addRemoteEventType(s);
}
}
public static void addPostInitTask(final Runnable runnable) {
if (active) {
runnable.run();
}
else {
postInitTasks.add(runnable);
}
}
public static void removePostInitTasks() {
postInitTasks.clear();
}
public static void activate() {
if (!active) {
active = true;
for (final DeferredEvent o : deferredEvents) {
fireEvent(o.eventInstance, o.annotations);
}
for (final Runnable r : postInitTasks) {
r.run();
}
deferredEvents.clear();
LogUtil.log("activated CDI eventing subsystem.");
}
InitVotes.voteFor(CDI.class);
}
static class DeferredEvent {
final Object eventInstance;
final Annotation[] annotations;
DeferredEvent(final Object eventInstance, final Annotation[] annotations) {
this.eventInstance = eventInstance;
this.annotations = annotations;
}
}
private static boolean isRemoteCommunicationEnabled() {
return ((ClientMessageBusImpl) ErraiBus.get()).isRemoteCommunicationEnabled();
}
}