Please wait. This can take some minutes ...
Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance.
Project price only 1 $
You can buy this project and download/modify it how often you want.
org.aktivecortex.core.eventbus.DistributedClusterSelector Maven / Gradle / Ivy
/*
* Copyright (C) 2012-2013. Aktive Cortex
*
* 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.aktivecortex.core.eventbus;
import com.google.common.base.Predicate;
import com.google.common.collect.HashMultimap;
import com.google.common.collect.Iterables;
import com.google.common.collect.SetMultimap;
import org.aktivecortex.core.axon2backport.saga.annotation.AbstractAnnotatedSaga;
import org.aktivecortex.core.axon2backport.saga.annotation.SagaEventHandler;
import org.aktivecortex.core.utils.reflection.TargetLengthBasedClassNameAbbreviator;
import org.axonframework.domain.Event;
import org.axonframework.eventhandling.*;
import org.axonframework.eventhandling.annotation.EventHandler;
import org.axonframework.util.Assert;
import org.axonframework.util.ReflectionUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.InitializingBean;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import static com.google.common.collect.Maps.newHashMap;
import static com.google.common.collect.Sets.newHashSet;
public class DistributedClusterSelector implements ClusterSelector, InitializingBean {
private static final Logger logger = LoggerFactory.getLogger(DistributedClusterSelector.class);
private final TargetLengthBasedClassNameAbbreviator abbreviator = new TargetLengthBasedClassNameAbbreviator(36);
private Set competingListeners;
private Cluster competingCluster;
private Cluster nonCompetingCluster;
public void setCompetingCluster(Cluster competingCluster) {
this.competingCluster = competingCluster;
}
public void setNonCompetingCluster(Cluster nonCompetingCluster) {
this.nonCompetingCluster = nonCompetingCluster;
}
@Override
public Cluster selectCluster(EventListener eventListener) {
final String targetName = getTargetName(eventListener);
logger.info("------------ ------------ EventListener Inspection START ------------ ------------ ");
logger.info("------------ [{}] ", targetName);
Cluster candidate = findCandidate(getTarget(eventListener)) ? competingCluster : nonCompetingCluster;
Set> handledEvents = getHandledEvents(eventListener);
attachHandledEvents(candidate, handledEvents);
partitionEvents(candidate, handledEvents, targetName, false);
logger.info("Returning candidate cluster {} for eventlistener {}", candidate.getClass().getSimpleName(), targetName);
logger.info("------------ ------------ EventListener Inspection END ------------ ------------ ");
return candidate;
}
private boolean findCandidate(final Object eventListener) {
String matches = Iterables.find(competingListeners, new Predicate(){
@Override
public boolean apply(String arg0) {
try {
Class clazz = Class.forName(arg0);
return clazz.isAssignableFrom(eventListener.getClass());
} catch (Exception e) {
return false;
}
}
}, "KO");
return !matches.equals("KO");
}
@SuppressWarnings("unchecked")
private void attachHandledEvents(Cluster candidate, Set> handledEvents) {
Set> clusterEvents = newHashSet();
if (candidate.getMetaData().isPropertySet(DistributedCluster.CLUSTER_EVENTS)
&& candidate.getMetaData().getProperty(DistributedCluster.CLUSTER_EVENTS)!= null) {
clusterEvents = (Set>) candidate.getMetaData().getProperty(DistributedCluster.CLUSTER_EVENTS);
} else {
candidate.getMetaData().setProperty(DistributedCluster.CLUSTER_EVENTS, clusterEvents);
}
clusterEvents.addAll(handledEvents);
logger.debug("Events {} will be forwarded through cluster {}", handledEvents, candidate.getClass().getSimpleName());
}
@SuppressWarnings("unchecked")
private void attachSagaEvents(Cluster candidate, Set> handledEvents) {
Set> sagaEvents = newHashSet();
if (candidate.getMetaData().isPropertySet(DistributedCluster.CLUSTER_SAGA_EVENTS)
&& candidate.getMetaData().getProperty(DistributedCluster.CLUSTER_SAGA_EVENTS)!= null) {
sagaEvents = (Set>) candidate.getMetaData().getProperty(DistributedCluster.CLUSTER_SAGA_EVENTS);
} else {
candidate.getMetaData().setProperty(DistributedCluster.CLUSTER_SAGA_EVENTS, sagaEvents);
}
sagaEvents.addAll(handledEvents);
}
@SuppressWarnings("unchecked")
private void partitionEvents(final Cluster candidate, final Set> handledEvents, String listener, boolean stateful) {
SetMultimap> partitions = HashMultimap.create();
String key = stateful ? abbreviator.abbreviate(listener) : null;
if (candidate.getMetaData().isPropertySet(DistributedCluster.CLUSTER_PARTITIONS)
&& candidate.getMetaData().getProperty(DistributedCluster.CLUSTER_PARTITIONS)!= null) {
partitions = (SetMultimap>) candidate.getMetaData().getProperty(DistributedCluster.CLUSTER_PARTITIONS);
String matchingKey = filter(partitions, new Predicate>(){
@Override
public boolean apply(Class event) {
return handledEvents.contains(event);
}});
if (null!= matchingKey && !matchingKey.equals(key)) {
logger.warn("Overlapping partition found!\n" +
"An existing set of events \n[{}]\n identified by key [{}] " +
"intersect with set: \n[{}]\n that is trying to be associated with key [{}]." +
"\nProceeding to the union of the two sets to which is assigned the previous key [{}].",
new Object[]{partitions.get(matchingKey), matchingKey, handledEvents, key, matchingKey});
key = matchingKey;
}
} else {
candidate.getMetaData().setProperty(DistributedCluster.CLUSTER_PARTITIONS, partitions);
}
if (null != key){
partitions.putAll(key, handledEvents);
buildKeyMap(candidate, key, handledEvents);
logger.debug("Events {} will be partitioned by key {}", handledEvents, key);
}
}
public String abbreviate(String fqClassName) {
int lastIndex = fqClassName.lastIndexOf('.');
if (lastIndex != -1) {
return fqClassName.substring(lastIndex + 1, fqClassName.length());
}
return fqClassName;
}
@SuppressWarnings("unchecked")
private void buildKeyMap(Cluster candidate, String key, Set> handledEvents) {
Map keys = newHashMap();
if (candidate.getMetaData().isPropertySet(DistributedCluster.KEY_MAP)
&& candidate.getMetaData().getProperty(DistributedCluster.KEY_MAP)!= null) {
keys = (Map) candidate.getMetaData().getProperty(DistributedCluster.KEY_MAP);
} else {
candidate.getMetaData().setProperty(DistributedCluster.KEY_MAP, keys);
}
for (Class value : handledEvents) {
final String eventName = value.getName();
if (!keys.containsKey(eventName)){
keys.put(eventName, key);
}
}
}
private K filter(SetMultimap data, Predicate predicate) {
for (K key : data.keys()) {
for (V value : data.get(key)) {
if (predicate.apply(value)) {
return key;
}
}
}
return null;
}
private Set> getHandledEvents(EventListener eventListener) {
Object target = getTarget(eventListener);
return extractEvents(target);
}
private Set> extractEvents(Object target) {
Map, Method> handledEvents = new HashMap, Method>();
for (Method m : ReflectionUtils.methodsOf(target.getClass())) {
validate(m, handledEvents);
}
return handledEvents.keySet();
}
private void validate(Method method, Map, Method> handledEvents) {
if (method.isAnnotationPresent(EventHandler.class)) {
if (method.getParameterTypes().length > 2 || method.getParameterTypes().length < 1) {
throw new UnsupportedHandlerMethodException(String.format(
"Event Handling class %s contains method %s that has no or more than two parameters. "
+ "Either remove @EventHandler annotation or provide to one or two parameters.", method.getDeclaringClass().getSimpleName(),
method.getName()), method);
}
if (!Event.class.isAssignableFrom(method.getParameterTypes()[0])) {
throw new UnsupportedHandlerMethodException(String.format("Event Handling class %s contains method %s that has an invalid parameter. "
+ "Parameter must extend from Event", method.getDeclaringClass().getSimpleName(), method.getName()), method);
}
if (method.getParameterTypes().length == 2 && !TransactionStatus.class.equals(method.getParameterTypes()[1])) {
throw new UnsupportedHandlerMethodException(String.format("Event Handling class %s contains method %s that has an invalid parameter. "
+ "The (optional) second parameter must be of type: %s", method.getDeclaringClass().getSimpleName(), method.getName(),
TransactionStatus.class.getName()), method);
}
Method[] forbiddenMethods = EventListener.class.getDeclaredMethods();
for (Method forbiddenMethod : forbiddenMethods) {
if (method.getName().equals(forbiddenMethod.getName()) && Arrays.equals(method.getParameterTypes(), forbiddenMethod.getParameterTypes())) {
throw new UnsupportedHandlerMethodException(String.format("Event Handling class %s contains method %s that has a naming conflict with a "
+ "method on the EventHandler interface. Please rename the method.", method.getDeclaringClass().getSimpleName(), method.getName()),
method);
}
}
Method previous = handledEvents.put(method.getParameterTypes()[0], method);
if (previous != null && previous.getDeclaringClass().equals(method.getDeclaringClass())) {
throw new UnsupportedHandlerMethodException(String.format(
"Event Handling class %s contains two methods that handle the same event type: [%s] and [%s]", method.getDeclaringClass()
.getSimpleName(), method.getName(), previous.getName()), method);
}
}
}
private String getTargetName(EventListener eventListener) {
return getTarget(eventListener).getClass().getName();
}
private Object getTarget(EventListener eventListener) {
if (eventListener instanceof EventListenerProxy) {
EventListenerProxy proxy = (EventListenerProxy) eventListener;
return proxy.getTarget();
}
return eventListener;
}
@SuppressWarnings("unchecked")
@Override
public void afterPropertiesSet() throws Exception {
Assert.notNull(competingCluster, "competingCluster property not set");
Assert.notNull(nonCompetingCluster, "nonCompetingCluster property not set");
competingListeners = (Set) competingCluster.getMetaData().getProperty(DistributedCluster.COMPETING_EVENT_LISTENERS);
Assert.notNull(competingListeners, "competingListeners property not available");
Assert.isFalse(competingListeners.isEmpty(), "competingListeners property can't be empty");
inspectSagas();
logger.info("The following listeners will be treated as competing ones {}", competingListeners);
logger.info("Each event handled by of these listeners will be forwarded through the cluster (that must adhere to producer-consumer semantics, e.g. a JMS Queue) with name: {}", competingCluster.getClass()
.getSimpleName());
logger.info("All other listeners wil be treated as non competing and related events will be forwarded through the cluster (that must adhere to publisher-subscriber semantics, e.g. a JMS Topic) with name: {}",
nonCompetingCluster.getClass().getSimpleName());
}
@SuppressWarnings("unchecked")
private void inspectSagas() throws Exception {
logger.info("------------ ------------ SAGA Inspection START ------------ ------------ ");
Set competingSagas = (Set) competingCluster.getMetaData().getProperty(DistributedCluster.COMPETING_SAGAS);
Map, Method> handledEvents = new HashMap, Method>();
if(null==competingSagas) {
logger.warn("No Saga configured. Are you sure?");
return;
}
for (String sagaClassName : competingSagas) {
logger.info("Inspecting saga [{}]", sagaClassName);
Class sagaClass = (Class) Class.forName(sagaClassName);
final Map, Method> sagaEvents = new HashMap, Method>();
for (Method method : ReflectionUtils.methodsOf(sagaClass)) {
getSagaEvent(method, sagaEvents);
}
partitionEvents(competingCluster, sagaEvents.keySet(), sagaClassName, true);
handledEvents.putAll(sagaEvents);
}
attachHandledEvents(competingCluster, handledEvents.keySet());
attachSagaEvents(competingCluster, handledEvents.keySet());
logger.info("------------ ------------ SAGA Inspection END ------------ ------------ ");
}
private void getSagaEvent(Method method, Map, Method> handledEvents) {
if (method.isAnnotationPresent(SagaEventHandler.class)) {
if (!Event.class.isAssignableFrom(method.getParameterTypes()[0])) {
throw new UnsupportedHandlerMethodException(String.format("Event Handling class %s contains method %s that has an invalid parameter. "
+ "Parameter must extend from Event", method.getDeclaringClass().getSimpleName(), method.getName()), method);
}
Method previous = handledEvents.put(method.getParameterTypes()[0], method);
if (previous != null && previous.getDeclaringClass().equals(method.getDeclaringClass())) {
throw new UnsupportedHandlerMethodException(String.format(
"Saga class %s contains two methods that handle the same event type: [%s] and [%s]", method.getDeclaringClass()
.getSimpleName(), method.getName(), previous.getName()), method);
}
}
}
}