/* * Copyright 2013 The Sculptor Project Team, including the original * author or authors. * * 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.sculptor.framework.event.annotation; import java.lang.reflect.Constructor; import java.util.Date; import org.apache.commons.beanutils.ConstructorUtils; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.Around; import org.aspectj.lang.annotation.Aspect; import org.sculptor.framework.context.ServiceContext; import org.sculptor.framework.event.Event; import org.sculptor.framework.event.EventBus; import org.sculptor.framework.util.FactoryHelper; import org.springframework.beans.BeansException; import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContextAware; /** * Advice that publish event to the topic and eventBus specified by the * {@link Publish} annotation. If eventType is not specified by the annotation * one of the return value or method parameters must be an {@link Event}, which * is published. When eventType is specified in the annotation then a new event * of this type is created using a constructor matching the method return value * or method parameters. */ @Aspect public class PublishAdvice implements ApplicationContextAware { private ApplicationContext applicationContext; @Around("@annotation(publish)") public Object publish(ProceedingJoinPoint joinPoint, Publish publish) throws Throwable { if (applicationContext == null) { throw new IllegalArgumentException("No ApplicationContext autowired - advice must be configured by Spring"); } Object retVal = joinPoint.proceed(); String topic = publish.topic(); Event event = null; if (publish.eventType() == NullEventType.class) { if (retVal instanceof Event) { event = (Event) retVal; } else { for (Object each : joinPoint.getArgs()) { if (each instanceof Event) { event = (Event) each; break; } } } } else { event = createEvent(publish.eventType(), retVal, joinPoint.getArgs()); } if (event == null) { throw new IllegalArgumentException( "Return value or some argument need to be of event type, or match constructor of specified eventType"); } EventBus eventBus = getEventBus(publish.eventBus()); eventBus.publish(topic, event); return retVal; } protected EventBus getEventBus(String name) { Object bean = getApplicationContext().getBean(name); if (!(bean instanceof EventBus)) { throw new IllegalStateException("Wrong EventBus type, got: " + bean.getClass().getName()); } return (EventBus) bean; } private Event createEvent(Class<?> clazz, Object retVal, Object[] args) { Object occured; if (isJoda(clazz)) { occured = createJodaDateTime(); } else { occured = new Date(); } Event event = null; if (retVal != null) { try { Object[] constructorParams = { occured, retVal }; Object raw = ConstructorUtils.invokeConstructor(clazz, constructorParams); event = (Event) raw; } catch (Exception e) { } } try { Object[] filteredArgs = removeServiceContext(args); Object[] constructorParams; if (filteredArgs.length > 0 && filteredArgs[0] != null && filteredArgs[0].getClass() == occured.getClass()) { // already got a timestamp as first arg constructorParams = filteredArgs; } else if (filteredArgs.length == 0) { constructorParams = new Object[] { occured }; } else { constructorParams = new Object[filteredArgs.length + 1]; constructorParams[0] = occured; System.arraycopy(filteredArgs, 0, constructorParams, 1, filteredArgs.length); } Object raw = ConstructorUtils.invokeConstructor(clazz, constructorParams); event = (Event) raw; } catch (Exception e) { } return event; } private Object[] removeServiceContext(Object[] args) { if (args.length > 1 && args[0] instanceof ServiceContext) { Object[] result = new Object[args.length - 1]; System.arraycopy(args, 1, result, 0, result.length); return result; } return args; } /** * Check if joda date time library is used, without introducing runtime * dependency. */ private boolean isJoda(Class<?> clazz) { for (Constructor<?> each : clazz.getConstructors()) { Class<?>[] parameterTypes = each.getParameterTypes(); if (parameterTypes.length > 0) { if (parameterTypes[0].getName().startsWith("org.joda.time.")) { return true; } } } return false; } private Object createJodaDateTime() { return FactoryHelper.newInstanceFromName("org.joda.time.DateTime"); } protected ApplicationContext getApplicationContext() { return applicationContext; } @Override public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { this.applicationContext = applicationContext; } }