/*
* (C) Copyright 2006-2009 Nuxeo SA (http://nuxeo.com/) and others.
*
* 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.
*
* 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("Type cannot be determined for component [" + component
+ "]. Please ensure that it has a local interface.");
}
}
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;
}
}