/******************************************************************************
* Copyright (c) 2006, 2010 VMware Inc.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* and Apache License v2.0 which accompanies this distribution.
* The Eclipse Public License is available at
* http://www.eclipse.org/legal/epl-v10.html and the Apache License v2.0
* is available at http://www.opensource.org/licenses/apache2.0.php.
* You may elect to redistribute this code under either of these licenses.
*
* Contributors:
* VMware Inc.
*****************************************************************************/
package org.eclipse.gemini.blueprint.config.internal.adapter;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.security.AccessController;
import java.security.PrivilegedAction;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Dictionary;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.eclipse.gemini.blueprint.util.internal.ReflectionUtils;
import org.springframework.util.Assert;
import org.springframework.util.ObjectUtils;
import org.springframework.util.StringUtils;
/**
* Local utility class used by adapters. Handles things such as method discovery.
*
*
* @author Costin Leau
*
*/
public abstract class CustomListenerAdapterUtils {
private static final Log log = LogFactory.getLog(CustomListenerAdapterUtils.class);
/**
* Specialised reflection utility that determines all methods that accept two parameters such:
*
* <pre> methodName(Type serviceType, Type1 arg)
*
* methodName(Type serviceType, Type2 arg)
*
* methodName(AnotherType serviceType, Type1 arg)
*
* methodName(Type serviceType) </pre>
*
* It will return a map which has the serviceType (first argument) as type and contains as list the variants of
* methods using the second argument. This method is normally used by listeners when determining custom methods.
*
* @param target
* @param methodName
* @param possibleArgumentTypes
* @param modifier
* @return
*/
static Map<Class<?>, List<Method>> determineCustomMethods(final Class<?> target, final String methodName,
final Class<?>[] possibleArgumentTypes, final boolean onlyPublic) {
if (!StringUtils.hasText(methodName)) {
return Collections.emptyMap();
}
Assert.notEmpty(possibleArgumentTypes);
if (System.getSecurityManager() != null) {
return AccessController.doPrivileged(new PrivilegedAction<Map<Class<?>, List<Method>>>() {
public Map<Class<?>, List<Method>> run() {
return doDetermineCustomMethods(target, methodName, possibleArgumentTypes, onlyPublic);
}
});
} else {
return doDetermineCustomMethods(target, methodName, possibleArgumentTypes, onlyPublic);
}
}
private static Map<Class<?>, List<Method>> doDetermineCustomMethods(final Class<?> target, final String methodName,
final Class<?>[] possibleArgumentTypes, final boolean onlyPublic) {
final Map<Class<?>, List<Method>> methods = new LinkedHashMap<Class<?>, List<Method>>(3);
final boolean trace = log.isTraceEnabled();
org.springframework.util.ReflectionUtils.doWithMethods(target,
new org.springframework.util.ReflectionUtils.MethodCallback() {
public void doWith(Method method) throws IllegalArgumentException, IllegalAccessException {
if (!method.isBridge() && methodName.equals(method.getName())) {
if (onlyPublic && !Modifier.isPublic(method.getModifiers())) {
if (trace)
log.trace("Only public methods are considered; ignoring " + method);
return;
}
// take a look at the variables
Class<?>[] args = method.getParameterTypes();
if (args != null) {
// Properties can be ignored
if (args.length == 1) {
addMethod(args[0], method, methods);
}
// or passed as Map, Dictionary
else if (args.length == 2) {
Class<?> propType = args[1];
for (int i = 0; i < possibleArgumentTypes.length; i++) {
Class<?> clazz = possibleArgumentTypes[i];
if (clazz.isAssignableFrom(propType)) {
addMethod(args[0], method, methods);
}
}
}
}
}
}
private void addMethod(Class<?> key, Method mt, Map<Class<?>, List<Method>> methods) {
if (trace)
log.trace("discovered custom method [" + mt.toString() + "] on " + target);
List<Method> mts = methods.get(key);
if (mts == null) {
mts = new ArrayList<Method>(2);
methods.put(key, mts);
org.springframework.util.ReflectionUtils.makeAccessible(mt);
mts.add(mt);
return;
}
// add a method only if there is still space
if (mts.size() == 1) {
Method m = mts.get(0);
if (m.getParameterTypes().length == mt.getParameterTypes().length) {
if (trace)
log.trace("Method w/ signature " + methodSignature(m)
+ " has been already discovered; ignoring it");
} else {
org.springframework.util.ReflectionUtils.makeAccessible(mt);
mts.add(mt);
}
}
}
private String methodSignature(Method m) {
StringBuilder sb = new StringBuilder();
int mod = m.getModifiers();
if (mod != 0) {
sb.append(Modifier.toString(mod) + " ");
}
sb.append(m.getReturnType() + " ");
sb.append(m.getName() + "(");
Class<?>[] params = m.getParameterTypes();
for (int j = 0; j < params.length; j++) {
sb.append(params[j]);
if (j < (params.length - 1))
sb.append(",");
}
sb.append(")");
return sb.toString();
}
});
return methods;
}
/**
* Shortcut method that uses as possible argument types, Dictionary.class, Map.class or even nothing.
*
* @param target
* @param methodName
* @return
*/
static Map<Class<?>, List<Method>> determineCustomMethods(Class<?> target, final String methodName,
boolean onlyPublic) {
return determineCustomMethods(target, methodName, new Class[] { Dictionary.class, Map.class }, onlyPublic);
}
/**
* Invoke the custom listener method. Takes care of iterating through the method map (normally acquired through
* {@link #determineCustomMethods(Class, String, Class[])} and invoking the method using the arguments.
*
* @param target
* @param methods
* @param service
* @param properties
*/
// the properties field is Dictionary implementing a Map interface
static void invokeCustomMethods(Object target, Map<Class<?>, List<Method>> methods, Object service, Map properties) {
if (methods != null && !methods.isEmpty()) {
boolean trace = log.isTraceEnabled();
Object[] argsWMap = new Object[] { service, properties };
Object[] argsWOMap = new Object[] { service };
for (Iterator<Map.Entry<Class<?>, List<Method>>> iter = methods.entrySet().iterator(); iter.hasNext();) {
Map.Entry<Class<?>, List<Method>> entry = iter.next();
Class<?> key = entry.getKey();
// find the compatible types (accept null service)
if (service == null || key.isInstance(service)) {
List<Method> mts = entry.getValue();
for (Method method : mts) {
if (trace)
log.trace("Invoking listener custom method " + method);
Class<?>[] argTypes = method.getParameterTypes();
Object[] arguments = (argTypes.length > 1 ? argsWMap : argsWOMap);
try {
ReflectionUtils.invokeMethod(method, target, arguments);
}
// make sure to log exceptions and continue with the
// rest of the methods
catch (Exception ex) {
Exception cause = ReflectionUtils.getInvocationException(ex);
log.warn("Custom method [" + method + "] threw exception when passing service ["
+ ObjectUtils.identityToString(service) + "]", cause);
}
}
}
}
}
}
}