    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    }