001/** 002 * GRANITE DATA SERVICES 003 * Copyright (C) 2006-2013 GRANITE DATA SERVICES S.A.S. 004 * 005 * This file is part of the Granite Data Services Platform. 006 * 007 * Granite Data Services is free software; you can redistribute it and/or 008 * modify it under the terms of the GNU Lesser General Public 009 * License as published by the Free Software Foundation; either 010 * version 2.1 of the License, or (at your option) any later version. 011 * 012 * Granite Data Services is distributed in the hope that it will be useful, 013 * but WITHOUT ANY WARRANTY; without even the implied warranty of 014 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser 015 * General Public License for more details. 016 * 017 * You should have received a copy of the GNU Lesser General Public 018 * License along with this library; if not, write to the Free Software 019 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, 020 * USA, or see <http://www.gnu.org/licenses/>. 021 */ 022package org.granite.messaging.service; 023 024import java.lang.reflect.Method; 025import java.lang.reflect.ParameterizedType; 026import java.lang.reflect.Type; 027import java.util.ArrayList; 028import java.util.Arrays; 029import java.util.List; 030 031import org.granite.config.GraniteConfig; 032import org.granite.config.flex.Destination; 033import org.granite.context.GraniteContext; 034import org.granite.logging.Logger; 035import org.granite.messaging.amf.io.convert.Converter; 036import org.granite.messaging.amf.io.convert.Converters; 037import org.granite.messaging.service.annotations.IgnoredMethod; 038import org.granite.messaging.service.annotations.RemoteDestination; 039import org.granite.util.StringUtil; 040import org.granite.util.TypeUtil; 041 042import flex.messaging.messages.Message; 043 044/** 045 * @author Franck WOLFF 046 * @author Pedro GONCALVES 047 */ 048public class DefaultMethodMatcher implements MethodMatcher { 049 050 private static final Logger log = Logger.getLogger(DefaultMethodMatcher.class); 051 052 053 public ServiceInvocationContext findServiceMethod( 054 Message message, 055 Destination destination, 056 Object service, 057 String methodName, 058 Object[] params) throws NoSuchMethodException { 059 060 GraniteConfig config = GraniteContext.getCurrentInstance().getGraniteConfig(); 061 Converters converters = config.getConverters(); 062 063 Class<?> serviceClass = service.getClass(); 064 ParameterizedType[] serviceDeclaringTypes = TypeUtil.getDeclaringTypes(serviceClass); 065 066 MatchingMethod match = null; 067 if (params == null || params.length == 0) 068 match = new MatchingMethod(serviceClass.getMethod(methodName, (Class[])null), null); 069 else { 070 List<MatchingMethod> matchingMethods = new ArrayList<MatchingMethod>(); 071 072 List<Method> methods = new ArrayList<Method>(); 073 for (Class<?> serviceInterface : serviceClass.getInterfaces()) 074 methods.addAll(Arrays.asList(serviceInterface.getMethods())); 075 076 methods.addAll(Arrays.asList(serviceClass.getMethods())); 077 078 for (Method method : methods) { 079 080 if (!methodName.equals(method.getName())) 081 continue; 082 083 Type[] paramTypes = method.getGenericParameterTypes(); 084 if (paramTypes.length != params.length) 085 continue; 086 087 // Methods marked with @IgnoredMethod cannot be called remotely 088 if (method.isAnnotationPresent(IgnoredMethod.class)) 089 continue; 090 091 findAndChange(paramTypes, method.getDeclaringClass(), serviceDeclaringTypes); 092 093 Converter[] convertersArray = getConvertersArray(converters, params, paramTypes); 094 if (convertersArray != null) 095 matchingMethods.add(new MatchingMethod(method, convertersArray)); 096 } 097 098 if (matchingMethods.size() == 1) 099 match = matchingMethods.get(0); 100 else if (matchingMethods.size() > 1) { 101 // Multiple matches found 102 match = resolveMatchingMethod(matchingMethods, serviceClass); 103 } 104 } 105 106 if (match == null) 107 throw new NoSuchMethodException(serviceClass.getName() + '.' + methodName + StringUtil.toString(params)); 108 109 params = convert(match.convertersArray, params, match.serviceMethod.getGenericParameterTypes()); 110 111 return new ServiceInvocationContext(message, destination, service, match.serviceMethod, params); 112 } 113 114 protected Converter[] getConvertersArray(Converters converters, Object[] values, Type[] targetTypes) { 115 Converter[] convertersArray = new Converter[values.length]; 116 for (int i = 0; i < values.length; i++) { 117 convertersArray[i] = converters.getConverter(values[i], targetTypes[i]); 118 if (convertersArray[i] == null) 119 return null; 120 } 121 return convertersArray; 122 } 123 124 protected Object[] convert(Converter[] convertersArray, Object[] values, Type[] targetTypes) { 125 if (values.length > 0) { 126 for (int i = 0; i < convertersArray.length; i++) 127 values[i] = convertersArray[i].convert(values[i], targetTypes[i]); 128 } 129 return values; 130 } 131 132 protected MatchingMethod resolveMatchingMethod(List<MatchingMethod> methods, Class<?> serviceClass) { 133 134 // Prefer methods of interfaces/classes marked with @RemoteDestination 135 for (MatchingMethod m : methods) { 136 if (m.serviceMethod.getDeclaringClass().isAnnotationPresent(RemoteDestination.class)) 137 return m; 138 } 139 140 // Then prefer method declared by the serviceClass (with EJBs, we have always 2 methods, one in the interface, 141 // the other in the proxy, and the @RemoteDestination annotation cannot be find on the proxy class even 142 // it is present on the original class). 143 List<MatchingMethod> serviceClassMethods = new ArrayList<MatchingMethod>(); 144 for (MatchingMethod m : methods) { 145 if (m.serviceMethod.getDeclaringClass().equals(serviceClass)) 146 serviceClassMethods.add(m); 147 } 148 if (serviceClassMethods.size() == 1) 149 return serviceClassMethods.get(0); 150 151 log.warn("Ambiguous method match for " + methods.get(0).serviceMethod.getName() + ", selecting first found " + methods.get(0).serviceMethod); 152 return methods.get(0); 153 } 154 155 /** 156 * If there is only one TypeVariable in method's argument list, it will be replaced 157 * by the type of the superclass of the service. 158 */ 159 protected void findAndChange(Type[] paramTypes, Class<?> declaringClass, ParameterizedType[] declaringTypes) { 160 for (int j = 0; j < paramTypes.length; j++) 161 paramTypes[j] = TypeUtil.resolveTypeVariable(paramTypes[j], declaringClass, declaringTypes); 162 } 163 164 /** 165 * Returns actual type argument of a given class. 166 */ 167 protected Type getGenericType(Class<?> clazz) { 168 try { 169 ParameterizedType genericSuperclass = (ParameterizedType)clazz.getGenericSuperclass(); 170 Type[] actualTypeArguments = genericSuperclass.getActualTypeArguments(); 171 if (actualTypeArguments != null && actualTypeArguments.length == 1) 172 return actualTypeArguments[0]; 173 } catch (Exception e) { 174 // fallback... 175 } 176 return null; 177 } 178 179 private static class MatchingMethod { 180 181 public final Method serviceMethod; 182 public final Converter[] convertersArray; 183 184 public MatchingMethod(Method serviceMethod, Converter[] convertersArray) { 185 this.serviceMethod = serviceMethod; 186 this.convertersArray = convertersArray; 187 } 188 189 @Override 190 public String toString() { 191 return "MatchingMethod {serviceMethod=" + serviceMethod + ", convertersArray=" + (convertersArray != null ? Arrays.toString(convertersArray) : "[]") + "}"; 192 } 193 } 194}