/*
* Copyright 2007 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.springmodules.xt.model.event.edp;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import org.apache.log4j.Logger;
import org.springframework.context.ApplicationEvent;
/**
* Default {@link org.springmodules.xt.model.event.EDPInvoker} implementation.
* <br><br>
* The DefaultEDPInvoker will invoke the configured Event Driven Pojo (EDP) bean (see {@link #setInvokedBean(Object )}) if the bean has a method
* with name equal to the configured one (see {@link #setInvokedMethodName(String )}), and that accepts a single parameter whose type
* is the same as, or is a super class/interface of, the published event.
* <br><br>
* I.e., given an event of type <i>FooEvent</i>, and a method name set as <i>onEvent</i>, the corresponding EDP bean will provide a method with the
* following signature:
* <br>
* <i>public void onEvent(FooEvent );</i>
* <br><br>
* Please note that if the EDP bean provides several overloaded implementations of the invokable method, the DefaultEDPInvoker will invoke
* <b>all</b> methods supporting a given published event, that is, all methods accepting a parameter whose type is the same as, or is
* a super class/interface of the published event type.
* <br><br>
* <b>Note</b>: This class is <b>not</b> thread-safe.
*
* @author Sergio Bossa
*/
public class DefaultEDPInvoker implements EDPInvoker {
/**
* The default name of the methods that must be invoked upon published events: "handleEvent".
*/
public static final String DEFAULT_METHOD_NAME = "handleEvent";
private static final Logger logger = Logger.getLogger(DefaultEDPInvoker.class);
private Object invokedBean;
private String invokedMethodName = DEFAULT_METHOD_NAME;
private boolean continueOnFailingInvocation = true;
private Map<Class, List<Method>> methodsCache = new HashMap<Class, List<Method>>();
public Object getInvokedBean() {
return this.invokedBean;
}
/**
* Set the bean to invoke upon published events.
* @param invokedBean The invoked bean.
*/
public void setInvokedBean(Object invokedBean) {
this.invokedBean = invokedBean;
}
public String getInvokedMethodName() {
return this.invokedMethodName;
}
/**
* Set the name of the bean method to invoke upon published events. If not set, it defaults to
* {@link #DEFAULT_METHOD_NAME}.
* @param invokedMethodName The invoked bean method name.
*/
public void setInvokedMethodName(String invokedMethodName) {
this.invokedMethodName = invokedMethodName;
}
/**
* Set to true if the invoker must continue invoking methods after an invocation failure.
*/
public void setContinueOnFailingInvocation(boolean continueOnFailingInvocation) {
this.continueOnFailingInvocation = continueOnFailingInvocation;
}
/**
* Return true if the invoker must continue invoking methods after an invocation failure.
*/
public boolean isContinueOnFailingInvocation() {
return this.continueOnFailingInvocation;
}
/**
* Invoke, if any, the bean method with name defined by {@link #setInvokedMethodName(String )}, and with a single argument whose
* type is the same as, or is a super class/interface of, the given {@link org.springframework.context.ApplicationEvent}.
* @param applicationEvent The published event.
*/
public void onApplicationEvent(ApplicationEvent applicationEvent) {
if (logger.isInfoEnabled()) {
logger.info(new StringBuilder("On application event: ").append(applicationEvent).toString());
}
if (this.invokedBean != null) {
if (logger.isInfoEnabled()) {
logger.info(new StringBuilder("Accessing Event Driven POJO: ").append(this.invokedBean).toString());
}
List<Method> invokableMethods = this.methodsCache.get(applicationEvent.getClass());
if (invokableMethods == null) {
invokableMethods = new LinkedList<Method>();
Method[] methods = this.invokedBean.getClass().getMethods();
for (Method m : methods) {
if (m.getName().equals(this.invokedMethodName)
&& m.getParameterTypes().length == 1
&& m.getParameterTypes()[0].isAssignableFrom(applicationEvent.getClass())) {
invokableMethods.add(m);
}
}
this.methodsCache.put(applicationEvent.getClass(), new LinkedList<Method>(invokableMethods));
} else {
if (logger.isDebugEnabled()) {
logger.debug(new StringBuilder("Found cached methods for event type ")
.append(applicationEvent.getClass())
.append(" : ").append(invokableMethods)
.toString());
}
}
for (Method m : invokableMethods) {
try {
if (logger.isInfoEnabled()) {
logger.info(new StringBuilder("Invoking: ").append(m).toString());
}
m.invoke(this.invokedBean, applicationEvent);
} catch (IllegalArgumentException ex) {
logger.error("The method " + this.invokedMethodName + " cannot be invoked.", ex);
if (!this.continueOnFailingInvocation) {
throw new IllegalStateException("The method " + this.invokedMethodName + " cannot be invoked.", ex);
}
} catch (IllegalAccessException ex) {
logger.error("The method " + this.invokedMethodName + " cannot be invoked.", ex);
if (!this.continueOnFailingInvocation) {
throw new IllegalStateException("The method " + this.invokedMethodName + " cannot be invoked.", ex);
}
} catch (InvocationTargetException ex) {
logger.error("The method " + this.invokedMethodName + " cannot be invoked.", ex);
if (!this.continueOnFailingInvocation) {
throw new IllegalStateException("The method " + this.invokedMethodName + " cannot be invoked.", ex);
}
}
}
} else {
logger.warn("No bean configured!");
}
}
}