001: /*
002: * Licensed to the Apache Software Foundation (ASF) under one or more
003: * contributor license agreements. See the NOTICE file distributed with
004: * this work for additional information regarding copyright ownership.
005: * The ASF licenses this file to You under the Apache License, Version 2.0
006: * (the "License"); you may not use this file except in compliance with
007: * the License. You may obtain a copy of the License at
008: *
009: * http://www.apache.org/licenses/LICENSE-2.0
010: *
011: * Unless required by applicable law or agreed to in writing, software
012: * distributed under the License is distributed on an "AS IS" BASIS,
013: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014: * See the License for the specific language governing permissions and
015: * limitations under the License.
016: */
017: package org.apache.cocoon.transformation;
018:
019: import java.io.IOException;
020: import java.util.HashMap;
021: import java.util.Map;
022:
023: import org.apache.avalon.framework.activity.Initializable;
024: import org.apache.avalon.framework.parameters.Parameters;
025: import org.apache.cocoon.ProcessingException;
026: import org.apache.cocoon.components.flow.FlowHelper;
027: import org.apache.cocoon.components.flow.WebContinuation;
028: import org.apache.cocoon.environment.SourceResolver;
029: import org.apache.commons.jxpath.JXPathContext;
030: import org.apache.regexp.RE;
031: import org.xml.sax.Attributes;
032: import org.xml.sax.SAXException;
033: import org.xml.sax.helpers.AttributesImpl;
034:
035: /**
036: * @cocoon.sitemap.component.documentation
037: * Transformer implementation of the JPath XSP tag library.
038: *
039: * @cocoon.sitemap.component.name jpath
040: * @cocoon.sitemap.component.logger sitemap.transformer.jpath
041: *
042: *
043: * <p>
044: * This transformer (so far) supports the following jpath elements:
045: *
046: * <ul>
047: * <li><jpath:value-of select=".."/> element.
048: * <li><jpath:continuation/> element.
049: * <li><jpath:if test="..">..</jpath:if> element.
050: * <li>jpath:action attribute on all elements that implicitly replaces any
051: * occurance of the string 'id' with the continuation id (useful when
052: * writing form action attributes). eg:
053: * <pre><form name="myform" jpath:action="../cont/id">..</form></pre>
054: * </ul>
055: * </p>
056: *
057: * @author <a href="mailto:crafterm@apache.org">Marcus Crafter</a>
058: * @version $Id: JPathTransformer.java 433543 2006-08-22 06:22:54Z crossley $
059: */
060: public class JPathTransformer extends AbstractSAXTransformer implements
061: Initializable {
062:
063: /** namespace constant */
064: public static final String JPATH_NAMESPACE_URI = "http://apache.org/xsp/jpath/1.0";
065:
066: /** jpath:action attribute constant */
067: public static final String JPATH_ACTION = "jpath:action";
068:
069: /** jpath:value-of element constant */
070: public static final String JPATH_VALUEOF = "value-of";
071:
072: /** jpath:value-of select attribute constant */
073: public static final String JPATH_VALUEOF_SELECT = "select";
074:
075: /** jpath:continuation element constant */
076: public static final String JPATH_CONTINUATION = "continuation";
077:
078: /** jpath:continuation select attribute constant */
079: public static final String JPATH_CONTINUATION_SELECT = "select";
080:
081: /** jpath:if element constant */
082: public static final String JPATH_IF = "if";
083:
084: /** jpath generic test attribute */
085: public static final String JPATH_TEST = "test";
086:
087: // web contination
088: private WebContinuation m_kont;
089:
090: // regular expression for matching 'id' strings with jpath:action
091: private RE m_re;
092:
093: // jxpath context
094: private JXPathContext m_jxpathContext;
095:
096: // jpath:value-of variable cache
097: private Map m_cache;
098:
099: /**
100: * Initialize this transformer.
101: *
102: * @exception Exception if an error occurs
103: */
104: public void initialize() throws Exception {
105: this .defaultNamespaceURI = JPATH_NAMESPACE_URI;
106: m_re = new RE("id");
107: m_cache = new HashMap();
108: }
109:
110: /**
111: * Setup this transformer
112: *
113: * @param resolver a {@link SourceResolver} instance
114: * @param objectModel the objectModel
115: * @param src <code>src</code> parameter
116: * @param parameters optional parameters
117: * @exception ProcessingException if an error occurs
118: * @exception SAXException if an error occurs
119: * @exception IOException if an error occurs
120: */
121: public void setup(SourceResolver resolver, Map objectModel,
122: String src, Parameters parameters)
123: throws ProcessingException, SAXException, IOException {
124:
125: super .setup(resolver, objectModel, src, parameters);
126:
127: // setup the jpath transformer for this thread
128: Object bean = FlowHelper.getContextObject(objectModel);
129: m_kont = FlowHelper.getWebContinuation(objectModel);
130: m_jxpathContext = JXPathContext.newContext(bean);
131: }
132:
133: /**
134: * Intercept startElement to ensure all <jpath:action> attributes
135: * are modified.
136: *
137: * @param uri a <code>String</code> value
138: * @param loc a <code>String</code> value
139: * @param raw a <code>String</code> value
140: * @param a an <code>Attributes</code> value
141: * @exception SAXException if an error occurs
142: */
143: public void startElement(String uri, String loc, String raw,
144: Attributes a) throws SAXException {
145:
146: AttributesImpl impl = new AttributesImpl(a);
147: checkJPathAction(impl);
148:
149: super .startElement(uri, loc, raw, impl);
150: }
151:
152: /**
153: * Entry method for all elements in our namespace
154: *
155: * @param uri a <code>String</code> value
156: * @param name a <code>String</code> value
157: * @param raw a <code>String</code> value
158: * @param attr an <code>Attributes</code> value
159: * @exception ProcessingException if an error occurs
160: * @exception IOException if an error occurs
161: * @exception SAXException if an error occurs
162: */
163: public void startTransformingElement(String uri, String name,
164: String raw, Attributes attr) throws ProcessingException,
165: IOException, SAXException {
166:
167: if (JPATH_VALUEOF.equals(name)) {
168: doValueOf(attr);
169: } else if (JPATH_CONTINUATION.equals(name)) {
170: doContinuation(attr);
171: } else if (JPATH_IF.equals(name)) {
172: doIf(attr);
173: } else {
174: super .startTransformingElement(uri, name, raw, attr);
175: }
176: }
177:
178: /**
179: * Exit method for all elements in our namespace
180: *
181: * @param uri a <code>String</code> value
182: * @param name a <code>String</code> value
183: * @param raw a <code>String</code> value
184: * @exception ProcessingException if an error occurs
185: * @exception IOException if an error occurs
186: * @exception SAXException if an error occurs
187: */
188: public void endTransformingElement(String uri, String name,
189: String raw) throws ProcessingException, IOException,
190: SAXException {
191:
192: if (JPATH_VALUEOF.equals(name)
193: || JPATH_CONTINUATION.equals(name)) {
194: return; // do nothing
195: } else if (JPATH_IF.equals(name)) {
196: finishIf();
197: } else {
198: super .endTransformingElement(uri, name, raw);
199: }
200: }
201:
202: /**
203: * Helper method to check for the existance of an attribute named
204: * jpath:action. If existing the string 'id' is replaced with the
205: * continuation id.
206: *
207: * @param a an {@link AttributesImpl} instance
208: */
209: private void checkJPathAction(final AttributesImpl a) {
210:
211: // check for jpath:action attribute
212: int idx = a.getIndex(JPATH_ACTION);
213:
214: if (idx != -1 && JPATH_NAMESPACE_URI.equals(a.getURI(idx))) {
215: if (getLogger().isDebugEnabled()) {
216: getLogger().debug("found jpath:action, adjusting");
217: }
218:
219: String value = a.getValue(idx);
220:
221: // REVISIT(MC): support for continuation level
222: String id = m_kont.getContinuation(0).getId();
223:
224: a.removeAttribute(idx);
225: a.addAttribute("", "action", "action", "CDATA", m_re.subst(
226: value, id));
227: }
228: }
229:
230: /**
231: * Helper method for obtaining the value of a particular variable.
232: *
233: * @param variable variable name
234: * @return variable value as an <code>Object</code>
235: */
236: private Object getValue(final String variable) {
237:
238: Object value;
239:
240: if (m_cache.containsKey(variable)) {
241: value = m_cache.get(variable);
242: } else {
243: value = JXPathContext.compile(variable).getValue(
244: m_jxpathContext);
245:
246: if (value == null) {
247: if (getLogger().isWarnEnabled()) {
248: final String msg = "Value for jpath variable '"
249: + variable + "' does not exist";
250: getLogger().warn(msg);
251: }
252: }
253:
254: m_cache.put(variable, value);
255: }
256:
257: return value;
258: }
259:
260: /**
261: * Helper method to process a <jpath:value-of select="."> tag
262: *
263: * @param a an {@link Attributes} instance
264: * @exception SAXException if a SAX error occurs
265: * @exception ProcessingException if a processing error occurs
266: */
267: private void doValueOf(final Attributes a) throws SAXException,
268: ProcessingException {
269:
270: final String select = a.getValue(JPATH_VALUEOF_SELECT);
271:
272: if (null != select) {
273: sendTextEvent((String) getValue(select));
274: } else {
275: throw new ProcessingException("jpath:" + JPATH_VALUEOF
276: + " specified without a select attribute");
277: }
278: }
279:
280: /**
281: * Helper method to process a <jpath:continuation select=""/> element.
282: *
283: * @param a an <code>Attributes</code> value
284: * @exception SAXException if an error occurs
285: */
286: private void doContinuation(final Attributes a) throws SAXException {
287:
288: final String level = a.getValue(JPATH_CONTINUATION_SELECT);
289:
290: final String id = (level != null) ? m_kont.getContinuation(
291: Integer.decode(level).intValue()).getId() : m_kont
292: .getContinuation(0).getId();
293:
294: sendTextEvent(id);
295: }
296:
297: /**
298: * Helper method to process a <jpath:if test="..."> element.
299: *
300: * @param a an <code>Attributes</code> value
301: * @exception SAXException if an error occurs
302: */
303: private void doIf(final Attributes a) throws SAXException {
304:
305: // handle nested jpath:if statements, if ignoreEventsCount is > 0, then
306: // we are processing a nested jpath:if statement for which the parent
307: // jpath:if test resulted in a false (ie. disallow subelements) result.
308:
309: if (ignoreEventsCount > 0) {
310: ++ignoreEventsCount;
311: return;
312: }
313:
314: // get the test variable
315: final Object value = getValue(a.getValue(JPATH_TEST));
316:
317: final boolean isTrueBoolean = value instanceof Boolean
318: && ((Boolean) value).booleanValue() == true;
319: final boolean isNonNullNonBoolean = value != null
320: && !(value instanceof Boolean);
321:
322: if (isTrueBoolean || isNonNullNonBoolean) {
323: // do nothing, allow all subelements
324: if (getLogger().isDebugEnabled()) {
325: getLogger().debug(
326: "jpath:if results in allowing subelements");
327: }
328: } else {
329: // disallow all subelements
330: if (getLogger().isDebugEnabled()) {
331: getLogger().debug(
332: "jpath:if results in disallowing subelements");
333: }
334: ++ignoreEventsCount;
335: }
336: }
337:
338: /**
339: * Helper method to process a </jpath:if> element.
340: *
341: * @exception SAXException if an error occurs
342: */
343: private void finishIf() throws SAXException {
344:
345: // end recording (and dump resulting document fragment) if we've reached
346: // the closing jpath:if tag for which the recording was started.
347: if (ignoreEventsCount > 0) {
348: --ignoreEventsCount;
349: }
350:
351: if (getLogger().isDebugEnabled()) {
352: getLogger().debug("jpath:if closed");
353: }
354: }
355:
356: /**
357: * Release all held resources.
358: */
359: public void recycle() {
360: m_cache.clear();
361: m_kont = null;
362: m_jxpathContext = null;
363:
364: super.recycle();
365: }
366: }
|