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 }