001 // Copyright 2004, 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 package org.apache.tapestry.internal.event.impl; 015 016 import edu.emory.mathcs.backport.java.util.concurrent.ConcurrentHashMap; 017 import org.apache.hivemind.util.Defense; 018 import org.apache.tapestry.IActionListener; 019 import org.apache.tapestry.IComponent; 020 import org.apache.tapestry.IForm; 021 import org.apache.tapestry.IRequestCycle; 022 import org.apache.tapestry.event.BrowserEvent; 023 import org.apache.tapestry.event.ResetEventListener; 024 import org.apache.tapestry.form.FormSupport; 025 import org.apache.tapestry.internal.event.ComponentEventProperty; 026 import org.apache.tapestry.internal.event.EventBoundListener; 027 import org.apache.tapestry.internal.event.IComponentEventInvoker; 028 import org.apache.tapestry.listener.ListenerInvoker; 029 import org.apache.tapestry.spec.IComponentSpecification; 030 import org.apache.tapestry.spec.IEventListener; 031 import org.apache.tapestry.util.IdAllocator; 032 033 import java.util.*; 034 035 036 /** 037 * Implementation of {@link IComponentEventInvoker}. 038 * 039 * @author jkuhnert 040 */ 041 public class ComponentEventInvoker implements IComponentEventInvoker, ResetEventListener 042 { 043 static final ComponentEventProperty[] EMPTY_PROPERTIES = new ComponentEventProperty[0]; 044 045 // Mapped component id path -> List of IEventListeners 046 private Map _components = new ConcurrentHashMap(); 047 // Mapped form id path -> List of IEventListeners 048 private Map _formComponents = new ConcurrentHashMap(); 049 // Used to invoke actual listener methods 050 private ListenerInvoker _invoker; 051 052 // Cached set of ComponentEventProperty[] arrays mapped to specific components 053 private Map _propertyCache = new ConcurrentHashMap(); 054 055 /** 056 * {@inheritDoc} 057 */ 058 public void invokeListeners(IComponent component, IRequestCycle cycle, BrowserEvent event) 059 { 060 Defense.notNull(component, "component"); 061 Defense.notNull(cycle, "cycle"); 062 Defense.notNull(event, "event"); 063 064 invokeComponentListeners(component, cycle, event); 065 066 invokeElementListeners(component, cycle, event); 067 } 068 069 /** 070 * {@inheritDoc} 071 */ 072 public void invokeFormListeners(FormSupport formSupport, final IRequestCycle cycle, final BrowserEvent event) 073 { 074 Defense.notNull(formSupport, "formSupport"); 075 Defense.notNull(cycle, "cycle"); 076 Defense.notNull(event, "event"); 077 078 IForm form = formSupport.getForm(); 079 String formIdPath = form.getExtendedId(); 080 081 String targetId = (String)event.getTarget().get("id"); 082 String strippedId = IdAllocator.convertAllocatedComponentId(targetId); 083 if (targetId == null) 084 return; 085 086 List comps = getFormEventListeners(formIdPath); 087 if (comps == null) 088 return; 089 090 boolean disableFocus = false; 091 092 for (int i=0; i < comps.size(); i++) { 093 094 IComponentSpecification spec = (IComponentSpecification)comps.get(i); 095 EventBoundListener[] listeners = spec.getFormEvents(formIdPath, event); 096 097 IComponent target = null; 098 if (spec.isPageSpecification()) { 099 100 target = form.getPage(); 101 } else { 102 103 target = findComponent(form.getPage().getComponents().values(), spec); 104 } 105 106 for (int e=0; e < listeners.length; e++) { 107 108 // ensure ~only~ the method that targeted this event gets called! 109 110 if (!listeners[e].getComponentId().endsWith(strippedId)) 111 continue; 112 113 // clear validation errors but not input if async validation is 114 // disabled 115 116 if (!listeners[e].isValidateForm()) { 117 118 form.getDelegate().clearErrors(); 119 } 120 121 // handle disabling focus 122 if (!disableFocus && !listeners[e].shouldFocusForm()) 123 disableFocus = true; 124 125 // defer execution until after form is done rewinding 126 127 form.addDeferredRunnable( 128 new FormRunnable(target.getListeners().getListener(listeners[e].getMethodName()), 129 target, 130 cycle)); 131 } 132 } 133 134 // Form uses cycle attributes to test whether or not to focus . 135 // The attribute existing at all is enough to bypass focusing. 136 if (disableFocus) { 137 138 cycle.disableFocus(); 139 } 140 } 141 142 void invokeComponentListeners(IComponent component, IRequestCycle cycle, BrowserEvent event) 143 { 144 String idPath = component.getExtendedId(); 145 List listeners = getEventListeners(idPath); 146 if (listeners == null) 147 return; 148 149 for (int i = 0; i < listeners.size(); i++) { 150 151 IComponentSpecification listener = (IComponentSpecification)listeners.get(i); 152 153 IComponent target = null; 154 ComponentEventProperty props = null; 155 156 if (listener.isPageSpecification()) { 157 158 target = component.getPage(); 159 props = listener.getComponentEvents(idPath); 160 } else { 161 162 target = findComponent(component.getPage().getComponents().values(), listener); 163 props = target.getSpecification().getComponentEvents(idPath); 164 } 165 if (props == null) 166 continue; 167 168 List clisteners = props.getEventListeners(event.getName()); 169 for (int e=0; e < clisteners.size(); e++) { 170 171 EventBoundListener eventListener = (EventBoundListener)clisteners.get(e); 172 173 _invoker.invokeListener(target.getListeners().getListener(eventListener.getMethodName()), target, cycle); 174 } 175 176 } 177 } 178 179 void invokeElementListeners(IComponent component, IRequestCycle cycle, BrowserEvent event) 180 { 181 String targetId = (String)event.getTarget().get("id"); 182 if (targetId == null) 183 return; 184 185 ComponentEventProperty prop = component.getSpecification().getElementEvents(targetId); 186 if (prop == null) 187 return; 188 189 List listeners = prop.getEventListeners(event.getName()); 190 191 for (int i=0; i < listeners.size(); i++) { 192 193 EventBoundListener listener = (EventBoundListener)listeners.get(i); 194 195 _invoker.invokeListener(component.getListeners().getListener(listener.getMethodName()), component, cycle); 196 } 197 } 198 199 IComponent findComponent(Collection comps, IComponentSpecification spec) 200 { 201 IComponent ret = null; 202 203 Iterator it = comps.iterator(); 204 205 while (it.hasNext()) { 206 IComponent comp = (IComponent)it.next(); 207 208 if (comp.getSpecification().equals(spec)) { 209 ret = comp; 210 break; 211 } 212 213 ret = findComponent(comp.getComponents().values(), spec); 214 if (ret != null) 215 break; 216 } 217 218 return ret; 219 } 220 221 /** Local runnable for deferred form connections. */ 222 class FormRunnable implements Runnable { 223 224 private IActionListener _listener; 225 private IComponent _component; 226 private IRequestCycle _cycle; 227 228 public FormRunnable(IActionListener listener, IComponent comp, IRequestCycle cycle) 229 { 230 _listener = listener; 231 _component = comp; 232 _cycle = cycle; 233 } 234 235 public void run() 236 { 237 _invoker.invokeListener(_listener, _component, _cycle); 238 } 239 } 240 241 /** 242 * {@inheritDoc} 243 */ 244 public void addEventListener(String componentId, IComponentSpecification listener) 245 { 246 List listeners = (List)_components.get(componentId); 247 248 if (listeners == null) { 249 listeners = new ArrayList(); 250 _components.put(componentId, listeners); 251 } 252 253 if (!listeners.contains(listener)) { 254 listeners.add(listener); 255 } 256 257 _propertyCache.remove(componentId); 258 } 259 260 /** 261 * {@inheritDoc} 262 */ 263 public List getEventListeners(String componentId) 264 { 265 if (componentId == null) 266 return null; 267 268 return (List)_components.get(componentId); 269 } 270 271 public ComponentEventProperty[] getEventPropertyListeners(String componentIdPath) 272 { 273 if (componentIdPath == null) 274 return EMPTY_PROPERTIES; 275 276 ComponentEventProperty[] ret = (ComponentEventProperty[])_propertyCache.get(componentIdPath); 277 if (ret != null) 278 return ret; 279 280 List listeners = getEventListeners(componentIdPath); 281 if (listeners == null || listeners.size() < 1) 282 return EMPTY_PROPERTIES; 283 284 List props = new ArrayList(); 285 for (int i=0; i < listeners.size(); i++) { 286 287 IEventListener listener = (IEventListener)listeners.get(i); 288 289 props.add(listener.getComponentEvents(componentIdPath)); 290 } 291 292 ret = (ComponentEventProperty[])props.toArray(new ComponentEventProperty[props.size()]); 293 294 _propertyCache.put(componentIdPath, ret); 295 296 return ret; 297 } 298 299 /** 300 * {@inheritDoc} 301 */ 302 public void addFormEventListener(String formId, IComponentSpecification listener) 303 { 304 List listeners = (List)_formComponents.get(formId); 305 306 if (listeners == null) { 307 listeners = new ArrayList(); 308 _formComponents.put(formId, listeners); 309 } 310 311 if (!listeners.contains(listener)) { 312 listeners.add(listener); 313 } 314 } 315 316 /** 317 * {@inheritDoc} 318 */ 319 public List getFormEventListeners(String formId) 320 { 321 if (formId == null) 322 return null; 323 324 return (List)_formComponents.get(formId); 325 } 326 327 /** 328 * {@inheritDoc} 329 */ 330 public void resetEventDidOccur() 331 { 332 _components.clear(); 333 _formComponents.clear(); 334 _propertyCache.clear(); 335 } 336 337 /** Injected. */ 338 public void setInvoker(ListenerInvoker invoker) 339 { 340 _invoker = invoker; 341 } 342 }