Java tutorial
// Copyright 2004 The Apache Software Foundation // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package org.apache.tapestry.engine; import java.io.BufferedInputStream; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.net.URL; import java.util.Collections; import java.util.HashMap; import java.util.Iterator; import java.util.Locale; import java.util.Map; import org.apache.commons.lang.builder.ToStringBuilder; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.apache.tapestry.ApplicationRuntimeException; import org.apache.tapestry.IAsset; import org.apache.tapestry.IComponent; import org.apache.tapestry.IEngine; import org.apache.tapestry.ILocation; import org.apache.tapestry.IMarkupWriter; import org.apache.tapestry.INamespace; import org.apache.tapestry.IRequestCycle; import org.apache.tapestry.IResourceLocation; import org.apache.tapestry.Tapestry; import org.apache.tapestry.parse.ComponentTemplate; import org.apache.tapestry.parse.ITemplateParserDelegate; import org.apache.tapestry.parse.TemplateParseException; import org.apache.tapestry.parse.TemplateParser; import org.apache.tapestry.parse.TemplateToken; import org.apache.tapestry.resolver.ComponentSpecificationResolver; import org.apache.tapestry.spec.IApplicationSpecification; import org.apache.tapestry.spec.IComponentSpecification; import org.apache.tapestry.util.DelegatingPropertySource; import org.apache.tapestry.util.IRenderDescription; import org.apache.tapestry.util.LocalizedPropertySource; import org.apache.tapestry.util.MultiKey; import org.apache.tapestry.util.PropertyHolderPropertySource; /** * Default implementation of {@link ITemplateSource}. Templates, once parsed, * stay in memory until explicitly cleared. * * <p>An instance of this class acts as a singleton shared by all sessions, so it * must be threadsafe. * * @author Howard Lewis Ship * @version $Id: DefaultTemplateSource.java,v 1.13.2.1 2004/07/22 14:19:37 hlship Exp $ * **/ public class DefaultTemplateSource implements ITemplateSource, IRenderDescription { private static final Log LOG = LogFactory.getLog(DefaultTemplateSource.class); // The name of the component/application/etc property that will be used to // determine the encoding to use when loading the template private static final String TEMPLATE_ENCODING_PROPERTY_NAME = "org.apache.tapestry.template-encoding"; // Cache of previously retrieved templates. Key is a multi-key of // specification resource path and locale (local may be null), value // is the ComponentTemplate. private Map _cache = Collections.synchronizedMap(new HashMap()); // Previously read templates; key is the IResourceLocation, value // is the ComponentTemplate. private Map _templates = Collections.synchronizedMap(new HashMap()); /** * Number of tokens (each template contains multiple tokens). * **/ private int _tokenCount; private static final int BUFFER_SIZE = 2000; private TemplateParser _parser; /** @since 2.2 **/ private IResourceLocation _applicationRootLocation; /** @since 3.0 **/ private ITemplateSourceDelegate _delegate; /** * Clears the template cache. This is used during debugging. * **/ public void reset() { _cache.clear(); _templates.clear(); _tokenCount = 0; } /** * Reads the template for the component. * * <p>Returns null if the template can't be found. * **/ public ComponentTemplate getTemplate(IRequestCycle cycle, IComponent component) { IComponentSpecification specification = component.getSpecification(); IResourceLocation specificationLocation = specification.getSpecificationLocation(); Locale locale = component.getPage().getLocale(); Object key = new MultiKey(new Object[] { specificationLocation, locale }, false); ComponentTemplate result = searchCache(key); if (result != null) return result; result = findTemplate(cycle, specificationLocation, component, locale); if (result == null) { result = getTemplateFromDelegate(cycle, component, locale); if (result != null) return result; String stringKey = component.getSpecification().isPageSpecification() ? "DefaultTemplateSource.no-template-for-page" : "DefaultTemplateSource.no-template-for-component"; throw new ApplicationRuntimeException(Tapestry.format(stringKey, component.getExtendedId(), locale), component, component.getLocation(), null); } saveToCache(key, result); return result; } private ComponentTemplate searchCache(Object key) { return (ComponentTemplate) _cache.get(key); } private void saveToCache(Object key, ComponentTemplate template) { _cache.put(key, template); } private ComponentTemplate getTemplateFromDelegate(IRequestCycle cycle, IComponent component, Locale locale) { if (_delegate == null) { IEngine engine = cycle.getEngine(); IApplicationSpecification spec = engine.getSpecification(); if (spec.checkExtension(Tapestry.TEMPLATE_SOURCE_DELEGATE_EXTENSION_NAME)) _delegate = (ITemplateSourceDelegate) spec.getExtension( Tapestry.TEMPLATE_SOURCE_DELEGATE_EXTENSION_NAME, ITemplateSourceDelegate.class); else _delegate = NullTemplateSourceDelegate.getSharedInstance(); } return _delegate.findTemplate(cycle, component, locale); } /** * Finds the template for the given component, using the following rules: * <ul> * <li>If the component has a $template asset, use that * <li>Look for a template in the same folder as the component * <li>If a page in the application namespace, search in the application root * <li>Fail! * </ul> * * @return the template, or null if not found * **/ private ComponentTemplate findTemplate(IRequestCycle cycle, IResourceLocation location, IComponent component, Locale locale) { IAsset templateAsset = component.getAsset(TEMPLATE_ASSET_NAME); if (templateAsset != null) return readTemplateFromAsset(cycle, component, templateAsset); String name = location.getName(); int dotx = name.lastIndexOf('.'); String templateBaseName = name.substring(0, dotx + 1) + getTemplateExtension(component); ComponentTemplate result = findStandardTemplate(cycle, location, component, templateBaseName, locale); if (result == null && component.getSpecification().isPageSpecification() && component.getNamespace().isApplicationNamespace()) result = findPageTemplateInApplicationRoot(cycle, component, templateBaseName, locale); return result; } private ComponentTemplate findPageTemplateInApplicationRoot(IRequestCycle cycle, IComponent component, String templateBaseName, Locale locale) { if (LOG.isDebugEnabled()) LOG.debug("Checking for " + templateBaseName + " in application root"); if (_applicationRootLocation == null) _applicationRootLocation = Tapestry.getApplicationRootLocation(cycle); IResourceLocation baseLocation = _applicationRootLocation.getRelativeLocation(templateBaseName); IResourceLocation localizedLocation = baseLocation.getLocalization(locale); if (localizedLocation == null) return null; return getOrParseTemplate(cycle, localizedLocation, component); } /** * Reads an asset to get the template. * **/ private ComponentTemplate readTemplateFromAsset(IRequestCycle cycle, IComponent component, IAsset asset) { InputStream stream = asset.getResourceAsStream(cycle); char[] templateData = null; try { String encoding = getTemplateEncoding(cycle, component, null); templateData = readTemplateStream(stream, encoding); stream.close(); } catch (IOException ex) { throw new ApplicationRuntimeException( Tapestry.format("DefaultTemplateSource.unable-to-read-template", asset), ex); } IResourceLocation resourceLocation = asset.getResourceLocation(); return constructTemplateInstance(cycle, templateData, resourceLocation, component); } /** * Search for the template corresponding to the resource and the locale. * This may be in the template map already, or may involve reading and * parsing the template. * * @return the template, or null if not found. * **/ private ComponentTemplate findStandardTemplate(IRequestCycle cycle, IResourceLocation location, IComponent component, String templateBaseName, Locale locale) { if (LOG.isDebugEnabled()) LOG.debug("Searching for localized version of template for " + location + " in locale " + locale.getDisplayName()); IResourceLocation baseTemplateLocation = location.getRelativeLocation(templateBaseName); IResourceLocation localizedTemplateLocation = baseTemplateLocation.getLocalization(locale); if (localizedTemplateLocation == null) return null; return getOrParseTemplate(cycle, localizedTemplateLocation, component); } /** * Returns a previously parsed template at the specified location (which must already * be localized). If not already in the template Map, then the * location is parsed and stored into the templates Map, then returned. * **/ private ComponentTemplate getOrParseTemplate(IRequestCycle cycle, IResourceLocation location, IComponent component) { ComponentTemplate result = (ComponentTemplate) _templates.get(location); if (result != null) return result; // Ok, see if it exists. result = parseTemplate(cycle, location, component); if (result != null) _templates.put(location, result); return result; } /** * Reads the template for the given resource; returns null if the * resource doesn't exist. Note that this method is only invoked * from a synchronized block, so there shouldn't be threading * issues here. * **/ private ComponentTemplate parseTemplate(IRequestCycle cycle, IResourceLocation location, IComponent component) { String encoding = getTemplateEncoding(cycle, component, location.getLocale()); char[] templateData = readTemplate(location, encoding); if (templateData == null) return null; return constructTemplateInstance(cycle, templateData, location, component); } /** * This method is currently synchronized, because * {@link TemplateParser} is not threadsafe. Another good candidate * for a pooling mechanism, especially because parsing a template * may take a while. * **/ private synchronized ComponentTemplate constructTemplateInstance(IRequestCycle cycle, char[] templateData, IResourceLocation location, IComponent component) { if (_parser == null) _parser = new TemplateParser(); ITemplateParserDelegate delegate = new TemplateParserDelegateImpl(component, cycle); TemplateToken[] tokens; try { tokens = _parser.parse(templateData, delegate, location); } catch (TemplateParseException ex) { throw new ApplicationRuntimeException( Tapestry.format("DefaultTemplateSource.unable-to-parse-template", location), ex); } if (LOG.isDebugEnabled()) LOG.debug("Parsed " + tokens.length + " tokens from template"); _tokenCount += tokens.length; return new ComponentTemplate(templateData, tokens); } /** * Reads the template, given the complete path to the * resource. Returns null if the resource doesn't exist. * **/ private char[] readTemplate(IResourceLocation location, String encoding) { if (LOG.isDebugEnabled()) LOG.debug("Reading template " + location); URL url = location.getResourceURL(); if (url == null) { if (LOG.isDebugEnabled()) LOG.debug("Template does not exist."); return null; } if (LOG.isDebugEnabled()) LOG.debug("Reading template from URL " + url); InputStream stream = null; try { stream = url.openStream(); return readTemplateStream(stream, encoding); } catch (IOException ex) { throw new ApplicationRuntimeException( Tapestry.format("DefaultTemplateSource.unable-to-read-template", location), ex); } finally { Tapestry.close(stream); } } /** * Reads a Stream into memory as an array of characters. * **/ private char[] readTemplateStream(InputStream stream, String encoding) throws IOException { char[] charBuffer = new char[BUFFER_SIZE]; StringBuffer buffer = new StringBuffer(); InputStreamReader reader; if (encoding != null) reader = new InputStreamReader(new BufferedInputStream(stream), encoding); else reader = new InputStreamReader(new BufferedInputStream(stream)); try { while (true) { int charsRead = reader.read(charBuffer, 0, BUFFER_SIZE); if (charsRead <= 0) break; buffer.append(charBuffer, 0, charsRead); } } finally { reader.close(); } // OK, now reuse the charBuffer variable to // produce the final result. int length = buffer.length(); charBuffer = new char[length]; // Copy the character out of the StringBuffer and into the // array. buffer.getChars(0, length, charBuffer, 0); return charBuffer; } public String toString() { ToStringBuilder builder = new ToStringBuilder(this); builder.append("tokenCount", _tokenCount); builder.append("templates", _templates.keySet()); return builder.toString(); } /** * Checks for the {@link Tapestry#TEMPLATE_EXTENSION_PROPERTY} in the component's * specification, then in the component's namespace's specification. Returns * {@link Tapestry#DEFAULT_TEMPLATE_EXTENSION} if not otherwise overriden. * **/ private String getTemplateExtension(IComponent component) { String extension = component.getSpecification().getProperty(Tapestry.TEMPLATE_EXTENSION_PROPERTY); if (extension != null) return extension; extension = component.getNamespace().getSpecification().getProperty(Tapestry.TEMPLATE_EXTENSION_PROPERTY); if (extension != null) return extension; return Tapestry.DEFAULT_TEMPLATE_EXTENSION; } /** @since 1.0.6 **/ public synchronized void renderDescription(IMarkupWriter writer) { writer.print("DefaultTemplateSource["); if (_tokenCount > 0) { writer.print(_tokenCount); writer.print(" tokens"); } if (_cache != null) { boolean first = true; Iterator i = _cache.entrySet().iterator(); while (i.hasNext()) { if (first) { writer.begin("ul"); first = false; } Map.Entry e = (Map.Entry) i.next(); Object key = e.getKey(); ComponentTemplate template = (ComponentTemplate) e.getValue(); writer.begin("li"); writer.print(key.toString()); writer.print(" ("); writer.print(template.getTokenCount()); writer.print(" tokens)"); writer.println(); writer.end(); } if (!first) { writer.end(); // <ul> writer.beginEmpty("br"); } } writer.print("]"); } private String getTemplateEncoding(IRequestCycle cycle, IComponent component, Locale locale) { IPropertySource source = getComponentPropertySource(cycle, component); if (locale != null) source = new LocalizedPropertySource(locale, source); return getTemplateEncodingProperty(source); } private IPropertySource getComponentPropertySource(IRequestCycle cycle, IComponent component) { DelegatingPropertySource source = new DelegatingPropertySource(); // Search for the encoding property in the following order: // First search the component specification source.addSource(new PropertyHolderPropertySource(component.getSpecification())); // Then search its library specification source.addSource(new PropertyHolderPropertySource(component.getNamespace().getSpecification())); // Then search the rest of the standard path source.addSource(cycle.getEngine().getPropertySource()); return source; } private String getTemplateEncodingProperty(IPropertySource source) { return source.getPropertyValue(TEMPLATE_ENCODING_PROPERTY_NAME); } }