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.internal.services.PersistentFieldManager;
019 import org.apache.tapestry5.ioc.internal.util.CollectionFactory;
020 import org.apache.tapestry5.ioc.internal.util.OneShotLock;
021 import org.apache.tapestry5.ioc.services.PerThreadValue;
022 import org.apache.tapestry5.ioc.services.PerthreadManager;
023 import org.apache.tapestry5.runtime.Component;
024 import org.apache.tapestry5.runtime.PageLifecycleListener;
025 import org.apache.tapestry5.services.PersistentFieldBundle;
026 import org.apache.tapestry5.services.pageload.ComponentResourceSelector;
027 import org.slf4j.Logger;
028
029 import java.util.List;
030 import java.util.Map;
031 import java.util.concurrent.atomic.AtomicInteger;
032 import java.util.regex.Pattern;
033
034 public class PageImpl implements Page
035 {
036 private final String name;
037
038 private final ComponentResourceSelector selector;
039
040 private final PersistentFieldManager persistentFieldManager;
041
042 private ComponentPageElement rootElement;
043
044 private final List<PageLifecycleListener> lifecycleListeners = CollectionFactory.newThreadSafeList();
045
046 private final List<PageResetListener> resetListeners = CollectionFactory.newList();
047
048 private boolean loadComplete;
049
050 private final OneShotLock lock = new OneShotLock();
051
052 private final Map<String, ComponentPageElement> idToComponent = CollectionFactory.newCaseInsensitiveMap();
053
054 private Stats stats;
055
056 private final AtomicInteger attachCount = new AtomicInteger();
057
058 private List<Runnable> pageVerifyCallbacks = CollectionFactory.newList();
059
060 /**
061 * Obtained from the {@link org.apache.tapestry5.internal.services.PersistentFieldManager} when
062 * first needed,
063 * discarded at the end of the request.
064 */
065 private final PerThreadValue<PersistentFieldBundle> fieldBundle;
066
067 private static final Pattern SPLIT_ON_DOT = Pattern.compile("\\.");
068
069 /**
070 * @param name canonicalized page name
071 * @param selector used to locate resources
072 * @param persistentFieldManager for access to cross-request persistent values
073 * @param perThreadManager for managing per-request mutable state
074 */
075 public PageImpl(String name, ComponentResourceSelector selector, PersistentFieldManager persistentFieldManager,
076 PerthreadManager perThreadManager)
077 {
078 this.name = name;
079 this.selector = selector;
080 this.persistentFieldManager = persistentFieldManager;
081
082 fieldBundle = perThreadManager.createValue();
083 }
084
085 public void setStats(Stats stats)
086 {
087 this.stats = stats;
088 }
089
090 public Stats getStats()
091 {
092 return stats;
093 }
094
095 @Override
096 public String toString()
097 {
098 return String.format("Page[%s %s]", name, selector.toShortString());
099 }
100
101 public synchronized ComponentPageElement getComponentElementByNestedId(String nestedId)
102 {
103 assert nestedId != null;
104
105 if (nestedId.equals(""))
106 return rootElement;
107
108 ComponentPageElement element = idToComponent.get(nestedId);
109
110 if (element == null)
111 {
112 element = rootElement;
113
114 for (String id : SPLIT_ON_DOT.split(nestedId))
115 {
116 element = element.getEmbeddedElement(id);
117 }
118
119 idToComponent.put(nestedId, element);
120 }
121
122 return element;
123 }
124
125 public ComponentResourceSelector getSelector()
126 {
127 return selector;
128 }
129
130 public void setRootElement(ComponentPageElement component)
131 {
132 lock.check();
133
134 rootElement = component;
135 }
136
137 public ComponentPageElement getRootElement()
138 {
139 return rootElement;
140 }
141
142 public Component getRootComponent()
143 {
144 return rootElement.getComponent();
145 }
146
147 public void addLifecycleListener(PageLifecycleListener listener)
148 {
149 lock.check();
150
151 lifecycleListeners.add(listener);
152 }
153
154 public void removeLifecycleListener(PageLifecycleListener listener)
155 {
156 lock.check();
157
158 lifecycleListeners.remove(listener);
159 }
160
161 public boolean detached()
162 {
163 boolean result = false;
164
165 for (PageLifecycleListener listener : lifecycleListeners)
166 {
167 try
168 {
169 listener.containingPageDidDetach();
170 } catch (RuntimeException ex)
171 {
172 getLogger().error(StructureMessages.detachFailure(listener, ex), ex);
173 result = true;
174 }
175 }
176
177 return result;
178 }
179
180 public void loaded()
181 {
182 lock.check();
183
184 for (PageLifecycleListener listener : lifecycleListeners)
185 {
186 listener.containingPageDidLoad();
187 }
188
189 lock.lock();
190
191
192 for (Runnable callback : pageVerifyCallbacks)
193 {
194 callback.run();
195 }
196
197 // These are never needed again, so we can get rid of them. The PageLifecycleListener interface is too complicated,
198 // we don't know what's needed when, and rely on the listeners themselves to unregister when no longer needed. A better design
199 // would be more like these pageVerifyCallbacks: just use Runnable and know when it is safe to throw them away. Something
200 // to refactor to over time.
201
202 pageVerifyCallbacks = null;
203
204 loadComplete = true;
205 }
206
207 public void attached()
208 {
209 attachCount.incrementAndGet();
210
211 for (PageLifecycleListener listener : lifecycleListeners)
212 listener.restoreStateBeforePageAttach();
213
214 for (PageLifecycleListener listener : lifecycleListeners)
215 listener.containingPageDidAttach();
216 }
217
218 public Logger getLogger()
219 {
220 return rootElement.getLogger();
221 }
222
223 public void persistFieldChange(ComponentResources resources, String fieldName, Object newValue)
224 {
225 if (!loadComplete)
226 throw new RuntimeException(StructureMessages.persistChangeBeforeLoadComplete());
227
228 persistentFieldManager.postChange(name, resources, fieldName, newValue);
229 }
230
231 public Object getFieldChange(String nestedId, String fieldName)
232 {
233 if (!fieldBundle.exists())
234 fieldBundle.set(persistentFieldManager.gatherChanges(name));
235
236 return fieldBundle.get().getValue(nestedId, fieldName);
237 }
238
239 public void discardPersistentFieldChanges()
240 {
241 persistentFieldManager.discardChanges(name);
242 }
243
244 public String getName()
245 {
246 return name;
247 }
248
249 public void addResetListener(PageResetListener listener)
250 {
251 assert listener != null;
252 lock.check();
253
254 resetListeners.add(listener);
255 }
256
257 public void addVerifyListener(Runnable callback)
258 {
259 assert callback != null;
260
261 lock.check();
262
263 pageVerifyCallbacks.add(callback);
264 }
265
266 public void pageReset()
267 {
268 for (PageResetListener l : resetListeners)
269 {
270 l.containingPageDidReset();
271 }
272 }
273
274 public boolean hasResetListeners()
275 {
276 return !resetListeners.isEmpty();
277 }
278
279 public int getAttachCount()
280 {
281 return attachCount.get();
282 }
283 }