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    }