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