    001    // Copyright 2010, 2011 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.services;
    016
    017    import org.apache.tapestry5.func.F;
    018    import org.apache.tapestry5.func.Mapper;
    019    import org.apache.tapestry5.internal.structure.Page;
    020    import org.apache.tapestry5.ioc.internal.util.CollectionFactory;
    021    import org.apache.tapestry5.services.InvalidationListener;
    022    import org.apache.tapestry5.services.pageload.ComponentRequestSelectorAnalyzer;
    023    import org.apache.tapestry5.services.pageload.ComponentResourceSelector;
    024
    025    import java.lang.ref.SoftReference;
    026    import java.util.Map;
    027    import java.util.Set;
    028
    029    public class PageSourceImpl implements PageSource, InvalidationListener
    030    {
    031        private final ComponentRequestSelectorAnalyzer selectorAnalyzer;
    032
    033        private final PageLoader pageLoader;
    034
    035        private static final class CachedPageKey
    036        {
    037            final String pageName;
    038
    039            final ComponentResourceSelector selector;
    040
    041            public CachedPageKey(String pageName, ComponentResourceSelector selector)
    042            {
    043                this.pageName = pageName;
    044                this.selector = selector;
    045            }
    046
    047            public int hashCode()
    048            {
    049                return 37 * pageName.hashCode() + selector.hashCode();
    050            }
    051
    052            public boolean equals(Object obj)
    053            {
    054                if (this == obj)
    055                    return true;
    056
    057                if (!(obj instanceof CachedPageKey))
    058                    return false;
    059
    060                CachedPageKey other = (CachedPageKey) obj;
    061
    062                return pageName.equals(other.pageName) && selector.equals(other.selector);
    063            }
    064        }
    065
    066        private final Map<CachedPageKey, SoftReference<Page>> pageCache = CollectionFactory.newConcurrentMap();
    067
    068        public PageSourceImpl(PageLoader pageLoader, ComponentRequestSelectorAnalyzer selectorAnalyzer)
    069        {
    070            this.pageLoader = pageLoader;
    071            this.selectorAnalyzer = selectorAnalyzer;
    072        }
    073
    074        public void objectWasInvalidated()
    075        {
    076            clearCache();
    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        public void clearCache()
    113        {
    114            pageCache.clear();
    115        }
    116
    117        public Set<Page> getAllPages()
    118        {
    119            return F.flow(pageCache.values()).map(new Mapper<SoftReference<Page>, Page>()
    120            {
    121                public Page map(SoftReference<Page> element)
    122                {
    123                    return element.get();
    124                }
    125            }).removeNulls().toSet();
    126        }
    127    }