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
015package org.apache.tapestry;
016
017import java.util.EventListener;
018import java.util.Locale;
019
020import javax.swing.event.EventListenerList;
021
022import org.apache.commons.logging.Log;
023import org.apache.commons.logging.LogFactory;
024import org.apache.hivemind.ApplicationRuntimeException;
025import org.apache.tapestry.event.ChangeObserver;
026import org.apache.tapestry.event.PageAttachListener;
027import org.apache.tapestry.event.PageBeginRenderListener;
028import org.apache.tapestry.event.PageDetachListener;
029import org.apache.tapestry.event.PageEndRenderListener;
030import org.apache.tapestry.event.PageEvent;
031import org.apache.tapestry.event.PageRenderListener;
032import org.apache.tapestry.event.PageValidateListener;
033import 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
042public 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}