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}