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