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