001: // Copyright 2006, 2007 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.tapestry.internal.services;
016:
017: import static org.apache.tapestry.ioc.internal.util.CollectionFactory.newConcurrentMap;
018:
019: import java.net.URL;
020: import java.util.List;
021: import java.util.Locale;
022: import java.util.Map;
023: import java.util.Set;
024:
025: import org.apache.tapestry.internal.InternalConstants;
026: import org.apache.tapestry.internal.event.InvalidationEventHubImpl;
027: import org.apache.tapestry.internal.events.UpdateListener;
028: import org.apache.tapestry.internal.parser.ComponentTemplate;
029: import org.apache.tapestry.internal.parser.TemplateToken;
030: import org.apache.tapestry.internal.util.MultiKey;
031: import org.apache.tapestry.internal.util.URLChangeTracker;
032: import org.apache.tapestry.ioc.Resource;
033: import org.apache.tapestry.model.ComponentModel;
034:
035: /**
036: * Service implementation that manages a cache of parsed component templates.
037: */
038: public final class ComponentTemplateSourceImpl extends
039: InvalidationEventHubImpl implements ComponentTemplateSource,
040: UpdateListener {
041:
042: private final TemplateParser _parser;
043:
044: private final PageTemplateLocator _locator;
045:
046: private final URLChangeTracker _tracker;
047:
048: /**
049: * Caches from a key (combining component name and locale) to a resource. Often, many different
050: * keys will point to the same resource (i.e., "foo:en_US", "foo:en_UK", and "foo:en" may all be
051: * parsed from the same "foo.html" resource). The resource may end up being null, meaning the
052: * template does not exist in any locale.
053: */
054: private final Map<MultiKey, Resource> _templateResources = newConcurrentMap();
055:
056: /**
057: * Cache of parsed templates, keyed on resource.
058: */
059: private final Map<Resource, ComponentTemplate> _templates = newConcurrentMap();
060:
061: private final ComponentTemplate _missingTemplate = new ComponentTemplate() {
062: public Set<String> getComponentIds() {
063: return null;
064: }
065:
066: public Resource getResource() {
067: return null;
068: }
069:
070: public List<TemplateToken> getTokens() {
071: return null;
072: }
073:
074: public boolean isMissing() {
075: return true;
076: }
077: };
078:
079: public ComponentTemplateSourceImpl(TemplateParser parser,
080: PageTemplateLocator locator) {
081: this (parser, locator, new URLChangeTracker());
082: }
083:
084: ComponentTemplateSourceImpl(TemplateParser parser,
085: PageTemplateLocator locator, URLChangeTracker tracker) {
086: _parser = parser;
087: _locator = locator;
088: _tracker = tracker;
089: }
090:
091: /**
092: * Resolves the component name to a {@link Resource} and finds the localization of that resource
093: * (the combination of component name and locale is resolved to a resource). The localized
094: * resource is used as the key to a cache of {@link ComponentTemplate}s.
095: * <p>
096: * If a template doesn't exist, then the missing ComponentTemplate is returned.
097: */
098: public ComponentTemplate getTemplate(ComponentModel componentModel,
099: Locale locale) {
100: String componentName = componentModel.getComponentClassName();
101:
102: MultiKey key = new MultiKey(componentName, locale);
103:
104: // First cache is key to resource.
105:
106: Resource resource = _templateResources.get(key);
107:
108: if (resource == null) {
109: resource = locateTemplateResource(componentModel, locale);
110: _templateResources.put(key, resource);
111: }
112:
113: // If we haven't yet parsed the template into the cache, do so now.
114:
115: ComponentTemplate result = _templates.get(resource);
116:
117: if (result == null) {
118: result = parseTemplate(resource);
119: _templates.put(resource, result);
120: }
121:
122: return result;
123: }
124:
125: private ComponentTemplate parseTemplate(Resource r) {
126: // In a race condition, we may parse the same template more than once. This will likely add
127: // the resource to the tracker multiple times. Not likely this will cause a big issue.
128:
129: URL resourceURL = r.toURL();
130:
131: if (resourceURL == null)
132: return _missingTemplate;
133:
134: _tracker.add(resourceURL);
135:
136: return _parser.parseTemplate(r);
137: }
138:
139: private Resource locateTemplateResource(
140: ComponentModel initialModel, Locale locale) {
141: ComponentModel model = initialModel;
142: while (model != null) {
143:
144: Resource baseResource = baseResourceForModel(model);
145: Resource localized = baseResource.forLocale(locale);
146:
147: // In a race condition, we may hit this method a couple of times, and overwrite previous
148: // results with identical new results.
149:
150: // If found a properly localized version of the base resource for the model,
151: // then we've found a match (even if we had to ascend a couple of levels
152: // to reach it).
153:
154: if (localized != null)
155: return localized;
156:
157: // Not on the classpath, the the locator see if its a) a page and b) a resource inside
158: // the context
159:
160: localized = _locator
161: .findPageTemplateResource(model, locale);
162:
163: if (localized != null)
164: return localized;
165:
166: // Otherwise, this component doesn't have its own template ... lets work up to its
167: // base class and check there.
168:
169: model = model.getParentModel();
170: }
171:
172: // This will be a Resource whose URL is null, which will be picked up later and force the
173: // return of the empty template.
174:
175: return baseResourceForModel(initialModel);
176: }
177:
178: private Resource baseResourceForModel(ComponentModel model) {
179: return model.getBaseResource().withExtension(
180: InternalConstants.TEMPLATE_EXTENSION);
181: }
182:
183: /**
184: * Checks to see if any parsed resource has changed. If so, then all internal caches are
185: * cleared, and an invalidation event is fired. This is brute force ... a more targetted
186: * dependency management strategy may come later.
187: */
188: public void checkForUpdates() {
189: if (_tracker.containsChanges()) {
190: _tracker.clear();
191: _templateResources.clear();
192: _templates.clear();
193: fireInvalidationEvent();
194: }
195: }
196: }
|