    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}