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.util.ArrayList;
027 import java.util.Arrays;
028 import java.util.List;
029
030 import org.granite.config.GraniteConfig;
031 import org.granite.config.flex.Destination;
032 import org.granite.context.GraniteContext;
033 import org.granite.logging.Logger;
034 import org.granite.messaging.amf.io.convert.Converter;
035 import org.granite.messaging.amf.io.convert.Converters;
036 import org.granite.messaging.service.annotations.IgnoredMethod;
037 import org.granite.messaging.service.annotations.RemoteDestination;
038 import org.granite.util.StringUtil;
039 import org.granite.util.TypeUtil;
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 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 }