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
015package org.apache.tapestry5.internal.services;
016
017import java.util.Collections;
018import java.util.List;
019import java.util.Locale;
020import java.util.Map;
021
022import org.apache.tapestry5.SymbolConstants;
023import org.apache.tapestry5.TapestryConstants;
024import org.apache.tapestry5.internal.event.InvalidationEventHubImpl;
025import org.apache.tapestry5.internal.parser.ComponentTemplate;
026import org.apache.tapestry5.internal.parser.TemplateToken;
027import org.apache.tapestry5.internal.util.MultiKey;
028import org.apache.tapestry5.ioc.Location;
029import org.apache.tapestry5.ioc.Resource;
030import org.apache.tapestry5.ioc.annotations.Inject;
031import org.apache.tapestry5.ioc.annotations.PostInjection;
032import org.apache.tapestry5.ioc.annotations.Symbol;
033import org.apache.tapestry5.ioc.internal.util.CollectionFactory;
034import org.apache.tapestry5.ioc.internal.util.URLChangeTracker;
035import org.apache.tapestry5.ioc.services.ClasspathURLConverter;
036import org.apache.tapestry5.model.ComponentModel;
037import org.apache.tapestry5.services.InvalidationEventHub;
038import org.apache.tapestry5.services.UpdateListener;
039import org.apache.tapestry5.services.UpdateListenerHub;
040import org.apache.tapestry5.services.pageload.ComponentResourceLocator;
041import org.apache.tapestry5.services.pageload.ComponentResourceSelector;
042import org.apache.tapestry5.services.templates.ComponentTemplateLocator;
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    /**
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}