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