/**
* 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;
}
};
}