    001// Copyright 2006-2014 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.MetaDataConstants;
    019import org.apache.tapestry5.internal.services.PersistentFieldManager;
    020import org.apache.tapestry5.ioc.internal.util.CollectionFactory;
    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.ioc.util.ExceptionUtils;
    025import org.apache.tapestry5.runtime.Component;
    026import org.apache.tapestry5.runtime.PageLifecycleListener;
    027import org.apache.tapestry5.services.MetaDataLocator;
    028import org.apache.tapestry5.services.PersistentFieldBundle;
    029import org.apache.tapestry5.services.pageload.ComponentResourceSelector;
    030import org.slf4j.Logger;
    031
    032import java.util.List;
    033import java.util.Map;
    034import java.util.concurrent.atomic.AtomicInteger;
    035import java.util.regex.Pattern;
    036
    037public class PageImpl implements Page
    038{
    039    private final String name;
    040
    041    private final ComponentResourceSelector selector;
    042
    043    private final PersistentFieldManager persistentFieldManager;
    044
    045    private ComponentPageElement rootElement;
    046
    047    private List<Runnable> loadedCallbacks = CollectionFactory.newList();
    048
    049    private final List<Runnable> attachCallbacks = CollectionFactory.newList();
    050
    051    private final List<Runnable> detachCallbacks = CollectionFactory.newList();
    052
    053    private final List<Runnable> resetCallbacks = CollectionFactory.newList();
    054
    055    private boolean loadComplete;
    056
    057    private final OneShotLock lifecycleListenersLock = new OneShotLock();
    058
    059    private final OneShotLock verifyListenerLocks = new OneShotLock();
    060
    061    // May end up with multiple mappings for the same id (with different case) to the same component.
    062    // That's OK.
    063    private final Map<String, ComponentPageElement> idToComponent = CollectionFactory.newConcurrentMap();
    064
    065    private Stats stats;
    066
    067    private final AtomicInteger attachCount = new AtomicInteger();
    068
    069    private final boolean exactParameterCountMatch;
    070
    071    private List<Runnable> pageVerifyCallbacks = CollectionFactory.newList();
    072
    073    /**
    074     * Obtained from the {@link org.apache.tapestry5.internal.services.PersistentFieldManager} when
    075     * first needed,
    076     * discarded at the end of the request.
    077     */
    078    private final PerThreadValue<PersistentFieldBundle> fieldBundle;
    079
    080    private static final Pattern SPLIT_ON_DOT = Pattern.compile("\\.");
    081
    082    /**
    083     * @param name
    084     *         canonicalized page name
    085     * @param selector
    086     *         used to locate resources
    087     * @param persistentFieldManager
    088     *         for access to cross-request persistent values
    089     * @param perThreadManager
    090     * @param metaDataLocator
    091     */
    092    public PageImpl(String name, ComponentResourceSelector selector, PersistentFieldManager persistentFieldManager,
    093                    PerthreadManager perThreadManager, MetaDataLocator metaDataLocator)
    094    {
    095        this.name = name;
    096        this.selector = selector;
    097        this.persistentFieldManager = persistentFieldManager;
    098
    099        fieldBundle = perThreadManager.createValue();
    100
    101        exactParameterCountMatch = metaDataLocator.findMeta(MetaDataConstants.UNKNOWN_ACTIVATION_CONTEXT_CHECK, name, Boolean.class);
    102    }
    103
    104    public void setStats(Stats stats)
    105    {
    106        this.stats = stats;
    107    }
    108
    109    public Stats getStats()
    110    {
    111        return stats;
    112    }
    113
    114    @Override
    115    public String toString()
    116    {
    117        return String.format("Page[%s %s]", name, selector.toShortString());
    118    }
    119
    120    public ComponentPageElement getComponentElementByNestedId(String nestedId)
    121    {
    122        assert nestedId != null;
    123
    124        if (nestedId.equals(""))
    125        {
    126            return rootElement;
    127        }
    128
    129        ComponentPageElement element = idToComponent.get(nestedId);
    130
    131        if (element == null)
    132        {
    133            element = rootElement;
    134
    135            for (String id : SPLIT_ON_DOT.split(nestedId))
    136            {
    137                element = element.getEmbeddedElement(id);
    138            }
    139
    140            idToComponent.put(nestedId, element);
    141        }
    142
    143        return element;
    144    }
    145
    146    public ComponentResourceSelector getSelector()
    147    {
    148        return selector;
    149    }
    150
    151    public void setRootElement(ComponentPageElement component)
    152    {
    153        lifecycleListenersLock.check();
    154
    155        rootElement = component;
    156    }
    157
    158    public ComponentPageElement getRootElement()
    159    {
    160        return rootElement;
    161    }
    162
    163    public Component getRootComponent()
    164    {
    165        return rootElement.getComponent();
    166    }
    167
    168    public void addLifecycleListener(final PageLifecycleListener listener)
    169    {
    170        assert listener != null;
    171
    172        addPageLoadedCallback(new Runnable()
    173        {
    174            public void run()
    175            {
    176                listener.containingPageDidLoad();
    177            }
    178        });
    179
    180        addPageAttachedCallback(new Runnable()
    181        {
    182            public void run()
    183            {
    184                listener.containingPageDidAttach();
    185            }
    186        });
    187
    188        addPageDetachedCallback(new Runnable()
    189        {
    190            public void run()
    191            {
    192                listener.containingPageDidDetach();
    193            }
    194        });
    195    }
    196
    197    public void removeLifecycleListener(PageLifecycleListener listener)
    198    {
    199        lifecycleListenersLock.check();
    200
    201        throw new UnsupportedOperationException("It is not longer possible to remove a page lifecycle listener; please convert your code to use the addPageLoadedCallback() method instead.");
    202    }
    203
    204    public boolean detached()
    205    {
    206        boolean result = false;
    207
    208        for (Runnable callback : detachCallbacks)
    209        {
    210            try
    211            {
    212                callback.run();
    213            } catch (RuntimeException ex)
    214            {
    215                result = true;
    216
    217                getLogger().error(String.format("Callback %s failed during page detach: %s", callback, ExceptionUtils.toMessage(ex)), ex);
    218            }
    219        }
    220
    221        return result;
    222    }
    223
    224    public void loaded()
    225    {
    226        lifecycleListenersLock.lock();
    227
    228        invokeCallbacks(loadedCallbacks);
    229
    230        loadedCallbacks = null;
    231
    232        verifyListenerLocks.lock();
    233
    234        invokeCallbacks(pageVerifyCallbacks);
    235
    236        pageVerifyCallbacks = null;
    237
    238        loadComplete = true;
    239    }
    240
    241    public void attached()
    242    {
    243        attachCount.incrementAndGet();
    244
    245        invokeCallbacks(attachCallbacks);
    246    }
    247
    248    public Logger getLogger()
    249    {
    250        return rootElement.getLogger();
    251    }
    252
    253    public void persistFieldChange(ComponentResources resources, String fieldName, Object newValue)
    254    {
    255        if (!loadComplete)
    256        {
    257            throw new RuntimeException(StructureMessages.persistChangeBeforeLoadComplete());
    258        }
    259
    260        persistentFieldManager.postChange(name, resources, fieldName, newValue);
    261    }
    262
    263    public Object getFieldChange(String nestedId, String fieldName)
    264    {
    265        if (!fieldBundle.exists())
    266        {
    267            fieldBundle.set(persistentFieldManager.gatherChanges(name));
    268        }
    269
    270        return fieldBundle.get().getValue(nestedId, fieldName);
    271    }
    272
    273    public void discardPersistentFieldChanges()
    274    {
    275        persistentFieldManager.discardChanges(name);
    276    }
    277
    278    public String getName()
    279    {
    280        return name;
    281    }
    282
    283    public void addResetCallback(Runnable callback)
    284    {
    285        assert callback != null;
    286
    287        lifecycleListenersLock.check();
    288
    289        resetCallbacks.add(callback);
    290    }
    291
    292    public void addResetListener(final PageResetListener listener)
    293    {
    294        assert listener != null;
    295
    296        addResetCallback(new Runnable()
    297        {
    298            public void run()
    299            {
    300                listener.containingPageDidReset();
    301            }
    302        });
    303    }
    304
    305    public void addVerifyCallback(Runnable callback)
    306    {
    307        verifyListenerLocks.check();
    308
    309        assert callback != null;
    310
    311        pageVerifyCallbacks.add(callback);
    312    }
    313
    314    public void pageReset()
    315    {
    316        invokeCallbacks(resetCallbacks);
    317    }
    318
    319    public boolean hasResetListeners()
    320    {
    321        return !resetCallbacks.isEmpty();
    322    }
    323
    324    public int getAttachCount()
    325    {
    326        return attachCount.get();
    327    }
    328
    329    public boolean isExactParameterCountMatch()
    330    {
    331        return exactParameterCountMatch;
    332    }
    333
    334    public void addPageLoadedCallback(Runnable callback)
    335    {
    336        lifecycleListenersLock.check();
    337
    338        assert callback != null;
    339
    340        loadedCallbacks.add(callback);
    341    }
    342
    343    public void addPageAttachedCallback(Runnable callback)
    344    {
    345        lifecycleListenersLock.check();
    346
    347        assert callback != null;
    348
    349        attachCallbacks.add(callback);
    350    }
    351
    352    public void addPageDetachedCallback(Runnable callback)
    353    {
    354        lifecycleListenersLock.check();
    355
    356        assert callback != null;
    357
    358        detachCallbacks.add(callback);
    359    }
    360
    361    private void invokeCallbacks(List<Runnable> callbacks)
    362    {
    363        for (Runnable callback : callbacks)
    364        {
    365            callback.run();
    366        }
    367    }
    368}