001// Licensed under the Apache License, Version 2.0 (the "License"); 002// you may not use this file except in compliance with the License. 003// You may obtain a copy of the License at 004// 005// http://www.apache.org/licenses/LICENSE-2.0 006// 007// Unless required by applicable law or agreed to in writing, software 008// distributed under the License is distributed on an "AS IS" BASIS, 009// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 010// See the License for the specific language governing permissions and 011// limitations under the License. 012 013package org.apache.tapestry5.internal.services; 014 015import org.apache.tapestry5.TapestryConstants; 016import org.apache.tapestry5.commons.Location; 017import org.apache.tapestry5.commons.Resource; 018import org.apache.tapestry5.commons.services.InvalidationEventHub; 019import org.apache.tapestry5.commons.util.CollectionFactory; 020import org.apache.tapestry5.commons.util.MultiKey; 021import org.apache.tapestry5.http.TapestryHttpSymbolConstants; 022import org.apache.tapestry5.internal.event.InvalidationEventHubImpl; 023import org.apache.tapestry5.internal.parser.ComponentTemplate; 024import org.apache.tapestry5.internal.parser.TemplateToken; 025import org.apache.tapestry5.ioc.annotations.Inject; 026import org.apache.tapestry5.ioc.annotations.PostInjection; 027import org.apache.tapestry5.ioc.annotations.Symbol; 028import org.apache.tapestry5.ioc.internal.util.URLChangeTracker; 029import org.apache.tapestry5.ioc.services.ClasspathURLConverter; 030import org.apache.tapestry5.ioc.services.ThreadLocale; 031import org.apache.tapestry5.ioc.services.UpdateListener; 032import org.apache.tapestry5.ioc.services.UpdateListenerHub; 033import org.apache.tapestry5.model.ComponentModel; 034import org.apache.tapestry5.services.pageload.ComponentRequestSelectorAnalyzer; 035import org.apache.tapestry5.services.pageload.ComponentResourceLocator; 036import org.apache.tapestry5.services.pageload.ComponentResourceSelector; 037import org.apache.tapestry5.services.templates.ComponentTemplateLocator; 038 039import java.util.Collections; 040import java.util.List; 041import java.util.Locale; 042import java.util.Map; 043 044/** 045 * Service implementation that manages a cache of parsed component templates. 046 */ 047public final class ComponentTemplateSourceImpl extends InvalidationEventHubImpl implements ComponentTemplateSource, 048 UpdateListener 049{ 050 private final TemplateParser parser; 051 052 private final URLChangeTracker tracker; 053 054 private final ComponentResourceLocator locator; 055 056 private final ComponentRequestSelectorAnalyzer componentRequestSelectorAnalyzer; 057 058 private final ThreadLocale threadLocale; 059 060 /** 061 * Caches from a key (combining component name and locale) to a resource. Often, many different keys will point to 062 * the same resource (i.e., "foo:en_US", "foo:en_UK", and "foo:en" may all be parsed from the same "foo.tml" 063 * resource). The resource may end up being null, meaning the template does not exist in any locale. 064 */ 065 private final Map<MultiKey, Resource> templateResources = CollectionFactory.newConcurrentMap(); 066 067 /** 068 * Cache of parsed templates, keyed on resource. 069 */ 070 private final Map<Resource, ComponentTemplate> templates = CollectionFactory.newConcurrentMap(); 071 072 private final ComponentTemplate missingTemplate = new ComponentTemplate() 073 { 074 public Map<String, Location> getComponentIds() 075 { 076 return Collections.emptyMap(); 077 } 078 079 public Resource getResource() 080 { 081 return null; 082 } 083 084 public List<TemplateToken> getTokens() 085 { 086 return Collections.emptyList(); 087 } 088 089 public boolean isMissing() 090 { 091 return true; 092 } 093 094 public List<TemplateToken> getExtensionPointTokens(String extensionPointId) 095 { 096 return null; 097 } 098 099 public boolean isExtension() 100 { 101 return false; 102 } 103 104 public boolean usesStrictMixinParameters() 105 { 106 return false; 107 } 108 }; 109 110 public ComponentTemplateSourceImpl(@Inject 111 @Symbol(TapestryHttpSymbolConstants.PRODUCTION_MODE) 112 boolean productionMode, TemplateParser parser, ComponentResourceLocator locator, 113 ClasspathURLConverter classpathURLConverter, 114 ComponentRequestSelectorAnalyzer componentRequestSelectorAnalyzer, 115 ThreadLocale threadLocale) 116 { 117 this(productionMode, parser, locator, new URLChangeTracker(classpathURLConverter), componentRequestSelectorAnalyzer, threadLocale); 118 } 119 120 ComponentTemplateSourceImpl(boolean productionMode, TemplateParser parser, ComponentResourceLocator locator, 121 URLChangeTracker tracker, ComponentRequestSelectorAnalyzer componentRequestSelectorAnalyzer, 122 ThreadLocale threadLocale) 123 { 124 super(productionMode); 125 126 this.parser = parser; 127 this.locator = locator; 128 this.tracker = tracker; 129 this.componentRequestSelectorAnalyzer = componentRequestSelectorAnalyzer; 130 this.threadLocale = threadLocale; 131 } 132 133 @PostInjection 134 public void registerAsUpdateListener(UpdateListenerHub hub) 135 { 136 hub.addUpdateListener(this); 137 } 138 139 @PostInjection 140 public void setupReload(ReloadHelper helper) 141 { 142 helper.addReloadCallback(new Runnable() 143 { 144 public void run() 145 { 146 invalidate(); 147 } 148 }); 149 } 150 151 public ComponentTemplate getTemplate(ComponentModel componentModel, ComponentResourceSelector selector) 152 { 153 String componentName = componentModel.getComponentClassName(); 154 155 MultiKey key = new MultiKey(componentName, selector); 156 157 // First cache is key to resource. 158 159 Resource resource = templateResources.get(key); 160 161 if (resource == null) 162 { 163 resource = locateTemplateResource(componentModel, selector); 164 templateResources.put(key, resource); 165 } 166 167 // If we haven't yet parsed the template into the cache, do so now. 168 169 ComponentTemplate result = templates.get(resource); 170 171 if (result == null) 172 { 173 result = parseTemplate(resource); 174 templates.put(resource, result); 175 } 176 177 return result; 178 } 179 180 /** 181 * Resolves the component name to a localized {@link Resource} (using the {@link ComponentTemplateLocator} chain of 182 * command service). The localized resource is used as the key to a cache of {@link ComponentTemplate}s. 183 * 184 * If a template doesn't exist, then the missing ComponentTemplate is returned. 185 */ 186 public ComponentTemplate getTemplate(ComponentModel componentModel, Locale locale) 187 { 188 final Locale original = threadLocale.getLocale(); 189 try 190 { 191 threadLocale.setLocale(locale); 192 return getTemplate(componentModel, componentRequestSelectorAnalyzer.buildSelectorForRequest()); 193 } 194 finally { 195 threadLocale.setLocale(original); 196 } 197 } 198 199 private ComponentTemplate parseTemplate(Resource r) 200 { 201 // In a race condition, we may parse the same template more than once. This will likely add 202 // the resource to the tracker multiple times. Not likely this will cause a big issue. 203 204 if (!r.exists()) 205 return missingTemplate; 206 207 tracker.add(r.toURL()); 208 209 return parser.parseTemplate(r); 210 } 211 212 private Resource locateTemplateResource(ComponentModel initialModel, ComponentResourceSelector selector) 213 { 214 ComponentModel model = initialModel; 215 while (model != null) 216 { 217 Resource localized = locator.locateTemplate(model, selector); 218 219 if (localized != null) 220 return localized; 221 222 // Otherwise, this component doesn't have its own template ... lets work up to its 223 // base class and check there. 224 225 model = model.getParentModel(); 226 } 227 228 // This will be a Resource whose URL is null, which will be picked up later and force the 229 // return of the empty template. 230 231 return initialModel.getBaseResource().withExtension(TapestryConstants.TEMPLATE_EXTENSION); 232 } 233 234 /** 235 * Checks to see if any parsed resource has changed. If so, then all internal caches are cleared, and an 236 * invalidation event is fired. This is brute force ... a more targeted dependency management strategy may come 237 * later. 238 */ 239 public void checkForUpdates() 240 { 241 if (tracker.containsChanges()) 242 { 243 invalidate(); 244 } 245 } 246 247 private void invalidate() 248 { 249 tracker.clear(); 250 templateResources.clear(); 251 templates.clear(); 252 fireInvalidationEvent(); 253 } 254 255 public InvalidationEventHub getInvalidationEventHub() 256 { 257 return this; 258 } 259}