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
021 package org.granite.messaging.service;
022
023 import java.lang.reflect.Method;
024 import java.lang.reflect.ParameterizedType;
025 import java.lang.reflect.Type;
026 import java.lang.reflect.TypeVariable;
027 import java.util.ArrayList;
028 import java.util.Arrays;
029 import java.util.List;
030
031 import org.granite.config.GraniteConfig;
032 import org.granite.config.flex.Destination;
033 import org.granite.context.GraniteContext;
034 import org.granite.logging.Logger;
035 import org.granite.messaging.amf.io.convert.Converter;
036 import org.granite.messaging.amf.io.convert.Converters;
037 import org.granite.messaging.service.annotations.IgnoredMethod;
038 import org.granite.messaging.service.annotations.RemoteDestination;
039 import org.granite.util.StringUtil;
040
041 import flex.messaging.messages.Message;
042
043 /**
044 * @author Franck WOLFF
045 * @author Pedro GONCALVES
046 */
047 public 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 Type serviceClassGenericType = getGenericType(serviceClass);
064
065 Converter[] convertersArray = null;
066 Method serviceMethod = null;
067 if (params == null || params.length == 0)
068 serviceMethod = serviceClass.getMethod(methodName, (Class[])null);
069 else {
070 List<Method> matchingMethods = new ArrayList<Method>();
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 if (serviceClassGenericType != null)
092 findAndChange(paramTypes, serviceClassGenericType);
093
094 convertersArray = getConvertersArray(converters, params, paramTypes);
095 if (convertersArray != null)
096 matchingMethods.add(method);
097 }
098
099 if (matchingMethods.size() == 1)
100 serviceMethod = matchingMethods.get(0);
101 else if (matchingMethods.size() > 1) {
102 // Multiple matches found
103 serviceMethod = resolveMatchingMethod(matchingMethods);
104 }
105 }
106
107 if (serviceMethod == null)
108 throw new NoSuchMethodException(serviceClass.getName() + '.' + methodName + StringUtil.toString(params));
109
110 params = convert(convertersArray, params, serviceMethod.getGenericParameterTypes());
111
112 return new ServiceInvocationContext(message, destination, service, serviceMethod, params);
113 }
114
115 protected Converter[] getConvertersArray(Converters converters, Object[] values, Type[] targetTypes) {
116 Converter[] convertersArray = new Converter[values.length];
117 for (int i = 0; i < values.length; i++) {
118 convertersArray[i] = converters.getConverter(values[i], targetTypes[i]);
119 if (convertersArray[i] == null)
120 return null;
121 }
122 return convertersArray;
123 }
124
125 protected Object[] convert(Converter[] convertersArray, Object[] values, Type[] targetTypes) {
126 if (values.length > 0) {
127 for (int i = 0; i < convertersArray.length; i++)
128 values[i] = convertersArray[i].convert(values[i], targetTypes[i]);
129 }
130 return values;
131 }
132
133 protected Method resolveMatchingMethod(List<Method> methods) {
134 Method method = null;
135 // Prefer methods of interfaces/classes marked with @RemoteDestination
136 for (Method m : methods) {
137 if (m.getDeclaringClass().isAnnotationPresent(RemoteDestination.class)) {
138 method = m;
139 break;
140 }
141 }
142 if (method != null)
143 return method;
144
145 log.warn("Ambiguous method match for " + methods.get(0).getName() + ", selecting first found " + methods.get(0));
146 return methods.get(0);
147 }
148
149 /**
150 * If there is only one TypeVariable in method's argument list, it will be replaced
151 * by the type of the superclass of the service.
152 */
153 protected boolean findAndChange(Type[] paramTypes, Type superType) {
154 int idx = -1;
155 boolean find = false;
156 for (int j = 0; j < paramTypes.length; j++) {
157 Type type = paramTypes[j];
158
159 if (type instanceof TypeVariable<?>) {
160 if (!find) {
161 idx = j;
162 find = true;
163 } else {
164 throw new RuntimeException("There's two variable types.");
165 }
166 }
167 }
168
169 if (find)
170 paramTypes[idx] = superType;
171
172 return find;
173 }
174
175 /**
176 * Returns actual type argument of a given class.
177 */
178 protected Type getGenericType(Class<?> clazz) {
179 try {
180 ParameterizedType genericSuperclass = (ParameterizedType)clazz.getGenericSuperclass();
181 Type[] actualTypeArguments = genericSuperclass.getActualTypeArguments();
182 if (actualTypeArguments != null && actualTypeArguments.length == 1)
183 return actualTypeArguments[0];
184 } catch (Exception e) {
185 // fallback...
186 }
187 return null;
188 }
189 }