001    // Copyright 2006, 2007, 2008, 2009, 2010, 2011 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.tapestry5.internal.structure;
016    
017    import org.apache.tapestry5.ComponentResources;
018    import org.apache.tapestry5.ioc.services.PerthreadManager;
019    import org.apache.tapestry5.runtime.Component;
020    import org.apache.tapestry5.runtime.PageLifecycleListener;
021    import org.apache.tapestry5.services.pageload.ComponentResourceSelector;
022    import org.slf4j.Logger;
023    
024    /**
025     * Represents a unique page within the application. Pages are part of the <em>internal</em> structure of a Tapestry
026     * application; end developers who refer to "page" are really referring to the {@link #getRootComponent() root
027     * component} of the actual page.
028     * <p/>
029     * Starting in release 5.2, the nature of pages changed considerably. Pages are no longer pooled instances. Instead,
030     * pages are shared instances (per locale) but all internal <em>mutable</em> state is stored inside
031     * {@link PerthreadManager}. Page construction time is considered to extend past the
032     * {@link PageLifecycleListener#containingPageDidLoad()} lifecycle notification. This is not quite perfect from a
033     * threading point-of-view (arguably, even write-once-read-many fields should be protected by synchronized blocks or
034     * other mechanisms). At best, we can be assured that the entire page construction phase is protected by a single
035     * synchronized block (but not on the page itself). An ideal system would build the page bottom to top so that all
036     * assignments could take place in constructors, assigning to final fields. Maybe some day.
037     * <p/>
038     * The Page object is never visible to end-user code. The page also exists to provide a kind of service to components
039     * embedded (directly or indirectly) within the page.
040     */
041    public interface Page
042    {
043        /**
044         * Page construction statistics for the page.
045         *
046         * @since 5.3
047         */
048        public final class Stats
049        {
050            /**
051             * Time, in milliseconds, to construct the page. This includes time to construct components inside the page,
052             * as well as hooking everything together. You'll often see that the first page is expensive to construct,
053             * and later pages that use a similar mix of components are very cheap.
054             */
055            public final long assemblyTime;
056    
057            /**
058             * The total number of components in the page, including the root component. This does not include the number of mixins.
059             */
060            public final int componentCount;
061    
062            /**
063             * The "weight" of the page is an arbitrary number that factors the number of components, mixins, component template elements,
064             * bindings, and other factors.
065             */
066            public final int weight;
067    
068            public Stats(long assemblyTime, int componentCount, int weight)
069            {
070                this.assemblyTime = assemblyTime;
071                this.componentCount = componentCount;
072                this.weight = weight;
073            }
074        }
075    
076        /**
077         * Returns the short, logical name for the page. This is the page name as it might included in
078         * an action or page
079         * render URL (though it will be converted to lower case when it is included).
080         */
081        String getName();
082    
083        /**
084         * The selector (which includes Locale) used when the page was constructor.
085         */
086        ComponentResourceSelector getSelector();
087    
088        /**
089         * Invoked during page construction time to connect the page's root component to the page
090         * instance.
091         */
092        void setRootElement(ComponentPageElement component);
093    
094        /**
095         * The root component of the page. This is the wrapper around the end developer's view of the
096         * page.
097         */
098        ComponentPageElement getRootElement();
099    
100        /**
101         * The root component of the page. A convenience over invoking getRootElement().getComponent().
102         */
103        Component getRootComponent();
104    
105        /**
106         * Invoked to inform the page that it is being detached from the current request. This occurs
107         * just before the page
108         * is returned to the page pool.
109         * <p/>
110         * A page may be clean or dirty. A page is dirty if its dirty count is greater than zero (meaning that, during the
111         * render of the page, some components did not fully render), or if any of its listeners throw an exception from
112         * containingPageDidDetach().
113         * <p/>
114         * The page pool should discard pages that are dirty, rather than store them into the pool.
115         * <p/>
116         * Under Tapestry 5.2 and pool-less pages, the result is ignored; all mutable state is expected to be discarded
117         * automatically from the {@link PerthreadManager}. A future release of Tapestry will likely convert this method to
118         * type void.
119         *
120         * @return true if the page is "dirty", false otherwise
121         * @see org.apache.tapestry5.runtime.PageLifecycleListener#containingPageDidDetach()
122         */
123        boolean detached();
124    
125        /**
126         * Invoked to inform the page that it is attached to the current request. This occurs when a
127         * page is first referenced within a request. If the page was created from scratch for this request, the call
128         * to {@link #loaded()} will preceded the call to {@link #attached()}.
129         * <p/>
130         * First all listeners have {@link PageLifecycleListener#restoreStateBeforePageAttach()} invoked, followed by
131         * {@link PageLifecycleListener#containingPageDidAttach()}.
132         */
133        void attached();
134    
135        /**
136         * Inform the page that it is now completely loaded.
137         *
138         * @see org.apache.tapestry5.runtime.PageLifecycleListener#containingPageDidLoad()
139         */
140        void loaded();
141    
142        /**
143         * Adds a listener that is notified of large scale page events.
144         */
145        void addLifecycleListener(PageLifecycleListener listener);
146    
147        /**
148         * Removes a listener that was previously added.
149         *
150         * @since 5.2.0
151         */
152        void removeLifecycleListener(PageLifecycleListener listener);
153    
154        /**
155         * Returns the logger of the root component element. Any logging about page construction or
156         * activity should be sent
157         * to this logger.
158         */
159        Logger getLogger();
160    
161        /**
162         * Retrieves a component element by its nested id (a sequence of simple ids, separated by dots).
163         * The individual
164         * names in the nested id are matched without regards to case. A nested id of '' (the empty
165         * string) returns the root
166         * element of the page.
167         *
168         * @throws IllegalArgumentException if the nestedId does not correspond to a component
169         */
170        ComponentPageElement getComponentElementByNestedId(String nestedId);
171    
172        /**
173         * Posts a change to a persistent field.
174         *
175         * @param resources the component resources for the component or mixin containing the field whose
176         *                  value changed
177         * @param fieldName the name of the field
178         * @param newValue  the new value for the field
179         */
180        void persistFieldChange(ComponentResources resources, String fieldName, Object newValue);
181    
182        /**
183         * Gets a change for a field within the component.
184         *
185         * @param nestedId  the nested component id of the component containing the field
186         * @param fieldName the name of the persistent field
187         * @return the value, or null if no value is stored
188         */
189        Object getFieldChange(String nestedId, String fieldName);
190    
191        /**
192         * Discards all persistent field changes for the page containing the component. Changes are
193         * eliminated from
194         * persistent storage (such as the {@link org.apache.tapestry5.services.Session}) which will
195         * take effect in the <em>next</em> request (the attached page instance is not affected).
196         */
197        void discardPersistentFieldChanges();
198    
199        /**
200         * Adds a new listener for page reset events.
201         *
202         * @param listener will receive notifications when the page is accessed from a different page
203         * @since 5.2.0
204         */
205        void addResetListener(PageResetListener listener);
206    
207        /**
208         * Adds a verify callback, which is allowed while the page is loading. Such callbacks are invoked once,
209         * after the page has been loaded succesfully. This was added specifically to ensure that components
210         * only verify that required parameters are bound after all components and mixins of the page have had a chance
211         * to initialize.
212         *
213         * @param callback to be invoked after page loaded
214         * @since 5.3
215         */
216        void addVerifyListener(Runnable callback);
217    
218        /**
219         * Returns true if there are any {@link PageResetListener} listeners.
220         *
221         * @since 5.2.0
222         */
223        boolean hasResetListeners();
224    
225        /**
226         * Invoked to notify {@link PageResetListener} listeners.
227         */
228        void pageReset();
229    
230    
231        /**
232         * Invoked once at the end of page construction, to provide page construction statistics.
233         *
234         * @since 5.3
235         */
236        void setStats(Stats stats);
237    
238        /**
239         * Returns the page construction statistics for this page.
240         */
241        Stats getStats();
242    
243        /**
244         * Returns the number of times the page has been attached to a request. This is a rough measure
245         * of how important the page is, relative to other pages. This value is volatile, changing constantly.
246         *
247         * @since 5.3
248         */
249        int getAttachCount();
250    
251    
252    }