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}