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    }