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 }