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    import java.util.ArrayList;
020    import java.util.List;
021    
022    import org.apache.hivemind.ApplicationRuntimeException;
023    import org.apache.hivemind.util.Defense;
024    import org.apache.tapestry.IPage;
025    import org.apache.tapestry.IRequestCycle;
026    import org.apache.tapestry.engine.ILink;
027    import org.apache.tapestry.event.BrowserEvent;
028    
029    /**
030     * Logic for mapping a listener method name to an actual method invocation; this
031     * may require a little searching to find the correct version of the method,
032     * based on the number of parameters to the method (there's a lot of flexibility
033     * in terms of what methods may be considered a listener method).
034     * 
035     * @author Howard M. Lewis Ship
036     * @since 4.0
037     */
038    public class ListenerMethodInvokerImpl implements ListenerMethodInvoker
039    {
040    
041        /**
042         * Methods with a name appropriate for this class, sorted into descending
043         * order by number of parameters.
044         */
045    
046        private final Method[] _methods;
047    
048        /**
049         * The listener method name, used in some error messages.
050         */
051    
052        private final String _name;
053    
054        public ListenerMethodInvokerImpl(String name, Method[] methods)
055        {
056            Defense.notNull(name, "name");
057            Defense.notNull(methods, "methods");
058    
059            _name = name;
060            _methods = methods;
061        }
062    
063        public void invokeListenerMethod(Object target, IRequestCycle cycle)
064        {
065            Object[] listenerParameters = cycle.getListenerParameters();
066            
067            if (listenerParameters == null)
068                listenerParameters = new Object[0];
069            
070            if (searchAndInvoke(target, cycle, listenerParameters))
071                return;
072            
073            throw new ApplicationRuntimeException(ListenerMessages
074                    .noListenerMethodFound(_name, listenerParameters, target),
075                    target, null, null);
076        }
077        
078        private boolean searchAndInvoke(Object target, IRequestCycle cycle, Object[] listenerParameters)
079        {
080            BrowserEvent event = null;
081            if (listenerParameters.length > 0 
082                    && BrowserEvent.class.isInstance(listenerParameters[listenerParameters.length - 1]))
083                event = (BrowserEvent)listenerParameters[listenerParameters.length - 1];
084            
085            List invokeParms = new ArrayList();
086            
087            methods:
088                for (int i = 0; i < _methods.length; i++, invokeParms.clear()) {
089                    
090                    if (!_methods[i].getName().equals(_name))
091                       continue;
092                    
093                    Class[] parms = _methods[i].getParameterTypes();
094                    
095                    // impossible to call this
096                    if (parms.length > (listenerParameters.length + 1) )
097                        continue;
098                    
099                    int listenerIndex = 0;
100                    for (int p = 0; p < parms.length && listenerIndex < (listenerParameters.length + 1); p++) {
101                        
102                        // special case for BrowserEvent
103                        if (BrowserEvent.class.isAssignableFrom(parms[p])) {
104                            if (event == null)
105                                continue methods;
106                            
107                            if (!invokeParms.contains(event))
108                                invokeParms.add(event);
109                            
110                            continue;
111                        }
112                        
113                        // special case for request cycle
114                        if (IRequestCycle.class.isAssignableFrom(parms[p])) {
115                            invokeParms.add(cycle);
116                            continue;
117                        }
118                        
119                        if (event != null && listenerIndex < (listenerParameters.length + 1)
120                                || listenerIndex < listenerParameters.length) {
121                            invokeParms.add(listenerParameters[listenerIndex]);
122                            listenerIndex++;
123                        }
124                    }
125                    
126                    if (invokeParms.size() != parms.length)
127                        continue;
128                    
129                    invokeListenerMethod(_methods[i], target, cycle,
130                            invokeParms.toArray(new Object[invokeParms.size()]));
131                    
132                    return true;
133                }
134            
135            return false;
136        }
137        
138        private void invokeListenerMethod(Method listenerMethod, Object target,
139                IRequestCycle cycle, Object[] parameters)
140        {
141            
142            Object methodResult = null;
143            
144            try
145            {
146                methodResult = invokeTargetMethod(target, listenerMethod, parameters);
147            }
148            catch (InvocationTargetException ex)
149            {
150                Throwable targetException = ex.getTargetException();
151                
152                if (targetException instanceof ApplicationRuntimeException)
153                    throw (ApplicationRuntimeException) targetException;
154    
155                throw new ApplicationRuntimeException(ListenerMessages
156                        .listenerMethodFailure(listenerMethod, target,
157                                targetException), target, null, targetException);
158            }
159            catch (Exception ex)
160            {
161                throw new ApplicationRuntimeException(ListenerMessages
162                        .listenerMethodFailure(listenerMethod, target, ex), target,
163                        null, ex);
164    
165            }
166            
167            // void methods return null
168            
169            if (methodResult == null) return;
170            
171            // The method scanner, inside ListenerMapSourceImpl,
172            // ensures that only methods that return void, String,
173            // or assignable to ILink or IPage are considered.
174    
175            if (methodResult instanceof String)
176            {
177                cycle.activate((String) methodResult);
178                return;
179            }
180            
181            if (methodResult instanceof ILink)
182            {
183                ILink link = (ILink) methodResult;
184    
185                String url = link.getAbsoluteURL();
186    
187                cycle.sendRedirect(url);
188                return;
189            }
190    
191            cycle.activate((IPage) methodResult);
192        }
193        
194        /**
195         * Provided as a hook so that subclasses can perform any additional work
196         * before or after invoking the listener method.
197         */
198    
199        protected Object invokeTargetMethod(Object target, Method listenerMethod,
200                Object[] parameters)
201            throws IllegalAccessException, InvocationTargetException
202        {
203            return listenerMethod.invoke(target, parameters);
204        }
205    }