All Downloads are FREE. Search and download functionalities are using the official Maven repository.

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);
			}
		}
	}

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy