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     * <p/>
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}