/* * 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.transformation; import java.io.IOException; import java.util.HashMap; import java.util.Map; import org.apache.avalon.framework.activity.Initializable; import org.apache.avalon.framework.parameters.Parameters; import org.apache.cocoon.ProcessingException; import org.apache.cocoon.components.flow.FlowHelper; import org.apache.cocoon.components.flow.WebContinuation; import org.apache.cocoon.environment.SourceResolver; import org.apache.commons.jxpath.JXPathContext; import org.apache.regexp.RE; import org.xml.sax.Attributes; import org.xml.sax.SAXException; import org.xml.sax.helpers.AttributesImpl; /** * @cocoon.sitemap.component.documentation * Transformer implementation of the JPath XSP tag library. * * @cocoon.sitemap.component.name jpath * @cocoon.sitemap.component.logger sitemap.transformer.jpath * * * <p> * This transformer (so far) supports the following jpath elements: * * <ul> * <li><jpath:value-of select=".."/> element. * <li><jpath:continuation/> element. * <li><jpath:if test="..">..</jpath:if> element. * <li>jpath:action attribute on all elements that implicitly replaces any * occurance of the string 'id' with the continuation id (useful when * writing form action attributes). eg: * <pre><form name="myform" jpath:action="../cont/id">..</form></pre> * </ul> * </p> * * @author <a href="mailto:crafterm@apache.org">Marcus Crafter</a> * @version $Id$ */ public class JPathTransformer extends AbstractSAXTransformer implements Initializable { /** namespace constant */ public static final String JPATH_NAMESPACE_URI = "http://apache.org/xsp/jpath/1.0"; /** jpath:action attribute constant */ public static final String JPATH_ACTION = "jpath:action"; /** jpath:value-of element constant */ public static final String JPATH_VALUEOF = "value-of"; /** jpath:value-of select attribute constant */ public static final String JPATH_VALUEOF_SELECT = "select"; /** jpath:continuation element constant */ public static final String JPATH_CONTINUATION = "continuation"; /** jpath:continuation select attribute constant */ public static final String JPATH_CONTINUATION_SELECT = "select"; /** jpath:if element constant */ public static final String JPATH_IF = "if"; /** jpath generic test attribute */ public static final String JPATH_TEST = "test"; // web contination private WebContinuation m_kont; // regular expression for matching 'id' strings with jpath:action private RE m_re; // jxpath context private JXPathContext m_jxpathContext; // jpath:value-of variable cache private Map m_cache; /** * Initialize this transformer. * * @exception Exception if an error occurs */ public void initialize() throws Exception { this.defaultNamespaceURI = JPATH_NAMESPACE_URI; m_re = new RE("id"); m_cache = new HashMap(); } /** * Setup this transformer * * @param resolver a {@link SourceResolver} instance * @param objectModel the objectModel * @param src <code>src</code> parameter * @param parameters optional parameters * @exception ProcessingException if an error occurs * @exception SAXException if an error occurs * @exception IOException if an error occurs */ public void setup(SourceResolver resolver, Map objectModel, String src, Parameters parameters) throws ProcessingException, SAXException, IOException { super.setup(resolver, objectModel, src, parameters); // setup the jpath transformer for this thread Object bean = FlowHelper.getContextObject(objectModel); m_kont = FlowHelper.getWebContinuation(objectModel); m_jxpathContext = JXPathContext.newContext(bean); } /** * Intercept startElement to ensure all <jpath:action> attributes * are modified. * * @param uri a <code>String</code> value * @param loc a <code>String</code> value * @param raw a <code>String</code> value * @param a an <code>Attributes</code> value * @exception SAXException if an error occurs */ public void startElement(String uri, String loc, String raw, Attributes a) throws SAXException { AttributesImpl impl = new AttributesImpl(a); checkJPathAction(impl); super.startElement(uri, loc, raw, impl); } /** * Entry method for all elements in our namespace * * @param uri a <code>String</code> value * @param name a <code>String</code> value * @param raw a <code>String</code> value * @param attr an <code>Attributes</code> value * @exception ProcessingException if an error occurs * @exception IOException if an error occurs * @exception SAXException if an error occurs */ public void startTransformingElement( String uri, String name, String raw, Attributes attr ) throws ProcessingException ,IOException, SAXException { if (JPATH_VALUEOF.equals(name)) { doValueOf(attr); } else if (JPATH_CONTINUATION.equals(name)) { doContinuation(attr); } else if (JPATH_IF.equals(name)) { doIf(attr); } else { super.startTransformingElement(uri, name, raw, attr); } } /** * Exit method for all elements in our namespace * * @param uri a <code>String</code> value * @param name a <code>String</code> value * @param raw a <code>String</code> value * @exception ProcessingException if an error occurs * @exception IOException if an error occurs * @exception SAXException if an error occurs */ public void endTransformingElement( String uri, String name, String raw ) throws ProcessingException, IOException, SAXException { if (JPATH_VALUEOF.equals(name) || JPATH_CONTINUATION.equals(name)) { return; // do nothing } else if (JPATH_IF.equals(name)) { finishIf(); } else { super.endTransformingElement(uri, name, raw); } } /** * Helper method to check for the existance of an attribute named * jpath:action. If existing the string 'id' is replaced with the * continuation id. * * @param a an {@link AttributesImpl} instance */ private void checkJPathAction(final AttributesImpl a) { // check for jpath:action attribute int idx = a.getIndex(JPATH_ACTION); if (idx != -1 && JPATH_NAMESPACE_URI.equals(a.getURI(idx))) { if (getLogger().isDebugEnabled()) { getLogger().debug("found jpath:action, adjusting"); } String value = a.getValue(idx); // REVISIT(MC): support for continuation level String id = m_kont.getContinuation(0).getId(); a.removeAttribute(idx); a.addAttribute( "", "action", "action", "CDATA", m_re.subst(value, id) ); } } /** * Helper method for obtaining the value of a particular variable. * * @param variable variable name * @return variable value as an <code>Object</code> */ private Object getValue(final String variable) { Object value; if (m_cache.containsKey(variable)) { value = m_cache.get(variable); } else { value = JXPathContext.compile(variable).getValue(m_jxpathContext); if (value == null) { if (getLogger().isWarnEnabled()) { final String msg = "Value for jpath variable '" + variable + "' does not exist"; getLogger().warn(msg); } } m_cache.put(variable, value); } return value; } /** * Helper method to process a <jpath:value-of select="."> tag * * @param a an {@link Attributes} instance * @exception SAXException if a SAX error occurs * @exception ProcessingException if a processing error occurs */ private void doValueOf(final Attributes a) throws SAXException, ProcessingException { final String select = a.getValue(JPATH_VALUEOF_SELECT); if (null != select) { sendTextEvent((String)getValue(select)); } else { throw new ProcessingException( "jpath:" + JPATH_VALUEOF + " specified without a select attribute" ); } } /** * Helper method to process a <jpath:continuation select=""/> element. * * @param a an <code>Attributes</code> value * @exception SAXException if an error occurs */ private void doContinuation(final Attributes a) throws SAXException { final String level = a.getValue(JPATH_CONTINUATION_SELECT); final String id = (level != null) ? m_kont.getContinuation(Integer.decode(level).intValue()).getId() : m_kont.getContinuation(0).getId(); sendTextEvent(id); } /** * Helper method to process a <jpath:if test="..."> element. * * @param a an <code>Attributes</code> value * @exception SAXException if an error occurs */ private void doIf(final Attributes a) throws SAXException { // handle nested jpath:if statements, if ignoreEventsCount is > 0, then // we are processing a nested jpath:if statement for which the parent // jpath:if test resulted in a false (ie. disallow subelements) result. if (ignoreEventsCount > 0) { ++ignoreEventsCount; return; } // get the test variable final Object value = getValue(a.getValue(JPATH_TEST)); final boolean isTrueBoolean = value instanceof Boolean && ((Boolean)value).booleanValue() == true; final boolean isNonNullNonBoolean = value != null && !(value instanceof Boolean); if (isTrueBoolean || isNonNullNonBoolean) { // do nothing, allow all subelements if (getLogger().isDebugEnabled()) { getLogger().debug("jpath:if results in allowing subelements"); } } else { // disallow all subelements if (getLogger().isDebugEnabled()) { getLogger().debug("jpath:if results in disallowing subelements"); } ++ignoreEventsCount; } } /** * Helper method to process a </jpath:if> element. * * @exception SAXException if an error occurs */ private void finishIf() throws SAXException { // end recording (and dump resulting document fragment) if we've reached // the closing jpath:if tag for which the recording was started. if (ignoreEventsCount > 0) { --ignoreEventsCount; } if (getLogger().isDebugEnabled()) { getLogger().debug("jpath:if closed"); } } /** * Release all held resources. */ public void recycle() { m_cache.clear(); m_kont = null; m_jxpathContext = null; super.recycle(); } }