KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > apache > tapestry > services > impl > TemplateSourceImpl


1 // Copyright 2004, 2005 The Apache Software Foundation
2
//
3
// Licensed under the Apache License, Version 2.0 (the "License");
4
// you may not use this file except in compliance with the License.
5
// You may obtain a copy of the License at
6
//
7
// http://www.apache.org/licenses/LICENSE-2.0
8
//
9
// Unless required by applicable law or agreed to in writing, software
10
// distributed under the License is distributed on an "AS IS" BASIS,
11
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
// See the License for the specific language governing permissions and
13
// limitations under the License.
14

15 package org.apache.tapestry.services.impl;
16
17 import java.io.BufferedInputStream JavaDoc;
18 import java.io.IOException JavaDoc;
19 import java.io.InputStream JavaDoc;
20 import java.io.InputStreamReader JavaDoc;
21 import java.net.URL JavaDoc;
22 import java.util.Collections JavaDoc;
23 import java.util.HashMap JavaDoc;
24 import java.util.Locale JavaDoc;
25 import java.util.Map JavaDoc;
26
27 import org.apache.commons.logging.Log;
28 import org.apache.hivemind.ApplicationRuntimeException;
29 import org.apache.hivemind.Resource;
30 import org.apache.tapestry.IAsset;
31 import org.apache.tapestry.IComponent;
32 import org.apache.tapestry.IPage;
33 import org.apache.tapestry.IRequestCycle;
34 import org.apache.tapestry.Tapestry;
35 import org.apache.tapestry.engine.ITemplateSourceDelegate;
36 import org.apache.tapestry.event.ResetEventListener;
37 import org.apache.tapestry.parse.ComponentTemplate;
38 import org.apache.tapestry.parse.ITemplateParser;
39 import org.apache.tapestry.parse.ITemplateParserDelegate;
40 import org.apache.tapestry.parse.TemplateParseException;
41 import org.apache.tapestry.parse.TemplateParser;
42 import org.apache.tapestry.parse.TemplateToken;
43 import org.apache.tapestry.resolver.ComponentSpecificationResolver;
44 import org.apache.tapestry.services.ComponentPropertySource;
45 import org.apache.tapestry.services.TemplateSource;
46 import org.apache.tapestry.spec.IComponentSpecification;
47 import org.apache.tapestry.util.MultiKey;
48
49 /**
50  * Implementation of {@link org.apache.tapestry.services.TemplateSource}. Templates, once parsed,
51  * stay in memory until explicitly cleared.
52  *
53  * @author Howard Lewis Ship
54  */

