/*
* (C) Copyright 2006-2009 Nuxeo SA (http://nuxeo.com/) and contributors.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the GNU Lesser General Public License
* (LGPL) version 2.1 which accompanies this distribution, and is available at
* http://www.gnu.org/licenses/lgpl.html
*
* This library 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
* Lesser General Public License for more details.
*
* Contributors:
* Thierry Delprat
*/
package org.nuxeo.ecm.platform.ui.web.util;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.jboss.seam.Component;
import org.jboss.seam.annotations.Name;
import org.jboss.seam.util.EJB;
/**
*
* This class provides helper methods for accessing a Seam Component
* <p>
* Why this class?
* <p>
* At startup time, Seam generates CGLib Wrappers around each Seam component.
* This wrapper holds all interceptors that are used for bijection.
* Because of that, accessing a Seam component by its reference will lead
* to call a disinjected instance (all @In member variables are null).
* <p>
* Seam components are usually accessed via EL or via injected references,
* in this cases, Seam takes care of everything and you get a functional instance.
* But in some cases, you need to access a Seam component:
* <ul>
* <li>from an object that has no direct access to Seam (ie: can't use injection),
* <li>from an object that stored a call-back reference (storing the 'this' of the Seam component).
* </ul>
* In these cases, this helper class is useful.
*
* This class provides helper functions for :
* <ul>
* <li>getting a Wrapped Seam component via its name or its reference,
* <li>executing a method via a method name on a Seam component.
* </ul>
*
* @author tiry
*/
public final class SeamComponentCallHelper {
// This is an utility class.
private SeamComponentCallHelper() { }
/**
* Gets the CGLib-wrapped Seam component from its name.
*
* @param seamName
* the name of the Seam component
* @return the Wrapped Seam component
*/
public static Object getSeamComponentByName(String seamName) {
// Find the component we're calling
Component component = Component.forName(seamName);
if (component == null) {
throw new RuntimeException("No such component: " + seamName);
}
// Create an instance of the component
Object seamComponent = Component.getInstance(seamName, true);
return seamComponent;
}
/**
* Gets the CGLib-wrapped Seam component from a reference.
*
* @param seamRef
* reference of the object behind the Seam component
* @return the Wrapped Seam component
*/
public static Object getSeamComponentByRef(Object seamRef) {
String seamName = getSeamComponentName(seamRef);
if (seamName == null) {
return null;
}
return getSeamComponentByName(seamName);
}
/**
* Calls a Seam component by name.
*
* @param seamName
* the name of the Seam component
* @param methodName
* the method name (for ejb3 method must be exposed in the local
* interface)
* @param params
* parameters as Object[]
* @return the result of the call
* @throws RuntimeException
*/
public static Object callSeamComponentByName(String seamName,
String methodName, Object[] params) {
Object seamComponent = getSeamComponentByName(seamName);
Component component = Component.forName(seamName);
Class type = null;
if (component.getType().isSessionBean()
&& !component.getBusinessInterfaces().isEmpty()) {
for (Class c : component.getBusinessInterfaces()) {
if (c.isAnnotationPresent(EJB.LOCAL)) {
type = component.getBusinessInterfaces().iterator().next();
break;
}
}
if (type == null) {
throw new RuntimeException(String.format(
"Type cannot be determined for component [%s]. "
+ "Please ensure that it has a "
+ "local interface.", component));
}
}
if (type == null) {
type = component.getBeanClass();
}
Method m = findMethod(methodName, type, params);
if (m == null) {
throw new RuntimeException("No compatible method found.");
}
try {
Object result = m.invoke(seamComponent, params);
return result;
} catch (IllegalArgumentException e) {
throw new RuntimeException("Error calling method " + e.getMessage(), e);
} catch (IllegalAccessException e) {
throw new RuntimeException("Error calling method " + e.getMessage(), e);
} catch (InvocationTargetException e) {
throw new RuntimeException("Error calling method " + e.getMessage(), e);
}
}
/**
* Calls a Seam component by reference.
*
* @param seamRef
* the reference on the object behind the Seam component
* @param methodName
* the method name (for ejb3 method must be exposed in the local
* interface)
* @param params
* parameters as Object[]
* @return the result of the call
* @throws RuntimeException
*/
public static Object callSeamComponentByRef(Object seamRef,
String methodName, Object[] params) {
String seamName = getSeamComponentName(seamRef);
return callSeamComponentByName(seamName, methodName, params);
}
/**
* Calls a Seam component by reference.
*
* @param seamRef
* the reference on the object behind the Seam component
* @param methodName
* the method name (for ejb3 method must be exposed in the local
* interface)
* @param param
* parameter as Object
* @return the result of the call
* @throws RuntimeException
*/
public static Object callSeamComponentByRef(Object seamRef,
String methodName, Object param) {
List<Object> params = new ArrayList<Object>();
params.add(param);
return callSeamComponentByRef(seamRef, methodName, params.toArray());
}
/**
* Calls a Seam component by name.
*
* @param seamName
* the name of the Seam component
* @param methodName
* the method name (for ejb3 method must be exposed in the local
* interface)
* @param param
* parameters as Object[]
* @return the result of the call
* @throws RuntimeException
*/
public static Object callSeamComponentByName(String seamName,
String methodName, Object param) {
List<Object> params = new ArrayList<Object>();
params.add(param);
return callSeamComponentByName(seamName, methodName, params.toArray());
}
// Internal methods
/**
* Extracts the Seam name from annotation given a reference.
*/
private static String getSeamComponentName(Object seamRef) {
Name componentName = seamRef.getClass().getAnnotation(Name.class);
if (componentName == null) {
return null;
}
return componentName.value();
}
/**
* Finds the method in the local interface of the Seam component.
*/
private static Method findMethod(String name, Class cls, Object[] params) {
Map<Method, Integer> candidates = new HashMap<Method, Integer>();
// for (Method m : cls.getDeclaredMethods()) {
for (Method method : cls.getMethods()) {
if (name.equals(method.getName())
&& method.getParameterTypes().length == params.length) {
int score = 0;
// XXX should do better check !!!
candidates.put(method, score);
}
}
Method bestMethod = null;
int bestScore = 0;
for (Method method : candidates.keySet()) {
int thisScore = candidates.get(method);
if (bestMethod == null || thisScore > bestScore) {
bestMethod = method;
bestScore = thisScore;
}
}
return bestMethod;
}
}