Clover coverage report - Code Coverage for tapestry release 4.0.1
Coverage timestamp: Fri Mar 31 2006 09:12:14 EST
file stats: LOC: 580   Methods: 24
NCLOC: 334   Classes: 1
 
 Source file Conditionals Statements Methods TOTAL
TemplateSourceImpl.java 66.7% 86% 95.8% 82.8%
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.Iterator;
 25    import java.util.Locale;
 26    import java.util.Map;
 27   
 28    import org.apache.commons.logging.Log;
 29    import org.apache.hivemind.ApplicationRuntimeException;
 30    import org.apache.hivemind.Resource;
 31    import org.apache.tapestry.IAsset;
 32    import org.apache.tapestry.IComponent;
 33    import org.apache.tapestry.IPage;
 34    import org.apache.tapestry.IRequestCycle;
 35    import org.apache.tapestry.Tapestry;
 36    import org.apache.tapestry.engine.ITemplateSourceDelegate;
 37    import org.apache.tapestry.event.ReportStatusEvent;
 38    import org.apache.tapestry.event.ReportStatusListener;
 39    import org.apache.tapestry.event.ResetEventListener;
 40    import org.apache.tapestry.l10n.ResourceLocalizer;
 41    import org.apache.tapestry.parse.ComponentTemplate;
 42    import org.apache.tapestry.parse.ITemplateParser;
 43    import org.apache.tapestry.parse.ITemplateParserDelegate;
 44    import org.apache.tapestry.parse.TemplateParseException;
 45    import org.apache.tapestry.parse.TemplateToken;
 46    import org.apache.tapestry.parse.TextToken;
 47    import org.apache.tapestry.parse.TokenType;
 48    import org.apache.tapestry.resolver.ComponentSpecificationResolver;
 49    import org.apache.tapestry.services.ComponentPropertySource;
 50    import org.apache.tapestry.services.TemplateSource;
 51    import org.apache.tapestry.spec.IComponentSpecification;
 52    import org.apache.tapestry.util.MultiKey;
 53   
 54    /**
 55    * Implementation of {@link org.apache.tapestry.services.TemplateSource}. Templates, once parsed,
 56    * stay in memory until explicitly cleared.
 57    *
 58    * @author Howard Lewis Ship
 59    */
 60   
 61    public class TemplateSourceImpl implements TemplateSource, ResetEventListener, ReportStatusListener
 62    {
 63    private String _serviceId;
 64   
 65    private Log _log;
 66   
 67    // The name of the component/application/etc property that will be used to
 68    // determine the encoding to use when loading the template
 69   
 70    public static final String TEMPLATE_ENCODING_PROPERTY_NAME = "org.apache.tapestry.template-encoding";
 71   
 72    // Cache of previously retrieved templates. Key is a multi-key of
 73    // specification resource path and locale (local may be null), value
 74    // is the ComponentTemplate.
 75   
 76    private Map _cache = Collections.synchronizedMap(new HashMap());
 77   
 78    // Previously read templates; key is the Resource, value
 79    // is the ComponentTemplate.
 80   
 81    private Map _templates = Collections.synchronizedMap(new HashMap());
 82   
 83    private static final int BUFFER_SIZE = 2000;
 84   
 85    private ITemplateParser _parser;
 86   
 87    /** @since 2.2 */
 88   
 89    private Resource _contextRoot;
 90   
 91    /** @since 3.0 */
 92   
 93    private ITemplateSourceDelegate _delegate;
 94   
 95    /** @since 4.0 */
 96   
 97    private ComponentSpecificationResolver _componentSpecificationResolver;
 98   
 99    /** @since 4.0 */
 100   
 101    private ComponentPropertySource _componentPropertySource;
 102   
 103    /** @since 4.0 */
 104   
 105    private ResourceLocalizer _localizer;
 106   
 107    /**
 108    * Clears the template cache. This is used during debugging.
 109    */
 110   
 111  0 public void resetEventDidOccur()
 112    {
 113  0 _cache.clear();
 114  0 _templates.clear();
 115    }
 116   
 117  34 public void reportStatus(ReportStatusEvent event)
 118    {
 119  34 event.title(_serviceId);
 120   
 121  34 int templateCount = 0;
 122  34 int tokenCount = 0;
 123  34 int characterCount = 0;
 124   
 125  34 Iterator i = _templates.values().iterator();
 126   
 127  34 while (i.hasNext())
 128    {
 129  204 ComponentTemplate template = (ComponentTemplate) i.next();
 130   
 131  204 templateCount++;
 132   
 133  204 int count = template.getTokenCount();
 134   
 135  204 tokenCount += count;
 136   
 137  204 for (int j = 0; j < count; j++)
 138    {
 139  3878 TemplateToken token = template.getToken(j);
 140   
 141  3878 if (token.getType() == TokenType.TEXT)
 142    {
 143  1682 TextToken tt = (TextToken) token;
 144   
 145  1682 characterCount += tt.getLength();
 146    }
 147    }
 148    }
 149   
 150  34 event.property("parsed templates", templateCount);
 151  34 event.property("total template tokens", tokenCount);
 152  34 event.property("total template characters", characterCount);
 153   
 154  34 event.section("Parsed template token counts");
 155   
 156  34 i = _templates.entrySet().iterator();
 157   
 158  34 while (i.hasNext())
 159    {
 160  204 Map.Entry entry = (Map.Entry) i.next();
 161   
 162  204 String key = entry.getKey().toString();
 163   
 164  204 ComponentTemplate template = (ComponentTemplate) entry.getValue();
 165   
 166  204 event.property(key, template.getTokenCount());
 167    }
 168    }
 169   
 170    /**
 171    * Reads the template for the component.
 172    */
 173   
 174  392 public ComponentTemplate getTemplate(IRequestCycle cycle, IComponent component)
 175    {
 176  392 IComponentSpecification specification = component.getSpecification();
 177  392 Resource resource = specification.getSpecificationLocation();
 178   
 179  392 Locale locale = component.getPage().getLocale();
 180   
 181  392 Object key = new MultiKey(new Object[]
 182    { resource, locale }, false);
 183   
 184  392 ComponentTemplate result = searchCache(key);
 185  392 if (result != null)
 186  64 return result;
 187   
 188  328 result = findTemplate(cycle, resource, component, locale);
 189   
 190  328 if (result == null)
 191    {
 192  0 result = _delegate.findTemplate(cycle, component, locale);
 193   
 194  0 if (result != null)
 195  0 return result;
 196   
 197  0 String message = component.getSpecification().isPageSpecification() ? ImplMessages
 198    .noTemplateForPage(component.getExtendedId(), locale) : ImplMessages
 199    .noTemplateForComponent(component.getExtendedId(), locale);
 200   
 201  0 throw new ApplicationRuntimeException(message, component, component.getLocation(), null);
 202    }
 203   
 204  328 saveToCache(key, result);
 205   
 206  328 return result;
 207    }
 208   
 209  392 private ComponentTemplate searchCache(Object key)
 210    {
 211  392 return (ComponentTemplate) _cache.get(key);
 212    }
 213   
 214  328 private void saveToCache(Object key, ComponentTemplate template)
 215    {
 216  328 _cache.put(key, template);
 217   
 218    }
 219   
 220    /**
 221    * Finds the template for the given component, using the following rules:
 222    * <ul>
 223    * <li>If the component has a $template asset, use that
 224    * <li>Look for a template in the same folder as the component
 225    * <li>If a page in the application namespace, search in the application root
 226    * <li>Fail!
 227    * </ul>
 228    *
 229    * @return the template, or null if not found
 230    */
 231   
 232  328 private ComponentTemplate findTemplate(IRequestCycle cycle, Resource resource,
 233    IComponent component, Locale locale)
 234    {
 235  328 IAsset templateAsset = component.getAsset(TEMPLATE_ASSET_NAME);
 236   
 237  328 if (templateAsset != null)
 238  4 return readTemplateFromAsset(cycle, component, templateAsset);
 239   
 240  324 String name = resource.getName();
 241  324 int dotx = name.lastIndexOf('.');
 242  324 String templateExtension = getTemplateExtension(component);
 243  324 String templateBaseName = name.substring(0, dotx + 1) + templateExtension;
 244   
 245  324 ComponentTemplate result = findStandardTemplate(
 246    cycle,
 247    resource,
 248    component,
 249    templateBaseName,
 250    locale);
 251   
 252  324 if (result == null && component.getSpecification().isPageSpecification()
 253    && component.getNamespace().isApplicationNamespace())
 254  114 result = findPageTemplateInApplicationRoot(
 255    cycle,
 256    (IPage) component,
 257    templateExtension,
 258    locale);
 259   
 260  324 return result;
 261    }
 262   
 263  114 private ComponentTemplate findPageTemplateInApplicationRoot(IRequestCycle cycle, IPage page,
 264    String templateExtension, Locale locale)
 265    {
 266    // Note: a subtle change from release 3.0 to 4.0.
 267    // In release 3.0, you could use a <page> element to define a page named Foo whose
 268    // specification was Bar.page. We would then search for /Bar.page. Confusing? Yes.
 269    // In 4.0, we are more reliant on the page name, which may include a folder prefix (i.e.,
 270    // "admin/EditUser", so when we search it is based on the page name and not the
 271    // specification resource file name. We would search for Foo.html. Moral of the
 272    // story is to use the page name for the page specifiation and the template.
 273   
 274  114 String templateBaseName = page.getPageName() + "." + templateExtension;
 275   
 276  114 if (_log.isDebugEnabled())
 277  0 _log.debug("Checking for " + templateBaseName + " in application root");
 278   
 279  114 Resource baseLocation = _contextRoot.getRelativeResource(templateBaseName);
 280  114 Resource localizedLocation = _localizer.findLocalization(baseLocation, locale);
 281   
 282  114 if (localizedLocation == null)
 283  0 return null;
 284   
 285  114 return getOrParseTemplate(cycle, localizedLocation, page);
 286    }
 287   
 288    /**
 289    * Reads an asset to get the template.
 290    */
 291   
 292  4 private ComponentTemplate readTemplateFromAsset(IRequestCycle cycle, IComponent component,
 293    IAsset asset)
 294    {
 295  4 InputStream stream = asset.getResourceAsStream();
 296   
 297  4 char[] templateData = null;
 298   
 299  4 try
 300    {
 301  4 String encoding = getTemplateEncoding(component, null);
 302   
 303  4 templateData = readTemplateStream(stream, encoding);
 304   
 305  4 stream.close();
 306    }
 307    catch (IOException ex)
 308    {
 309  0 throw new ApplicationRuntimeException(ImplMessages.unableToReadTemplate(asset), ex);
 310    }
 311   
 312  4 Resource resourceLocation = asset.getResourceLocation();
 313   
 314  4 return constructTemplateInstance(cycle, templateData, resourceLocation, component);
 315    }
 316   
 317    /**
 318    * Search for the template corresponding to the resource and the locale. This may be in the
 319    * template map already, or may involve reading and parsing the template.
 320    *
 321    * @return the template, or null if not found.
 322    */
 323   
 324  324 private ComponentTemplate findStandardTemplate(IRequestCycle cycle, Resource resource,
 325    IComponent component, String templateBaseName, Locale locale)
 326    {
 327  324 if (_log.isDebugEnabled())
 328  0 _log.debug("Searching for localized version of template for " + resource
 329    + " in locale " + locale.getDisplayName());
 330   
 331  324 Resource baseTemplateLocation = resource.getRelativeResource(templateBaseName);
 332   
 333  324 Resource localizedTemplateLocation = _localizer.findLocalization(
 334    baseTemplateLocation,
 335    locale);
 336   
 337  324 if (localizedTemplateLocation == null)
 338  114 return null;
 339   
 340  210 return getOrParseTemplate(cycle, localizedTemplateLocation, component);
 341   
 342    }
 343   
 344    /**
 345    * Returns a previously parsed template at the specified location (which must already be
 346    * localized). If not already in the template Map, then the location is parsed and stored into
 347    * the templates Map, then returned.
 348    */
 349   
 350  324 private ComponentTemplate getOrParseTemplate(IRequestCycle cycle, Resource resource,
 351    IComponent component)
 352    {
 353   
 354  324 ComponentTemplate result = (ComponentTemplate) _templates.get(resource);
 355  324 if (result != null)
 356  34 return result;
 357   
 358    // Ok, see if it exists.
 359   
 360  290 result = parseTemplate(cycle, resource, component);
 361   
 362  290 if (result != null)
 363  290 _templates.put(resource, result);
 364   
 365  290 return result;
 366    }
 367   
 368    /**
 369    * Reads the template for the given resource; returns null if the resource doesn't exist. Note
 370    * that this method is only invoked from a synchronized block, so there shouldn't be threading
 371    * issues here.
 372    */
 373   
 374  290 private ComponentTemplate parseTemplate(IRequestCycle cycle, Resource resource,
 375    IComponent component)
 376    {
 377  290 String encoding = getTemplateEncoding(component, resource.getLocale());
 378   
 379  290 char[] templateData = readTemplate(resource, encoding);
 380  290 if (templateData == null)
 381  0 return null;
 382   
 383  290 return constructTemplateInstance(cycle, templateData, resource, component);
 384    }
 385   
 386    /**
 387    * This method is currently synchronized, because {@link TemplateParser}is not threadsafe.
 388    * Another good candidate for a pooling mechanism, especially because parsing a template may
 389    * take a while.
 390    */
 391   
 392  294 private synchronized ComponentTemplate constructTemplateInstance(IRequestCycle cycle,
 393    char[] templateData, Resource resource, IComponent component)
 394    {
 395  294 String componentAttributeName = _componentPropertySource.getComponentProperty(
 396    component,
 397    "org.apache.tapestry.jwcid-attribute-name");
 398   
 399  294 ITemplateParserDelegate delegate = new DefaultParserDelegate(component,
 400    componentAttributeName, cycle, _componentSpecificationResolver);
 401   
 402  294 TemplateToken[] tokens;
 403   
 404  294 try
 405    {
 406  294 tokens = _parser.parse(templateData, delegate, resource);
 407    }
 408    catch (TemplateParseException ex)
 409    {
 410  0 throw new ApplicationRuntimeException(ImplMessages.unableToParseTemplate(resource), ex);
 411    }
 412   
 413  294 if (_log.isDebugEnabled())
 414  0 _log.debug("Parsed " + tokens.length + " tokens from template");
 415   
 416  294 return new ComponentTemplate(templateData, tokens);
 417    }
 418   
 419    /**
 420    * Reads the template, given the complete path to the resource. Returns null if the resource
 421    * doesn't exist.
 422    */
 423   
 424  290 private char[] readTemplate(Resource resource, String encoding)
 425    {
 426  290 if (_log.isDebugEnabled())
 427  0 _log.debug("Reading template " + resource);
 428   
 429  290 URL url = resource.getResourceURL();
 430   
 431  290 if (url == null)
 432    {
 433  0 if (_log.isDebugEnabled())
 434  0 _log.debug("Template does not exist.");
 435   
 436  0 return null;
 437    }
 438   
 439  290 if (_log.isDebugEnabled())
 440  0 _log.debug("Reading template from URL " + url);
 441   
 442  290 InputStream stream = null;
 443   
 444  290 try
 445    {
 446  290 stream = url.openStream();
 447   
 448  290 return readTemplateStream(stream, encoding);
 449    }
 450    catch (IOException ex)
 451    {
 452  0 throw new ApplicationRuntimeException(ImplMessages.unableToReadTemplate(resource), ex);
 453    }
 454    finally
 455    {
 456  290 Tapestry.close(stream);
 457    }
 458   
 459    }
 460   
 461    /**
 462    * Reads a Stream into memory as an array of characters.
 463    */
 464   
 465  294 private char[] readTemplateStream(InputStream stream, String encoding) throws IOException
 466    {
 467  294 char[] charBuffer = new char[BUFFER_SIZE];
 468  294 StringBuffer buffer = new StringBuffer();
 469   
 470  294 InputStreamReader reader;
 471  294 if (encoding != null)
 472  12 reader = new InputStreamReader(new BufferedInputStream(stream), encoding);
 473    else
 474  282 reader = new InputStreamReader(new BufferedInputStream(stream));
 475   
 476  294 try
 477    {
 478  294 while (true)
 479    {
 480  596 int charsRead = reader.read(charBuffer, 0, BUFFER_SIZE);
 481   
 482  596 if (charsRead <= 0)
 483  294 break;
 484   
 485  302 buffer.append(charBuffer, 0, charsRead);
 486    }
 487    }
 488    finally
 489    {
 490  294 reader.close();
 491    }
 492   
 493    // OK, now reuse the charBuffer variable to
 494    // produce the final result.
 495   
 496  294 int length = buffer.length();
 497   
 498  294 charBuffer = new char[length];
 499   
 500    // Copy the character out of the StringBuffer and into the
 501    // array.
 502   
 503  294 buffer.getChars(0, length, charBuffer, 0);
 504   
 505  294 return charBuffer;
 506    }
 507   
 508    /**
 509    * Checks for the {@link Tapestry#TEMPLATE_EXTENSION_PROPERTY}in the component's specification,
 510    * then in the component's namespace's specification. Returns
 511    * {@link Tapestry#DEFAULT_TEMPLATE_EXTENSION}if not otherwise overriden.
 512    */
 513   
 514  324 private String getTemplateExtension(IComponent component)
 515    {
 516  324 return _componentPropertySource.getComponentProperty(
 517    component,
 518    Tapestry.TEMPLATE_EXTENSION_PROPERTY);
 519    }
 520   
 521  294 private String getTemplateEncoding(IComponent component, Locale locale)
 522    {
 523  294 return _componentPropertySource.getLocalizedComponentProperty(
 524    component,
 525    locale,
 526    TEMPLATE_ENCODING_PROPERTY_NAME);
 527    }
 528   
 529    /** @since 4.0 */
 530   
 531  78 public void setParser(ITemplateParser parser)
 532    {
 533  78 _parser = parser;
 534    }
 535   
 536    /** @since 4.0 */
 537   
 538  78 public void setLog(Log log)
 539    {
 540  78 _log = log;
 541    }
 542   
 543    /** @since 4.0 */
 544   
 545  78 public void setDelegate(ITemplateSourceDelegate delegate)
 546    {
 547  78 _delegate = delegate;
 548    }
 549   
 550    /** @since 4.0 */
 551   
 552  78 public void setComponentSpecificationResolver(ComponentSpecificationResolver resolver)
 553    {
 554  78 _componentSpecificationResolver = resolver;
 555    }
 556   
 557    /** @since 4.0 */
 558  78 public void setContextRoot(Resource contextRoot)
 559    {
 560  78 _contextRoot = contextRoot;
 561    }
 562   
 563    /** @since 4.0 */
 564  78 public void setComponentPropertySource(ComponentPropertySource componentPropertySource)
 565    {
 566  78 _componentPropertySource = componentPropertySource;
 567    }
 568   
 569    /** @since 4.0 */
 570  78 public void setServiceId(String serviceId)
 571    {
 572  78 _serviceId = serviceId;
 573    }
 574   
 575    /** @since 4.0 */
 576  78 public void setLocalizer(ResourceLocalizer localizer)
 577    {
 578  78 _localizer = localizer;
 579    }
 580    }