/* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You 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.cocoon.generation; import java.beans.PropertyDescriptor; import java.io.CharArrayReader; import java.io.IOException; import java.io.PrintStream; import java.io.PrintWriter; import java.io.Serializable; import java.io.StringReader; import java.lang.reflect.Field; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.text.DateFormat; import java.text.DecimalFormat; import java.text.DecimalFormatSymbols; import java.text.NumberFormat; import java.text.SimpleDateFormat; import java.util.Enumeration; import java.util.HashMap; import java.util.Iterator; import java.util.LinkedList; import java.util.List; import java.util.Locale; import java.util.Map; import java.util.Properties; import java.util.Stack; import java.util.TimeZone; import org.apache.avalon.framework.parameters.Parameters; import org.apache.avalon.framework.service.ServiceException; import org.apache.avalon.framework.service.ServiceManager; import org.apache.cocoon.ProcessingException; import org.apache.cocoon.caching.CacheableProcessingComponent; import org.apache.cocoon.components.flow.FlowHelper; import org.apache.cocoon.components.flow.WebContinuation; import org.apache.cocoon.components.flow.javascript.fom.FOM_JavaScriptFlowHelper; import org.apache.cocoon.components.source.SourceUtil; import org.apache.cocoon.environment.ObjectModelHelper; import org.apache.cocoon.environment.Request; import org.apache.cocoon.environment.SourceResolver; import org.apache.cocoon.transformation.ServiceableTransformer; import org.apache.cocoon.util.jxpath.NamespacesTablePointer; import org.apache.cocoon.util.location.LocatedRuntimeException; import org.apache.cocoon.util.location.Location; import org.apache.cocoon.util.location.LocationUtils; import org.apache.cocoon.xml.IncludeXMLConsumer; import org.apache.cocoon.xml.NamespacesTable; import org.apache.cocoon.xml.RedundantNamespacesFilter; import org.apache.cocoon.xml.XMLConsumer; import org.apache.cocoon.xml.XMLUtils; import org.apache.cocoon.xml.dom.DOMBuilder; import org.apache.cocoon.xml.dom.DOMStreamer; import org.apache.commons.jexl.Expression; import org.apache.commons.jexl.ExpressionFactory; import org.apache.commons.jexl.JexlContext; import org.apache.commons.jexl.util.Introspector; import org.apache.commons.jexl.util.introspection.Info; import org.apache.commons.jexl.util.introspection.UberspectImpl; import org.apache.commons.jexl.util.introspection.VelMethod; import org.apache.commons.jexl.util.introspection.VelPropertyGet; import org.apache.commons.jexl.util.introspection.VelPropertySet; import org.apache.commons.jxpath.*; import org.apache.commons.lang.ArrayUtils; import org.apache.commons.lang.StringUtils; import org.apache.excalibur.source.Source; import org.apache.excalibur.source.SourceException; import org.apache.excalibur.source.SourceValidity; import org.apache.excalibur.xml.sax.XMLizable; import org.mozilla.javascript.Context; import org.mozilla.javascript.Function; import org.mozilla.javascript.JavaScriptException; import org.mozilla.javascript.NativeArray; import org.mozilla.javascript.NativeJavaClass; import org.mozilla.javascript.ScriptRuntime; import org.mozilla.javascript.Scriptable; import org.mozilla.javascript.ScriptableObject; import org.mozilla.javascript.Undefined; import org.mozilla.javascript.Wrapper; import org.w3c.dom.Node; import org.w3c.dom.NodeList; import org.xml.sax.Attributes; import org.xml.sax.ContentHandler; import org.xml.sax.Locator; import org.xml.sax.SAXException; import org.xml.sax.ext.LexicalHandler; import org.xml.sax.helpers.AttributesImpl; /** * @cocoon.sitemap.component.documentation * Provides a generic page template with embedded JSTL and XPath * expression substitution to access data sent by Cocoon Flowscripts. * * @cocoon.sitemap.component.name jx * @cocoon.sitemap.component.label content * @cocoon.sitemap.component.logger sitemap.generator.jx * * @cocoon.sitemap.component.pooling.max 16 * * @deprecated Replaced with the template block: {@link org.apache.cocoon.template.JXTemplateGenerator}. * * @version $Id$ */ public class JXTemplateGenerator extends ServiceableGenerator implements CacheableProcessingComponent { // Quick'n dirty hack to replace all SAXParseException by a located runtime exception private static final class JXTException extends LocatedRuntimeException { JXTException(String message, Location loc, Throwable thr) { super(message, thr, loc); } } private static final JXPathContextFactory jxpathContextFactory = JXPathContextFactory.newInstance(); private static final Attributes EMPTY_ATTRS = XMLUtils.EMPTY_ATTRIBUTES; private final NamespacesTable namespaces = new NamespacesTable(); private static final Iterator EMPTY_ITER = new Iterator() { public boolean hasNext() { return false; } public Object next() { return null; } public void remove() { // EMPTY } }; private static final Iterator NULL_ITER = new Iterator() { public boolean hasNext() { return true; } public Object next() { return null; } public void remove() { // EMPTY } }; private XMLConsumer getConsumer() { return this.xmlConsumer; } public static class ErrorHolder extends Exception { private Error err; public ErrorHolder(Error err) { super(err.getMessage()); this.err = err; } public void printStackTrace(PrintStream ps) { err.printStackTrace(ps); } public void printStackTrace(PrintWriter pw) { err.printStackTrace(pw); } public void printStackTrace() { err.printStackTrace(); } public Error getError() { return err; } } /** * Facade to the Location to be set on the consumer prior to * sending other events, location member changeable */ public static class LocationFacade implements Locator { private Location locator; public LocationFacade(Location initialLocation) { this.locator = initialLocation; } public void setDocumentLocation(Location newLocation) { this.locator = newLocation; } public int getColumnNumber() { return this.locator.getColumnNumber(); } public int getLineNumber() { return this.locator.getLineNumber(); } public String getPublicId() { return null; } public String getSystemId() { return this.locator.getURI(); } } /** * Jexl Introspector that supports Rhino JavaScript objects * as well as Java Objects */ static class JSIntrospector extends UberspectImpl { static class JSMethod implements VelMethod { Scriptable scope; String name; public JSMethod(Scriptable scope, String name) { this.scope = scope; this.name = name; } public Object invoke(Object thisArg, Object[] args) throws Exception { Context cx = Context.enter(); try { Object result; Scriptable thisObj = !(thisArg instanceof Scriptable) ? Context.toObject(thisArg, scope) : (Scriptable)thisArg; result = ScriptableObject.getProperty(thisObj, name); Object[] newArgs = null; if (args != null) { newArgs = new Object[args.length]; int len = args.length; for (int i = 0; i < len; i++) { newArgs[i] = args[i]; if (args[i] != null && !(args[i] instanceof Number) && !(args[i] instanceof Boolean) && !(args[i] instanceof String) && !(args[i] instanceof Scriptable)) { newArgs[i] = Context.toObject(args[i], scope); } } } result = ScriptRuntime.call(cx, result, thisObj, newArgs, scope); if (result == Undefined.instance || result == Scriptable.NOT_FOUND) { result = null; } else if (!(result instanceof NativeJavaClass)) { while (result instanceof Wrapper) { result = ((Wrapper)result).unwrap(); } } return result; } catch (JavaScriptException e) { throw new java.lang.reflect.InvocationTargetException(e); } finally { Context.exit(); } } public boolean isCacheable() { return false; } public String getMethodName() { return name; } public Class getReturnType() { return Object.class; } } static class JSPropertyGet implements VelPropertyGet { Scriptable scope; String name; public JSPropertyGet(Scriptable scope, String name) { this.scope = scope; this.name = name; } public Object invoke(Object thisArg) throws Exception { Context cx = Context.enter(); try { Scriptable thisObj = !(thisArg instanceof Scriptable) ? Context.toObject(thisArg, scope) : (Scriptable)thisArg; Object result = ScriptableObject.getProperty(thisObj, name); if (result == Scriptable.NOT_FOUND) { result = ScriptableObject.getProperty(thisObj, "get" + StringUtils.capitalize(name)); if (result != Scriptable.NOT_FOUND && result instanceof Function) { try { result = ((Function)result).call( cx, ScriptableObject.getTopLevelScope(thisObj), thisObj, new Object[] {}); } catch (JavaScriptException exc) { exc.printStackTrace(); result = null; } } } if (result == Scriptable.NOT_FOUND || result == Undefined.instance) { result = null; } else if (result instanceof Wrapper && !(result instanceof NativeJavaClass)) { result = ((Wrapper)result).unwrap(); } return result; } finally { Context.exit(); } } public boolean isCacheable() { return false; } public String getMethodName() { return name; } } static class JSPropertySet implements VelPropertySet { Scriptable scope; String name; public JSPropertySet(Scriptable scope, String name) { this.scope = scope; this.name = name; } public Object invoke(Object thisArg, Object rhs) throws Exception { Context.enter(); try { Scriptable thisObj; Object arg = rhs; if (!(thisArg instanceof Scriptable)) { thisObj = Context.toObject(thisArg, scope); } else { thisObj = (Scriptable)thisArg; } if (arg != null && !(arg instanceof Number) && !(arg instanceof Boolean) && !(arg instanceof String) && !(arg instanceof Scriptable)) { arg = Context.toObject(arg, scope); } ScriptableObject.putProperty(thisObj, name, arg); return rhs; } finally { Context.exit(); } } public boolean isCacheable() { return false; } public String getMethodName() { return name; } } static class NativeArrayIterator implements Iterator { NativeArray arr; int index; public NativeArrayIterator(NativeArray arr) { this.arr = arr; this.index = 0; } public boolean hasNext() { return index < (int)arr.jsGet_length(); } public Object next() { Context.enter(); try { Object result = arr.get(index++, arr); if (result == Undefined.instance || result == Scriptable.NOT_FOUND) { result = null; } else { if (!(result instanceof NativeJavaClass)) { while (result instanceof Wrapper) { result = ((Wrapper)result).unwrap(); } } } return result; } finally { Context.exit(); } } public void remove() { arr.delete(index); } } static class ScriptableIterator implements Iterator { Scriptable scope; Object[] ids; int index; public ScriptableIterator(Scriptable scope) { this.scope = scope; this.ids = scope.getIds(); this.index = 0; } public boolean hasNext() { return index < ids.length; } public Object next() { Context.enter(); try { Object result = ScriptableObject.getProperty(scope, ids[index++].toString()); if (result == Undefined.instance || result == Scriptable.NOT_FOUND) { result = null; } else if (!(result instanceof NativeJavaClass)) { while (result instanceof Wrapper) { result = ((Wrapper)result).unwrap(); } } return result; } finally { Context.exit(); } } public void remove() { Context.enter(); try { scope.delete(ids[index].toString()); } finally { Context.exit(); } } } public Iterator getIterator(Object obj, Info i) throws Exception { if (!(obj instanceof Scriptable)) { // support Enumeration if (obj instanceof Enumeration) { final Enumeration e = (Enumeration)obj; return new Iterator() { public boolean hasNext() { return e.hasMoreElements(); } public Object next() { return e.nextElement(); } public void remove() { // no action } }; } if (obj instanceof Iterator) { // support Iterator return (Iterator)obj; } return super.getIterator(obj, i); } if (obj instanceof NativeArray) { return new NativeArrayIterator((NativeArray)obj); } return new ScriptableIterator((Scriptable)obj); } public VelMethod getMethod(Object obj, String methodName, Object[] args, Info i) throws Exception { return !(obj instanceof Scriptable) ? super.getMethod(obj, methodName, args, i) : new JSMethod((Scriptable)obj, methodName); } public VelPropertyGet getPropertyGet(Object obj, String identifier, Info i) throws Exception { return !(obj instanceof Scriptable) ? super.getPropertyGet(obj, identifier, i) : new JSPropertyGet((Scriptable)obj, identifier); } public VelPropertySet getPropertySet(Object obj, String identifier, Object arg, Info i) throws Exception { return !(obj instanceof Scriptable) ? super.getPropertySet(obj, identifier, arg, i) : new JSPropertySet((Scriptable)obj, identifier); } } static class MyJexlContext extends HashMap implements JexlContext { private MyJexlContext closure; MyJexlContext() { this(null); } MyJexlContext(MyJexlContext closure) { this.closure = closure; } public Map getVars() { return this; } public void setVars(Map map) { putAll(map); } public boolean containsKey(Object key) { return this.get(key) !=null; } public Object get(Object key) { if (key.equals("this")) { return this; } Object result = super.get(key); if (result == null && closure != null) { result = closure.get(key); } return result; } } static class MyVariables implements Variables { MyVariables closure; Map localVariables = new HashMap(); static final String[] VARIABLES = new String[] { "cocoon", "continuation", "flowContext", "request", "response", "context", "session", "parameters" }; Object cocoon; // backward compatibility Object bean, kont, request, response, session, context, parameters; MyVariables(Object cocoon, Object bean, WebContinuation kont, Object request, Object session, Object context, Object parameters) { this.cocoon = cocoon; this.bean = bean; this.kont = kont; this.request = request; this.session = session; this.context = context; this.parameters = parameters; } public MyVariables(MyVariables parent) { this.closure = parent; } public boolean isDeclaredVariable(String varName) { int len = VARIABLES.length; for (int i = 0; i < len; i++) { if (varName.equals(VARIABLES[i])) { return true; } } if (localVariables.containsKey(varName)) { return true; } if (closure != null) { return closure.isDeclaredVariable(varName); } return false; } public Object getVariable(String varName) { Object result = localVariables.get(varName); if (result != null) { return result; } if (closure != null) { return closure.getVariable(varName); } if (varName.equals("cocoon")) { return cocoon; } // backward compatibility if (varName.equals("continuation")) { return kont; } else if (varName.equals("flowContext")) { return bean; } else if (varName.equals("request")) { return request; } else if (varName.equals("session")) { return session; } else if (varName.equals("context")) { return context; } else if (varName.equals("parameters")) { return parameters; } return null; } public void declareVariable(String varName, Object value) { localVariables.put(varName, value); } public void undeclareVariable(String varName) { localVariables.remove(varName); } } static { // Hack: there's no _nice_ way to add my introspector to Jexl right now try { Field field = Introspector.class.getDeclaredField("uberSpect"); field.setAccessible(true); field.set(null, new JSIntrospector()); } catch (Exception e) { e.printStackTrace(); } } /** The namespace used by this generator */ public final static String NS = "http://apache.org/cocoon/templates/jx/1.0"; final static String TEMPLATE = "template"; final static String FOR_EACH = "forEach"; final static String IF = "if"; final static String CHOOSE = "choose"; final static String WHEN = "when"; final static String OTHERWISE = "otherwise"; final static String OUT = "out"; final static String IMPORT = "import"; final static String SET = "set"; final static String MACRO = "macro"; final static String EVALBODY = "evalBody"; final static String EVAL = "eval"; final static String PARAMETER = "parameter"; final static String FORMAT_NUMBER = "formatNumber"; final static String FORMAT_DATE = "formatDate"; final static String COMMENT = "comment"; final static String CACHE_KEY = "cache-key"; final static String VALIDITY = "cache-validity"; /** * Compile a single Jexl expr (contained in ${}) or XPath expression * (contained in #{}) */ private static JXTExpression compileExpr(String expr, String errorPrefix, Location location) throws JXTException { try { return compileExpr(expr); } catch (Exception exc) { throw new JXTException(errorPrefix + exc.getMessage(), location, exc); } } private static JXTExpression compileExpr(String inStr) throws Exception { try { if (inStr == null) { return null; } StringReader in = new StringReader(inStr.trim()); int ch; boolean xpath = false; boolean inExpr = false; StringBuffer expr = new StringBuffer(); while ((ch = in.read()) != -1) { char c = (char)ch; if (inExpr) { if (c == '\\') { ch = in.read(); expr.append((ch == -1) ? '\\' : (char)ch); } else if (c == '}') { return compile(expr.toString(), xpath); } else { expr.append(c); } } else { if (c == '$' || c == '#') { ch = in.read(); if (ch == '{') { inExpr = true; xpath = c == '#'; continue; } } // hack: invalid expression? // just return the original and swallow exception return new JXTExpression(inStr, null); } } if (inExpr) { // unclosed #{} or ${} throw new Exception("Unterminated " + (xpath ? "#" : "$") + "{"); } } catch (IOException ignored) { ignored.printStackTrace(); } return new JXTExpression(inStr, null); } /* * Compile an integer expression (returns either a Compiled Expression * or an Integer literal) */ private static JXTExpression compileInt(String val, String msg, Location location) throws SAXException { JXTExpression res = compileExpr(val, msg, location); if (res != null) { if (res.compiledExpression == null) { res.compiledExpression = Integer.valueOf(res.raw); } return res; } return null; } private static JXTExpression compileBoolean(String val, String msg, Location location) throws SAXException { JXTExpression res = compileExpr(val, msg, location); if (res != null) { if (res.compiledExpression == null) { res.compiledExpression = Boolean.valueOf(res.raw); } return res; } return null; } private static JXTExpression compile(final String variable, boolean xpath) throws Exception { Object compiled; if (xpath) { compiled = JXPathContext.compile(variable); } else { compiled = ExpressionFactory.createExpression(variable); } return new JXTExpression(variable, compiled); } static private Object getValue(JXTExpression expr, JexlContext jexlContext, JXPathContext jxpathContext, Boolean lenient) throws Exception { if (expr != null) { Object compiled = expr.compiledExpression; try { if (compiled instanceof CompiledExpression) { CompiledExpression e = (CompiledExpression)compiled; boolean oldLenient = jxpathContext.isLenient(); if (lenient != null) { jxpathContext.setLenient(lenient.booleanValue()); } try { return e.getValue(jxpathContext); } finally { jxpathContext.setLenient(oldLenient); } } else if (compiled instanceof Expression) { Expression e = (Expression)compiled; return e.evaluate(jexlContext); } return compiled; } catch (InvocationTargetException e) { Throwable t = e.getTargetException(); if (t instanceof Exception) { throw (Exception)t; } throw (Error)t; } } else { return null; } } static private Object getValue(JXTExpression expr, JexlContext jexlContext, JXPathContext jxpathContext) throws Exception { return getValue(expr, jexlContext, jxpathContext, null); } static private int getIntValue(JXTExpression expr, JexlContext jexlContext, JXPathContext jxpathContext) throws Exception { Object res = getValue(expr, jexlContext, jxpathContext); return res instanceof Number ? ((Number)res).intValue() : 0; } static private Number getNumberValue(JXTExpression expr, JexlContext jexlContext, JXPathContext jxpathContext) throws Exception { Object res = getValue(expr, jexlContext, jxpathContext); if (res instanceof Number) { return (Number)res; } if (res != null) { return Double.valueOf(res.toString()); } return null; } static private String getStringValue(JXTExpression expr, JexlContext jexlContext, JXPathContext jxpathContext) throws Exception { Object res = getValue(expr, jexlContext, jxpathContext); if (res != null) { return res.toString(); } if (expr != null && expr.compiledExpression == null) { return expr.raw; } return null; } static private Boolean getBooleanValue(JXTExpression expr, JexlContext jexlContext, JXPathContext jxpathContext) throws Exception { Object res = getValue(expr, jexlContext, jxpathContext); return res instanceof Boolean ? (Boolean)res : null; } private Object getNode(JXTExpression expr, JexlContext jexlContext, JXPathContext jxpathContext) throws Exception { return getNode(expr, jexlContext, jxpathContext, null); } // Hack: try to prevent JXPath from converting result to a String private Object getNode(JXTExpression expr, JexlContext jexlContext, JXPathContext jxpathContext, Boolean lenient) throws Exception { try { Object compiled = expr.compiledExpression; if (compiled instanceof CompiledExpression) { CompiledExpression e = (CompiledExpression)compiled; boolean oldLenient = jxpathContext.isLenient(); if (lenient != null) jxpathContext.setLenient(lenient.booleanValue()); try { Iterator iter = e.iteratePointers(jxpathContext); if (iter.hasNext()) { Pointer first = (Pointer)iter.next(); if (iter.hasNext()) { List result = new LinkedList(); result.add(first.getNode()); boolean dom = (first.getNode() instanceof Node); while (iter.hasNext()) { Object obj = ((Pointer)iter.next()).getNode(); dom = dom && (obj instanceof Node); result.add(obj); } Object[] arr; if (dom) { arr = new Node[result.size()]; } else { arr = new Object[result.size()]; } result.toArray(arr); return arr; } return first.getNode(); } return null; } finally { jxpathContext.setLenient(oldLenient); } } else if (compiled instanceof Expression) { Expression e = (Expression)compiled; return e.evaluate(jexlContext); } return expr.raw; } catch (InvocationTargetException e) { Throwable t = e.getTargetException(); if (t instanceof Exception) { throw (Exception)t; } throw (Error)t; } } static class Event { final Location location; Event next; // in document order Event(Location locator) { this.location = locator != null ? locator : Location.UNKNOWN; } public String locationString() { return location.toString(); } } static class TextEvent extends Event { TextEvent(Location location, char[] chars, int start, int length) throws SAXException { super(location); StringBuffer buf = new StringBuffer(); this.raw = new char[length]; System.arraycopy(chars, start, this.raw, 0, length); CharArrayReader in = new CharArrayReader(chars, start, length); int ch; boolean inExpr = false; boolean xpath = false; try { top: while ((ch = in.read()) != -1) { // column++; char c = (char)ch; processChar: while (true) { if (inExpr) { if (c == '\\') { ch = in.read(); buf.append(ch == -1 ? '\\' : (char)ch); } else if (c == '}') { String str = buf.toString(); Object compiledExpression; try { if (xpath) { compiledExpression = JXPathContext.compile(str); } else { compiledExpression = ExpressionFactory.createExpression(str); } } catch (Exception exc) { throw new JXTException(exc.getMessage(), this.location, exc); } substitutions.add(new JXTExpression(str, compiledExpression)); buf.setLength(0); inExpr = false; } else { buf.append(c); } } else if (c == '$' || c == '#') { ch = in.read(); if (ch == '{') { xpath = c == '#'; inExpr = true; if (buf.length() > 0) { char[] charArray = new char[buf.length()]; buf.getChars(0, buf.length(), charArray, 0); substitutions.add(charArray); buf.setLength(0); } continue top; } buf.append(c); if (ch != -1) { c = (char)ch; continue processChar; } } else { buf.append(c); } break; } } } catch (IOException ignored) { // won't happen ignored.printStackTrace(); } if (inExpr) { // unclosed #{} or ${} buf.insert(0, (xpath ? "#" : "$") + "{"); } if (buf.length() > 0) { char[] charArray = new char[buf.length()]; buf.getChars(0, buf.length(), charArray, 0); substitutions.add(charArray); } else if (substitutions.isEmpty()) { substitutions.add(ArrayUtils.EMPTY_CHAR_ARRAY); } } final List substitutions = new LinkedList(); final char[] raw; } static class Characters extends TextEvent { Characters(Location location, char[] chars, int start, int length) throws SAXException { super(location, chars, start, length); } } static class StartDocument extends Event { StartDocument(Location location) { super(location); templateProperties = new HashMap(); } SourceValidity compileTime; EndDocument endDocument; // null if document fragment Map templateProperties; } static class EndDocument extends Event { EndDocument(Location location) { super(location); } } static class EndElement extends Event { EndElement(Location location, StartElement startElement) { super(location); this.startElement = startElement; } final StartElement startElement; } static class EndPrefixMapping extends Event { EndPrefixMapping(Location location, String prefix) { super(location); this.prefix = prefix; } final String prefix; } static class IgnorableWhitespace extends TextEvent { IgnorableWhitespace(Location location, char[] chars, int start, int length) throws SAXException { super(location, chars, start, length); } } static class ProcessingInstruction extends Event { ProcessingInstruction(Location location, String target, String data) { super(location); this.target = target; this.data = data; } final String target; final String data; } static class SkippedEntity extends Event { SkippedEntity(Location location, String name) { super(location); this.name = name; } final String name; } abstract static class AttributeEvent { AttributeEvent(String namespaceURI, String localName, String raw, String type) { this.namespaceURI = namespaceURI; this.localName = localName; this.raw = raw; this.type = type; } final String namespaceURI; final String localName; final String raw; final String type; } static class CopyAttribute extends AttributeEvent { CopyAttribute(String namespaceURI, String localName, String raw, String type, String value) { super(namespaceURI, localName, raw, type); this.value = value; } final String value; } static class Subst { // VOID } static class Literal extends Subst { Literal(String val) { this.value = val; } final String value; } static class JXTExpression extends Subst { JXTExpression(String raw, Object expr) { this.raw = raw; this.compiledExpression = expr; } String raw; Object compiledExpression; } static class SubstituteAttribute extends AttributeEvent { SubstituteAttribute(String namespaceURI, String localName, String raw, String type, List substs) { super(namespaceURI, localName, raw, type); this.substitutions = substs; } final List substitutions; } static class StartElement extends Event { StartElement(Location location, String namespaceURI, String localName, String raw, Attributes attrs) throws SAXException { super(location); this.namespaceURI = namespaceURI; this.localName = localName; this.raw = raw; this.qname = "{" + namespaceURI + "}" + localName; StringBuffer buf = new StringBuffer(); int len = attrs.getLength(); for (int i = 0; i < len; i++) { String uri = attrs.getURI(i); String local = attrs.getLocalName(i); String qname = attrs.getQName(i); String type = attrs.getType(i); String value = attrs.getValue(i); StringReader in = new StringReader(value); int ch; buf.setLength(0); boolean inExpr = false; List substEvents = new LinkedList(); boolean xpath = false; try { top: while ((ch = in.read()) != -1) { char c = (char)ch; processChar: while (true) { if (inExpr) { if (c == '\\') { ch = in.read(); buf.append(ch == -1 ? '\\' : (char)ch); } else if (c == '}') { String str = buf.toString(); JXTExpression compiledExpression; try { compiledExpression = compile(str, xpath); } catch (Exception exc) { throw new JXTException(exc.getMessage(), location, exc); } substEvents.add(compiledExpression); buf.setLength(0); inExpr = false; } else { buf.append(c); } } else if (c == '$' || c == '#') { ch = in.read(); if (ch == '{') { if (buf.length() > 0) { substEvents.add(new Literal(buf.toString())); buf.setLength(0); } inExpr = true; xpath = c == '#'; continue top; } buf.append(c); if (ch != -1) { c = (char)ch; continue processChar; } } else { buf.append(c); } break; } } } catch (IOException ignored) { ignored.printStackTrace(); } if (inExpr) { // unclosed #{} or ${} String msg = "Unterminated " + (xpath ? "#" : "$") + "{"; throw new JXTException(msg, location, null); } if (buf.length() > 0) { if (substEvents.size() == 0) { attributeEvents.add(new CopyAttribute(uri, local, qname, type, buf.toString())); } else { substEvents.add(new Literal(buf.toString())); attributeEvents.add(new SubstituteAttribute(uri, local, qname, type, substEvents)); } } else { if (substEvents.size() > 0) { attributeEvents.add(new SubstituteAttribute(uri, local, qname, type, substEvents)); } else { attributeEvents.add(new CopyAttribute(uri, local, qname, type, "")); } } } this.attributes = new AttributesImpl(attrs); } final String namespaceURI; final String localName; final String raw; final String qname; final List attributeEvents = new LinkedList(); final Attributes attributes; EndElement endElement; } static class StartPrefixMapping extends Event { StartPrefixMapping(Location location, String prefix, String uri) { super(location); this.prefix = prefix; this.uri = uri; } final String prefix; final String uri; } static class EndCDATA extends Event { EndCDATA(Location location) { super(location); } } static class EndDTD extends Event { EndDTD(Location location) { super(location); } } static class EndEntity extends Event { EndEntity(Location location, String name) { super(location); this.name = name; } final String name; } static class StartCDATA extends Event { StartCDATA(Location location) { super(location); } } static class StartDTD extends Event { StartDTD(Location location, String name, String publicId, String systemId) { super(location); this.name = name; this.publicId = publicId; this.systemId = systemId; } final String name; final String publicId; final String systemId; } static class StartEntity extends Event { public StartEntity(Location location, String name) { super(location); this.name = name; } final String name; } static class StartInstruction extends Event { StartInstruction(StartElement startElement) { super(startElement.location); this.startElement = startElement; } final StartElement startElement; EndInstruction endInstruction; } static class EndInstruction extends Event { EndInstruction(Location locator, StartInstruction startInstruction) { super(locator); this.startInstruction = startInstruction; startInstruction.endInstruction = this; } final StartInstruction startInstruction; } static class StartForEach extends StartInstruction { StartForEach(StartElement raw, JXTExpression items, JXTExpression var, JXTExpression varStatus, JXTExpression begin, JXTExpression end, JXTExpression step, Boolean lenient) { super(raw); this.items = items; this.var = var; this.varStatus = varStatus; this.begin = begin; this.end = end; this.step = step; this.lenient = lenient; } final JXTExpression items; final JXTExpression var; final JXTExpression varStatus; final JXTExpression begin; final JXTExpression end; final JXTExpression step; final Boolean lenient; } static class StartIf extends StartInstruction { StartIf(StartElement raw, JXTExpression test) { super(raw); this.test = test; } final JXTExpression test; } static class StartChoose extends StartInstruction { StartChoose(StartElement raw) { super(raw); } StartWhen firstChoice; StartOtherwise otherwise; } static class StartWhen extends StartInstruction { StartWhen(StartElement raw, JXTExpression test) { super(raw); this.test = test; } final JXTExpression test; StartWhen nextChoice; } static class StartOtherwise extends StartInstruction { StartOtherwise(StartElement raw) { super(raw); } } static class StartOut extends StartInstruction { StartOut(StartElement raw, JXTExpression expr, Boolean lenient) { super(raw); this.compiledExpression = expr; this.lenient = lenient; } final JXTExpression compiledExpression; final Boolean lenient; } static class StartImport extends StartInstruction { StartImport(StartElement raw, AttributeEvent uri, JXTExpression select) { super(raw); this.uri = uri; this.select = select; } final AttributeEvent uri; final JXTExpression select; } static class StartTemplate extends StartInstruction { StartTemplate(StartElement raw) { super(raw); } } static class StartEvalBody extends StartInstruction { StartEvalBody(StartElement raw) { super(raw); } } static class StartEval extends StartInstruction { StartEval(StartElement raw, JXTExpression value) { super(raw); this.value = value; } final JXTExpression value; } static class StartDefine extends StartInstruction { StartDefine(StartElement raw, String namespace, String name) { super(raw); this.namespace = namespace; this.name = name; this.qname = "{" + namespace + "}" + name; this.parameters = new HashMap(); } final String name; final String namespace; final String qname; final Map parameters; Event body; void finish() throws SAXException { Event e = next; boolean params = true; while (e != this.endInstruction) { if (e instanceof StartParameter) { StartParameter startParam = (StartParameter)e; if (!params) { throw new JXTException("<parameter> not allowed here: \"" + startParam.name + "\"", startParam.location, null); } Object prev = parameters.put(startParam.name, startParam); if (prev != null) { throw new JXTException("duplicate parameter: \"" + startParam.name + "\"", location, null); } e = startParam.endInstruction; } else if (e instanceof IgnorableWhitespace) { // EMPTY } else if (e instanceof Characters) { // check for whitespace char[] ch = ((TextEvent)e).raw; int len = ch.length; for (int i = 0; i < len; i++) { if (!Character.isWhitespace(ch[i])) { if (params) { params = false; body = e; } break; } } } else { if (params) { params = false; body = e; } } e = e.next; } if (this.body == null) { this.body = this.endInstruction; } } } static class StartParameter extends StartInstruction { StartParameter(StartElement raw, String name, String optional, String default_) { super(raw); this.name = name; this.optional = optional; this.default_ = default_; } final String name; final String optional; final String default_; } static class StartSet extends StartInstruction { StartSet(StartElement raw, JXTExpression var, JXTExpression value) { super(raw); this.var = var; this.value = value; } final JXTExpression var; final JXTExpression value; } static class StartComment extends StartInstruction { StartComment(StartElement raw) { super(raw); } } // formatNumber tag (borrows from Jakarta taglibs JSTL) private static Locale parseLocale(String locale, String variant) { Locale ret = null; String language = locale; String country = null; int index = StringUtils.indexOfAny(locale, "-_"); if (index > -1) { language = locale.substring(0, index); country = locale.substring(index + 1); } if (StringUtils.isEmpty(language)) { throw new IllegalArgumentException("No language in locale"); } if (country == null) { ret = variant != null ? new Locale(language, "", variant) : new Locale(language, ""); } else if (country.length() > 0) { ret = variant != null ? new Locale(language, country, variant) : new Locale(language, country); } else { throw new IllegalArgumentException("Empty country in locale"); } return ret; } private static final String NUMBER = "number"; private static final String CURRENCY = "currency"; private static final String PERCENT = "percent"; static class LocaleAwareInstruction extends StartInstruction { private JXTExpression locale; LocaleAwareInstruction(StartElement startElement, JXTExpression locale) { super(startElement); this.locale = locale; } protected Locale getLocale(JexlContext jexl, JXPathContext jxp) throws Exception { Object locVal = getValue(this.locale, jexl, jxp); if (locVal == null) locVal = getStringValue(this.locale, jexl, jxp); if (locVal != null) { return locVal instanceof Locale ? (Locale) locVal : parseLocale(locVal.toString(), null); } else { return Locale.getDefault(); } } } static class StartFormatNumber extends LocaleAwareInstruction { JXTExpression value; JXTExpression type; JXTExpression pattern; JXTExpression currencyCode; JXTExpression currencySymbol; JXTExpression isGroupingUsed; JXTExpression maxIntegerDigits; JXTExpression minIntegerDigits; JXTExpression maxFractionDigits; JXTExpression minFractionDigits; JXTExpression var; private static Class currencyClass; static { try { currencyClass = Class.forName("java.util.Currency"); // container's runtime is J2SE 1.4 or greater } catch (Exception cnfe) { // EMPTY } } public StartFormatNumber(StartElement raw, JXTExpression var, JXTExpression value, JXTExpression type, JXTExpression pattern, JXTExpression currencyCode, JXTExpression currencySymbol, JXTExpression isGroupingUsed, JXTExpression maxIntegerDigits, JXTExpression minIntegerDigits, JXTExpression maxFractionDigits, JXTExpression minFractionDigits, JXTExpression locale) { super(raw, locale); this.var = var; this.value = value; this.type = type; this.pattern = pattern; this.currencyCode = currencyCode; this.currencySymbol = currencySymbol; this.isGroupingUsed = isGroupingUsed; this.maxIntegerDigits = maxIntegerDigits; this.minIntegerDigits = minIntegerDigits; this.maxFractionDigits = maxFractionDigits; this.minFractionDigits = minFractionDigits; } String format(JexlContext jexl, JXPathContext jxp) throws Exception { // Determine formatting locale String var = getStringValue(this.var, jexl, jxp); Number input = getNumberValue(this.value, jexl, jxp); String type = getStringValue(this.type, jexl, jxp); String pattern = getStringValue(this.pattern, jexl, jxp); String currencyCode = getStringValue(this.currencyCode, jexl, jxp); String currencySymbol = getStringValue(this.currencySymbol, jexl, jxp); Boolean isGroupingUsed = getBooleanValue(this.isGroupingUsed, jexl, jxp); Number maxIntegerDigits = getNumberValue(this.maxIntegerDigits, jexl, jxp); Number minIntegerDigits = getNumberValue(this.minIntegerDigits, jexl, jxp); Number maxFractionDigits = getNumberValue(this.maxFractionDigits, jexl, jxp); Number minFractionDigits = getNumberValue(this.minFractionDigits, jexl, jxp); Locale loc = getLocale(jexl,jxp); String formatted; if (loc != null) { // Create formatter NumberFormat formatter = null; if (StringUtils.isNotEmpty(pattern)) { // if 'pattern' is specified, 'type' is ignored DecimalFormatSymbols symbols = new DecimalFormatSymbols(loc); formatter = new DecimalFormat(pattern, symbols); } else { formatter = createFormatter(loc, type); } if (StringUtils.isNotEmpty(pattern) || CURRENCY.equalsIgnoreCase(type)) { setCurrency(formatter, currencyCode, currencySymbol); } configureFormatter(formatter, isGroupingUsed, maxIntegerDigits, minIntegerDigits, maxFractionDigits, minFractionDigits); formatted = formatter.format(input); } else { // no formatting locale available, use toString() formatted = input.toString(); } if (var != null) { jexl.getVars().put(var, formatted); jxp.getVariables().declareVariable(var, formatted); return null; } return formatted; } private NumberFormat createFormatter(Locale loc, String type) throws Exception { NumberFormat formatter = null; if ((type == null) || NUMBER.equalsIgnoreCase(type)) { formatter = NumberFormat.getNumberInstance(loc); } else if (CURRENCY.equalsIgnoreCase(type)) { formatter = NumberFormat.getCurrencyInstance(loc); } else if (PERCENT.equalsIgnoreCase(type)) { formatter = NumberFormat.getPercentInstance(loc); } else { throw new IllegalArgumentException("Invalid type: \"" + type + "\": should be \"number\" or \"currency\" or \"percent\""); } return formatter; } /* * Applies the 'groupingUsed', 'maxIntegerDigits', 'minIntegerDigits', * 'maxFractionDigits', and 'minFractionDigits' attributes to the given * formatter. */ private void configureFormatter(NumberFormat formatter, Boolean isGroupingUsed, Number maxIntegerDigits, Number minIntegerDigits, Number maxFractionDigits, Number minFractionDigits) { if (isGroupingUsed != null) formatter.setGroupingUsed(isGroupingUsed.booleanValue()); if (maxIntegerDigits != null) formatter.setMaximumIntegerDigits(maxIntegerDigits.intValue()); if (minIntegerDigits != null) formatter.setMinimumIntegerDigits(minIntegerDigits.intValue()); if (maxFractionDigits != null) formatter.setMaximumFractionDigits(maxFractionDigits.intValue()); if (minFractionDigits != null) formatter.setMinimumFractionDigits(minFractionDigits.intValue()); } /* * Override the formatting locale's default currency symbol with the * specified currency code (specified via the "currencyCode" attribute) or * currency symbol (specified via the "currencySymbol" attribute). * * If both "currencyCode" and "currencySymbol" are present, * "currencyCode" takes precedence over "currencySymbol" if the * java.util.Currency class is defined in the container's runtime (that * is, if the container's runtime is J2SE 1.4 or greater), and * "currencySymbol" takes precendence over "currencyCode" otherwise. * * If only "currencyCode" is given, it is used as a currency symbol if * java.util.Currency is not defined. * * Example: * * JDK "currencyCode" "currencySymbol" Currency symbol being displayed * ----------------------------------------------------------------------- * all --- --- Locale's default currency symbol * * <1.4 EUR --- EUR * >=1.4 EUR --- Locale's currency symbol for Euro * * all --- \u20AC \u20AC * * <1.4 EUR \u20AC \u20AC * >=1.4 EUR \u20AC Locale's currency symbol for Euro */ private void setCurrency(NumberFormat formatter, String currencyCode, String currencySymbol) throws Exception { String code = null; String symbol = null; if (currencyCode == null) { if (currencySymbol == null) { return; } symbol = currencySymbol; } else if (currencySymbol != null) { if (currencyClass != null) { code = currencyCode; } else { symbol = currencySymbol; } } else if (currencyClass != null) { code = currencyCode; } else { symbol = currencyCode; } if (code != null) { Object[] methodArgs = new Object[1]; /* * java.util.Currency.getInstance() */ Method m = currencyClass.getMethod("getInstance", new Class[] {String.class}); methodArgs[0] = code; Object currency = m.invoke(null, methodArgs); /* * java.text.NumberFormat.setCurrency() */ Class[] paramTypes = new Class[1]; paramTypes[0] = currencyClass; Class numberFormatClass = Class.forName("java.text.NumberFormat"); m = numberFormatClass.getMethod("setCurrency", paramTypes); methodArgs[0] = currency; m.invoke(formatter, methodArgs); } else { /* * Let potential ClassCastException propagate up (will almost * never happen) */ DecimalFormat df = (DecimalFormat) formatter; DecimalFormatSymbols dfs = df.getDecimalFormatSymbols(); dfs.setCurrencySymbol(symbol); df.setDecimalFormatSymbols(dfs); } } } // formatDate tag (borrows from Jakarta taglibs JSTL) static class StartFormatDate extends LocaleAwareInstruction { private static final String DATE = "date"; private static final String TIME = "time"; private static final String DATETIME = "both"; JXTExpression var; JXTExpression value; JXTExpression type; JXTExpression pattern; JXTExpression timeZone; JXTExpression dateStyle; JXTExpression timeStyle; JXTExpression locale; StartFormatDate(StartElement raw, JXTExpression var, JXTExpression value, JXTExpression type, JXTExpression pattern, JXTExpression timeZone, JXTExpression dateStyle, JXTExpression timeStyle, JXTExpression locale) { super(raw, locale); this.var = var; this.value = value; this.type = type; this.pattern = pattern; this.timeZone = timeZone; this.dateStyle = dateStyle; this.timeStyle = timeStyle; } String format(JexlContext jexl, JXPathContext jxp) throws Exception { String var = getStringValue(this.var, jexl, jxp); Object value = getValue(this.value, jexl, jxp); String pattern = getStringValue(this.pattern, jexl, jxp); Object timeZone = getValue(this.timeZone, jexl, jxp); String type = getStringValue(this.type, jexl, jxp); String timeStyle = getStringValue(this.timeStyle, jexl, jxp); String dateStyle = getStringValue(this.dateStyle, jexl, jxp); String formatted = null; // Create formatter Locale locale = getLocale(jexl, jxp); DateFormat formatter = createFormatter(locale, type, dateStyle, timeStyle); // Apply pattern, if present if (pattern != null) { try { ((SimpleDateFormat) formatter).applyPattern(pattern); } catch (ClassCastException cce) { formatter = new SimpleDateFormat(pattern, locale); } } // Set time zone TimeZone tz = null; if ((timeZone instanceof String) && timeZone.equals("")) { timeZone = null; } if (timeZone != null) { if (timeZone instanceof String) { tz = TimeZone.getTimeZone((String) timeZone); } else if (timeZone instanceof TimeZone) { tz = (TimeZone) timeZone; } else { throw new IllegalArgumentException("Illegal timeZone value: \""+timeZone+"\""); } } if (tz != null) { formatter.setTimeZone(tz); } formatted = formatter.format(value); if (var != null) { jexl.getVars().put(var, formatted); jxp.getVariables().declareVariable(var, formatted); return null; } return formatted; } private DateFormat createFormatter(Locale loc, String type, String dateStyle, String timeStyle) throws Exception { DateFormat formatter = null; if ((type == null) || DATE.equalsIgnoreCase(type)) { formatter = DateFormat.getDateInstance(getStyle(dateStyle), loc); } else if (TIME.equalsIgnoreCase(type)) { formatter = DateFormat.getTimeInstance(getStyle(timeStyle), loc); } else if (DATETIME.equalsIgnoreCase(type)) { formatter = DateFormat.getDateTimeInstance(getStyle(dateStyle), getStyle(timeStyle), loc); } else { throw new IllegalArgumentException("Invalid type: \""+ type+"\""); } return formatter; } private static final String DEFAULT = "default"; private static final String SHORT = "short"; private static final String MEDIUM = "medium"; private static final String LONG = "long"; private static final String FULL = "full"; private int getStyle(String style) { int ret = DateFormat.DEFAULT; if (style != null) { if (DEFAULT.equalsIgnoreCase(style)) { ret = DateFormat.DEFAULT; } else if (SHORT.equalsIgnoreCase(style)) { ret = DateFormat.SHORT; } else if (MEDIUM.equalsIgnoreCase(style)) { ret = DateFormat.MEDIUM; } else if (LONG.equalsIgnoreCase(style)) { ret = DateFormat.LONG; } else if (FULL.equalsIgnoreCase(style)) { ret = DateFormat.FULL; } else { throw new IllegalArgumentException("Invalid style: \"" + style + "\": should be \"default\" or \"short\" or \"medium\" or \"long\" or \"full\""); } } return ret; } } static class Parser implements ContentHandler, LexicalHandler { StartDocument startEvent; Event lastEvent; Stack stack = new Stack(); Locator locator; Location charLocation; StringBuffer charBuf; public Parser() { // EMPTY } StartDocument getStartEvent() { return this.startEvent; } void recycle() { startEvent = null; lastEvent = null; stack.clear(); locator = null; charLocation = null; charBuf = null; } private void addEvent(Event ev) throws SAXException { if (ev != null) { if (lastEvent == null) { lastEvent = startEvent = new StartDocument(LocationUtils.getLocation(locator, "template")); } else { flushChars(); } lastEvent.next = ev; lastEvent = ev; } else { throw new NullPointerException("null event"); } } void flushChars() throws SAXException { if (charBuf != null) { char[] chars = new char[charBuf.length()]; charBuf.getChars(0, charBuf.length(), chars, 0); Characters ev = new Characters(charLocation, chars, 0, chars.length); lastEvent.next = ev; lastEvent = ev; charLocation = null; charBuf = null; } } public void characters(char[] ch, int start, int length) throws SAXException { if (charBuf == null) { charBuf = new StringBuffer(length); charLocation = LocationUtils.getLocation(locator, "[text]"); } charBuf.append(ch, start, length); } public void endDocument() throws SAXException { StartDocument startDoc = (StartDocument)stack.pop(); EndDocument endDoc = new EndDocument(LocationUtils.getLocation(locator, "template")); startDoc.endDocument = endDoc; addEvent(endDoc); } public void endElement(String namespaceURI, String localName, String raw) throws SAXException { Event start = (Event)stack.pop(); Event newEvent = null; if (NS.equals(namespaceURI)) { StartInstruction startInstruction = (StartInstruction)start; EndInstruction endInstruction = new EndInstruction(LocationUtils.getLocation(locator, "<"+raw+">"), startInstruction); newEvent = endInstruction; if (start instanceof StartWhen) { StartWhen startWhen = (StartWhen)start; StartChoose startChoose = (StartChoose)stack.peek(); if (startChoose.firstChoice != null) { StartWhen w = startChoose.firstChoice; while (w.nextChoice != null) { w = w.nextChoice; } w.nextChoice = startWhen; } else { startChoose.firstChoice = startWhen; } } else if (start instanceof StartOtherwise) { StartOtherwise startOtherwise = (StartOtherwise)start; StartChoose startChoose = (StartChoose)stack.peek(); startChoose.otherwise = startOtherwise; } } else { StartElement startElement = (StartElement)start; newEvent = startElement.endElement = new EndElement(LocationUtils.getLocation(locator, "<"+raw+">"), startElement); } addEvent(newEvent); if (start instanceof StartDefine) { StartDefine startDefine = (StartDefine)start; startDefine.finish(); } } public void endPrefixMapping(String prefix) throws SAXException { EndPrefixMapping endPrefixMapping = new EndPrefixMapping(LocationUtils.getLocation(locator, null), prefix); addEvent(endPrefixMapping); } public void ignorableWhitespace(char[] ch, int start, int length) throws SAXException { Event ev = new IgnorableWhitespace(LocationUtils.getLocation(locator, null), ch, start, length); addEvent(ev); } public void processingInstruction(String target, String data) throws SAXException { Event pi = new ProcessingInstruction(LocationUtils.getLocation(locator, null), target, data); addEvent(pi); } public void setDocumentLocator(Locator locator) { this.locator = locator; } public void skippedEntity(String name) throws SAXException { addEvent(new SkippedEntity(LocationUtils.getLocation(locator, null), name)); } public void startDocument() { startEvent = new StartDocument(LocationUtils.getLocation(locator, null)); lastEvent = startEvent; stack.push(lastEvent); } public void startElement(String namespaceURI, String localName, String qname, Attributes attrs) throws SAXException { Event newEvent = null; Location locator = LocationUtils.getLocation(this.locator, "<"+qname+">"); AttributesImpl elementAttributes = new AttributesImpl(attrs); int attributeCount = elementAttributes.getLength(); for (int i = 0; i < attributeCount; i++) { String attributeURI = elementAttributes.getURI(i); if (StringUtils.equals(attributeURI, NS)) { getStartEvent().templateProperties.put(elementAttributes.getLocalName(i), compileExpr(elementAttributes.getValue(i), null, locator)); elementAttributes.removeAttribute(i--); } } StartElement startElement = new StartElement(locator, namespaceURI, localName, qname, elementAttributes); if (NS.equals(namespaceURI)) { if (localName.equals(FOR_EACH)) { String items = attrs.getValue("items"); String select = attrs.getValue("select"); JXTExpression begin = compileInt(attrs.getValue("begin"), FOR_EACH, locator); JXTExpression end = compileInt(attrs.getValue("end"), FOR_EACH, locator); JXTExpression step = compileInt(attrs.getValue("step"), FOR_EACH, locator); JXTExpression var = compileExpr(attrs.getValue("var"), null, locator); JXTExpression varStatus = compileExpr(attrs.getValue("varStatus"), null, locator); if (items == null) { if (select == null && (begin == null || end == null)) { throw new JXTException("forEach: \"select\", \"items\", or both \"begin\" and \"end\" must be specified", locator, null); } } else if (select != null) { throw new JXTException("forEach: only one of \"select\" or \"items\" may be specified", locator, null); } JXTExpression expr = compileExpr(items == null ? select : items, null, locator); String lenientValue = attrs.getValue("lenient"); Boolean lenient = (lenientValue == null) ? null : Boolean.valueOf(lenientValue); StartForEach startForEach = new StartForEach(startElement, expr, var, varStatus, begin, end, step,lenient); newEvent = startForEach; } else if (localName.equals(FORMAT_NUMBER)) { JXTExpression value = compileExpr(attrs.getValue("value"), null, locator); JXTExpression type = compileExpr(attrs.getValue("type"), null, locator); JXTExpression pattern = compileExpr(attrs.getValue("pattern"), null, locator); JXTExpression currencyCode = compileExpr(attrs.getValue("currencyCode"), null, locator); JXTExpression currencySymbol = compileExpr(attrs.getValue("currencySymbol"), null, locator); JXTExpression isGroupingUsed = compileBoolean(attrs.getValue("isGroupingUsed"), null, locator); JXTExpression maxIntegerDigits = compileInt(attrs.getValue("maxIntegerDigits"), null, locator); JXTExpression minIntegerDigits = compileInt(attrs.getValue("minIntegerDigits"), null, locator); JXTExpression maxFractionDigits = compileInt(attrs.getValue("maxFractionDigits"), null, locator); JXTExpression minFractionDigits = compileInt(attrs.getValue("minFractionDigits"), null, locator); JXTExpression var = compileExpr(attrs.getValue("var"), null, locator); JXTExpression locale = compileExpr(attrs.getValue("locale"), null, locator); StartFormatNumber startFormatNumber = new StartFormatNumber(startElement, var, value, type, pattern, currencyCode, currencySymbol, isGroupingUsed, maxIntegerDigits, minIntegerDigits, maxFractionDigits, minFractionDigits, locale); newEvent = startFormatNumber; } else if (localName.equals(FORMAT_DATE)) { JXTExpression var = compileExpr(attrs.getValue("var"), null, locator); JXTExpression value = compileExpr(attrs.getValue("value"), null, locator); JXTExpression type = compileExpr(attrs.getValue("type"), null, locator); JXTExpression pattern = compileExpr(attrs.getValue("pattern"), null, locator); JXTExpression timeZone = compileExpr(attrs.getValue("timeZone"), null, locator); JXTExpression dateStyle = compileExpr(attrs.getValue("dateStyle"), null, locator); JXTExpression timeStyle = compileExpr(attrs.getValue("timeStyle"), null, locator); JXTExpression locale = compileExpr(attrs.getValue("locale"), null, locator); StartFormatDate startFormatDate = new StartFormatDate(startElement, var, value, type, pattern, timeZone, dateStyle, timeStyle, locale); newEvent = startFormatDate; } else if (localName.equals(CHOOSE)) { StartChoose startChoose = new StartChoose(startElement); newEvent = startChoose; } else if (localName.equals(WHEN)) { if (stack.size() == 0 || !(stack.peek() instanceof StartChoose)) { throw new JXTException("<when> must be within <choose>", locator, null); } String test = attrs.getValue("test"); if (test != null) { JXTExpression expr = compileExpr(test, "when: \"test\": ", locator); StartWhen startWhen = new StartWhen(startElement, expr); newEvent = startWhen; } else { throw new JXTException("when: \"test\" is required", locator, null); } } else if (localName.equals(OUT)) { String value = attrs.getValue("value"); if (value != null) { JXTExpression expr = compileExpr(value, "out: \"value\": ", locator); String lenientValue = attrs.getValue("lenient"); Boolean lenient = lenientValue == null ? null : Boolean.valueOf(lenientValue); newEvent = new StartOut(startElement, expr, lenient); } else { throw new JXTException("out: \"value\" is required", locator, null); } } else if (localName.equals(OTHERWISE)) { if (stack.size() != 0 && (stack.peek() instanceof StartChoose)) { StartOtherwise startOtherwise = new StartOtherwise(startElement); newEvent = startOtherwise; } else { throw new JXTException( "<otherwise> must be within <choose>", locator, null); } } else if (localName.equals(IF)) { String test = attrs.getValue("test"); if (test != null) { JXTExpression expr = compileExpr(test, "if: \"test\": ", locator); StartIf startIf = new StartIf(startElement, expr); newEvent = startIf; } else { throw new JXTException("if: \"test\" is required", locator, null); } } else if (localName.equals(MACRO)) { // <macro name="myTag" targetNamespace="myNamespace"> // <parameter name="paramName" required="Boolean" default="value"/> // body // </macro> String namespace = StringUtils.defaultString(attrs.getValue("targetNamespace")); String name = attrs.getValue("name"); if (name != null) { StartDefine startDefine = new StartDefine(startElement, namespace, name); newEvent = startDefine; } else { throw new JXTException("macro: \"name\" is required", locator, null); } } else if (localName.equals(PARAMETER)) { if (stack.size() == 0 || !(stack.peek() instanceof StartDefine)) { throw new JXTException("<parameter> not allowed here", locator, null); } else { String name = attrs.getValue("name"); String optional = attrs.getValue("optional"); String default_ = attrs.getValue("default"); if (name != null) { StartParameter startParameter = new StartParameter(startElement, name, optional, default_); newEvent = startParameter; } else { throw new JXTException("parameter: \"name\" is required", locator, null); } } } else if (localName.equals(EVALBODY)) { newEvent = new StartEvalBody(startElement); } else if (localName.equals(EVAL)) { String value = attrs.getValue("select"); JXTExpression valueExpr = compileExpr(value, "eval: \"select\":", locator); newEvent = new StartEval(startElement, valueExpr); } else if (localName.equals(SET)) { String var = attrs.getValue("var"); String value = attrs.getValue("value"); JXTExpression varExpr = null; JXTExpression valueExpr = null; if (var != null) { varExpr = compileExpr(var, "set: \"var\":", locator); } if (value != null) { valueExpr = compileExpr(value, "set: \"value\":", locator); } StartSet startSet = new StartSet(startElement, varExpr, valueExpr); newEvent = startSet; } else if (localName.equals(IMPORT)) { // <import uri="${root}/foo/bar.xml" context="${foo}"/> AttributeEvent uri = null; Iterator iter = startElement.attributeEvents.iterator(); while (iter.hasNext()) { AttributeEvent e = (AttributeEvent)iter.next(); if (e.localName.equals("uri")) { uri = e; break; } } if (uri != null) { // If "context" is present then its value will be used // as the context object in the imported template String select = attrs.getValue("context"); JXTExpression expr = null; if (select != null) { expr = compileExpr(select, "import: \"context\": ", locator); } StartImport startImport = new StartImport(startElement, uri, expr); newEvent = startImport; } else { throw new JXTException("import: \"uri\" is required", locator, null); } } else if (localName.equals(TEMPLATE)) { StartTemplate startTemplate = new StartTemplate(startElement); newEvent = startTemplate; } else if (localName.equals(COMMENT)) { // <jx:comment>This will be parsed</jx:comment> StartComment startJXComment = new StartComment(startElement); newEvent = startJXComment; } else { throw new JXTException("unrecognized tag: " + localName, locator, null); } } else { newEvent = startElement; } stack.push(newEvent); addEvent(newEvent); } public void startPrefixMapping(String prefix, String uri) throws SAXException { addEvent(new StartPrefixMapping(LocationUtils.getLocation(locator, null), prefix, uri)); } public void comment(char ch[], int start, int length) throws SAXException { // DO NOTHING } public void endCDATA() throws SAXException { addEvent(new EndCDATA(LocationUtils.getLocation(locator, null))); } public void endDTD() throws SAXException { addEvent(new EndDTD(LocationUtils.getLocation(locator, null))); } public void endEntity(String name) throws SAXException { addEvent(new EndEntity(LocationUtils.getLocation(locator, null), name)); } public void startCDATA() throws SAXException { addEvent(new StartCDATA(LocationUtils.getLocation(locator, null))); } public void startDTD(String name, String publicId, String systemId) throws SAXException { addEvent(new StartDTD(LocationUtils.getLocation(locator, null), name, publicId, systemId)); } public void startEntity(String name) throws SAXException { addEvent(new StartEntity(LocationUtils.getLocation(locator, null), name)); } } /** * Adapter that makes this generator usable as a transformer * (Note there is a performance penalty for this however: * you effectively recompile the template for every instance document) */ public static class TransformerAdapter extends ServiceableTransformer { static class TemplateConsumer extends Parser implements XMLConsumer { public TemplateConsumer() { this.gen = new JXTemplateGenerator(); } public void setup(SourceResolver resolver, Map objectModel, String src, Parameters parameters) throws ProcessingException, SAXException, IOException { this.gen.setup(resolver, objectModel, null, parameters); } public void service(ServiceManager manager) throws ServiceException { this.gen.service(manager); } public void endDocument() throws SAXException { super.endDocument(); gen.performGeneration(gen.getConsumer(), gen.getJexlContext(), gen.getJXPathContext(), null, getStartEvent(), null); } void setConsumer(XMLConsumer consumer) { gen.setConsumer(consumer); } void recycle() { super.recycle(); gen.recycle(); } JXTemplateGenerator gen; } TemplateConsumer templateConsumer = new TemplateConsumer(); public void recycle() { super.recycle(); templateConsumer.recycle(); } public void setup(SourceResolver resolver, Map objectModel, String src, Parameters parameters) throws ProcessingException, SAXException, IOException { super.setup(resolver, objectModel, src, parameters); templateConsumer.setup(resolver, objectModel, src, parameters); } public void service(ServiceManager manager) throws ServiceException { super.service(manager); templateConsumer.service(manager); } public void setConsumer(XMLConsumer xmlConsumer) { super.setConsumer(templateConsumer); templateConsumer.setConsumer(xmlConsumer); } } private JXPathContext jxpathContext; private MyJexlContext globalJexlContext; private Variables variables; private static Map cache = new HashMap(); private Source inputSource; private Map definitions; private Map cocoon; private JXPathContext getJXPathContext() { return jxpathContext; } private MyJexlContext getJexlContext() { return globalJexlContext; } /* (non-Javadoc) * @see org.apache.avalon.excalibur.pool.Recyclable#recycle() */ public void recycle() { if (this.resolver != null) { this.resolver.release(this.inputSource); } this.inputSource = null; this.jxpathContext = null; this.globalJexlContext = null; this.variables = null; this.definitions = null; this.cocoon = null; this.namespaces.clear(); super.recycle(); } /* (non-Javadoc) * @see org.apache.cocoon.sitemap.SitemapModelComponent#setup(org.apache.cocoon.environment.SourceResolver, java.util.Map, java.lang.String, org.apache.avalon.framework.parameters.Parameters) */ public void setup(SourceResolver resolver, Map objectModel, String src, Parameters parameters) throws ProcessingException, SAXException, IOException { super.setup(resolver, objectModel, src, parameters); if (src != null) { try { this.inputSource = resolver.resolveURI(src); } catch (SourceException se) { throw SourceUtil.handle("Error during resolving of '" + src + "'.", se); } final String uri = inputSource.getURI(); boolean regenerate = false; StartDocument startEvent = null; synchronized (cache) { startEvent = (StartDocument)cache.get(uri); if (startEvent != null) { int valid = SourceValidity.UNKNOWN; if (startEvent.compileTime != null) { valid = startEvent.compileTime.isValid(); } if (valid == SourceValidity.UNKNOWN && startEvent.compileTime != null) { SourceValidity validity = inputSource.getValidity(); valid = startEvent.compileTime.isValid(validity); } if (valid != SourceValidity.VALID) { regenerate = true; } } else { regenerate = true; } } if (regenerate) { Parser parser = new Parser(); SourceUtil.parse(this.manager, this.inputSource, parser); startEvent = parser.getStartEvent(); startEvent.compileTime = this.inputSource.getValidity(); synchronized (cache) { cache.put(uri, startEvent); } } } Object bean = FlowHelper.getContextObject(objectModel); WebContinuation kont = FlowHelper.getWebContinuation(objectModel); setContexts(bean, kont, parameters, objectModel); this.definitions = new HashMap(); } private void fillContext(Object contextObject, Map map) { if (contextObject != null) { // Hack: I use jxpath to populate the context object's properties // in the jexl context final JXPathBeanInfo bi = JXPathIntrospector.getBeanInfo(contextObject.getClass()); if (bi.isDynamic()) { Class cl = bi.getDynamicPropertyHandlerClass(); try { DynamicPropertyHandler h = (DynamicPropertyHandler)cl.newInstance(); String[] result = h.getPropertyNames(contextObject); int len = result.length; for (int i = 0; i < len; i++) { try { map.put(result[i], h.getProperty(contextObject, result[i])); } catch (Exception exc) { exc.printStackTrace(); } } } catch (Exception ignored) { ignored.printStackTrace(); } } else { PropertyDescriptor[] props = bi.getPropertyDescriptors(); int len = props.length; for (int i = 0; i < len; i++) { try { Method read = props[i].getReadMethod(); if (read != null) { map.put(props[i].getName(), read.invoke(contextObject, null)); } } catch (Exception ignored) { ignored.printStackTrace(); } } } } } private void setContexts(Object contextObject, WebContinuation kont, Parameters parameters, Map objectModel) { final Request request = ObjectModelHelper.getRequest(objectModel); final Object session = request.getSession(false); final Object app = ObjectModelHelper.getContext(objectModel); cocoon = new HashMap(); Object fomRequest = FOM_JavaScriptFlowHelper.getFOM_Request(objectModel); cocoon.put("request", fomRequest != null ? fomRequest : request); if (session != null) { cocoon.put("session", FOM_JavaScriptFlowHelper.getFOM_Session(objectModel)); } cocoon.put("context", FOM_JavaScriptFlowHelper.getFOM_Context(objectModel)); cocoon.put("continuation", FOM_JavaScriptFlowHelper.getFOM_WebContinuation(objectModel)); cocoon.put("parameters", parameters); this.variables = new MyVariables(cocoon, contextObject, kont, request, session, app, parameters); Map map; if (contextObject instanceof Map) { map = (Map)contextObject; } else { map = new HashMap(); fillContext(contextObject, map); } jxpathContext = jxpathContextFactory.newContext(null, contextObject); jxpathContext.setNamespaceContextPointer(new NamespacesTablePointer(namespaces)); jxpathContext.setVariables(variables); jxpathContext.setLenient(parameters.getParameterAsBoolean("lenient-xpath", false)); globalJexlContext = new MyJexlContext(); globalJexlContext.setVars(map); map = globalJexlContext.getVars(); map.put("cocoon", cocoon); if (contextObject != null) { map.put("flowContext", contextObject); // FIXME (VG): Is this required (what it's used for - examples)? // Here I use Rhino's live-connect objects to allow Jexl to call // java constructors Object javaPkg = FOM_JavaScriptFlowHelper.getJavaPackage(objectModel); Object pkgs = FOM_JavaScriptFlowHelper.getPackages(objectModel); map.put("java", javaPkg); map.put("Packages", pkgs); } if (kont != null) { map.put("continuation", kont); } map.put("request", request); map.put("context", app); map.put("parameters", parameters); if (session != null) { map.put("session", session); } } /* (non-Javadoc) * @see org.apache.cocoon.generation.Generator#generate() */ public void generate() throws IOException, SAXException, ProcessingException { final String cacheKey = this.inputSource.getURI(); StartDocument startEvent; synchronized (cache) { startEvent = (StartDocument)cache.get(cacheKey); } performGeneration(this.xmlConsumer, globalJexlContext, jxpathContext, null, startEvent, null); } private void performGeneration(final XMLConsumer consumer, MyJexlContext jexlContext, JXPathContext jxpathContext, StartElement macroCall, Event startEvent, Event endEvent) throws SAXException { cocoon.put("consumer", consumer); RedundantNamespacesFilter filter = new RedundantNamespacesFilter(this.xmlConsumer); // EventPrinterPipe log = new EventPrinterPipe(); // log.setConsumer(filter); execute(filter, globalJexlContext, jxpathContext, null, startEvent, null); } interface CharHandler { public void characters(char[] ch, int offset, int length) throws SAXException; } private void characters(JexlContext jexlContext, JXPathContext jxpathContext, TextEvent event, CharHandler handler) throws SAXException { Iterator iter = event.substitutions.iterator(); while (iter.hasNext()) { Object subst = iter.next(); char[] chars; if (subst instanceof char[]) { chars = (char[])subst; } else { JXTExpression expr = (JXTExpression)subst; try { Object val = getValue(expr, jexlContext, jxpathContext); chars = val != null ? val.toString().toCharArray() : ArrayUtils.EMPTY_CHAR_ARRAY; } catch (Exception e) { throw new JXTException(e.getMessage(), event.location, e); } } handler.characters(chars, 0, chars.length); } } /** dump a DOM document, using an IncludeXMLConsumer to filter out start/end document events */ private void executeDOM(final XMLConsumer consumer, MyJexlContext jexlContext, JXPathContext jxpathContext, Node node) throws SAXException { IncludeXMLConsumer includer = new IncludeXMLConsumer(consumer); DOMStreamer streamer = new DOMStreamer(includer); streamer.stream(node); } private void call(Location location, StartElement macroCall, final XMLConsumer consumer, MyJexlContext jexlContext, JXPathContext jxpathContext, Event startEvent, Event endEvent) throws SAXException { try { execute(consumer, jexlContext, jxpathContext, macroCall, startEvent, endEvent); } catch (Exception exc) { throw new JXTException(macroCall.localName + ": " + exc.getMessage(), location, exc); } } public static class LoopTagStatus { Object current; int index; int count; boolean first; boolean last; int begin; int end; int step; public Object getCurrent() { return current; } public int getIndex() { return index; } public int getCount() { return count; } public boolean isFirst() { return first; } public boolean isLast() { return last; } public int getBegin() { return begin; } public int getEnd() { return end; } public int getStep() { return step; } } private void execute(final XMLConsumer consumer, MyJexlContext jexlContext, JXPathContext jxpathContext, StartElement macroCall, Event startEvent, Event endEvent) throws SAXException { Event ev = startEvent; LocationFacade loc = new LocationFacade(ev.location); consumer.setDocumentLocator(loc); while (ev != endEvent) { loc.setDocumentLocation(ev.location); if (ev instanceof Characters) { TextEvent text = (TextEvent)ev; Iterator iter = text.substitutions.iterator(); while (iter.hasNext()) { Object subst = iter.next(); char[] chars; if (subst instanceof char[]) { chars = (char[])subst; } else { JXTExpression expr = (JXTExpression)subst; try { Object val = getNode(expr, jexlContext, jxpathContext); if (val instanceof Node) { executeDOM(consumer, jexlContext, jxpathContext, (Node)val); continue; } else if (val instanceof NodeList) { NodeList nodeList = (NodeList)val; int len = nodeList.getLength(); for (int i = 0; i < len; i++) { Node n = nodeList.item(i); executeDOM(consumer, jexlContext, jxpathContext, n); } continue; } else if (val instanceof Node[]) { Node[] nodeList = (Node[])val; int len = nodeList.length; for (int i = 0; i < len; i++) { Node n = nodeList[i]; executeDOM(consumer, jexlContext, jxpathContext, n); } continue; } else if (val instanceof XMLizable) { ((XMLizable)val).toSAX(new IncludeXMLConsumer(consumer)); continue; } chars = val != null ? val.toString().toCharArray() : ArrayUtils.EMPTY_CHAR_ARRAY; } catch (Exception e) { throw new JXTException(e.getMessage(), ev.location, e); } } consumer.characters(chars, 0, chars.length); } } else if (ev instanceof EndElement) { EndElement endElement = (EndElement)ev; StartElement startElement = endElement.startElement; StartDefine def = (StartDefine)definitions.get(startElement.qname); if (def == null) { consumer.endElement(startElement.namespaceURI, startElement.localName, startElement.raw); namespaces.leaveScope(consumer); } } else if (ev instanceof EndPrefixMapping) { EndPrefixMapping endPrefixMapping = (EndPrefixMapping)ev; namespaces.removeDeclaration(endPrefixMapping.prefix); } else if (ev instanceof IgnorableWhitespace) { TextEvent text = (TextEvent)ev; characters(jexlContext, jxpathContext, text, new CharHandler() { public void characters(char[] ch, int offset, int len) throws SAXException { consumer.ignorableWhitespace(ch, offset, len); } }); } else if (ev instanceof SkippedEntity) { SkippedEntity skippedEntity = (SkippedEntity)ev; consumer.skippedEntity(skippedEntity.name); } else if (ev instanceof StartIf) { StartIf startIf = (StartIf)ev; Object val; try { val = getValue(startIf.test, jexlContext, jxpathContext, Boolean.TRUE); } catch (Exception e) { throw new JXTException(e.getMessage(), ev.location, e); } boolean result = false; if (val instanceof Boolean) { result = ((Boolean)val).booleanValue(); } else { result = (val != null); } if (!result) { ev = startIf.endInstruction.next; continue; } } else if (ev instanceof StartForEach) { StartForEach startForEach = (StartForEach)ev; final Object items = startForEach.items; Iterator iter = null; int begin, end, step; String var, varStatus; try { if (items != null) { JXTExpression expr = (JXTExpression)items; if (expr.compiledExpression instanceof CompiledExpression) { CompiledExpression compiledExpression = (CompiledExpression)expr.compiledExpression; Object val = compiledExpression.getPointer(jxpathContext, expr.raw).getNode(); // FIXME: workaround for JXPath bug iter = val instanceof NativeArray ? new JSIntrospector.NativeArrayIterator((NativeArray)val) : compiledExpression.iteratePointers(jxpathContext); } else if (expr.compiledExpression instanceof Expression) { Expression e = (Expression)expr.compiledExpression; Object result = e.evaluate(jexlContext); if (result != null) { iter = Introspector.getUberspect().getIterator(result, new Info(ev.location.getURI(), ev.location.getLineNumber(), ev.location.getColumnNumber())); } if (iter == null) { iter = EMPTY_ITER; } } else { // literal value iter = new Iterator() { Object val = items; public boolean hasNext() { return val != null; } public Object next() { Object res = val; val = null; return res; } public void remove() { // EMPTY } }; } } else { iter = NULL_ITER; } begin = startForEach.begin == null ? 0 : getIntValue(startForEach.begin, jexlContext, jxpathContext); end = startForEach.end == null ? Integer.MAX_VALUE : getIntValue(startForEach.end, jexlContext, jxpathContext); step = startForEach.step == null ? 1 : getIntValue(startForEach.step, jexlContext, jxpathContext); var = getStringValue(startForEach.var, jexlContext, jxpathContext); varStatus = getStringValue(startForEach.varStatus, jexlContext, jxpathContext); } catch (Exception exc) { throw new JXTException(exc.getMessage(), ev.location, exc); } MyJexlContext localJexlContext = new MyJexlContext(jexlContext); MyVariables localJXPathVariables = new MyVariables((MyVariables)jxpathContext.getVariables()); int i = 0; // Move to the begin row while (i < begin && iter.hasNext()) { iter.next(); i++; } LoopTagStatus status = null; if (varStatus != null) { status = new LoopTagStatus(); status.begin = begin; status.end = end; status.step = step; status.first = true; localJexlContext.put(varStatus, status); localJXPathVariables.declareVariable(varStatus, status); } int skipCounter, count = 1; JXPathContext localJXPathContext = null; while (i <= end && iter.hasNext()) { Object value = iter.next(); if (value instanceof Pointer) { Pointer ptr = (Pointer)value; localJXPathContext = jxpathContext.getRelativeContext(ptr); localJXPathContext.setNamespaceContextPointer(new NamespacesTablePointer(namespaces)); try { value = ptr.getNode(); } catch (Exception exc) { throw new JXTException(exc.getMessage(), ev.location, null); } } else { localJXPathContext = jxpathContextFactory.newContext(jxpathContext, value); localJXPathContext.setNamespaceContextPointer(new NamespacesTablePointer(namespaces)); } localJXPathContext.setVariables(localJXPathVariables); if (var != null) { localJexlContext.put(var, value); } if (status != null) { status.index = i; status.count = count; status.first = i == begin; status.current = value; status.last = (i == end || !iter.hasNext()); } execute(consumer, localJexlContext, localJXPathContext, macroCall, startForEach.next, startForEach.endInstruction); // Skip rows skipCounter = step; while (--skipCounter > 0 && iter.hasNext()) { iter.next(); } // Increase index i += step; count++; } ev = startForEach.endInstruction.next; continue; } else if (ev instanceof StartChoose) { StartChoose startChoose = (StartChoose)ev; StartWhen startWhen = startChoose.firstChoice; while (startWhen != null) { Object val; try { val = getValue(startWhen.test, jexlContext, jxpathContext, Boolean.TRUE); } catch (Exception e) { throw new JXTException(e.getMessage(), ev.location, e); } boolean result; if (val instanceof Boolean) { result = ((Boolean)val).booleanValue(); } else { result = (val != null); } if (result) { execute(consumer, jexlContext, jxpathContext, macroCall, startWhen.next, startWhen.endInstruction); break; } startWhen = startWhen.nextChoice; } if (startWhen == null && startChoose.otherwise != null) { execute(consumer, jexlContext, jxpathContext, macroCall, startChoose.otherwise.next, startChoose.otherwise.endInstruction); } ev = startChoose.endInstruction.next; continue; } else if (ev instanceof StartSet) { StartSet startSet = (StartSet)ev; Object value = null; String var = null; try { if (startSet.var != null) { var = getStringValue(startSet.var, jexlContext, jxpathContext); } if (startSet.value != null) { value = getNode(startSet.value, jexlContext, jxpathContext); } } catch (Exception exc) { throw new JXTException(exc.getMessage(), ev.location, exc); } if (value == null) { NodeList nodeList = toDOMNodeList("set", startSet, jexlContext, macroCall); // JXPath doesn't handle NodeList, so convert it to an array int len = nodeList.getLength(); Node[] nodeArr = new Node[len]; for (int i = 0; i < len; i++) { nodeArr[i] = nodeList.item(i); } value = nodeArr; } if (var != null) { jxpathContext.getVariables().declareVariable(var, value); jexlContext.put(var, value); } ev = startSet.endInstruction.next; continue; } else if (ev instanceof StartElement) { StartElement startElement = (StartElement)ev; StartDefine def = (StartDefine)definitions.get(startElement.qname); if (def != null) { Map attributeMap = new HashMap(); Iterator i = startElement.attributeEvents.iterator(); while (i.hasNext()) { String attributeName; Object attributeValue; AttributeEvent attrEvent = (AttributeEvent) i.next(); attributeName = attrEvent.localName; if (attrEvent instanceof CopyAttribute) { CopyAttribute copy = (CopyAttribute)attrEvent; attributeValue = copy.value; } else if (attrEvent instanceof SubstituteAttribute) { SubstituteAttribute substEvent = (SubstituteAttribute)attrEvent; if (substEvent.substitutions.size() == 1 && substEvent.substitutions.get(0) instanceof JXTExpression) { JXTExpression expr = (JXTExpression)substEvent.substitutions.get(0); Object val; try { val = getNode(expr, jexlContext, jxpathContext); } catch (Exception e) { throw new JXTException(e.getMessage(), ev.location, e); } attributeValue = val != null ? val : ""; } else { StringBuffer buf = new StringBuffer(); Iterator iterSubst = substEvent.substitutions.iterator(); while (iterSubst.hasNext()) { Subst subst = (Subst)iterSubst.next(); if (subst instanceof Literal) { Literal lit = (Literal)subst; buf.append(lit.value); } else if (subst instanceof JXTExpression) { JXTExpression expr = (JXTExpression)subst; Object val; try { val = getValue(expr, jexlContext, jxpathContext); } catch (Exception e) { throw new JXTException(e.getMessage(), ev.location, e); } buf.append(val != null ? val.toString() : ""); } } attributeValue = buf.toString(); } } else { throw new Error("this shouldn't have happened"); } attributeMap.put(attributeName, attributeValue); } MyVariables parent =(MyVariables)jxpathContext.getVariables(); MyVariables vars = new MyVariables(parent); MyJexlContext localJexlContext = new MyJexlContext(jexlContext); HashMap macro = new HashMap(); macro.put("body", startElement); macro.put("arguments", attributeMap); localJexlContext.put("macro", macro); vars.declareVariable("macro", macro); Iterator iter = def.parameters.entrySet().iterator(); while (iter.hasNext()) { Map.Entry e = (Map.Entry)iter.next(); String key = (String)e.getKey(); StartParameter startParam = (StartParameter)e.getValue(); Object default_ = startParam.default_; Object val = attributeMap.get(key); if (val == null) { val = default_; } localJexlContext.put(key, val); vars.declareVariable(key, val); } JXPathContext localJXPathContext = jxpathContextFactory.newContext(null, jxpathContext.getContextBean()); localJXPathContext.setNamespaceContextPointer(new NamespacesTablePointer(namespaces)); localJXPathContext.setVariables(vars); call(ev.location, startElement, consumer, localJexlContext, localJXPathContext, def.body, def.endInstruction); ev = startElement.endElement.next; continue; } Iterator i = startElement.attributeEvents.iterator(); AttributesImpl attrs = new AttributesImpl(); while (i.hasNext()) { AttributeEvent attrEvent = (AttributeEvent)i.next(); if (attrEvent instanceof CopyAttribute) { CopyAttribute copy = (CopyAttribute)attrEvent; attrs.addAttribute(copy.namespaceURI, copy.localName, copy.raw, copy.type, copy.value); } else if (attrEvent instanceof SubstituteAttribute) { StringBuffer buf = new StringBuffer(); SubstituteAttribute substEvent = (SubstituteAttribute)attrEvent; Iterator iterSubst = substEvent.substitutions.iterator(); while (iterSubst.hasNext()) { Subst subst = (Subst)iterSubst.next(); if (subst instanceof Literal) { Literal lit = (Literal)subst; buf.append(lit.value); } else if (subst instanceof JXTExpression) { JXTExpression expr = (JXTExpression)subst; Object val; try { val = getValue(expr, jexlContext, jxpathContext); } catch (Exception e) { throw new JXTException(e.getMessage(), ev.location, e); } buf.append(val != null ? val.toString() : ""); } } attrs.addAttribute(attrEvent.namespaceURI, attrEvent.localName, attrEvent.raw, attrEvent.type, buf.toString()); } } namespaces.enterScope(consumer); consumer.startElement(startElement.namespaceURI, startElement.localName, startElement.raw, attrs); } else if (ev instanceof StartFormatNumber) { StartFormatNumber startFormatNumber = (StartFormatNumber)ev; try { String result = startFormatNumber.format(jexlContext, jxpathContext); if (result != null) { char[] chars = result.toCharArray(); consumer.characters(chars, 0, chars.length); } } catch (Exception e) { throw new JXTException(e.getMessage(), ev.location, e); } } else if (ev instanceof StartFormatDate) { StartFormatDate startFormatDate = (StartFormatDate)ev; try { String result = startFormatDate.format(jexlContext, jxpathContext); if (result != null) { char[] chars = result.toCharArray(); consumer.characters(chars, 0, chars.length); } } catch (Exception e) { throw new JXTException(e.getMessage(), ev.location, e); } } else if (ev instanceof StartPrefixMapping) { StartPrefixMapping startPrefixMapping = (StartPrefixMapping)ev; namespaces.addDeclaration(startPrefixMapping.prefix, startPrefixMapping.uri); } else if (ev instanceof StartComment) { StartComment startJXComment = (StartComment)ev; // Parse the body of the comment NodeList nodeList = toDOMNodeList("comment", startJXComment, jexlContext, macroCall); // JXPath doesn't handle NodeList, so convert it to an array int len = nodeList.getLength(); final StringBuffer buf = new StringBuffer(); Properties omit = XMLUtils.createPropertiesForXML(true); for (int i = 0; i < len; i++) { try { buf.append(XMLUtils.serializeNode(nodeList.item(i), omit)); } catch (Exception e) { throw new JXTException(e.getMessage(), startJXComment.location, e); } } char[] chars = new char[buf.length()]; buf.getChars(0, chars.length, chars, 0); consumer.comment(chars, 0, chars.length); ev = startJXComment.endInstruction.next; continue; } else if (ev instanceof EndCDATA) { consumer.endCDATA(); } else if (ev instanceof EndDTD) { consumer.endDTD(); } else if (ev instanceof EndEntity) { consumer.endEntity(((EndEntity)ev).name); } else if (ev instanceof StartCDATA) { consumer.startCDATA(); } else if (ev instanceof StartDTD) { StartDTD startDTD = (StartDTD)ev; consumer.startDTD(startDTD.name, startDTD.publicId, startDTD.systemId); } else if (ev instanceof StartEntity) { consumer.startEntity(((StartEntity)ev).name); } else if (ev instanceof StartOut) { StartOut startOut = (StartOut)ev; Object val; try { val = getNode(startOut.compiledExpression, jexlContext, jxpathContext, startOut.lenient); if (val instanceof Node) { executeDOM(consumer, jexlContext, jxpathContext, (Node)val); } else if (val instanceof NodeList) { NodeList nodeList = (NodeList)val; int len = nodeList.getLength(); for (int i = 0; i < len; i++) { Node n = nodeList.item(i); executeDOM(consumer, jexlContext, jxpathContext, n); } } else if (val instanceof Node[]) { Node[] nodeList = (Node[])val; int len = nodeList.length; for (int i = 0;i < len; i++) { Node n = nodeList[i]; executeDOM(consumer, jexlContext, jxpathContext, n); } } else if (val instanceof XMLizable) { ((XMLizable)val).toSAX(new IncludeXMLConsumer(consumer)); } else { char[] ch = val == null ? ArrayUtils.EMPTY_CHAR_ARRAY : val.toString().toCharArray(); consumer.characters(ch, 0, ch.length); } } catch (Exception e) { throw new JXTException(e.getMessage(), ev.location, e); } } else if (ev instanceof StartTemplate) { // EMPTY } else if (ev instanceof StartEval) { StartEval startEval = (StartEval)ev; JXTExpression expr = startEval.value; try { Object val = getNode(expr, jexlContext, jxpathContext); if (!(val instanceof StartElement)) { throw new Exception("macro invocation required instead of: " + val); } StartElement call = (StartElement)val; execute(consumer, jexlContext, jxpathContext, call, call.next, call.endElement); } catch (Exception exc) { throw new JXTException(exc.getMessage(), ev.location, exc); } ev = startEval.endInstruction.next; continue; } else if (ev instanceof StartEvalBody) { StartEvalBody startEval = (StartEvalBody)ev; try { execute(consumer, jexlContext, jxpathContext, null, macroCall.next, macroCall.endElement); } catch (Exception exc) { throw new JXTException(exc.getMessage(), ev.location, exc); } ev = startEval.endInstruction.next; continue; } else if (ev instanceof StartDefine) { StartDefine startDefine = (StartDefine)ev; definitions.put(startDefine.qname, startDefine); ev = startDefine.endInstruction.next; continue; } else if (ev instanceof StartImport) { StartImport startImport = (StartImport)ev; String uri; AttributeEvent e = startImport.uri; if (e instanceof CopyAttribute) { CopyAttribute copy = (CopyAttribute)e; uri = copy.value; } else { StringBuffer buf = new StringBuffer(); SubstituteAttribute substAttr = (SubstituteAttribute)e; Iterator i = substAttr.substitutions.iterator(); while (i.hasNext()) { Subst subst = (Subst)i.next(); if (subst instanceof Literal) { Literal lit = (Literal)subst; buf.append(lit.value); } else if (subst instanceof JXTExpression) { JXTExpression expr = (JXTExpression)subst; Object val; try { val = getValue(expr, jexlContext, jxpathContext); } catch (Exception exc) { throw new JXTException(exc.getMessage(), ev.location, exc); } buf.append(val != null ? val.toString() : ""); } } uri = buf.toString(); } Source input = null; StartDocument doc; try { input = resolver.resolveURI(uri); SourceValidity validity = null; synchronized (cache) { doc = (StartDocument)cache.get(input.getURI()); if (doc != null) { boolean recompile = false; if (doc.compileTime == null) { recompile = true; } else { int valid = doc.compileTime.isValid(); if (valid == SourceValidity.UNKNOWN) { validity = input.getValidity(); valid = doc.compileTime.isValid(validity); } if (valid != SourceValidity.VALID) { recompile = true; } } if (recompile) { doc = null; // recompile } } } if (doc == null) { Parser parser = new Parser(); // call getValidity before using the stream is faster if the source is a SitemapSource if (validity == null) { validity = input.getValidity(); } SourceUtil.parse(this.manager, input, parser); doc = parser.getStartEvent(); doc.compileTime = validity; synchronized (cache) { cache.put(input.getURI(), doc); } } } catch (Exception exc) { throw new JXTException(exc.getMessage(), ev.location, exc); } finally { resolver.release(input); } JXPathContext selectJXPath = jxpathContext; MyJexlContext selectJexl = jexlContext; if (startImport.select != null) { try { Object obj = getValue(startImport.select, jexlContext, jxpathContext); selectJXPath = jxpathContextFactory.newContext(null, obj); selectJXPath.setNamespaceContextPointer(new NamespacesTablePointer(namespaces)); selectJXPath.setVariables(variables); selectJexl = new MyJexlContext(jexlContext); fillContext(obj, selectJexl); } catch (Exception exc) { throw new JXTException(exc.getMessage(), ev.location, exc); } } try { execute(consumer, selectJexl, selectJXPath, macroCall, doc.next, doc.endDocument); } catch (Exception exc) { throw new JXTException("Exception occurred in imported template " + uri + ": "+ exc.getMessage(), ev.location, exc); } ev = startImport.endInstruction.next; continue; } else if (ev instanceof StartDocument) { if (((StartDocument)ev).endDocument != null) { // if this isn't a document fragment consumer.startDocument(); } } else if (ev instanceof EndDocument) { consumer.endDocument(); } else if (ev instanceof ProcessingInstruction) { ProcessingInstruction pi = (ProcessingInstruction)ev; consumer.processingInstruction(pi.target, pi.data); } ev = ev.next; } } /* (non-Javadoc) * @see org.apache.cocoon.caching.CacheableProcessingComponent#getKey() */ public Serializable getKey() { JXTExpression cacheKeyExpr = (JXTExpression)getCurrentTemplateProperty(CACHE_KEY); try { final Serializable templateKey = (Serializable) getValue(cacheKeyExpr, globalJexlContext, jxpathContext); if (templateKey != null) { return new JXCacheKey(this.inputSource.getURI(), templateKey); } } catch (Exception e) { getLogger().error("error evaluating cache key", e); } return null; } /* (non-Javadoc) * @see org.apache.cocoon.caching.CacheableProcessingComponent#getValidity() */ public SourceValidity getValidity() { JXTExpression validityExpr = (JXTExpression)getCurrentTemplateProperty(VALIDITY); try { final SourceValidity sourceValidity = this.inputSource.getValidity(); final SourceValidity templateValidity = (SourceValidity) getValue(validityExpr, globalJexlContext, jxpathContext); if (sourceValidity != null && templateValidity != null) { return new JXSourceValidity(sourceValidity, templateValidity); } } catch (Exception e) { getLogger().error("error evaluating cache validity", e); } return null; } private Object getCurrentTemplateProperty(String propertyName) { final String uri = this.inputSource.getURI(); StartDocument startEvent; synchronized (cache) { startEvent = (StartDocument)cache.get(uri); } return (startEvent != null) ? startEvent.templateProperties.get(propertyName) : null; } private NodeList toDOMNodeList(String elementName, StartInstruction si, MyJexlContext jexlContext, StartElement macroCall) throws SAXException{ DOMBuilder builder = new DOMBuilder(); builder.startDocument(); builder.startElement(NS, elementName, elementName, EMPTY_ATTRS); execute(builder, jexlContext, jxpathContext, macroCall, si.next, si.endInstruction); builder.endElement(NS, elementName, elementName); builder.endDocument(); Node node = builder.getDocument().getDocumentElement(); return node.getChildNodes(); } static final class JXCacheKey implements Serializable { private final String templateUri; private final Serializable templateKey; private JXCacheKey(String templateUri, Serializable templateKey) { this.templateUri = templateUri; this.templateKey = templateKey; } public int hashCode() { return templateUri.hashCode() + templateKey.hashCode(); } public String toString() { return "TK:" + templateUri + "_" + templateKey; } public boolean equals(Object o) { if (o instanceof JXCacheKey) { JXCacheKey jxck = (JXCacheKey)o; return this.templateUri.equals(jxck.templateUri) && this.templateKey.equals(jxck.templateKey); } return false; } } static final class JXSourceValidity implements SourceValidity, Serializable { private final SourceValidity sourceValidity; private final SourceValidity templateValidity; private JXSourceValidity(SourceValidity sourceValidity, SourceValidity templateValidity) { this.sourceValidity = sourceValidity; this.templateValidity = templateValidity; } public int isValid() { switch (sourceValidity.isValid()) { case SourceValidity.INVALID: return SourceValidity.INVALID; case SourceValidity.UNKNOWN: if (templateValidity.isValid() == SourceValidity.INVALID) { return SourceValidity.INVALID; } return SourceValidity.UNKNOWN; case SourceValidity.VALID: return templateValidity.isValid(); } return SourceValidity.UNKNOWN; } public int isValid(SourceValidity otherValidity) { if (otherValidity instanceof JXSourceValidity) { JXSourceValidity otherJXValidity = (JXSourceValidity) otherValidity; switch (sourceValidity.isValid(otherJXValidity.sourceValidity)) { case SourceValidity.INVALID: return SourceValidity.INVALID; case SourceValidity.UNKNOWN: if (templateValidity.isValid(otherJXValidity.templateValidity) == SourceValidity.INVALID) { return SourceValidity.INVALID; } return SourceValidity.UNKNOWN; case SourceValidity.VALID: return templateValidity.isValid(otherJXValidity.templateValidity); } } return SourceValidity.UNKNOWN; } } }