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