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 }