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 @Override
169 public void run()
170 {
171 listener.containingPageDidLoad();
172 }
173 });
174
175 addPageAttachedCallback(new Runnable()
176 {
177 @Override
178 public void run()
179 {
180 listener.containingPageDidAttach();
181 }
182 });
183
184 addPageDetachedCallback(new Runnable()
185 {
186 @Override
187 public void run()
188 {
189 listener.containingPageDidDetach();
190 }
191 });
192 }
193
194 public void removeLifecycleListener(PageLifecycleListener listener)
195 {
196 lifecycleListenersLock.check();
197
198 throw new UnsupportedOperationException("It is not longer possible to remove a page lifecycle listener; please convert your code to use the addPageLoadedCallback() method instead.");
199 }
200
201 public boolean detached()
202 {
203 boolean result = false;
204
205 for (Runnable callback : detachCallbacks)
206 {
207 try
208 {
209 callback.run();
210 } catch (RuntimeException ex)
211 {
212 result = true;
213
214 getLogger().error(String.format("Callback %s failed during page detach: %s", callback, InternalUtils.toMessage(ex)), ex);
215 }
216 }
217
218 return result;
219 }
220
221 public void loaded()
222 {
223 lifecycleListenersLock.lock();
224
225 invokeCallbacks(loadedCallbacks);
226
227 loadedCallbacks = null;
228
229 verifyListenerLocks.lock();
230
231 invokeCallbacks(pageVerifyCallbacks);
232
233 pageVerifyCallbacks = null;
234
235 loadComplete = true;
236 }
237
238 public void attached()
239 {
240 attachCount.incrementAndGet();
241
242 invokeCallbacks(attachCallbacks);
243 }
244
245 public Logger getLogger()
246 {
247 return rootElement.getLogger();
248 }
249
250 public void persistFieldChange(ComponentResources resources, String fieldName, Object newValue)
251 {
252 if (!loadComplete)
253 {
254 throw new RuntimeException(StructureMessages.persistChangeBeforeLoadComplete());
255 }
256
257 persistentFieldManager.postChange(name, resources, fieldName, newValue);
258 }
259
260 public Object getFieldChange(String nestedId, String fieldName)
261 {
262 if (!fieldBundle.exists())
263 {
264 fieldBundle.set(persistentFieldManager.gatherChanges(name));
265 }
266
267 return fieldBundle.get().getValue(nestedId, fieldName);
268 }
269
270 public void discardPersistentFieldChanges()
271 {
272 persistentFieldManager.discardChanges(name);
273 }
274
275 public String getName()
276 {
277 return name;
278 }
279
280 @Override
281 public void addResetCallback(Runnable callback)
282 {
283 assert callback != null;
284
285 lifecycleListenersLock.check();
286
287 resetCallbacks.add(callback);
288 }
289
290 public void addResetListener(final PageResetListener listener)
291 {
292 assert listener != null;
293
294 addResetCallback(new Runnable()
295 {
296 @Override
297 public void run()
298 {
299 listener.containingPageDidReset();
300 }
301 });
302 }
303
304 public void addVerifyCallback(Runnable callback)
305 {
306 verifyListenerLocks.check();
307
308 assert callback != null;
309
310 pageVerifyCallbacks.add(callback);
311 }
312
313 public void pageReset()
314 {
315 invokeCallbacks(resetCallbacks);
316 }
317
318 public boolean hasResetListeners()
319 {
320 return !resetCallbacks.isEmpty();
321 }
322
323 public int getAttachCount()
324 {
325 return attachCount.get();
326 }
327
328 @Override
329 public void addPageLoadedCallback(Runnable callback)
330 {
331 lifecycleListenersLock.check();
332
333 assert callback != null;
334
335 loadedCallbacks.add(callback);
336 }
337
338 @Override
339 public void addPageAttachedCallback(Runnable callback)
340 {
341 lifecycleListenersLock.check();
342
343 assert callback != null;
344
345 attachCallbacks.add(callback);
346 }
347
348 @Override
349 public void addPageDetachedCallback(Runnable callback)
350 {
351 lifecycleListenersLock.check();
352
353 assert callback != null;
354
355 detachCallbacks.add(callback);
356 }
357
358 private void invokeCallbacks(List<Runnable> callbacks)
359 {
360 for (Runnable callback : callbacks)
361 {
362 callback.run();
363 }
364 }
365 }