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