org.focusns.common.event.support.EventInterceptor.java Source code

Java tutorial

Introduction

Here is the source code for org.focusns.common.event.support.EventInterceptor.java

Source

package org.focusns.common.event.support;

/*
 * #%L
 * FocusSNS Runtime
 * %%
 * Copyright (C) 2011 - 2013 FocusSNS
 * %%
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as 
 * published by the Free Software Foundation, either version 2.1 of the 
 * License, or (at your option) any later version.
 * 
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Lesser Public License for more details.
 * 
 * You should have received a copy of the GNU General Lesser Public 
 * License along with this program.  If not, see
 * <http://www.gnu.org/licenses/lgpl-2.1.html>.
 * #L%
 */

import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.focusns.common.event.annotation.Event;
import org.focusns.common.event.annotation.EventSubscriber;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.core.LocalVariableTableParameterNameDiscoverer;
import org.springframework.core.ParameterNameDiscoverer;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.util.ClassUtils;

@Aspect
public class EventInterceptor implements BeanFactoryPostProcessor, ApplicationContextAware {

    private static final Log log = LogFactory.getLog(EventInterceptor.class);

    private ApplicationContext appContext;

    private Map<String, Method> methodCache = new HashMap<String, Method>();

    private Map<Method, String> eventKeyCache = new HashMap<Method, String>();

    private Map<String, Event> eventMapping = new HashMap<String, Event>();

    private Map<String, Method> eventMethodMapping = new HashMap<String, Method>();

    private Map<String, String> eventSubscriberMapping = new HashMap<String, String>();

    private ParameterNameDiscoverer paramNameDiscoverer = new LocalVariableTableParameterNameDiscoverer();

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.appContext = applicationContext;
    }

    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
        //
        Map<String, Object> beansMap = beanFactory.getBeansWithAnnotation(EventSubscriber.class);
        for (Map.Entry<String, Object> entry : beansMap.entrySet()) {
            String beanName = entry.getKey();
            Object beanObject = entry.getValue();
            Class<?> beanClass = beanObject.getClass();
            //
            Method[] declearedMethods = beanClass.getDeclaredMethods();
            for (Method declearedMethod : declearedMethods) {
                //
                Event event = AnnotationUtils.getAnnotation(declearedMethod, Event.class);
                if (event == null) {
                    log.warn(String.format("Event Subscribe method %s must be annotated with @Event(\"xxx\")",
                            declearedMethod));
                } else {
                    String eventKey = generateEventKey(event);
                    eventMapping.put(eventKey, event);
                    eventMethodMapping.put(eventKey, declearedMethod);
                    eventSubscriberMapping.put(eventKey, beanName);
                }
            }
        }
    }

    @Around("@within(org.springframework.stereotype.Service)")
    public Object weave(ProceedingJoinPoint pjp) throws Throwable {
        // MethodInvocation invocation = (MethodInvocation) pjp;
        Method method = getMethod(pjp);
        Map<String, Object> args = getArguments(method, pjp.getArgs());
        //
        Object result = null;
        try {
            triggerEvent(Event.Point.BEFORE, method, args, null, null);
            //
            result = pjp.proceed();
            //
            triggerEvent(Event.Point.AFTER, method, args, result, null);
            //
        } catch (Throwable throwable) {
            //
            triggerEvent(Event.Point.AFTER_THROWING, method, args, result, throwable);
        }
        //
        return result;
    }

    protected void triggerEvent(Event.Point point, Method method, Map<String, Object> arguments, Object returnValue,
            Throwable throwable) throws Exception {
        //
        Event event = getEvent(method, point);
        //
        if (event != null) {
            //
            EventContext eventContext = null;
            if (point == Event.Point.BEFORE) {
                eventContext = new EventContext(appContext, method, arguments);
            } else if (point == Event.Point.AFTER) {
                eventContext = new EventContext(appContext, method, arguments, returnValue);
            } else if (point == Event.Point.AFTER_THROWING) {
                eventContext = new EventContext(appContext, method, arguments, returnValue, throwable);
            }
            //
            String eventKey = generateEventKey(event);
            String subscriberName = eventSubscriberMapping.get(eventKey);
            Object eventSubscriber = appContext.getBean(subscriberName);
            Method eventHandler = eventMethodMapping.get(eventKey);
            eventContext.setEventHandler(eventHandler);
            eventContext.setEventSubscriber(eventSubscriber);
            //
            appContext.publishEvent(eventContext);
        }
    }

    private Method getMethod(JoinPoint joinPoint) {
        Signature signature = joinPoint.getSignature();
        String key = signature.getDeclaringTypeName() + "." + signature.getName();
        //
        Method target = methodCache.get(key);
        if (target == null) {
            Object[] args = joinPoint.getArgs();
            String methodName = signature.getName();
            Class<?> classType = joinPoint.getTarget().getClass();
            for (Method method : classType.getDeclaredMethods()) {
                if (isSameMethod(method, methodName, args)) {
                    // cache and return
                    methodCache.put(key, method);
                    return method;
                }
            }
        }
        //
        return target;
    }

    private Event getEvent(Method method, Event.Point point) {
        //
        String eventKey = eventKeyCache.get(method);
        if (eventKey == null) {
            StringBuilder eventKeyBuilder = new StringBuilder();
            String methodName = method.getName();
            for (int i = 0; i < methodName.length(); i++) {
                char c = methodName.charAt(i);
                if (Character.isUpperCase(c)) {
                    eventKeyBuilder.append("_");
                }
                eventKeyBuilder.append(Character.toUpperCase(c));
            }
            //
            eventKey = eventKeyBuilder.append("|").append(point.name()).toString();
        }
        //
        return eventMapping.get(eventKey);
    }

    private boolean isSameMethod(Method target, String methodName, Object[] paramValues) {
        Class<?>[] paramTypes = target.getParameterTypes();
        for (int i = 0; i < paramTypes.length; i++) {
            if (methodName.equals(target.getName())
                    && ClassUtils.isAssignableValue(paramTypes[i], paramValues[i])) {
                return true;
            }
        }
        //
        return false;
    }

    private Map<String, Object> getArguments(Method method, Object[] args) {
        LinkedHashMap<String, Object> argumentMap = new LinkedHashMap<String, Object>();
        String[] paramNames = paramNameDiscoverer.getParameterNames(method);
        if (paramNames != null) {
            for (int i = 0; i < paramNames.length; i++) {
                argumentMap.put(paramNames[i], args[i]);
            }
        }
        return argumentMap;
    }

    private String generateEventKey(Event event) {
        return event.on() + "|" + event.point().name();
    }
}