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                if (!cycle.isRewinding())
246                    cycle.commitPageChanges();
247                
248                builder.render(cycle.isRewinding() ? NullWriter.getSharedInstance() : null, this, cycle);
249            }
250            finally
251            {
252                firePageEndRender();
253            }
254        }
255    
256        public void setChangeObserver(ChangeObserver value)
257        {
258            _changeObserver = value;
259        }
260    
261        /** @since 3.0 * */
262    
263        public void setPageName(String pageName)
264        {
265            if (_pageName != null)
266                throw new ApplicationRuntimeException(Tapestry
267                        .getMessage("AbstractPage.attempt-to-change-name"));
268    
269            _pageName = pageName;
270        }
271    
272        /**
273         * By default, pages are not protected and this method does nothing.
274         */
275    
276        public void validate(IRequestCycle cycle)
277        {
278            Tapestry.addMethodInvocation(Tapestry.ABSTRACTPAGE_VALIDATE_METHOD_ID);
279    
280            firePageValidate();
281        }
282    
283        public IRequestCycle getRequestCycle()
284        {
285            return _requestCycle;
286        }
287    
288        public void addPageDetachListener(PageDetachListener listener)
289        {
290            addListener(PageDetachListener.class, listener);
291        }
292    
293        private void addListener(Class listenerClass, EventListener listener)
294        {
295            if (_listenerList == null)
296                _listenerList = new EventListenerList();
297    
298            _listenerList.add(listenerClass, listener);
299        }
300    
301        /**
302         * @since 2.1-beta-2
303         */
304    
305        private void removeListener(Class listenerClass, EventListener listener)
306        {
307            if (_listenerList != null)
308                _listenerList.remove(listenerClass, listener);
309        }
310    
311        /** @since 4.0 */
312        public void addPageBeginRenderListener(PageBeginRenderListener listener)
313        {
314            addListener(PageBeginRenderListener.class, listener);
315        }
316    
317        /** @since 4.0 */
318        public void addPageEndRenderListener(PageEndRenderListener listener)
319        {
320            addListener(PageEndRenderListener.class, listener);
321        }
322    
323        /** @since 4.0 */
324        public void removePageBeginRenderListener(PageBeginRenderListener listener)
325        {
326            removeListener(PageBeginRenderListener.class, listener);
327        }
328    
329        /** @since 4.0 */
330        public void removePageEndRenderListener(PageEndRenderListener listener)
331        {
332            removeListener(PageEndRenderListener.class, listener);
333        }
334    
335        /**
336         * @since 4.0
337         */
338    
339        public void firePageAttached()
340        {
341            if (_listenerList == null)
342                return;
343    
344            PageEvent event = null;
345            Object[] listeners = _listenerList.getListenerList();
346    
347            for(int i = listeners.length-2; i >= 0; i -= 2) 
348            {
349                if (listeners[i] == PageAttachListener.class)
350                {
351                    PageAttachListener l = (PageAttachListener) listeners[i + 1];
352    
353                    if (event == null)
354                        event = new PageEvent(this, _requestCycle);
355    
356                    l.pageAttached(event);
357                }
358            }
359        }
360    
361        /**
362         * @since 1.0.5
363         */
364    
365        protected void firePageDetached()
366        {
367            if (_listenerList == null)
368                return;
369    
370            PageEvent event = null;
371            Object[] listeners = _listenerList.getListenerList();
372    
373            for (int i = 0; i < listeners.length; i += 2)
374            {
375                if (listeners[i] == PageDetachListener.class)
376                {
377                    PageDetachListener l = (PageDetachListener) listeners[i + 1];
378    
379                    if (event == null)
380                        event = new PageEvent(this, _requestCycle);
381    
382                    l.pageDetached(event);
383                }
384            }
385        }
386    
387        /**
388         * @since 1.0.5
389         */
390    
391        protected void firePageBeginRender()
392        {
393            if (_listenerList == null)
394                return;
395    
396            PageEvent event = null;
397            Object[] listeners = _listenerList.getListenerList();
398    
399            for(int i = listeners.length-2; i >= 0; i -= 2) 
400            {
401                if (listeners[i] == PageBeginRenderListener.class) 
402                {
403                    PageBeginRenderListener l = (PageBeginRenderListener)listeners[i + 1];
404    
405                    if (event == null)
406                        event = new PageEvent(this, _requestCycle);
407    
408                    l.pageBeginRender(event);
409                }
410            }
411        }
412    
413        /**
414         * @since 1.0.5
415         */
416    
417        protected void firePageEndRender()
418        {
419            if (_listenerList == null)
420                return;
421    
422            PageEvent event = null;
423            Object[] listeners = _listenerList.getListenerList();
424    
425            for (int i = 0; i < listeners.length; i += 2)
426            {
427                if (listeners[i] == PageEndRenderListener.class)
428                {
429                    PageEndRenderListener l = (PageEndRenderListener) listeners[i + 1];
430    
431                    if (event == null)
432                        event = new PageEvent(this, _requestCycle);
433    
434                    l.pageEndRender(event);
435                }
436            }
437        }
438    
439        /**
440         * @since 2.1-beta-2
441         */
442    
443        public void removePageDetachListener(PageDetachListener listener)
444        {
445            removeListener(PageDetachListener.class, listener);
446        }
447    
448        /** @since 2.2 * */
449    
450        public void beginPageRender()
451        {
452            firePageBeginRender();
453        }
454    
455        /** @since 2.2 * */
456    
457        public void endPageRender()
458        {
459            firePageEndRender();
460        }
461    
462        /** @since 3.0 * */
463    
464        public String getPageName()
465        {
466            return _pageName;
467        }
468    
469        public void addPageValidateListener(PageValidateListener listener)
470        {
471            addListener(PageValidateListener.class, listener);
472        }
473    
474        public void removePageValidateListener(PageValidateListener listener)
475        {
476            removeListener(PageValidateListener.class, listener);
477        }
478    
479        /** @since 4.0 */
480        public void addPageAttachListener(PageAttachListener listener)
481        {
482            addListener(PageAttachListener.class, listener);
483        }
484    
485        /** @since 4.0 */
486        public void removePageAttachListener(PageAttachListener listener)
487        {
488            removeListener(PageAttachListener.class, listener);
489        }
490    
491        protected void firePageValidate()
492        {
493            if (_listenerList == null)
494                return;
495    
496            PageEvent event = null;
497            Object[] listeners = _listenerList.getListenerList();
498    
499            for (int i = 0; i < listeners.length; i += 2)
500            {
501                if (listeners[i] == PageValidateListener.class)
502                {
503                    PageValidateListener l = (PageValidateListener) listeners[i + 1];
504    
505                    if (event == null)
506                        event = new PageEvent(this, _requestCycle);
507    
508                    l.pageValidate(event);
509                }
510            }
511        }
512    
513        /**
514         * Returns the output encoding to be used when rendering this page. This value is usually cached
515         * from the Engine.
516         * 
517         * @since 3.0
518         */
519        protected String getOutputEncoding()
520        {
521            if (_outputEncoding == null)
522                _outputEncoding = getEngine().getOutputEncoding();
523    
524            return _outputEncoding;
525        }
526    }