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