Clover coverage report - Code Coverage for tapestry release 4.0-alpha-3
Coverage timestamp: Mon May 16 2005 09:05:49 EDT
file stats: LOC: 502   Methods: 21
NCLOC: 281   Classes: 1
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%
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  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   
 }