    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.ObjectCreator;
    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 ObjectCreator<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(new ObjectCreator<PersistentFieldBundle>() {
    100            @Override
    101            public PersistentFieldBundle createObject() {
    102                return PageImpl.this.persistentFieldManager.gatherChanges(PageImpl.this.name);
    103            }
    104        });
    105
    106
    107        exactParameterCountMatch = metaDataLocator.findMeta(MetaDataConstants.UNKNOWN_ACTIVATION_CONTEXT_CHECK, name, Boolean.class);
    108    }
    109
    110    public void setStats(Stats stats)
    111    {
    112        this.stats = stats;
    113    }
    114
    115    public Stats getStats()
    116    {
    117        return stats;
    118    }
    119
    120    @Override
    121    public String toString()
    122    {
    123        return String.format("Page[%s %s]", name, selector.toShortString());
    124    }
    125
    126    public ComponentPageElement getComponentElementByNestedId(String nestedId)
    127    {
    128        assert nestedId != null;
    129
    130        if (nestedId.equals(""))
    131        {
    132            return rootElement;
    133        }
    134
    135        ComponentPageElement element = idToComponent.get(nestedId);
    136
    137        if (element == null)
    138        {
    139            element = rootElement;
    140
    141            for (String id : SPLIT_ON_DOT.split(nestedId))
    142            {
    143                element = element.getEmbeddedElement(id);
    144            }
    145
    146            idToComponent.put(nestedId, element);
    147        }
    148
    149        return element;
    150    }
    151
    152    public ComponentResourceSelector getSelector()
    153    {
    154        return selector;
    155    }
    156
    157    public void setRootElement(ComponentPageElement component)
    158    {
    159        lifecycleListenersLock.check();
    160
    161        rootElement = component;
    162    }
    163
    164    public ComponentPageElement getRootElement()
    165    {
    166        return rootElement;
    167    }
    168
    169    public Component getRootComponent()
    170    {
    171        return rootElement.getComponent();
    172    }
    173
    174    public void addLifecycleListener(final PageLifecycleListener listener)
    175    {
    176        assert listener != null;
    177
    178        addPageLoadedCallback(new Runnable()
    179        {
    180            public void run()
    181            {
    182                listener.containingPageDidLoad();
    183            }
    184        });
    185
    186        addPageAttachedCallback(new Runnable()
    187        {
    188            public void run()
    189            {
    190                listener.containingPageDidAttach();
    191            }
    192        });
    193
    194        addPageDetachedCallback(new Runnable()
    195        {
    196            public void run()
    197            {
    198                listener.containingPageDidDetach();
    199            }
    200        });
    201    }
    202
    203    public void removeLifecycleListener(PageLifecycleListener listener)
    204    {
    205        lifecycleListenersLock.check();
    206
    207        throw new UnsupportedOperationException("It is not longer possible to remove a page lifecycle listener; please convert your code to use the addPageLoadedCallback() method instead.");
    208    }
    209
    210    public boolean detached()
    211    {
    212        boolean result = false;
    213
    214        for (Runnable callback : detachCallbacks)
    215        {
    216            try
    217            {
    218                callback.run();
    219            } catch (RuntimeException ex)
    220            {
    221                result = true;
    222
    223                getLogger().error(String.format("Callback %s failed during page detach: %s", callback, ExceptionUtils.toMessage(ex)), ex);
    224            }
    225        }
    226
    227        return result;
    228    }
    229
    230    public void loaded()
    231    {
    232        lifecycleListenersLock.lock();
    233
    234        invokeCallbacks(loadedCallbacks);
    235
    236        loadedCallbacks = null;
    237
    238        verifyListenerLocks.lock();
    239
    240        invokeCallbacks(pageVerifyCallbacks);
    241
    242        pageVerifyCallbacks = null;
    243
    244        loadComplete = true;
    245    }
    246
    247    public void attached()
    248    {
    249        attachCount.incrementAndGet();
    250
    251        invokeCallbacks(attachCallbacks);
    252    }
    253
    254    public Logger getLogger()
    255    {
    256        return rootElement.getLogger();
    257    }
    258
    259    public void persistFieldChange(ComponentResources resources, String fieldName, Object newValue)
    260    {
    261        if (!loadComplete)
    262        {
    263            throw new RuntimeException(StructureMessages.persistChangeBeforeLoadComplete());
    264        }
    265
    266        persistentFieldManager.postChange(name, resources, fieldName, newValue);
    267    }
    268
    269    public Object getFieldChange(String nestedId, String fieldName)
    270    {
    271        return fieldBundle.createObject().getValue(nestedId, fieldName);
    272    }
    273
    274    public void discardPersistentFieldChanges()
    275    {
    276        persistentFieldManager.discardChanges(name);
    277    }
    278
    279    public String getName()
    280    {
    281        return name;
    282    }
    283
    284    public void addResetCallback(Runnable callback)
    285    {
    286        assert callback != null;
    287
    288        lifecycleListenersLock.check();
    289
    290        resetCallbacks.add(callback);
    291    }
    292
    293    public void addResetListener(final PageResetListener listener)
    294    {
    295        assert listener != null;
    296
    297        addResetCallback(new Runnable()
    298        {
    299            public void run()
    300            {
    301                listener.containingPageDidReset();
    302            }
    303        });
    304    }
    305
    306    public void addVerifyCallback(Runnable callback)
    307    {
    308        verifyListenerLocks.check();
    309
    310        assert callback != null;
    311
    312        pageVerifyCallbacks.add(callback);
    313    }
    314
    315    public void pageReset()
    316    {
    317        invokeCallbacks(resetCallbacks);
    318    }
    319
    320    public boolean hasResetListeners()
    321    {
    322        return !resetCallbacks.isEmpty();
    323    }
    324
    325    public int getAttachCount()
    326    {
    327        return attachCount.get();
    328    }
    329
    330    public boolean isExactParameterCountMatch()
    331    {
    332        return exactParameterCountMatch;
    333    }
    334
    335    public void addPageLoadedCallback(Runnable callback)
    336    {
    337        lifecycleListenersLock.check();
    338
    339        assert callback != null;
    340
    341        loadedCallbacks.add(callback);
    342    }
    343
    344    public void addPageAttachedCallback(Runnable callback)
    345    {
    346        lifecycleListenersLock.check();
    347
    348        assert callback != null;
    349
    350        attachCallbacks.add(callback);
    351    }
    352
    353    public void addPageDetachedCallback(Runnable callback)
    354    {
    355        lifecycleListenersLock.check();
    356
    357        assert callback != null;
    358
    359        detachCallbacks.add(callback);
    360    }
    361
    362    private void invokeCallbacks(List<Runnable> callbacks)
    363    {
    364        for (Runnable callback : callbacks)
    365        {
    366            callback.run();
    367        }
    368    }
    369}