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    }