001 // Copyright 2006, 2007, 2008, 2009, 2010, 2011, 2012 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.InternalUtils;
021 import org.apache.tapestry5.ioc.internal.util.OneShotLock;
022 import org.apache.tapestry5.ioc.services.PerThreadValue;
023 import org.apache.tapestry5.ioc.services.PerthreadManager;
024 import org.apache.tapestry5.runtime.Component;
025 import org.apache.tapestry5.runtime.PageLifecycleListener;
026 import org.apache.tapestry5.services.PersistentFieldBundle;
027 import org.apache.tapestry5.services.pageload.ComponentResourceSelector;
028 import org.slf4j.Logger;
029
030 import java.util.List;
031 import java.util.Map;
032 import java.util.concurrent.atomic.AtomicInteger;
033 import java.util.regex.Pattern;
034
035 public class PageImpl implements Page
036 {
037 private final String name;
038
039 private final ComponentResourceSelector selector;
040
041 private final PersistentFieldManager persistentFieldManager;
042
043 private ComponentPageElement rootElement;
044
045 private List<Runnable> loadedCallbacks = CollectionFactory.newList();
046
047 private final List<Runnable> attachCallbacks = CollectionFactory.newList();
048
049 private final List<Runnable> detachCallbacks = CollectionFactory.newList();
050
051 private final List<Runnable> resetCallbacks = CollectionFactory.newList();
052
053 private boolean loadComplete;
054
055 private final OneShotLock lifecycleListenersLock = new OneShotLock();
056
057 private final OneShotLock verifyListenerLocks = new OneShotLock();
058
059 // May end up with multiple mappings for the same id (with different case) to the same component.
060 // That's OK.
061 private final Map<String, ComponentPageElement> idToComponent = CollectionFactory.newConcurrentMap();
062
063 private Stats stats;
064
065 private final AtomicInteger attachCount = new AtomicInteger();
066
067 private List<Runnable> pageVerifyCallbacks = CollectionFactory.newList();
068
069 /**
070 * Obtained from the {@link org.apache.tapestry5.internal.services.PersistentFieldManager} when
071 * first needed,
072 * discarded at the end of the request.
073 */
074 private final PerThreadValue<PersistentFieldBundle> fieldBundle;
075
076 private static final Pattern SPLIT_ON_DOT = Pattern.compile("\\.");
077
078 /**
079 * @param name
080 * canonicalized page name
081 * @param selector
082 * used to locate resources
083 * @param persistentFieldManager
084 * for access to cross-request persistent values
085 * @param perThreadManager
086 * for managing per-request mutable state
087 */
088 public PageImpl(String name, ComponentResourceSelector selector, PersistentFieldManager persistentFieldManager,
089 PerthreadManager perThreadManager)
090 {
091 this.name = name;
092 this.selector = selector;
093 this.persistentFieldManager = persistentFieldManager;
094
095 fieldBundle = perThreadManager.createValue();
096 }
097
098 public void setStats(Stats stats)
099 {
100 this.stats = stats;
101 }
102
103 public Stats getStats()
104 {
105 return stats;
106 }
107
108 @Override
109 public String toString()
110 {
111 return String.format("Page[%s %s]", name, selector.toShortString());
112 }
113
114 public ComponentPageElement getComponentElementByNestedId(String nestedId)
115 {
116 assert nestedId != null;
117
118 if (nestedId.equals(""))
119 {
120 return rootElement;
121 }
122
123 ComponentPageElement element = idToComponent.get(nestedId);
124
125 if (element == null)
126 {
127 element = rootElement;
128
129 for (String id : SPLIT_ON_DOT.split(nestedId))
130 {
131 element = element.getEmbeddedElement(id);
132 }
133
134 idToComponent.put(nestedId, element);
135 }
136
137 return element;
138 }
139
140 public ComponentResourceSelector getSelector()
141 {
142 return selector;
143 }
144
145 public void setRootElement(ComponentPageElement component)
146 {
147 lifecycleListenersLock.check();
148
149 rootElement = component;
150 }
151
152 public ComponentPageElement getRootElement()
153 {
154 return rootElement;
155 }
156
157 public Component getRootComponent()
158 {
159 return rootElement.getComponent();
160 }
161
162 public void addLifecycleListener(final PageLifecycleListener listener)
163 {
164 assert listener != null;
165
166 addPageLoadedCallback(new Runnable()
167 {
168 public void run()
169 {
170 listener.containingPageDidLoad();
171 }
172 });
173
174 addPageAttachedCallback(new Runnable()
175 {
176 public void run()
177 {
178 listener.containingPageDidAttach();
179 }
180 });
181
182 addPageDetachedCallback(new Runnable()
183 {
184 public void run()
185 {
186 listener.containingPageDidDetach();
187 }
188 });
189 }
190
191 public void removeLifecycleListener(PageLifecycleListener listener)
192 {
193 lifecycleListenersLock.check();
194
195 throw new UnsupportedOperationException("It is not longer possible to remove a page lifecycle listener; please convert your code to use the addPageLoadedCallback() method instead.");
196 }
197
198 public boolean detached()
199 {
200 boolean result = false;
201
202 for (Runnable callback : detachCallbacks)
203 {
204 try
205 {
206 callback.run();
207 } catch (RuntimeException ex)
208 {
209 result = true;
210
211 getLogger().error(String.format("Callback %s failed during page detach: %s", callback, InternalUtils.toMessage(ex)), ex);
212 }
213 }
214
215 return result;
216 }
217
218 public void loaded()
219 {
220 lifecycleListenersLock.lock();
221
222 invokeCallbacks(loadedCallbacks);
223
224 loadedCallbacks = null;
225
226 verifyListenerLocks.lock();
227
228 invokeCallbacks(pageVerifyCallbacks);
229
230 pageVerifyCallbacks = null;
231
232 loadComplete = true;
233 }
234
235 public void attached()
236 {
237 attachCount.incrementAndGet();
238
239 invokeCallbacks(attachCallbacks);
240 }
241
242 public Logger getLogger()
243 {
244 return rootElement.getLogger();
245 }
246
247 public void persistFieldChange(ComponentResources resources, String fieldName, Object newValue)
248 {
249 if (!loadComplete)
250 {
251 throw new RuntimeException(StructureMessages.persistChangeBeforeLoadComplete());
252 }
253
254 persistentFieldManager.postChange(name, resources, fieldName, newValue);
255 }
256
257 public Object getFieldChange(String nestedId, String fieldName)
258 {
259 if (!fieldBundle.exists())
260 {
261 fieldBundle.set(persistentFieldManager.gatherChanges(name));
262 }
263
264 return fieldBundle.get().getValue(nestedId, fieldName);
265 }
266
267 public void discardPersistentFieldChanges()
268 {
269 persistentFieldManager.discardChanges(name);
270 }
271
272 public String getName()
273 {
274 return name;
275 }
276
277 public void addResetCallback(Runnable callback)
278 {
279 assert callback != null;
280
281 lifecycleListenersLock.check();
282
283 resetCallbacks.add(callback);
284 }
285
286 public void addResetListener(final PageResetListener listener)
287 {
288 assert listener != null;
289
290 addResetCallback(new Runnable()
291 {
292 public void run()
293 {
294 listener.containingPageDidReset();
295 }
296 });
297 }
298
299 public void addVerifyCallback(Runnable callback)
300 {
301 verifyListenerLocks.check();
302
303 assert callback != null;
304
305 pageVerifyCallbacks.add(callback);
306 }
307
308 public void pageReset()
309 {
310 invokeCallbacks(resetCallbacks);
311 }
312
313 public boolean hasResetListeners()
314 {
315 return !resetCallbacks.isEmpty();
316 }
317
318 public int getAttachCount()
319 {
320 return attachCount.get();
321 }
322
323 public void addPageLoadedCallback(Runnable callback)
324 {
325 lifecycleListenersLock.check();
326
327 assert callback != null;
328
329 loadedCallbacks.add(callback);
330 }
331
332 public void addPageAttachedCallback(Runnable callback)
333 {
334 lifecycleListenersLock.check();
335
336 assert callback != null;
337
338 attachCallbacks.add(callback);
339 }
340
341 public void addPageDetachedCallback(Runnable callback)
342 {
343 lifecycleListenersLock.check();
344
345 assert callback != null;
346
347 detachCallbacks.add(callback);
348 }
349
350 private void invokeCallbacks(List<Runnable> callbacks)
351 {
352 for (Runnable callback : callbacks)
353 {
354 callback.run();
355 }
356 }
357 }