|
|||||||||||||||||||
30 day Evaluation Version distributed via the Maven Jar Repository. Clover is not free. You have 30 days to evaluate it. Please visit http://www.thecortex.net/clover to obtain a licensed version of Clover | |||||||||||||||||||
Source file | Conditionals | Statements | Methods | TOTAL | |||||||||||||||
TemplateSourceImpl.java | 60% | 82.8% | 95.2% | 79.1% |
|
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;
|
|
18 |
import java.io.IOException;
|
|
19 |
import java.io.InputStream;
|
|
20 |
import java.io.InputStreamReader;
|
|
21 |
import java.net.URL;
|
|
22 |
import java.util.Collections;
|
|
23 |
import java.util.HashMap;
|
|
24 |
import java.util.Locale;
|
|
25 |
import java.util.Map;
|
|
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 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 _cache = Collections.synchronizedMap(new HashMap()); |
|
70 |
|
|
71 |
// Previously read templates; key is the Resource, value
|
|
72 |
// is the ComponentTemplate.
|
|
73 |
|
|
74 |
private Map _templates = Collections.synchronizedMap(new HashMap()); |
|
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 | 0 |
public void resetEventDidOccur() |
101 |
{ |
|
102 | 0 |
_cache.clear(); |
103 | 0 |
_templates.clear(); |
104 |
} |
|
105 |
|
|
106 |
/**
|
|
107 |
* Reads the template for the component.
|
|
108 |
*/
|
|
109 |
|
|
110 | 227 |
public ComponentTemplate getTemplate(IRequestCycle cycle, IComponent component)
|
111 |
{ |
|
112 | 227 |
IComponentSpecification specification = component.getSpecification(); |
113 | 227 |
Resource resource = specification.getSpecificationLocation(); |
114 |
|
|
115 | 227 |
Locale locale = component.getPage().getLocale(); |
116 |
|
|
117 | 227 |
Object key = new MultiKey(new Object[] |
118 |
{ resource, locale }, false);
|
|
119 |
|
|
120 | 227 |
ComponentTemplate result = searchCache(key); |
121 | 227 |
if (result != null) |
122 | 39 |
return result;
|
123 |
|
|
124 | 188 |
result = findTemplate(cycle, resource, component, locale); |
125 |
|
|
126 | 188 |
if (result == null) |
127 |
{ |
|
128 | 0 |
result = _delegate.findTemplate(cycle, component, locale); |
129 |
|
|
130 | 0 |
if (result != null) |
131 | 0 |
return result;
|
132 |
|
|
133 | 0 |
String message = component.getSpecification().isPageSpecification() ? ImplMessages |
134 |
.noTemplateForPage(component.getExtendedId(), locale) : ImplMessages |
|
135 |
.noTemplateForComponent(component.getExtendedId(), locale); |
|
136 |
|
|
137 | 0 |
throw new ApplicationRuntimeException(message, component, component.getLocation(), null); |
138 |
} |
|
139 |
|
|
140 | 188 |
saveToCache(key, result); |
141 |
|
|
142 | 188 |
return result;
|
143 |
} |
|
144 |
|
|
145 | 227 |
private ComponentTemplate searchCache(Object key)
|
146 |
{ |
|
147 | 227 |
return (ComponentTemplate) _cache.get(key);
|
148 |
} |
|
149 |
|
|
150 | 188 |
private void saveToCache(Object key, ComponentTemplate template) |
151 |
{ |
|
152 | 188 |
_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 | 188 |
private ComponentTemplate findTemplate(IRequestCycle cycle, Resource resource,
|
169 |
IComponent component, Locale locale) |
|
170 |
{ |
|
171 | 188 |
IAsset templateAsset = component.getAsset(TEMPLATE_ASSET_NAME); |
172 |
|
|
173 | 188 |
if (templateAsset != null) |
174 | 2 |
return readTemplateFromAsset(cycle, component, templateAsset);
|
175 |
|
|
176 | 186 |
String name = resource.getName(); |
177 | 186 |
int dotx = name.lastIndexOf('.');
|
178 | 186 |
String templateExtension = getTemplateExtension(component); |
179 | 186 |
String templateBaseName = name.substring(0, dotx + 1) + templateExtension; |
180 |
|
|
181 | 186 |
ComponentTemplate result = findStandardTemplate( |
182 |
cycle, |
|
183 |
resource, |
|
184 |
component, |
|
185 |
templateBaseName, |
|
186 |
locale); |
|
187 |
|
|
188 | 186 |
if (result == null && component.getSpecification().isPageSpecification() |
189 |
&& component.getNamespace().isApplicationNamespace()) |
|
190 | 48 |
result = findPageTemplateInApplicationRoot( |
191 |
cycle, |
|
192 |
(IPage) component, |
|
193 |
templateExtension, |
|
194 |
locale); |
|
195 |
|
|
196 | 186 |
return result;
|
197 |
} |
|
198 |
|
|
199 | 48 |
private ComponentTemplate findPageTemplateInApplicationRoot(IRequestCycle cycle, IPage page,
|
200 |
String templateExtension, Locale 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 | 48 |
String templateBaseName = page.getPageName() + "." + templateExtension;
|
211 |
|
|
212 | 48 |
if (_log.isDebugEnabled())
|
213 | 0 |
_log.debug("Checking for " + templateBaseName + " in application root"); |
214 |
|
|
215 | 48 |
Resource baseLocation = _contextRoot.getRelativeResource(templateBaseName); |
216 | 48 |
Resource localizedLocation = baseLocation.getLocalization(locale); |
217 |
|
|
218 | 48 |
if (localizedLocation == null) |
219 | 0 |
return null; |
220 |
|
|
221 | 48 |
return getOrParseTemplate(cycle, localizedLocation, page);
|
222 |
} |
|
223 |
|
|
224 |
/**
|
|
225 |
* Reads an asset to get the template.
|
|
226 |
*/
|
|
227 |
|
|
228 | 2 |
private ComponentTemplate readTemplateFromAsset(IRequestCycle cycle, IComponent component,
|
229 |
IAsset asset) |
|
230 |
{ |
|
231 | 2 |
InputStream stream = asset.getResourceAsStream(cycle); |
232 |
|
|
233 | 2 |
char[] templateData = null; |
234 |
|
|
235 | 2 |
try
|
236 |
{ |
|
237 | 2 |
String encoding = getTemplateEncoding(component, null);
|
238 |
|
|
239 | 2 |
templateData = readTemplateStream(stream, encoding); |
240 |
|
|
241 | 2 |
stream.close(); |
242 |
} |
|
243 |
catch (IOException ex)
|
|
244 |
{ |
|
245 | 0 |
throw new ApplicationRuntimeException(ImplMessages.unableToReadTemplate(asset), ex); |
246 |
} |
|
247 |
|
|
248 | 2 |
Resource resourceLocation = asset.getResourceLocation(); |
249 |
|
|
250 | 2 |
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 | 186 |
private ComponentTemplate findStandardTemplate(IRequestCycle cycle, Resource resource,
|
261 |
IComponent component, String templateBaseName, Locale locale) |
|
262 |
{ |
|
263 | 186 |
if (_log.isDebugEnabled())
|
264 | 0 |
_log.debug("Searching for localized version of template for " + resource
|
265 |
+ " in locale " + locale.getDisplayName());
|
|
266 |
|
|
267 | 186 |
Resource baseTemplateLocation = resource.getRelativeResource(templateBaseName); |
268 |
|
|
269 | 186 |
Resource localizedTemplateLocation = baseTemplateLocation.getLocalization(locale); |
270 |
|
|
271 | 186 |
if (localizedTemplateLocation == null) |
272 | 48 |
return null; |
273 |
|
|
274 | 138 |
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 | 186 |
private ComponentTemplate getOrParseTemplate(IRequestCycle cycle, Resource resource,
|
285 |
IComponent component) |
|
286 |
{ |
|
287 |
|
|
288 | 186 |
ComponentTemplate result = (ComponentTemplate) _templates.get(resource); |
289 | 186 |
if (result != null) |
290 | 17 |
return result;
|
291 |
|
|
292 |
// Ok, see if it exists.
|
|
293 |
|
|
294 | 169 |
result = parseTemplate(cycle, resource, component); |
295 |
|
|
296 | 169 |
if (result != null) |
297 | 169 |
_templates.put(resource, result); |
298 |
|
|
299 | 169 |
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 | 169 |
private ComponentTemplate parseTemplate(IRequestCycle cycle, Resource resource,
|
309 |
IComponent component) |
|
310 |
{ |
|
311 | 169 |
String encoding = getTemplateEncoding(component, resource.getLocale()); |
312 |
|
|
313 | 169 |
char[] templateData = readTemplate(resource, encoding);
|
314 | 169 |
if (templateData == null) |
315 | 0 |
return null; |
316 |
|
|
317 | 169 |
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 | 171 |
private synchronized ComponentTemplate constructTemplateInstance(IRequestCycle cycle, |
327 |
char[] templateData, Resource resource, IComponent component)
|
|
328 |
{ |
|
329 | 171 |
String componentAttributeName = _componentPropertySource.getComponentProperty( |
330 |
component, |
|
331 |
"org.apache.tapestry.jwcid-attribute-name");
|
|
332 |
|
|
333 | 171 |
ITemplateParserDelegate delegate = new DefaultParserDelegate(component,
|
334 |
componentAttributeName, cycle, _componentSpecificationResolver); |
|
335 |
|
|
336 | 171 |
TemplateToken[] tokens; |
337 |
|
|
338 | 171 |
try
|
339 |
{ |
|
340 | 171 |
tokens = _parser.parse(templateData, delegate, resource); |
341 |
} |
|
342 |
catch (TemplateParseException ex)
|
|
343 |
{ |
|
344 | 0 |
throw new ApplicationRuntimeException(ImplMessages.unableToParseTemplate(resource), ex); |
345 |
} |
|
346 |
|
|
347 | 171 |
if (_log.isDebugEnabled())
|
348 | 0 |
_log.debug("Parsed " + tokens.length + " tokens from template"); |
349 |
|
|
350 | 171 |
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 | 169 |
private char[] readTemplate(Resource resource, String encoding) |
359 |
{ |
|
360 | 169 |
if (_log.isDebugEnabled())
|
361 | 0 |
_log.debug("Reading template " + resource);
|
362 |
|
|
363 | 169 |
URL url = resource.getResourceURL(); |
364 |
|
|
365 | 169 |
if (url == null) |
366 |
{ |
|
367 | 0 |
if (_log.isDebugEnabled())
|
368 | 0 |
_log.debug("Template does not exist.");
|
369 |
|
|
370 | 0 |
return null; |
371 |
} |
|
372 |
|
|
373 | 169 |
if (_log.isDebugEnabled())
|
374 | 0 |
_log.debug("Reading template from URL " + url);
|
375 |
|
|
376 | 169 |
InputStream stream = null;
|
377 |
|
|
378 | 169 |
try
|
379 |
{ |
|
380 | 169 |
stream = url.openStream(); |
381 |
|
|
382 | 169 |
return readTemplateStream(stream, encoding);
|
383 |
} |
|
384 |
catch (IOException ex)
|
|
385 |
{ |
|
386 | 0 |
throw new ApplicationRuntimeException(ImplMessages.unableToReadTemplate(resource), ex); |
387 |
} |
|
388 |
finally
|
|
389 |
{ |
|
390 | 169 |
Tapestry.close(stream); |
391 |
} |
|
392 |
|
|
393 |
} |
|
394 |
|
|
395 |
/**
|
|
396 |
* Reads a Stream into memory as an array of characters.
|
|
397 |
*/
|
|
398 |
|
|
399 | 171 |
private char[] readTemplateStream(InputStream stream, String encoding) throws IOException |
400 |
{ |
|
401 | 171 |
char[] charBuffer = new char[BUFFER_SIZE]; |
402 | 171 |
StringBuffer buffer = new StringBuffer();
|
403 |
|
|
404 | 171 |
InputStreamReader reader; |
405 | 171 |
if (encoding != null) |
406 | 6 |
reader = new InputStreamReader(new BufferedInputStream(stream), encoding); |
407 |
else
|
|
408 | 165 |
reader = new InputStreamReader(new BufferedInputStream(stream)); |
409 |
|
|
410 | 171 |
try
|
411 |
{ |
|
412 | 171 |
while (true) |
413 |
{ |
|
414 | 345 |
int charsRead = reader.read(charBuffer, 0, BUFFER_SIZE);
|
415 |
|
|
416 | 345 |
if (charsRead <= 0)
|
417 | 171 |
break;
|
418 |
|
|
419 | 174 |
buffer.append(charBuffer, 0, charsRead); |
420 |
} |
|
421 |
} |
|
422 |
finally
|
|
423 |
{ |
|
424 | 171 |
reader.close(); |
425 |
} |
|
426 |
|
|
427 |
// OK, now reuse the charBuffer variable to
|
|
428 |
// produce the final result.
|
|
429 |
|
|
430 | 171 |
int length = buffer.length();
|
431 |
|
|
432 | 171 |
charBuffer = new char[length]; |
433 |
|
|
434 |
// Copy the character out of the StringBuffer and into the
|
|
435 |
// array.
|
|
436 |
|
|
437 | 171 |
buffer.getChars(0, length, charBuffer, 0); |
438 |
|
|
439 | 171 |
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 | 186 |
private String getTemplateExtension(IComponent component)
|
449 |
{ |
|
450 | 186 |
return _componentPropertySource.getComponentProperty(
|
451 |
component, |
|
452 |
Tapestry.TEMPLATE_EXTENSION_PROPERTY); |
|
453 |
} |
|
454 |
|
|
455 | 171 |
private String getTemplateEncoding(IComponent component, Locale locale)
|
456 |
{ |
|
457 | 171 |
return _componentPropertySource.getLocalizedComponentProperty(
|
458 |
component, |
|
459 |
locale, |
|
460 |
TEMPLATE_ENCODING_PROPERTY_NAME); |
|
461 |
} |
|
462 |
|
|
463 |
/** @since 4.0 */
|
|
464 |
|
|
465 | 45 |
public void setParser(ITemplateParser parser) |
466 |
{ |
|
467 | 45 |
_parser = parser; |
468 |
} |
|
469 |
|
|
470 |
/** @since 4.0 */
|
|
471 |
|
|
472 | 45 |
public void setLog(Log log) |
473 |
{ |
|
474 | 45 |
_log = log; |
475 |
} |
|
476 |
|
|
477 |
/** @since 4.0 */
|
|
478 |
|
|
479 | 45 |
public void setDelegate(ITemplateSourceDelegate delegate) |
480 |
{ |
|
481 | 45 |
_delegate = delegate; |
482 |
} |
|
483 |
|
|
484 |
/** @since 4.0 */
|
|
485 |
|
|
486 | 45 |
public void setComponentSpecificationResolver(ComponentSpecificationResolver resolver) |
487 |
{ |
|
488 | 45 |
_componentSpecificationResolver = resolver; |
489 |
} |
|
490 |
|
|
491 |
/** @since 4.0 */
|
|
492 | 45 |
public void setContextRoot(Resource contextRoot) |
493 |
{ |
|
494 | 45 |
_contextRoot = contextRoot; |
495 |
} |
|
496 |
|
|
497 |
/** @since 4.0 */
|
|
498 | 45 |
public void setComponentPropertySource(ComponentPropertySource componentPropertySource) |
499 |
{ |
|
500 | 45 |
_componentPropertySource = componentPropertySource; |
501 |
} |
|
502 |
} |
|