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 }