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    }