    001// Copyright 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.services;
    016
    017import org.apache.tapestry5.commons.services.InvalidationEventHub;
    018import org.apache.tapestry5.commons.util.CollectionFactory;
    019import org.apache.tapestry5.func.F;
    020import org.apache.tapestry5.func.Mapper;
    021import org.apache.tapestry5.internal.services.assets.ResourceChangeTracker;
    022import org.apache.tapestry5.internal.structure.Page;
    023import org.apache.tapestry5.ioc.annotations.ComponentClasses;
    024import org.apache.tapestry5.ioc.annotations.PostInjection;
    025import org.apache.tapestry5.services.ComponentMessages;
    026import org.apache.tapestry5.services.ComponentTemplates;
    027import org.apache.tapestry5.services.pageload.ComponentRequestSelectorAnalyzer;
    028import org.apache.tapestry5.services.pageload.ComponentResourceSelector;
    029
    030import java.lang.ref.SoftReference;
    031import java.util.Map;
    032import java.util.Set;
    033
    034public class PageSourceImpl implements PageSource
    035{
    036    private final ComponentRequestSelectorAnalyzer selectorAnalyzer;
    037
    038    private final PageLoader pageLoader;
    039
    040    private static final class CachedPageKey
    041    {
    042        final String pageName;
    043
    044        final ComponentResourceSelector selector;
    045
    046        public CachedPageKey(String pageName, ComponentResourceSelector selector)
    047        {
    048            this.pageName = pageName;
    049            this.selector = selector;
    050        }
    051
    052        public int hashCode()
    053        {
    054            return 37 * pageName.hashCode() + selector.hashCode();
    055        }
    056
    057        public boolean equals(Object obj)
    058        {
    059            if (this == obj)
    060                return true;
    061
    062            if (!(obj instanceof CachedPageKey))
    063                return false;
    064
    065            CachedPageKey other = (CachedPageKey) obj;
    066
    067            return pageName.equals(other.pageName) && selector.equals(other.selector);
    068        }
    069    }
    070
    071    private final Map<CachedPageKey, SoftReference<Page>> pageCache = CollectionFactory.newConcurrentMap();
    072
    073    public PageSourceImpl(PageLoader pageLoader, ComponentRequestSelectorAnalyzer selectorAnalyzer)
    074    {
    075        this.pageLoader = pageLoader;
    076        this.selectorAnalyzer = selectorAnalyzer;
    077    }
    078
    079    public Page getPage(String canonicalPageName)
    080    {
    081        ComponentResourceSelector selector = selectorAnalyzer.buildSelectorForRequest();
    082
    083        CachedPageKey key = new CachedPageKey(canonicalPageName, selector);
    084
    085        // The while loop looks superfluous, but it helps to ensure that the Page instance,
    086        // with all of its mutable construction-time state, is properly published to other
    087        // threads (at least, as I understand Brian Goetz's explanation, it should be).
    088
    089        while (true)
    090        {
    091            SoftReference<Page> ref = pageCache.get(key);
    092
    093            Page page = ref == null ? null : ref.get();
    094
    095            if (page != null)
    096            {
    097                return page;
    098            }
    099
    100            // In rare race conditions, we may see the same page loaded multiple times across
    101            // different threads. The last built one will "evict" the others from the page cache,
    102            // and the earlier ones will be GCed.
    103
    104            page = pageLoader.loadPage(canonicalPageName, selector);
    105
    106            ref = new SoftReference<Page>(page);
    107
    108            pageCache.put(key, ref);
    109        }
    110    }
    111
    112    @PostInjection
    113    public void setupInvalidation(@ComponentClasses InvalidationEventHub classesHub,
    114                                  @ComponentTemplates InvalidationEventHub templatesHub,
    115                                  @ComponentMessages InvalidationEventHub messagesHub,
    116                                  ResourceChangeTracker resourceChangeTracker)
    117    {
    118        classesHub.clearOnInvalidation(pageCache);
    119        templatesHub.clearOnInvalidation(pageCache);
    120        messagesHub.clearOnInvalidation(pageCache);
    121
    122        // Because Assets can be injected into pages, and Assets are invalidated when
    123        // an Asset's value is changed (partly due to the change, in 5.4, to include the asset's
    124        // checksum as part of the asset URL), then when we notice a change to
    125        // any Resource, it is necessary to discard all page instances.
    126        resourceChangeTracker.clearOnInvalidation(pageCache);
    127    }
    128
    129    public void clearCache()
    130    {
    131        pageCache.clear();
    132    }
    133
    134    public Set<Page> getAllPages()
    135    {
    136        return F.flow(pageCache.values()).map(new Mapper<SoftReference<Page>, Page>()
    137        {
    138            public Page map(SoftReference<Page> element)
    139            {
    140                return element.get();
    141            }
    142        }).removeNulls().toSet();
    143    }
    144}