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 }