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}