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    
015    package org.apache.tapestry;
016    
017    import java.util.EventListener;
018    import java.util.Locale;
019    
020    import javax.swing.event.EventListenerList;
021    
022    import org.apache.commons.logging.Log;
023    import org.apache.commons.logging.LogFactory;
024    import org.apache.hivemind.ApplicationRuntimeException;
025    import org.apache.tapestry.engine.NullWriter;
026    import org.apache.tapestry.event.ChangeObserver;
027    import org.apache.tapestry.event.PageAttachListener;
028    import org.apache.tapestry.event.PageBeginRenderListener;
029    import org.apache.tapestry.event.PageDetachListener;
030    import org.apache.tapestry.event.PageEndRenderListener;
031    import org.apache.tapestry.event.PageEvent;
032    import org.apache.tapestry.event.PageValidateListener;
033    import org.apache.tapestry.services.ResponseBuilder;
034    import org.apache.tapestry.util.StringSplitter;
035    
036    /**
037     * Abstract base class implementing the {@link IPage}interface.
038     * 
039     * @author Howard Lewis Ship, David Solis
040     * @since 0.2.9
041     */
042    
043    public abstract class AbstractPage extends BaseComponent implements IPage
044    {
045        private static final Log LOG = LogFactory.getLog(AbstractPage.class);
046    
047        /**
048         * Object to be notified when a observered property changes. Observered properties are the ones
049         * that will be persisted between request cycles. Unobserved properties are reconstructed.
050         */
051    
052        private ChangeObserver _changeObserver;
053    
054        /**
055         * The {@link IEngine}the page is currently attached to.
056         */
057    
058        private IEngine _engine;
059    
060        /**
061         * The qualified name of the page, which may be prefixed by the namespace.
062         * 
063         * @since 2.3
064         */
065    
066        private String _pageName;
067    
068        /**
069         * Set when the page is attached to the engine.
070         */
071    
072        private IRequestCycle _requestCycle;
073    
074        /**
075         * The locale of the page, initially determined from the {@link IEngine engine}.
076         */
077    
078        private Locale _locale;
079    
080        /**
081         * A list of listeners for the page.
082         * 
083         * @see PageBeginRenderListener
084         * @see PageEndRenderListener
085         * @see PageDetachListener
086         * @since 1.0.5
087         */
088    
089        private EventListenerList _listenerList;
090    
091        /**
092         * The output encoding to be used when rendering this page. This value is cached from the
093         * engine.
094         * 
095         * @since 3.0
096         */
097        private String _outputEncoding;
098    
099        /**
100         * Standard constructor; invokes {@link #initialize()}to configure initial values for
101         * properties of the page.
102         * 
103         * @since 2.2
104         */
105    
106        public AbstractPage()
107        {
108        }
109    
110        /**
111         * Prepares the page to be returned to the pool.
112         * <ul>
113         * <li>Clears the changeObserved property
114         * <li>Invokes {@link PageDetachListener#pageDetached(PageEvent)}on all listeners
115         * <li>Invokes {@link #initialize()}to clear/reset any properties
116         * <li>Clears the engine and requestCycle properties
117         * </ul>
118         * <p>
119         * Subclasses may override this method, but must invoke this implementation (usually, last).
120         * 
121         * @see PageDetachListener
122         */
123    
124        public void detach()
125        {
126            Tapestry.addMethodInvocation(Tapestry.ABSTRACTPAGE_DETACH_METHOD_ID);
127    
128            // Do this first,so that any changes to persistent properties do not
129            // cause errors.
130    
131            _changeObserver = null;
132    
133            firePageDetached();
134    
135            _engine = null;
136            _requestCycle = null;
137        }
138    
139        public IEngine getEngine()
140        {
141            return _engine;
142        }
143    
144        public ChangeObserver getChangeObserver()
145        {
146            return _changeObserver;
147        }
148    
149        /**
150         * Returns the name of the page.
151         */
152    
153        public String getExtendedId()
154        {
155            return _pageName;
156        }
157    
158        /**
159         * Pages always return null for idPath.
160         */
161    
162        public String getIdPath()
163        {
164            return null;
165        }
166    
167        /**
168         * Returns the locale for the page, which may be null if the locale is not known (null
169         * corresponds to the "default locale").
170         */
171    
172        public Locale getLocale()
173        {
174            return _locale;
175        }
176    
177        public void setLocale(Locale value)
178        {
179            if (_locale != null)
180                throw new ApplicationRuntimeException(Tapestry
181                        .getMessage("AbstractPage.attempt-to-change-locale"));
182    
183            _locale = value;
184        }
185    
186        public IComponent getNestedComponent(String path)
187        {
188            StringSplitter splitter;
189            IComponent current;
190            String[] elements;
191            int i;
192    
193            if (path == null)
194                return this;
195    
196            splitter = new StringSplitter('.');
197            current = this;
198    
199            elements = splitter.splitToArray(path);
200            for (i = 0; i < elements.length; i++)
201            {
202                current = current.getComponent(elements[i]);
203            }
204    
205            return current;
206    
207        }
208    
209        /**
210         * Called by the {@link IEngine engine}to attach the page to itself. Does <em>not</em> change
211         * the locale, but since a page is selected from the
212         * {@link org.apache.tapestry.engine.IPageSource}pool based on its locale matching the engine's
213         * locale, they should match anyway.
214         */
215    
216        public void attach(IEngine engine, IRequestCycle cycle)
217        {
218            if (_engine != null)
219                LOG.error(this + " attach(" + engine + "), but engine = " + _engine);
220    
221            _engine = engine;
222            _requestCycle = cycle;
223    
224            firePageAttached();
225        }
226    
227        /**
228         * Renders the page.
229         * <ul>
230         * <li>Invokes {@link PageBeginRenderListener#pageBeginRender(PageEvent)}
231         * <li>Invokes {@link #beginResponse(IMarkupWriter, IRequestCycle)}
232         * <li>Invokes {@link IRequestCycle#commitPageChanges()}(if not rewinding)
233         * <li>Invokes {@link #render(IMarkupWriter, IRequestCycle)}
234         * <li>Invokes {@link PageEndRenderListener#pageEndRender(PageEvent)}(this occurs even if a
235         * previous step throws an exception)
236         * </ul>
237         */
238    
239        public void renderPage(ResponseBuilder builder, IRequestCycle cycle)
240        {
241            try
242            {
243                firePageBeginRender();
244                
245                beginResponse(cycle.isRewinding() ? NullWriter.getSharedInstance() : builder.getWriter(), cycle);
246                
247                if (!cycle.isRewinding())
248                    cycle.commitPageChanges();
249                
250                builder.render(cycle.isRewinding() ? NullWriter.getSharedInstance() : null, this, cycle);
251            }
252            finally
253            {
254                firePageEndRender();
255            }
256        }
257    
258        public void setChangeObserver(ChangeObserver value)
259        {
260            _changeObserver = value;
261        }
262    
263        /** @since 3.0 * */
264    
265        public void setPageName(String pageName)
266        {
267            if (_pageName != null)
268                throw new ApplicationRuntimeException(Tapestry
269                        .getMessage("AbstractPage.attempt-to-change-name"));
270    
271            _pageName = pageName;
272        }
273    
274        /**
275         * By default, pages are not protected and this method does nothing.
276         */
277    
278        public void validate(IRequestCycle cycle)
279        {
280            Tapestry.addMethodInvocation(Tapestry.ABSTRACTPAGE_VALIDATE_METHOD_ID);
281    
282            firePageValidate();
283        }
284    
285        /**
286         * Does nothing, subclasses may override as needed.
287         * 
288         * @deprecated To be removed in 4.0. Implement {@link PageRenderListener}instead.
289         */
290    
291        public void beginResponse(IMarkupWriter writer, IRequestCycle cycle)
292        {
293        }
294    
295        public IRequestCycle getRequestCycle()
296        {
297            return _requestCycle;
298        }
299    
300        public void addPageDetachListener(PageDetachListener listener)
301        {
302            addListener(PageDetachListener.class, listener);
303        }
304    
305        private void addListener(Class listenerClass, EventListener listener)
306        {
307            if (_listenerList == null)
308                _listenerList = new EventListenerList();
309    
310            _listenerList.add(listenerClass, listener);
311        }
312    
313        /**
314         * @since 2.1-beta-2
315         */
316    
317        private void removeListener(Class listenerClass, EventListener listener)
318        {
319            if (_listenerList != null)
320                _listenerList.remove(listenerClass, listener);
321        }
322    
323        /** @since 4.0 */
324        public void addPageBeginRenderListener(PageBeginRenderListener listener)
325        {
326            addListener(PageBeginRenderListener.class, listener);
327        }
328    
329        /** @since 4.0 */
330        public void addPageEndRenderListener(PageEndRenderListener listener)
331        {
332            addListener(PageEndRenderListener.class, listener);
333        }
334    
335        /** @since 4.0 */
336        public void removePageBeginRenderListener(PageBeginRenderListener listener)
337        {
338            removeListener(PageBeginRenderListener.class, listener);
339        }
340    
341        /** @since 4.0 */
342        public void removePageEndRenderListener(PageEndRenderListener listener)
343        {
344            removeListener(PageEndRenderListener.class, listener);
345        }
346    
347        /**
348         * @since 4.0
349         */
350    
351        public void firePageAttached()
352        {
353            if (_listenerList == null)
354                return;
355    
356            PageEvent event = null;
357            Object[] listeners = _listenerList.getListenerList();
358    
359            for(int i = listeners.length-2; i >= 0; i -= 2) 
360            {
361                if (listeners[i] == PageAttachListener.class)
362                {
363                    PageAttachListener l = (PageAttachListener) listeners[i + 1];
364    
365                    if (event == null)
366                        event = new PageEvent(this, _requestCycle);
367    
368                    l.pageAttached(event);
369                }
370            }
371        }
372    
373        /**
374         * @since 1.0.5
375         */
376    
377        protected void firePageDetached()
378        {
379            if (_listenerList == null)
380                return;
381    
382            PageEvent event = null;
383            Object[] listeners = _listenerList.getListenerList();
384    
385            for (int i = 0; i < listeners.length; i += 2)
386            {
387                if (listeners[i] == PageDetachListener.class)
388                {
389                    PageDetachListener l = (PageDetachListener) listeners[i + 1];
390    
391                    if (event == null)
392                        event = new PageEvent(this, _requestCycle);
393    
394                    l.pageDetached(event);
395                }
396            }
397        }
398    
399        /**
400         * @since 1.0.5
401         */
402    
403        protected void firePageBeginRender()
404        {
405            if (_listenerList == null)
406                return;
407    
408            PageEvent event = null;
409            Object[] listeners = _listenerList.getListenerList();
410    
411            for(int i = listeners.length-2; i >= 0; i -= 2) 
412            {
413                if (listeners[i] == PageBeginRenderListener.class) 
414                {
415                    PageBeginRenderListener l = (PageBeginRenderListener)listeners[i + 1];
416    
417                    if (event == null)
418                        event = new PageEvent(this, _requestCycle);
419    
420                    l.pageBeginRender(event);
421                }
422            }
423        }
424    
425        /**
426         * @since 1.0.5
427         */
428    
429        protected void firePageEndRender()
430        {
431            if (_listenerList == null)
432                return;
433    
434            PageEvent event = null;
435            Object[] listeners = _listenerList.getListenerList();
436    
437            for (int i = 0; i < listeners.length; i += 2)
438            {
439                if (listeners[i] == PageEndRenderListener.class)
440                {
441                    PageEndRenderListener l = (PageEndRenderListener) listeners[i + 1];
442    
443                    if (event == null)
444                        event = new PageEvent(this, _requestCycle);
445    
446                    l.pageEndRender(event);
447                }
448            }
449        }
450    
451        /**
452         * @since 2.1-beta-2
453         */
454    
455        public void removePageDetachListener(PageDetachListener listener)
456        {
457            removeListener(PageDetachListener.class, listener);
458        }
459    
460        /** @since 2.2 * */
461    
462        public void beginPageRender()
463        {
464            firePageBeginRender();
465        }
466    
467        /** @since 2.2 * */
468    
469        public void endPageRender()
470        {
471            firePageEndRender();
472        }
473    
474        /** @since 3.0 * */
475    
476        public String getPageName()
477        {
478            return _pageName;
479        }
480    
481        public void addPageValidateListener(PageValidateListener listener)
482        {
483            addListener(PageValidateListener.class, listener);
484        }
485    
486        public void removePageValidateListener(PageValidateListener listener)
487        {
488            removeListener(PageValidateListener.class, listener);
489        }
490    
491        /** @since 4.0 */
492        public void addPageAttachListener(PageAttachListener listener)
493        {
494            addListener(PageAttachListener.class, listener);
495        }
496    
497        /** @since 4.0 */
498        public void removePageAttachListener(PageAttachListener listener)
499        {
500            removeListener(PageAttachListener.class, listener);
501        }
502    
503        protected void firePageValidate()
504        {
505            if (_listenerList == null)
506                return;
507    
508            PageEvent event = null;
509            Object[] listeners = _listenerList.getListenerList();
510    
511            for (int i = 0; i < listeners.length; i += 2)
512            {
513                if (listeners[i] == PageValidateListener.class)
514                {
515                    PageValidateListener l = (PageValidateListener) listeners[i + 1];
516    
517                    if (event == null)
518                        event = new PageEvent(this, _requestCycle);
519    
520                    l.pageValidate(event);
521                }
522            }
523        }
524    
525        /**
526         * Returns the output encoding to be used when rendering this page. This value is usually cached
527         * from the Engine.
528         * 
529         * @since 3.0
530         */
531        protected String getOutputEncoding()
532        {
533            if (_outputEncoding == null)
534                _outputEncoding = getEngine().getOutputEncoding();
535    
536            return _outputEncoding;
537        }
538    }