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    }