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
015package org.apache.tapestry5.internal.structure;
016
017import org.apache.tapestry5.ComponentResources;
018import org.apache.tapestry5.internal.services.PersistentFieldManager;
019import org.apache.tapestry5.ioc.internal.util.CollectionFactory;
020import org.apache.tapestry5.ioc.internal.util.InternalUtils;
021import org.apache.tapestry5.ioc.internal.util.OneShotLock;
022import org.apache.tapestry5.ioc.services.PerThreadValue;
023import org.apache.tapestry5.ioc.services.PerthreadManager;
024import org.apache.tapestry5.runtime.Component;
025import org.apache.tapestry5.runtime.PageLifecycleListener;
026import org.apache.tapestry5.services.PersistentFieldBundle;
027import org.apache.tapestry5.services.pageload.ComponentResourceSelector;
028import org.slf4j.Logger;
029
030import java.util.List;
031import java.util.Map;
032import java.util.concurrent.atomic.AtomicInteger;
033import java.util.regex.Pattern;
034
035public 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}