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
015package org.apache.tapestry.listener;
016
017import java.lang.reflect.InvocationTargetException;
018import java.lang.reflect.Method;
019
020import org.apache.hivemind.ApplicationRuntimeException;
021import org.apache.hivemind.util.Defense;
022import org.apache.tapestry.IPage;
023import org.apache.tapestry.IRequestCycle;
024import org.apache.tapestry.Tapestry;
025import 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 */
036public 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}