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