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    }