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