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