55
56 public class TemplateSourceImpl implements TemplateSource, ResetEventListener
57 {
58     private Log _log;
59
60     // The name of the component/application/etc property that will be used to
61
// determine the encoding to use when loading the template
62

63     public static final String JavaDoc TEMPLATE_ENCODING_PROPERTY_NAME = "org.apache.tapestry.template-encoding";
64
65     // Cache of previously retrieved templates. Key is a multi-key of
66
// specification resource path and locale (local may be null), value
67
// is the ComponentTemplate.
68

69     private Map JavaDoc _cache = Collections.synchronizedMap(new HashMap JavaDoc());
70
71     // Previously read templates; key is the Resource, value
72
// is the ComponentTemplate.
73

74     private Map JavaDoc _templates = Collections.synchronizedMap(new HashMap JavaDoc());
75
76     private static final int BUFFER_SIZE = 2000;
77
78     private ITemplateParser _parser;
79
80     /** @since 2.2 */
81
82     private Resource _contextRoot;
83
84     /** @since 3.0 */
85
86     private ITemplateSourceDelegate _delegate;
87
88     /** @since 4.0 */
89
90     private ComponentSpecificationResolver _componentSpecificationResolver;
91
92     /** @since 4.0 */
93
94     private ComponentPropertySource _componentPropertySource;
95
96     /**
97      * Clears the template cache. This is used during debugging.
98      */

99
100     public void resetEventDidOccur()
101     {
102         _cache.clear();
103         _templates.clear();
104     }
105
106     /**
107      * Reads the template for the component.
108      */

109
110     public ComponentTemplate getTemplate(IRequestCycle cycle, IComponent component)
111     {
112         IComponentSpecification specification = component.getSpecification();
113         Resource resource = specification.getSpecificationLocation();
114
115         Locale JavaDoc locale = component.getPage().getLocale();
116
117         Object JavaDoc key = new MultiKey(new Object JavaDoc[]
118         { resource, locale }, false);
119
120         ComponentTemplate result = searchCache(key);
121         if (result != null)
122             return result;
123
124         result = findTemplate(cycle, resource, component, locale);
125
126         if (result == null)
127         {
128             result = _delegate.findTemplate(cycle, component, locale);
129
130             if (result != null)
131                 return result;
132
133             String JavaDoc message = component.getSpecification().isPageSpecification() ? ImplMessages
134                     .noTemplateForPage(component.getExtendedId(), locale) : ImplMessages
135                     .noTemplateForComponent(component.getExtendedId(), locale);
136
137             throw new ApplicationRuntimeException(message, component, component.getLocation(), null);
138         }
139
140         saveToCache(key, result);
141
142         return result;
143     }
144
145     private ComponentTemplate searchCache(Object JavaDoc key)
146     {
147         return (ComponentTemplate) _cache.get(key);
148     }
149
150     private void saveToCache(Object JavaDoc key, ComponentTemplate template)
151     {
152         _cache.put(key, template);
153
154     }
155
156     /**
157      * Finds the template for the given component, using the following rules:
158      * <ul>
159      * <li>If the component has a $template asset, use that
160      * <li>Look for a template in the same folder as the component
161      * <li>If a page in the application namespace, search in the application root
162      * <li>Fail!
163      * </ul>
164      *
165      * @return the template, or null if not found
166      */

167
168     private ComponentTemplate findTemplate(IRequestCycle cycle, Resource resource,
169             IComponent component, Locale JavaDoc locale)
170     {
171         IAsset templateAsset = component.getAsset(TEMPLATE_ASSET_NAME);
172
173         if (templateAsset != null)
174             return readTemplateFromAsset(cycle, component, templateAsset);
175
176         String JavaDoc name = resource.getName();
177         int dotx = name.lastIndexOf('.');
178         String JavaDoc templateExtension = getTemplateExtension(component);
179         String JavaDoc templateBaseName = name.substring(0, dotx + 1) + templateExtension;
180
181         ComponentTemplate result = findStandardTemplate(
182                 cycle,
183                 resource,
184                 component,
185                 templateBaseName,
186                 locale);
187
188         if (result == null && component.getSpecification().isPageSpecification()
189                 && component.getNamespace().isApplicationNamespace())
190             result = findPageTemplateInApplicationRoot(
191                     cycle,
192                     (IPage) component,
193                     templateExtension,
194                     locale);
195
196         return result;
197     }
198
199     private ComponentTemplate findPageTemplateInApplicationRoot(IRequestCycle cycle, IPage page,
200             String JavaDoc templateExtension, Locale JavaDoc locale)
201     {
202         // Note: a subtle change from release 3.0 to 4.0.
203
// In release 3.0, you could use a <page> element to define a page named Foo whose
204
// specification was Bar.page. We would then search for /Bar.page. Confusing? Yes.
205
// In 4.0, we are more reliant on the page name, which may include a folder prefix (i.e.,
206
// "admin/EditUser", so when we search it is based on the page name and not the
207
// specification resource file name. We would search for Foo.html. Moral of the
208
// story is to use the page name for the page specifiation and the template.
209

210         String JavaDoc templateBaseName = page.getPageName() + "." + templateExtension;
211
212         if (_log.isDebugEnabled())
213             _log.debug("Checking for " + templateBaseName + " in application root");
214
215         Resource baseLocation = _contextRoot.getRelativeResource(templateBaseName);
216         Resource localizedLocation = baseLocation.getLocalization(locale);
217
218         if (localizedLocation == null)
219             return null;
220
221         return getOrParseTemplate(cycle, localizedLocation, page);
222     }
223
224     /**
225      * Reads an asset to get the template.
226      */

227
228     private ComponentTemplate readTemplateFromAsset(IRequestCycle cycle, IComponent component,
229             IAsset asset)
230     {
231         InputStream JavaDoc stream = asset.getResourceAsStream(cycle);
232
233         char[] templateData = null;
234
235         try
236         {
237             String JavaDoc encoding = getTemplateEncoding(component, null);
238
239             templateData = readTemplateStream(stream, encoding);
240
241             stream.close();
242         }
243         catch (IOException JavaDoc ex)
244         {
245             throw new ApplicationRuntimeException(ImplMessages.unableToReadTemplate(asset), ex);
246         }
247
248         Resource resourceLocation = asset.getResourceLocation();
249
250         return constructTemplateInstance(cycle, templateData, resourceLocation, component);
251     }
252
253     /**
254      * Search for the template corresponding to the resource and the locale. This may be in the
255      * template map already, or may involve reading and parsing the template.
256      *
257      * @return the template, or null if not found.
258      */

259
260     private ComponentTemplate findStandardTemplate(IRequestCycle cycle, Resource resource,
261             IComponent component, String JavaDoc templateBaseName, Locale JavaDoc locale)
262     {
263         if (_log.isDebugEnabled())
264             _log.debug("Searching for localized version of template for " + resource
265                     + " in locale " + locale.getDisplayName());
266
267         Resource baseTemplateLocation = resource.getRelativeResource(templateBaseName);
268
269         Resource localizedTemplateLocation = baseTemplateLocation.getLocalization(locale);
270
271         if (localizedTemplateLocation == null)
272             return null;
273
274         return getOrParseTemplate(cycle, localizedTemplateLocation, component);
275
276     }
277
278     /**
279      * Returns a previously parsed template at the specified location (which must already be
280      * localized). If not already in the template Map, then the location is parsed and stored into
281      * the templates Map, then returned.
282      */

283
284     private ComponentTemplate getOrParseTemplate(IRequestCycle cycle, Resource resource,
285             IComponent component)
286     {
287
288         ComponentTemplate result = (ComponentTemplate) _templates.get(resource);
289         if (result != null)
290             return result;
291
292         // Ok, see if it exists.
293

294         result = parseTemplate(cycle, resource, component);
295
296         if (result != null)
297             _templates.put(resource, result);
298
299         return result;
300     }
301
302     /**
303      * Reads the template for the given resource; returns null if the resource doesn't exist. Note
304      * that this method is only invoked from a synchronized block, so there shouldn't be threading
305      * issues here.
306      */

307
308     private ComponentTemplate parseTemplate(IRequestCycle cycle, Resource resource,
309             IComponent component)
310     {
311         String JavaDoc encoding = getTemplateEncoding(component, resource.getLocale());
312
313         char[] templateData = readTemplate(resource, encoding);
314         if (templateData == null)
315             return null;
316
317         return constructTemplateInstance(cycle, templateData, resource, component);
318     }
319
320     /**
321      * This method is currently synchronized, because {@link TemplateParser}is not threadsafe.
322      * Another good candidate for a pooling mechanism, especially because parsing a template may
323      * take a while.
324      */

325
326     private synchronized ComponentTemplate constructTemplateInstance(IRequestCycle cycle,
327             char[] templateData, Resource resource, IComponent component)
328     {
329         String JavaDoc componentAttributeName = _componentPropertySource.getComponentProperty(
330                 component,
331                 "org.apache.tapestry.jwcid-attribute-name");
332
333         ITemplateParserDelegate delegate = new DefaultParserDelegate(component,
334                 componentAttributeName, cycle, _componentSpecificationResolver);
335
336         TemplateToken[] tokens;
337
338         try
339         {
340             tokens = _parser.parse(templateData, delegate, resource);
341         }
342         catch (TemplateParseException ex)
343         {
344             throw new ApplicationRuntimeException(ImplMessages.unableToParseTemplate(resource), ex);
345         }
346
347         if (_log.isDebugEnabled())
348             _log.debug("Parsed " + tokens.length + " tokens from template");
349
350         return new ComponentTemplate(templateData, tokens);
351     }
352
353     /**
354      * Reads the template, given the complete path to the resource. Returns null if the resource
355      * doesn't exist.
356      */

357
358     private char[] readTemplate(Resource resource, String JavaDoc encoding)
359     {
360         if (_log.isDebugEnabled())
361             _log.debug("Reading template " + resource);
362
363         URL JavaDoc url = resource.getResourceURL();
364
365         if (url == null)
366         {
367             if (_log.isDebugEnabled())
368                 _log.debug("Template does not exist.");
369
370             return null;
371         }
372
373         if (_log.isDebugEnabled())
374             _log.debug("Reading template from URL " + url);
375
376         InputStream JavaDoc stream = null;
377
378         try
379         {
380             stream = url.openStream();
381
382             return readTemplateStream(stream, encoding);
383         }
384         catch (IOException JavaDoc ex)
385         {
386             throw new ApplicationRuntimeException(ImplMessages.unableToReadTemplate(resource), ex);
387         }
388         finally
389         {
390             Tapestry.close(stream);
391         }
392
393     }
394
395     /**
396      * Reads a Stream into memory as an array of characters.
397      */

398
399     private char[] readTemplateStream(InputStream JavaDoc stream, String JavaDoc encoding) throws IOException JavaDoc
400     {
401         char[] charBuffer = new char[BUFFER_SIZE];
402         StringBuffer JavaDoc buffer = new StringBuffer JavaDoc();
403
404         InputStreamReader JavaDoc reader;
405         if (encoding != null)
406             reader = new InputStreamReader JavaDoc(new BufferedInputStream JavaDoc(stream), encoding);
407         else
408             reader = new InputStreamReader JavaDoc(new BufferedInputStream JavaDoc(stream));
409
410         try
411         {
412             while (true)
413             {
414                 int charsRead = reader.read(charBuffer, 0, BUFFER_SIZE);
415
416                 if (charsRead <= 0)
417                     break;
418
419                 buffer.append(charBuffer, 0, charsRead);
420             }
421         }
422         finally
423         {
424             reader.close();
425         }
426
427         // OK, now reuse the charBuffer variable to
428
// produce the final result.
429

430         int length = buffer.length();
431
432         charBuffer = new char[length];
433
434         // Copy the character out of the StringBuffer and into the
435
// array.
436

437         buffer.getChars(0, length, charBuffer, 0);
438
439         return charBuffer;
440     }
441
442     /**
443      * Checks for the {@link Tapestry#TEMPLATE_EXTENSION_PROPERTY}in the component's specification,
444      * then in the component's namespace's specification. Returns
445      * {@link Tapestry#DEFAULT_TEMPLATE_EXTENSION}if not otherwise overriden.
446      */

447
448     private String JavaDoc getTemplateExtension(IComponent component)
449     {
450         return _componentPropertySource.getComponentProperty(
451                 component,
452                 Tapestry.TEMPLATE_EXTENSION_PROPERTY);
453     }
454
455     private String JavaDoc getTemplateEncoding(IComponent component, Locale JavaDoc locale)
456     {
457         return _componentPropertySource.getLocalizedComponentProperty(
458                 component,
459                 locale,
460                 TEMPLATE_ENCODING_PROPERTY_NAME);
461     }
462
463     /** @since 4.0 */
464
465     public void setParser(ITemplateParser parser)
466     {
467         _parser = parser;
468     }
469
470     /** @since 4.0 */
471
472     public void setLog(Log log)
473     {
474         _log = log;
475     }
476
477     /** @since 4.0 */
478
479     public void setDelegate(ITemplateSourceDelegate delegate)
480     {
481         _delegate = delegate;
482     }
483
484     /** @since 4.0 */
485
486     public void setComponentSpecificationResolver(ComponentSpecificationResolver resolver)
487     {
488         _componentSpecificationResolver = resolver;
489     }
490
491     /** @since 4.0 */
492     public void setContextRoot(Resource contextRoot)
493     {
494         _contextRoot = contextRoot;
495     }
496
497     /** @since 4.0 */
498     public void setComponentPropertySource(ComponentPropertySource componentPropertySource)
499     {
500         _componentPropertySource = componentPropertySource;
501     }
502 }
Popular Tags