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