001    // Copyright 2005 The Apache Software Foundation
002    //
003    // Licensed under the Apache License, Version 2.0 (the "License");
004    // you may not use this file except in compliance with the License.
005    // You may obtain a copy of the License at
006    //
007    //     http://www.apache.org/licenses/LICENSE-2.0
008    //
009    // Unless required by applicable law or agreed to in writing, software
010    // distributed under the License is distributed on an "AS IS" BASIS,
011    // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
012    // See the License for the specific language governing permissions and
013    // limitations under the License.
014    
015    package org.apache.tapestry.listener;
016    
017    import java.lang.reflect.InvocationTargetException;
018    import java.lang.reflect.Method;
019    
020    import org.apache.hivemind.ApplicationRuntimeException;
021    import org.apache.hivemind.util.Defense;
022    import org.apache.tapestry.IPage;
023    import org.apache.tapestry.IRequestCycle;
024    import org.apache.tapestry.Tapestry;
025    import org.apache.tapestry.engine.ILink;
026    
027    /**
028     * Logic for mapping a listener method name to an actual method invocation; this may require a
029     * little searching to find the correct version of the method, based on the number of parameters to
030     * the method (there's a lot of flexibility in terms of what methods may be considered a listener
031     * method).
032     * 
033     * @author Howard M. Lewis Ship
034     * @since 4.0
035     */
036    public class ListenerMethodInvokerImpl implements ListenerMethodInvoker
037    {
038        /**
039         * Methods with a name appropriate for this class, sorted into descending order by number of
040         * parameters.
041         */
042    
043        private final Method[] _methods;
044    
045        /**
046         * The listener method name, used in some error messages.
047         */
048    
049        private final String _name;
050    
051        public ListenerMethodInvokerImpl(String name, Method[] methods)
052        {
053            Defense.notNull(name, "name");
054            Defense.notNull(methods, "methods");
055    
056            _name = name;
057            _methods = methods;
058        }
059    
060        public void invokeListenerMethod(Object target, IRequestCycle cycle)
061        {
062            Object[] listenerParameters = cycle.getListenerParameters();
063    
064            // method(parameters)
065            if (searchAndInvoke(target, false, true, cycle, listenerParameters))
066                return;
067    
068            // method(IRequestCycle, parameters)
069            if (searchAndInvoke(target, true, true, cycle, listenerParameters))
070                return;
071    
072            // method()
073            if (searchAndInvoke(target, false, false, cycle, listenerParameters))
074                return;
075    
076            // method(IRequestCycle)
077            if (searchAndInvoke(target, true, false, cycle, listenerParameters))
078                return;
079    
080            throw new ApplicationRuntimeException(ListenerMessages.noListenerMethodFound(
081                    _name,
082                    listenerParameters,
083                    target), target, null, null);
084        }
085    
086        private boolean searchAndInvoke(Object target, boolean includeCycle, boolean includeParameters,
087                IRequestCycle cycle, Object[] listenerParameters)
088        {
089            int listenerParameterCount = Tapestry.size(listenerParameters);
090            int methodParameterCount = includeParameters ? listenerParameterCount : 0;
091    
092            if (includeCycle)
093                methodParameterCount++;
094    
095            for (int i = 0; i < _methods.length; i++)
096            {
097                Method m = _methods[i];
098    
099                // Since the methods are sorted, descending, by parameter count,
100                // there's no point in searching past that point.
101    
102                Class[] parameterTypes = m.getParameterTypes();
103    
104                if (parameterTypes.length < methodParameterCount)
105                    break;
106    
107                if (parameterTypes.length != methodParameterCount)
108                    continue;
109    
110                boolean firstIsCycle = parameterTypes.length > 0
111                        && parameterTypes[0] == IRequestCycle.class;
112    
113                // When we're searching for a "traditional" style listener method,
114                // one which takes the request cycle as its first parameter,
115                // then check that first parameter is *exactly* IRequestCycle
116                // On the other hand, if we're looking for new style
117                // listener methods (includeCycle is false), then ignore
118                // any methods whose first parameter is the request cycle
119                // (we'll catch those in a later search).
120    
121                if (includeCycle != firstIsCycle)
122                    continue;
123    
124                invokeListenerMethod(
125                        m,
126                        target,
127                        includeCycle,
128                        includeParameters,
129                        cycle,
130                        listenerParameters);
131    
132                return true;
133            }
134    
135            return false;
136        }
137    
138        private void invokeListenerMethod(Method listenerMethod, Object target, boolean includeCycle,
139                boolean includeParameters, IRequestCycle cycle, Object[] listenerParameters)
140        {
141            Object[] parameters = new Object[listenerMethod.getParameterTypes().length];
142            int cursor = 0;
143    
144            if (includeCycle)
145                parameters[cursor++] = cycle;
146    
147            if (includeParameters)
148                for (int i = 0; i < Tapestry.size(listenerParameters); i++)
149                    parameters[cursor++] = listenerParameters[i];
150    
151            Object methodResult = null;
152    
153            try
154            {
155                methodResult = invokeTargetMethod(target, listenerMethod, parameters);
156            }
157            catch (InvocationTargetException ex)
158            {
159                Throwable targetException = ex.getTargetException();
160    
161                if (targetException instanceof ApplicationRuntimeException)
162                    throw (ApplicationRuntimeException) targetException;
163    
164                throw new ApplicationRuntimeException(ListenerMessages.listenerMethodFailure(
165                        listenerMethod,
166                        target,
167                        targetException), target, null, targetException);
168            }
169            catch (Exception ex)
170            {
171                throw new ApplicationRuntimeException(ListenerMessages.listenerMethodFailure(
172                        listenerMethod,
173                        target,
174                        ex), target, null, ex);
175    
176            }
177    
178            // void methods return null
179    
180            if (methodResult == null)
181                return;
182    
183            // The method scanner, inside ListenerMapSourceImpl,
184            // ensures that only methods that return void, String,
185            // or assignable to ILink or IPage are considered.
186    
187            if (methodResult instanceof String)
188            {
189                cycle.activate((String) methodResult);
190                return;
191            }
192    
193            if (methodResult instanceof ILink)
194            {
195                ILink link = (ILink) methodResult;
196    
197                String url = link.getAbsoluteURL();
198    
199                cycle.sendRedirect(url);
200                return;
201            }
202    
203            cycle.activate((IPage) methodResult);
204        }
205    
206        /**
207         * Provided as a hook so that subclasses can perform any additional work before or after
208         * invoking the listener method.
209         */
210    
211        protected Object invokeTargetMethod(Object target, Method listenerMethod, Object[] parameters)
212                throws IllegalAccessException, InvocationTargetException
213        {
214            return listenerMethod.invoke(target, parameters);
215        }
216    }