Clover coverage report - Code Coverage for tapestry release 4.0-beta-2
Coverage timestamp: Sat Jul 9 2005 22:02:17 EDT
file stats: LOC: 502   Methods: 21
NCLOC: 281   Classes: 1
30 day Evaluation License registered to hlship@comcast.net Your 30 day evaluation period has expired. Please visit http://www.cenqua.com to obtain a licensed version of Clover
 
 Source file Conditionals Statements Methods TOTAL
TemplateSourceImpl.java 60% 82.8% 95.2% 79.1%
coverage coverage
 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  213 public ComponentTemplate getTemplate(IRequestCycle cycle, IComponent component)
 111    {
 112  213 IComponentSpecification specification = component.getSpecification();
 113  213 Resource resource = specification.getSpecificationLocation();
 114   
 115  213 Locale locale = component.getPage().getLocale();
 116   
 117  213 Object key = new MultiKey(new Object[]
 118    { resource, locale }, false);
 119   
 120  213 ComponentTemplate result = searchCache(key);
 121  213 if (result != null)
 122  38 return result;
 123   
 124  175 result = findTemplate(cycle, resource, component, locale);
 125   
 126  175 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  175 saveToCache(key, result);
 141   
 142  175 return result;
 143    }
 144   
 145  213 private ComponentTemplate searchCache(Object key)
 146    {
 147  213 return (ComponentTemplate) _cache.get(key);
 148    }
 149   
 150  175 private void saveToCache(Object key, ComponentTemplate template)
 151    {
 152  175 _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  175 private ComponentTemplate findTemplate(IRequestCycle cycle, Resource resource,
 169    IComponent component, Locale locale)
 170    {
 171  175 IAsset templateAsset = component.getAsset(TEMPLATE_ASSET_NAME);
 172   
 173  175 if (templateAsset != null)
 174  2 return readTemplateFromAsset(cycle, component, templateAsset);
 175   
 176  173 String name = resource.getName();
 177  173 int dotx = name.lastIndexOf('.');
 178  173 String templateExtension = getTemplateExtension(component);
 179  173 String templateBaseName = name.substring(0, dotx + 1) + templateExtension;
 180   
 181  173 ComponentTemplate result = findStandardTemplate(
 182    cycle,
 183    resource,
 184    component,
 185    templateBaseName,
 186    locale);
 187   
 188  173 if (result == null && component.getSpecification().isPageSpecification()
 189    && component.getNamespace().isApplicationNamespace())
 190  43 result = findPageTemplateInApplicationRoot(
 191    cycle,
 192    (IPage) component,
 193    templateExtension,
 194    locale);
 195   
 196  173 return result;
 197    }
 198   
 199  43 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  43 String templateBaseName = page.getPageName() + "." + templateExtension;
 211   
 212  43 if (_log.isDebugEnabled())
 213  0 _log.debug("Checking for " + templateBaseName + " in application root");
 214   
 215  43 Resource baseLocation = _contextRoot.getRelativeResource(templateBaseName);
 216  43 Resource localizedLocation = baseLocation.getLocalization(locale);
 217   
 218  43 if (localizedLocation == null)
 219  0 return null;
 220   
 221  43 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  173 private ComponentTemplate findStandardTemplate(IRequestCycle cycle, Resource resource,
 261    IComponent component, String templateBaseName, Locale locale)
 262    {
 263  173 if (_log.isDebugEnabled())
 264  0 _log.debug("Searching for localized version of template for " + resource
 265    + " in locale " + locale.getDisplayName());
 266   
 267  173 Resource baseTemplateLocation = resource.getRelativeResource(templateBaseName);
 268   
 269  173 Resource localizedTemplateLocation = baseTemplateLocation.getLocalization(locale);
 270   
 271  173 if (localizedTemplateLocation == null)
 272  43 return null;
 273   
 274  130 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  173 private ComponentTemplate getOrParseTemplate(IRequestCycle cycle, Resource resource,
 285    IComponent component)
 286    {
 287   
 288  173 ComponentTemplate result = (ComponentTemplate) _templates.get(resource);
 289  173 if (result != null)
 290  17 return result;
 291   
 292    // Ok, see if it exists.
 293   
 294  156 result = parseTemplate(cycle, resource, component);
 295   
 296  156 if (result != null)
 297  156 _templates.put(resource, result);
 298   
 299  156 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  156 private ComponentTemplate parseTemplate(IRequestCycle cycle, Resource resource,
 309    IComponent component)
 310    {
 311  156 String encoding = getTemplateEncoding(component, resource.getLocale());
 312   
 313  156 char[] templateData = readTemplate(resource, encoding);
 314  156 if (templateData == null)
 315  0 return null;
 316   
 317  156 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  158 private synchronized ComponentTemplate constructTemplateInstance(IRequestCycle cycle,
 327    char[] templateData, Resource resource, IComponent component)
 328    {
 329  158 String componentAttributeName = _componentPropertySource.getComponentProperty(
 330    component,
 331    "org.apache.tapestry.jwcid-attribute-name");
 332   
 333  158 ITemplateParserDelegate delegate = new DefaultParserDelegate(component,
 334    componentAttributeName, cycle, _componentSpecificationResolver);
 335   
 336  158 TemplateToken[] tokens;
 337   
 338  158 try
 339    {
 340  158 tokens = _parser.parse(templateData, delegate, resource);
 341    }
 342    catch (TemplateParseException ex)
 343    {
 344  0 throw new ApplicationRuntimeException(ImplMessages.unableToParseTemplate(resource), ex);
 345    }
 346   
 347  158 if (_log.isDebugEnabled())
 348  0 _log.debug("Parsed " + tokens.length + " tokens from template");
 349   
 350  158 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  156 private char[] readTemplate(Resource resource, String encoding)
 359    {
 360  156 if (_log.isDebugEnabled())
 361  0 _log.debug("Reading template " + resource);
 362   
 363  156 URL url = resource.getResourceURL();
 364   
 365  156 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  156 if (_log.isDebugEnabled())
 374  0 _log.debug("Reading template from URL " + url);
 375   
 376  156 InputStream stream = null;
 377   
 378  156 try
 379    {
 380  156 stream = url.openStream();
 381   
 382  156 return readTemplateStream(stream, encoding);
 383    }
 384    catch (IOException ex)
 385    {
 386  0 throw new ApplicationRuntimeException(ImplMessages.unableToReadTemplate(resource), ex);
 387    }
 388    finally
 389    {
 390  156 Tapestry.close(stream);
 391    }
 392   
 393    }
 394   
 395    /**
 396    * Reads a Stream into memory as an array of characters.
 397    */
 398   
 399  158 private char[] readTemplateStream(InputStream stream, String encoding) throws IOException
 400    {
 401  158 char[] charBuffer = new char[BUFFER_SIZE];
 402  158 StringBuffer buffer = new StringBuffer();
 403   
 404  158 InputStreamReader reader;
 405  158 if (encoding != null)
 406  6 reader = new InputStreamReader(new BufferedInputStream(stream), encoding);
 407    else
 408  152 reader = new InputStreamReader(new BufferedInputStream(stream));
 409   
 410  158 try
 411    {
 412  158 while (true)
 413    {
 414  319 int charsRead = reader.read(charBuffer, 0, BUFFER_SIZE);
 415   
 416  319 if (charsRead <= 0)
 417  158 break;
 418   
 419  161 buffer.append(charBuffer, 0, charsRead);
 420    }
 421    }
 422    finally
 423    {
 424  158 reader.close();
 425    }
 426   
 427    // OK, now reuse the charBuffer variable to
 428    // produce the final result.
 429   
 430  158 int length = buffer.length();
 431   
 432  158 charBuffer = new char[length];
 433   
 434    // Copy the character out of the StringBuffer and into the
 435    // array.
 436   
 437  158 buffer.getChars(0, length, charBuffer, 0);
 438   
 439  158 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  173 private String getTemplateExtension(IComponent component)
 449    {
 450  173 return _componentPropertySource.getComponentProperty(
 451    component,
 452    Tapestry.TEMPLATE_EXTENSION_PROPERTY);
 453    }
 454   
 455  158 private String getTemplateEncoding(IComponent component, Locale locale)
 456    {
 457  158 return _componentPropertySource.getLocalizedComponentProperty(
 458    component,
 459    locale,
 460    TEMPLATE_ENCODING_PROPERTY_NAME);
 461    }
 462   
 463    /** @since 4.0 */
 464   
 465  41 public void setParser(ITemplateParser parser)
 466    {
 467  41 _parser = parser;
 468    }
 469   
 470    /** @since 4.0 */
 471   
 472  41 public void setLog(Log log)
 473    {
 474  41 _log = log;
 475    }
 476   
 477    /** @since 4.0 */
 478   
 479  41 public void setDelegate(ITemplateSourceDelegate delegate)
 480    {
 481  41 _delegate = delegate;
 482    }
 483   
 484    /** @since 4.0 */
 485   
 486  41 public void setComponentSpecificationResolver(ComponentSpecificationResolver resolver)
 487    {
 488  41 _componentSpecificationResolver = resolver;
 489    }
 490   
 491    /** @since 4.0 */
 492  41 public void setContextRoot(Resource contextRoot)
 493    {
 494  41 _contextRoot = contextRoot;
 495    }
 496   
 497    /** @since 4.0 */
 498  41 public void setComponentPropertySource(ComponentPropertySource componentPropertySource)
 499    {
 500  41 _componentPropertySource = componentPropertySource;
 501    }
 502    }