    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    }