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