001/*
002 * (C) Copyright 2006-2009 Nuxeo SA (http://nuxeo.com/) and contributors.
003 *
004 * All rights reserved. This program and the accompanying materials
005 * are made available under the terms of the GNU Lesser General Public License
006 * (LGPL) version 2.1 which accompanies this distribution, and is available at
007 * http://www.gnu.org/licenses/lgpl.html
008 *
009 * This library is distributed in the hope that it will be useful,
010 * but WITHOUT ANY WARRANTY; without even the implied warranty of
011 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
012 * Lesser General Public License for more details.
013 *
014 * Contributors:
015 *     Thierry Delprat
016 */
017
018package org.nuxeo.ecm.platform.ui.web.util;
019
020import java.lang.reflect.InvocationTargetException;
021import java.lang.reflect.Method;
022import java.util.ArrayList;
023import java.util.HashMap;
024import java.util.List;
025import java.util.Map;
026
027import org.jboss.seam.Component;
028import org.jboss.seam.annotations.Name;
029import org.jboss.seam.util.EJB;
030
031/**
032 * This class provides helper methods for accessing a Seam Component
033 * <p>
034 * Why this class?
035 * <p>
036 * At startup time, Seam generates CGLib Wrappers around each Seam component. This wrapper holds all interceptors that
037 * are used for bijection. Because of that, accessing a Seam component by its reference will lead to call a disinjected
038 * instance (all @In member variables are null).
039 * <p>
040 * Seam components are usually accessed via EL or via injected references, in this cases, Seam takes care of everything
041 * and you get a functional instance. But in some cases, you need to access a Seam component:
042 * <ul>
043 * <li>from an object that has no direct access to Seam (ie: can't use injection),
044 * <li>from an object that stored a call-back reference (storing the 'this' of the Seam component).
045 * </ul>
046 * In these cases, this helper class is useful. This class provides helper functions for :
047 * <ul>
048 * <li>getting a Wrapped Seam component via its name or its reference,
049 * <li>executing a method via a method name on a Seam component.
050 * </ul>
051 *
052 * @author tiry
053 */
054public final class SeamComponentCallHelper {
055
056    // This is an utility class.
057    private SeamComponentCallHelper() {
058    }
059
060    /**
061     * Gets the CGLib-wrapped Seam component from its name.
062     *
063     * @param seamName the name of the Seam component
064     * @return the Wrapped Seam component
065     */
066    public static Object getSeamComponentByName(String seamName) {
067        // Find the component we're calling
068        Component component = Component.forName(seamName);
069
070        if (component == null) {
071            throw new RuntimeException("No such component: " + seamName);
072        }
073
074        // Create an instance of the component
075        Object seamComponent = Component.getInstance(seamName, true);
076
077        return seamComponent;
078    }
079
080    /**
081     * Gets the CGLib-wrapped Seam component from a reference.
082     *
083     * @param seamRef reference of the object behind the Seam component
084     * @return the Wrapped Seam component
085     */
086    public static Object getSeamComponentByRef(Object seamRef) {
087        String seamName = getSeamComponentName(seamRef);
088        if (seamName == null) {
089            return null;
090        }
091        return getSeamComponentByName(seamName);
092    }
093
094    /**
095     * Calls a Seam component by name.
096     *
097     * @param seamName the name of the Seam component
098     * @param methodName the method name (for ejb3 method must be exposed in the local interface)
099     * @param params parameters as Object[]
100     * @return the result of the call
101     * @throws RuntimeException
102     */
103    public static Object callSeamComponentByName(String seamName, String methodName, Object[] params) {
104
105        Object seamComponent = getSeamComponentByName(seamName);
106        Component component = Component.forName(seamName);
107
108        Class type = null;
109        if (component.getType().isSessionBean() && !component.getBusinessInterfaces().isEmpty()) {
110            for (Class c : component.getBusinessInterfaces()) {
111                if (c.isAnnotationPresent(EJB.LOCAL)) {
112                    type = component.getBusinessInterfaces().iterator().next();
113                    break;
114                }
115            }
116
117            if (type == null) {
118                throw new RuntimeException(String.format("Type cannot be determined for component [%s]. "
119                        + "Please ensure that it has a " + "local interface.", component));
120            }
121        }
122
123        if (type == null) {
124            type = component.getBeanClass();
125        }
126
127        Method m = findMethod(methodName, type, params);
128
129        if (m == null) {
130            throw new RuntimeException("No compatible method found.");
131        }
132
133        try {
134            Object result = m.invoke(seamComponent, params);
135            return result;
136        } catch (IllegalArgumentException e) {
137            throw new RuntimeException("Error calling method " + e.getMessage(), e);
138        } catch (IllegalAccessException e) {
139            throw new RuntimeException("Error calling method " + e.getMessage(), e);
140        } catch (InvocationTargetException e) {
141            throw new RuntimeException("Error calling method " + e.getMessage(), e);
142        }
143    }
144
145    /**
146     * Calls a Seam component by reference.
147     *
148     * @param seamRef the reference on the object behind the Seam component
149     * @param methodName the method name (for ejb3 method must be exposed in the local interface)
150     * @param params parameters as Object[]
151     * @return the result of the call
152     * @throws RuntimeException
153     */
154    public static Object callSeamComponentByRef(Object seamRef, String methodName, Object[] params) {
155        String seamName = getSeamComponentName(seamRef);
156        return callSeamComponentByName(seamName, methodName, params);
157    }
158
159    /**
160     * Calls a Seam component by reference.
161     *
162     * @param seamRef the reference on the object behind the Seam component
163     * @param methodName the method name (for ejb3 method must be exposed in the local interface)
164     * @param param parameter as Object
165     * @return the result of the call
166     * @throws RuntimeException
167     */
168    public static Object callSeamComponentByRef(Object seamRef, String methodName, Object param) {
169        List<Object> params = new ArrayList<Object>();
170        params.add(param);
171        return callSeamComponentByRef(seamRef, methodName, params.toArray());
172    }
173
174    /**
175     * Calls a Seam component by name.
176     *
177     * @param seamName the name of the Seam component
178     * @param methodName the method name (for ejb3 method must be exposed in the local interface)
179     * @param param parameters as Object[]
180     * @return the result of the call
181     * @throws RuntimeException
182     */
183    public static Object callSeamComponentByName(String seamName, String methodName, Object param) {
184        List<Object> params = new ArrayList<Object>();
185        params.add(param);
186        return callSeamComponentByName(seamName, methodName, params.toArray());
187    }
188
189    // Internal methods
190
191    /**
192     * Extracts the Seam name from annotation given a reference.
193     */
194    private static String getSeamComponentName(Object seamRef) {
195        Name componentName = seamRef.getClass().getAnnotation(Name.class);
196
197        if (componentName == null) {
198            return null;
199        }
200
201        return componentName.value();
202    }
203
204    /**
205     * Finds the method in the local interface of the Seam component.
206     */
207    private static Method findMethod(String name, Class cls, Object[] params) {
208        Map<Method, Integer> candidates = new HashMap<Method, Integer>();
209
210        // for (Method m : cls.getDeclaredMethods()) {
211        for (Method method : cls.getMethods()) {
212
213            if (name.equals(method.getName()) && method.getParameterTypes().length == params.length) {
214                int score = 0;
215                // XXX should do better check !!!
216                candidates.put(method, score);
217            }
218        }
219
220        Method bestMethod = null;
221        int bestScore = 0;
222
223        for (Method method : candidates.keySet()) {
224            int thisScore = candidates.get(method);
225            if (bestMethod == null || thisScore > bestScore) {
226                bestMethod = method;
227                bestScore = thisScore;
228            }
229        }
230
231        return bestMethod;
232    }
233
234}