Java tutorial
/** * License Agreement. * * Rich Faces - Natural Ajax for Java Server Faces (JSF) * * Copyright (C) 2007 Exadel, Inc. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License version 2.1 as published by the Free Software Foundation. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ package org.ajax4jsf.renderkit.compiler; import java.io.IOException; import java.io.InputStream; import java.io.Reader; import java.io.StringReader; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; import javax.faces.FacesException; import javax.faces.component.UIComponent; import javax.faces.context.FacesContext; import org.ajax4jsf.Messages; import org.ajax4jsf.renderkit.RendererBase; import org.ajax4jsf.resource.util.URLToStreamHelper; import org.apache.commons.digester.AbstractObjectCreationFactory; import org.apache.commons.digester.Digester; import org.apache.commons.digester.Rule; import org.apache.commons.digester.RulesBase; import org.apache.commons.digester.SetNextRule; import org.apache.commons.digester.SetPropertiesRule; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.xml.sax.Attributes; import org.xml.sax.SAXException; import org.xml.sax.SAXParseException; import org.xml.sax.helpers.LocatorImpl; /** * Compiler for XML string or stream for creation of {@link javax.faces.render.Renderer} internal * interpretator. Use Apache Digitester for parse . * @author asmirnov@exadel.com (latest modification by $Author: alexsmirnov $) * @version $Revision: 1.1.2.1 $ $Date: 2007/01/09 18:57:48 $ * */ public class HtmlCompiler { public static final String NS_PREFIX = "f:"; public static final String NS_UTIL_PREFIX = "u:"; public static final String ROOT_TAG = "template"; public static final String CALL_TAG = "call"; public static final String VALUE_CALL_TAG = "valueCall"; public static final String CALL_PARAM_TAG = "parameter"; public static final String VERBATUM_TAG = "verbatim"; public static final String BREAK_TAG = "break"; public static final String FACET_TAG = "insertFacet"; public static final String CHILDREN_TAG = "insertChildren"; public static final String IF_TAG = "if"; public static final String CHILD_TAG = "insertChild"; public static final String STYLE_TAG = "style"; public static final String CHILD_METHOD = "addChild"; public static final String RESOURCE_TAG = "resource"; public static final String ATTRIBUTE_TAG = "attribute"; public static final String CHILD_PARAM_METHOD = "addParameter"; public static final String ANY_PARENT = "*/"; public static final String SELECTOR_TAG = "selector"; public static final String IMPORT_RESOURCE_TAG = "importResource"; private static final Log log = LogFactory.getLog(HtmlCompiler.class); private Digester digestr = new Digester(); /** * Constructor with initialisation. */ public HtmlCompiler() { WithDefaultsRulesWrapper rules = new WithDefaultsRulesWrapper(new RulesBase()); // Add default rules to process plain tags. rules.addDefault(new PlainElementCreateRule()); rules.addDefault(new SetNextRule(CHILD_METHOD)); digestr.setDocumentLocator(new LocatorImpl()); digestr.setRules(rules); digestr.setValidating(false); digestr.setNamespaceAware(false); digestr.setUseContextClassLoader(true); // Concrete renderer method call rules. setCustomRules(digestr); } /** * Build custom rules for parse special tags and attributes. * For most tags, value can be in next forms ( for resource, preffixed with f: ): * <ul> * <li><b>value</b> as string or EL-expression</li> * <li><b>property</b> name for property of current component</li> * <li><b>baseSkin</b> name for property of base skin</li> * <li><b>skin</b> name for property of current skin</li> * <li><b>context</b> name for property of {@link TemplateContext} for trmplate encoding</li> * <li><b>default</b> use as value if main property <code>null</code></li> * </ul> * Supported : * <ul> * <li>Call renderer or utils method <f:call name='xxx' /></li> * <li>Call method parameter <f:parameter name='xxx' (value='....' | property='style' | baseSkin='paramName' | skin='paramName' | context='propertyName ) /></li> * <li>Encode plain text <f:verbatim [value='....' | property='style' | baseSkin='paramName' | skin='paramName' | context='propertyName ] >Some Text</f:text></li> * <li>Encode as text in HTML CSS parameter format <u:style name='background' (value='....' | property='style' | baseSkin='paramName' | skin='paramName' | context='propertyName )/></li> * <li>Get and encode resource <f:resource f:key='images/spacer.gif' [ ... any attributes ... ] /></li> * <li>Encode custom attribute <f:attribute name='style' (value='....' | property='style' | baseSkin='paramName' | skin='paramName' | context='propertyName ) [endorsed='true'] /></li> * <li>Template breakpoint <f:break name='xxxxx' /></li> * <li>Encode named facet <u:facet ( name='head | property='style' | baseSkin='paramName' | skin='paramName' | context='propertyName )' /></li> * <li>Encode children components for current <u:children /></li> * <li>Encode one child component for enclosed u:facet or u:children <u:child /'></li> * </ul> * @param digestr */ protected void setCustomRules(Digester digestr) { // Root element. not instantiate - already set by compile method. String pattern = NS_PREFIX + ROOT_TAG; digestr.addSetProperties(pattern); // call renderer method <f:call methodName='xxx' [methodParam='yyy'] /> pattern = ANY_PARENT + NS_PREFIX + CALL_TAG; digestr.addObjectCreate(pattern, MethodCallElement.class); digestr.addSetNext(pattern, CHILD_METHOD); digestr.addSetProperties(pattern); // for value of parent call renderer method <f:valueCall methodName='xxx' [methodParam='yyy'] /> pattern = ANY_PARENT + NS_PREFIX + VALUE_CALL_TAG; digestr.addObjectCreate(pattern, ValueMethodCallElement.class); digestr.addSetNext(pattern, CHILD_METHOD); digestr.addSetProperties(pattern); // call parameter <f:parapeter methodName='xxx' [methodParam='yyy'] /> // pattern = ANY_PARENT+NS_PREFIX+CALL_TAG+"/"+NS_PREFIX+CALL_PARAM_TAG; pattern = ANY_PARENT + NS_PREFIX + CALL_PARAM_TAG; digestr.addObjectCreate(pattern, MethodParameterElement.class); digestr.addSetNext(pattern, CHILD_PARAM_METHOD); digestr.addSetProperties(pattern); // Encode plain text <f:text>Some Text</f:text> pattern = ANY_PARENT + NS_PREFIX + VERBATUM_TAG; digestr.addObjectCreate(pattern, TextElement.class); digestr.addSetNext(pattern, CHILD_METHOD); digestr.addSetProperties(pattern); digestr.addBeanPropertySetter(pattern, "text"); // resource <f:resource f:key='images/spacer.gif' [f:data='...'] [ ... any attributes ... ] /> pattern = ANY_PARENT + NS_PREFIX + RESOURCE_TAG; digestr.addObjectCreate(pattern, ResourceElement.class); digestr.addSetNext(pattern, CHILD_METHOD); { Rule rule = new PutAttributesRule(digestr, new String[] { NS_PREFIX + "key", NS_PREFIX + "property", NS_PREFIX + "baseSkin", NS_PREFIX + "skin", NS_PREFIX + "context" }); digestr.addRule(pattern, rule); } { SetPropertiesRule rule = new SetPropertiesRule( new String[] { NS_PREFIX + "key", NS_PREFIX + "property", NS_PREFIX + "baseSkin", NS_PREFIX + "skin", NS_PREFIX + "context" }, new String[] { "value", "property", "baseSkin", "skin", "context" }); digestr.addRule(pattern, rule); } // custom attribute <f:attribute name='style' (value='....' | property='style' | skin='paramName' | call='methodName' | utilCall='methodName') [notNull='true'] /> pattern = ANY_PARENT + NS_PREFIX + ATTRIBUTE_TAG; digestr.addObjectCreate(pattern, AttributeElement.class); digestr.addSetNext(pattern, CHILD_METHOD); digestr.addSetProperties(pattern); // component clientId <f:id [f:required='true'] /> // breakpoint <f:break name="name"> pattern = ANY_PARENT + NS_PREFIX + BREAK_TAG; digestr.addObjectCreate(pattern, BreakPoint.class); digestr.addSetProperties(pattern); digestr.addSetNext(pattern, CHILD_METHOD); // conditional rendering <f:if then="true"> pattern = ANY_PARENT + NS_PREFIX + IF_TAG; digestr.addObjectCreate(pattern, IfElement.class); digestr.addSetProperties(pattern); digestr.addSetNext(pattern, CHILD_METHOD); // resources import <f:importResource src="..."> pattern = ANY_PARENT + NS_PREFIX + IMPORT_RESOURCE_TAG; digestr.addFactoryCreate(pattern, new AbstractObjectCreationFactory() { public Object createObject(Attributes attributes) throws Exception { if (attributes == null) { throw new IllegalArgumentException("Attributes set is null for " + IMPORT_RESOURCE_TAG + " element. Can not obtain required 'src' attribute!"); } String value = attributes.getValue("src"); if (value == null || value.length() == 0) { throw new IllegalArgumentException( "Missing required 'src' attribute for " + IMPORT_RESOURCE_TAG + " element!"); } return new ImportResourceElement(value); } }); digestr.addSetNext(pattern, CHILD_METHOD); // facet <u:insertFacet name="name"> pattern = ANY_PARENT + NS_UTIL_PREFIX + FACET_TAG; digestr.addObjectCreate(pattern, FacetElement.class); digestr.addSetProperties(pattern); digestr.addSetNext(pattern, CHILD_METHOD); // childrens <u:insertChildren> pattern = ANY_PARENT + NS_UTIL_PREFIX + CHILDREN_TAG; digestr.addObjectCreate(pattern, ChildrensElement.class); digestr.addSetNext(pattern, CHILD_METHOD); // { // Rule rule = new PutAttributesRule(digestr,new String[]{NS_PREFIX+"key",NS_PREFIX+"property",NS_PREFIX+"skin",NS_PREFIX+"context"}); // digestr.addRule(pattern,rule); // } { SetPropertiesRule rule = new SetPropertiesRule(new String[] { NS_PREFIX + "key", NS_PREFIX + "property", NS_PREFIX + "skin", NS_PREFIX + "context" }, new String[] { "value", "property", "skin", "context" }); digestr.addRule(pattern, rule); } // style(sheet) property <u:style> pattern = ANY_PARENT + NS_UTIL_PREFIX + STYLE_TAG; digestr.addObjectCreate(pattern, ClassElement.class); digestr.addSetProperties(pattern); digestr.addSetNext(pattern, CHILD_METHOD); // breakpoint <f:break name="name"> pattern = ANY_PARENT + NS_UTIL_PREFIX + CHILD_TAG; digestr.addObjectCreate(pattern, ChildElement.class); digestr.addSetProperties(pattern); digestr.addSetNext(pattern, CHILD_METHOD); //Selector element <u:selector></u:selector> pattern = ANY_PARENT + NS_UTIL_PREFIX + SELECTOR_TAG; digestr.addObjectCreate(pattern, SelectorElement.class); digestr.addSetProperties(pattern); digestr.addSetNext(pattern, CHILD_METHOD); } /** * Compile template for XML from simple string. * @param xml - String with template code * @return compiled template, ready for rendering. */ public PreparedTemplate compile(String xml) { Reader input = new StringReader(xml); return compile(input); } /** * Compile set of templates at time. Templates gets from resources in * classpath relative to base class. Used for initialisation set of templates in {@link RendererBase} subclasses constructor * @param base - base class for resources paths * @param templates - array with templates names. * @return {@link Map } , with keys - templates name, value - compiled template. */ public Map compileResources(Class base, String[] templates) { Map result = new HashMap(templates.length); for (int i = 0; i < templates.length; i++) { String template = templates[i]; result.put(template, compileResource(base, template)); } return result; } /** * Compile XML from resource in classpath. Resource always readed * as UTF-8 text , to avoid xml declarations. * @param base - class of base object. * @param resource - path to resource ( if not start with / , relative to base object ) * @return compiled XML, or Exception-generated stub, if parsing error occurs. */ public static PreparedTemplate compileResource(Class base, String resource) { String path; if (resource.startsWith("/")) { path = base.getPackage().getName().replace('.', '/') + resource; } else { path = base.getPackage().getName().replace('.', '/') + "/" + resource; } return compileResource(path); } /** * Compile XML from resource in classpath. Resource always readed * as UTF-8 text , to avoid xml declarations. * @param resource - absolute path to resource * @return compiled XML, or Exception-generated stub, if parsing error occurs. */ public static PreparedTemplate compileResource(String resource) { HtmlCompiler compiler = new HtmlCompiler(); ClassLoader loader = Thread.currentThread().getContextClassLoader(); InputStream input = URLToStreamHelper.urlToStreamSafe(loader.getResource(resource)); // Since parsing exceptions handled by compiler, we can not check parameter. PreparedTemplate compile = compiler.compile(input, resource); if (null != input) { try { input.close(); } catch (IOException e) { // can be ignored } } return compile; } public PreparedTemplate compile(Reader input) { // By default - empty result. we not throw any exceptions in compile phase - // since it can be in static class initialization. PreparedTemplate result = null; try { digestr.clear(); digestr.push(new RootElement()); result = (PreparedTemplate) digestr.parse(input); } catch (SAXParseException e) { // TODO - locate compilation error. How get name ? String errorstr = Messages.getMessage(Messages.PARSING_TEMPLATE_ERROR, new Object[] { "" + e.getLineNumber(), "" + e.getColumnNumber(), e.toString() }); // errorstr.append(digestr.getDocumentLocator().getLineNumber()).append(" and position ").append(digestr.getDocumentLocator().getColumnNumber()); log.error(errorstr, e); result = new CompiledError(errorstr.toString(), e); } catch (IOException e) { String errorstr = Messages.getMessage(Messages.TEMPLATE_IO_ERROR, e.toString()); log.error(errorstr, e); result = new CompiledError(errorstr.toString(), e); } catch (SAXException e) { String errorstr = Messages.getMessage(Messages.PARSING_TEMPLATE_ERROR_2, e.toString()); log.error(errorstr, e); result = new CompiledError(errorstr.toString(), e); } return result; } /** * Compile input {@link InputStream} to {@link PreparedTemplate} - set of Java classes to encode as JSF output. * @param input stream with template XML text. * @param sourcename ( optional ) name of resource, for debug purposes. * @return compiled template. */ public PreparedTemplate compile(InputStream input, String sourcename) { // By default - empty result. we not throw any exceptions in compile phase - // since it can be in static class initialization. PreparedTemplate result = null; try { if (log.isDebugEnabled()) { log.debug(Messages.getMessage(Messages.START_COMPILE_TEMPLATE_INFO, sourcename)); } digestr.clear(); RootElement root = new RootElement(); root.setTemplateName(sourcename); digestr.push(root); result = (PreparedTemplate) digestr.parse(input); } catch (SAXParseException e) { // TODO - locate compilation error. How get name ? String errorstr = Messages.getMessage(Messages.PARSING_TEMPLATE_ERROR_a, new Object[] { sourcename, "" + e.getLineNumber(), "" + e.getColumnNumber(), e.toString() }); // errorstr.append(digestr.getDocumentLocator().getLineNumber()).append(" and position ").append(digestr.getDocumentLocator().getColumnNumber()); log.error(errorstr, e); result = new CompiledError(errorstr.toString(), e); } catch (IOException e) { String errorstr = Messages.getMessage(Messages.TEMPLATE_IO_ERROR_a, sourcename, e.toString()); log.error(errorstr, e); result = new CompiledError(errorstr.toString(), e); } catch (SAXException e) { String errorstr = Messages.getMessage(Messages.PARSING_TEMPLATE_ERROR_2a, sourcename, e.toString()); log.error(errorstr, e); result = new CompiledError(errorstr.toString(), e); } if (log.isDebugEnabled()) { log.debug(Messages.getMessage(Messages.FINISH_COMPILE_TEMPLATE_INFO, sourcename)); } return result; } /** * Incapsulate parsing - compilation errors to throw at encoding. * Allow static / constructor initialisation, all errors will be * see in page at request time. For debugging purposes. * @author asmirnov@exadel.com (latest modification by $Author: alexsmirnov $) * @version $Revision: 1.1.2.1 $ $Date: 2007/01/09 18:57:48 $ * */ private static class CompiledError implements PreparedTemplate { private Exception cause = null; private String message = Messages.getMessage(Messages.TEMPLATE_NOT_COMPILED_ERROR); /** * @param cause * @param message */ public CompiledError(String message, Exception cause) { super(); this.cause = cause; this.message = message; } /** * @param cause */ CompiledError(Exception cause) { this.cause = cause; } public void encode(RendererBase renderer, FacesContext context, UIComponent component) throws IOException { // TODO - concrete compilation error. throw new FacesException(message, cause); } public List getChildren() { return Collections.EMPTY_LIST; } public void addChild(PreparedTemplate child) throws SAXException { // DO Nothing. throw new SAXException(Messages.getMessage(Messages.NO_CHILD_ALLOWED)); } public void encode(TemplateContext context) throws IOException { throw new FacesException(message, cause); } public void encode(TemplateContext context, String breakPoint) throws IOException { throw new FacesException(message, cause); } public void setParent(PreparedTemplate parent) { // DO Nothing. } public String getTag() { // TODO Auto-generated method stub return "f:error"; } public Object getValue(TemplateContext context) { // error don't have value return null; } }; }