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 org.apache.avalon.excalibur.pool.Recyclable;
020: import org.apache.avalon.framework.activity.Disposable;
021: import org.apache.avalon.framework.component.ComponentSelector;
022: import org.apache.avalon.framework.configuration.Configurable;
023: import org.apache.avalon.framework.configuration.Configuration;
024: import org.apache.avalon.framework.configuration.ConfigurationException;
025: import org.apache.avalon.framework.parameters.Parameters;
026: import org.apache.avalon.framework.service.ServiceException;
027: import org.apache.avalon.framework.service.ServiceManager;
028: import org.apache.avalon.framework.service.ServiceSelector;
029: import org.apache.avalon.framework.service.Serviceable;
030:
031: import org.apache.cocoon.components.sax.XMLDeserializer;
032: import org.apache.cocoon.components.sax.XMLSerializer;
033: import org.apache.cocoon.environment.SourceResolver;
034: import org.apache.cocoon.taglib.IterationTag;
035: import org.apache.cocoon.taglib.Tag;
036: import org.apache.cocoon.taglib.BodyTag;
037: import org.apache.cocoon.taglib.BodyContent;
038: import org.apache.cocoon.xml.AbstractXMLProducer;
039: import org.apache.cocoon.xml.XMLConsumer;
040: import org.apache.cocoon.xml.XMLProducer;
041: import org.apache.cocoon.xml.SaxBuffer;
042:
043: import org.apache.commons.collections.ArrayStack;
044: import org.apache.commons.collections.map.StaticBucketMap;
045: import org.xml.sax.Attributes;
046: import org.xml.sax.SAXException;
047:
048: import java.beans.BeanInfo;
049: import java.beans.IntrospectionException;
050: import java.beans.Introspector;
051: import java.beans.PropertyDescriptor;
052: import java.io.IOException;
053: import java.lang.reflect.Method;
054: import java.util.HashMap;
055: import java.util.Map;
056:
057: /**
058: * Transformer which implements the taglib functionalty.
059: *
060: * <p>Transformer processes incoming SAX events and for each element it tries to
061: * find {@link Tag} component with matching namespace and tag name.
062: *
063: * @author <a href="mailto:volker.schmitt@basf-it-services.com">Volker Schmitt</a>
064: * @version CVS $Id: TagTransformer.java 433543 2006-08-22 06:22:54Z crossley $
065: */
066: public class TagTransformer extends AbstractXMLProducer implements
067: Transformer, Serviceable, Configurable, Disposable, Recyclable {
068:
069: private int recordingLevel;
070: private int skipLevel;
071:
072: private String transformerHint;
073: private ServiceSelector transformerSelector;
074:
075: private final ArrayStack tagStack = new ArrayStack();
076: private final ArrayStack tagSelectorStack = new ArrayStack();
077: private final ArrayStack tagTransformerStack = new ArrayStack();
078:
079: private ServiceSelector tagNamespaceSelector;
080: private Tag currentTag;
081:
082: /** current SAX Event Consumer */
083: private XMLConsumer currentConsumer;
084:
085: /** backup of currentConsumer while recording */
086: private XMLConsumer currentConsumerBackup;
087:
088: private XMLSerializer xmlSerializer;
089:
090: /** The SourceResolver for this request */
091: private SourceResolver resolver;
092:
093: /** The current objectModel of the environment */
094: private Map objectModel;
095:
096: /** The parameters specified in the sitemap */
097: private Parameters parameters;
098:
099: /** The Avalon ServiceManager */
100: private ServiceManager manager;
101:
102: /** Array for dynamic calling of Tag set property methods */
103: private final String[] paramArray = new String[1];
104:
105: /** Map for caching Tag Introspection */
106: private static Map TAG_PROPERTIES_MAP = new StaticBucketMap();
107:
108: //
109: // Component Lifecycle Methods
110: //
111:
112: /**
113: * Avalon Serviceable Interface
114: * @param manager The Avalon Service Manager
115: */
116: public void service(ServiceManager manager) throws ServiceException {
117: this .manager = manager;
118: this .tagNamespaceSelector = (ServiceSelector) manager
119: .lookup(Tag.ROLE + "Selector");
120: }
121:
122: /**
123: * Avalon Configurable Interface
124: */
125: public void configure(Configuration conf)
126: throws ConfigurationException {
127: this .transformerHint = conf.getChild("transformer-hint")
128: .getValue(null);
129: if (this .transformerHint != null) {
130: try {
131: this .transformerSelector = (ServiceSelector) manager
132: .lookup(Transformer.ROLE + "Selector");
133: } catch (ServiceException e) {
134: String message = "Can't lookup transformer selector";
135: if (getLogger().isDebugEnabled()) {
136: getLogger().debug(message, e);
137: }
138: throw new ConfigurationException(message, e);
139: }
140: }
141: }
142:
143: /**
144: * Set the <code>EntityResolver</code>, objectModel <code>Map</code>,
145: * the source and sitemap <code>Parameters</code> used to process the request.
146: */
147: public void setup(SourceResolver resolver, Map objectModel,
148: String source, Parameters parameters) throws IOException,
149: SAXException {
150: this .resolver = resolver;
151: this .objectModel = objectModel;
152: this .parameters = parameters;
153: }
154:
155: /**
156: * Recycle this component.
157: */
158: public void recycle() {
159: this .recordingLevel = 0;
160: this .skipLevel = 0;
161: this .resolver = null;
162: this .objectModel = null;
163: this .parameters = null;
164: this .currentTag = null;
165: this .currentConsumer = null;
166: this .currentConsumerBackup = null;
167:
168: // can happen if there was a error in the pipeline
169: if (xmlSerializer != null) {
170: manager.release(xmlSerializer);
171: xmlSerializer = null;
172: }
173:
174: while (!tagStack.isEmpty()) {
175: Tag tag = (Tag) tagStack.pop();
176: if (tag == null)
177: continue;
178: ComponentSelector tagSelector = (ComponentSelector) tagSelectorStack
179: .pop();
180: tagSelector.release(tag);
181:
182: tagNamespaceSelector.release(tagSelector);
183: }
184:
185: while (!tagTransformerStack.isEmpty()) {
186: Transformer transformer = (Transformer) tagTransformerStack
187: .pop();
188: transformerSelector.release(transformer);
189: }
190:
191: if (!tagSelectorStack.isEmpty()) {
192: getLogger()
193: .fatalError(
194: "recycle: internal Error, tagSelectorStack not empty");
195: tagSelectorStack.clear();
196: }
197:
198: super .recycle();
199: }
200:
201: /**
202: * Dispose this component.
203: */
204: public void dispose() {
205: this .manager.release(tagNamespaceSelector);
206: tagNamespaceSelector = null;
207: if (transformerSelector != null) {
208: this .manager.release(transformerSelector);
209: transformerSelector = null;
210: }
211: }
212:
213: /*
214: * @see XMLProducer#setConsumer(XMLConsumer)
215: */
216: public void setConsumer(XMLConsumer consumer) {
217: this .currentConsumer = consumer;
218: super .setConsumer(consumer);
219: }
220:
221: //
222: // SAX Events Methods
223: //
224:
225: public void setDocumentLocator(org.xml.sax.Locator locator) {
226: // If we are skipping the body of a tag, ignore this...
227: if (this .skipLevel > 0) {
228: return;
229: }
230:
231: this .currentConsumer.setDocumentLocator(locator);
232: }
233:
234: public void startDocument() throws SAXException {
235: this .currentConsumer.startDocument();
236: }
237:
238: public void endDocument() throws SAXException {
239: this .currentConsumer.endDocument();
240: }
241:
242: public void processingInstruction(String target, String data)
243: throws SAXException {
244: // If we are skipping the body of a tag, ignore this...
245: if (this .skipLevel > 0) {
246: return;
247: }
248:
249: this .currentConsumer.processingInstruction(target, data);
250: }
251:
252: public void startDTD(String name, String publicId, String systemId)
253: throws SAXException {
254: // If we are skipping the body of a tag, ignore this...
255: if (this .skipLevel > 0) {
256: return;
257: }
258:
259: this .currentConsumer.startDTD(name, publicId, systemId);
260: }
261:
262: public void endDTD() throws SAXException {
263: // If we are skipping the body of a tag, ignore this...
264: if (this .skipLevel > 0) {
265: return;
266: }
267:
268: this .currentConsumer.endDTD();
269: }
270:
271: public void startPrefixMapping(String prefix, String uri)
272: throws SAXException {
273: // If we are skipping the body of a tag, ignore this...
274: if (this .skipLevel > 0) {
275: return;
276: }
277:
278: this .currentConsumer.startPrefixMapping(prefix, uri);
279: }
280:
281: public void endPrefixMapping(String prefix) throws SAXException {
282: // If we are skipping the body of a tag, ignore this...
283: if (this .skipLevel > 0) {
284: return;
285: }
286:
287: this .currentConsumer.endPrefixMapping(prefix);
288: }
289:
290: public void startCDATA() throws SAXException {
291: // If we are skipping the body of a tag, ignore this...
292: if (this .skipLevel > 0) {
293: return;
294: }
295:
296: this .currentConsumer.startCDATA();
297: }
298:
299: public void endCDATA() throws SAXException {
300: // If we are skipping the body of a tag, ignore this...
301: if (this .skipLevel > 0) {
302: return;
303: }
304:
305: this .currentConsumer.endCDATA();
306: }
307:
308: public void startElement(String namespaceURI, String localName,
309: String qName, Attributes atts) throws SAXException {
310: // Are we recording for iteration ?
311: if (this .recordingLevel > 0) {
312: this .recordingLevel++;
313: this .currentConsumer.startElement(namespaceURI, localName,
314: qName, atts);
315: return;
316: }
317:
318: // If we are skipping the body of a Tag
319: if (this .skipLevel > 0) {
320: // Remember to skip one more end element
321: this .skipLevel++;
322: // and ignore this start element
323: return;
324: }
325:
326: Tag tag = null;
327: if (namespaceURI != null && namespaceURI.length() > 0) {
328: // Try to find Tag corresponding to this element
329: ComponentSelector tagSelector = null;
330: try {
331: tagSelector = (ComponentSelector) tagNamespaceSelector
332: .select(namespaceURI);
333: tagSelectorStack.push(tagSelector);
334:
335: // namespace matches tag library, lookup tag now.
336: tag = (Tag) tagSelector.select(localName);
337: if (getLogger().isDebugEnabled()) {
338: getLogger().debug("startElement: Got tag " + qName);
339: }
340:
341: setupTag(tag, qName, atts);
342: } catch (SAXException e) {
343: throw e;
344: } catch (Exception ignore) {
345: // No namespace or tag found, process it as normal element (tag == null)
346: }
347: }
348:
349: tagStack.push(tag);
350: if (tag == null) {
351: currentConsumer.startElement(namespaceURI, localName,
352: qName, atts);
353: return;
354: }
355:
356: // Execute Tag
357: int eval = tag.doStartTag(namespaceURI, localName, qName, atts);
358: switch (eval) {
359: case Tag.EVAL_BODY:
360: skipLevel = 0;
361: if (tag instanceof IterationTag) {
362: // start recording for IterationTag
363: startRecording();
364: }
365: break;
366:
367: case Tag.SKIP_BODY:
368: skipLevel = 1;
369: break;
370:
371: default:
372: String tagName = tag.getClass().getName();
373: getLogger().warn(
374: "Bad return value from doStartTag(" + tagName
375: + "): " + eval);
376: break;
377: }
378: }
379:
380: public void endElement(String namespaceURI, String localName,
381: String qName) throws SAXException {
382: Object saxFragment = null;
383:
384: // Are we recording?
385: if (recordingLevel > 0) {
386: if (--recordingLevel > 0) {
387: currentConsumer.endElement(namespaceURI, localName,
388: qName);
389: return;
390: }
391: // Recording finished
392: saxFragment = endRecording();
393: }
394:
395: if (skipLevel > 0) {
396: if (--skipLevel > 0) {
397: return;
398: }
399: }
400:
401: Tag tag = (Tag) tagStack.pop();
402: if (tag != null) {
403: ComponentSelector tagSelector = (ComponentSelector) tagSelectorStack
404: .pop();
405: try {
406: if (saxFragment != null) {
407: // Start Iteration
408: IterationTag iterTag = (IterationTag) tag;
409: XMLDeserializer xmlDeserializer = null;
410: try {
411: xmlDeserializer = (XMLDeserializer) manager
412: .lookup(XMLDeserializer.ROLE);
413: xmlDeserializer.setConsumer(this );
414:
415: // BodyTag Support
416: XMLConsumer backup = this .currentConsumer;
417: if (tag instanceof BodyTag) {
418: SaxBuffer content = new SaxBuffer();
419: this .currentConsumer = content;
420: ((BodyTag) tag)
421: .setBodyContent(new BodyContent(
422: content, backup));
423: ((BodyTag) tag).doInitBody();
424: }
425:
426: do {
427: xmlDeserializer.deserialize(saxFragment);
428: } while (iterTag.doAfterBody() != Tag.SKIP_BODY);
429:
430: // BodyTag Support
431: if (tag instanceof BodyTag) {
432: this .currentConsumer = backup;
433: }
434:
435: } catch (ServiceException e) {
436: throw new SAXException(
437: "Can't obtain XMLDeserializer", e);
438: } finally {
439: if (xmlDeserializer != null) {
440: manager.release(xmlDeserializer);
441: }
442: }
443: }
444: tag.doEndTag(namespaceURI, localName, qName);
445: currentTag = tag.getParent();
446:
447: if (tag == this .currentConsumer) {
448: popConsumer();
449: }
450: } finally {
451: if (getLogger().isDebugEnabled()) {
452: getLogger().debug(
453: "endElement: Release tag " + qName);
454: }
455:
456: tagSelector.release(tag);
457: tagNamespaceSelector.release(tagSelector);
458:
459: if (transformerSelector != null
460: && tag instanceof XMLProducer) {
461: getLogger()
462: .debug("endElement: Release transformer");
463: Transformer transformer = (Transformer) tagTransformerStack
464: .pop();
465: transformerSelector.release(transformer);
466: }
467: }
468: } else {
469: this .currentConsumer.endElement(namespaceURI, localName,
470: qName);
471: }
472: }
473:
474: public void startEntity(String name) throws SAXException {
475: // If we are skipping the body of a tag, ignore this...
476: if (this .skipLevel > 0) {
477: return;
478: }
479:
480: this .currentConsumer.startEntity(name);
481: }
482:
483: public void endEntity(String name) throws SAXException {
484: // If we are skipping the body of a tag, ignore this...
485: if (this .skipLevel > 0) {
486: return;
487: }
488:
489: this .currentConsumer.endEntity(name);
490: }
491:
492: public void skippedEntity(String name) throws SAXException {
493: // If we are skipping the body of a tag, ignore this...
494: if (this .skipLevel > 0) {
495: return;
496: }
497:
498: this .currentConsumer.skippedEntity(name);
499: }
500:
501: public void characters(char[] ch, int start, int length)
502: throws SAXException {
503: // If we are skipping the body of a tag, ignore this...
504: if (this .skipLevel > 0) {
505: return;
506: }
507:
508: this .currentConsumer.characters(ch, start, length);
509: }
510:
511: public void comment(char[] ch, int start, int length)
512: throws SAXException {
513: // If we are skipping the body of a tag, ignore this...
514: if (this .skipLevel > 0) {
515: return;
516: }
517:
518: this .currentConsumer.comment(ch, start, length);
519: }
520:
521: public void ignorableWhitespace(char[] ch, int start, int length)
522: throws SAXException {
523: // If we are skipping the body of a tag, ignore this...
524: if (this .skipLevel > 0) {
525: return;
526: }
527:
528: this .currentConsumer.ignorableWhitespace(ch, start, length);
529: }
530:
531: //
532: // Internal Implementation Methods
533: //
534:
535: private void setupTag(Tag tag, String name, Attributes atts)
536: throws SAXException {
537: // Set Tag Parent
538: tag.setParent(this .currentTag);
539:
540: // Set Tag XML Consumer
541: if (tag instanceof XMLProducer) {
542: XMLConsumer tagConsumer;
543: if (transformerSelector != null) {
544: Transformer tagTransformer = null;
545: try {
546: // Add additional (Tag)Transformer to the output of the Tag
547: tagTransformer = (Transformer) transformerSelector
548: .select(transformerHint);
549: tagTransformerStack.push(tagTransformer);
550: tagTransformer.setConsumer(currentConsumer);
551: tagTransformer.setup(this .resolver,
552: this .objectModel, null, this .parameters);
553: } catch (SAXException e) {
554: throw e;
555: } catch (Exception e) {
556: throw new SAXException(
557: "Failed to setup tag transformer "
558: + transformerHint, e);
559: }
560: tagConsumer = tagTransformer;
561: } else {
562: tagConsumer = this .currentConsumer;
563: }
564:
565: ((XMLProducer) tag).setConsumer(tagConsumer);
566: }
567:
568: // Setup Tag
569: try {
570: tag.setup(this .resolver, this .objectModel, this .parameters);
571: } catch (IOException e) {
572: throw new SAXException("Could not set up tag " + name, e);
573: }
574:
575: if (tag instanceof XMLConsumer) {
576: this .currentConsumer = (XMLConsumer) tag;
577: }
578: this .currentTag = tag;
579:
580: // Set Tag-Attributes, Attributes are mapped to the coresponding Tag method
581: for (int i = 0; i < atts.getLength(); i++) {
582: String attributeName = atts.getLocalName(i);
583: String attributeValue = atts.getValue(i);
584: this .paramArray[0] = attributeValue;
585: try {
586: Method method = getWriteMethod(tag.getClass(),
587: attributeName);
588: method.invoke(tag, this .paramArray);
589: } catch (Throwable e) {
590: if (getLogger().isInfoEnabled()) {
591: getLogger().info(
592: "Tag " + name + " attribute "
593: + attributeName + " not set", e);
594: }
595: }
596: }
597: }
598:
599: /**
600: * Start recording for the iterator tag.
601: */
602: private void startRecording() throws SAXException {
603: try {
604: this .xmlSerializer = (XMLSerializer) manager
605: .lookup(XMLSerializer.ROLE);
606: } catch (ServiceException e) {
607: throw new SAXException("Can't lookup XMLSerializer", e);
608: }
609:
610: this .currentConsumerBackup = this .currentConsumer;
611: this .currentConsumer = this .xmlSerializer;
612: this .recordingLevel = 1;
613: }
614:
615: /**
616: * End recording for the iterator tag and returns recorded XML fragment.
617: */
618: private Object endRecording() {
619: // Restore XML Consumer
620: this .currentConsumer = this .currentConsumerBackup;
621: this .currentConsumerBackup = null;
622:
623: // Get XML Fragment
624: Object saxFragment = this .xmlSerializer.getSAXFragment();
625:
626: // Release Serializer
627: this .manager.release(this .xmlSerializer);
628: this .xmlSerializer = null;
629:
630: return saxFragment;
631: }
632:
633: /**
634: * Find previous XML consumer when processing of current consumer
635: * is complete.
636: */
637: private void popConsumer() {
638: Tag loop = this .currentTag;
639: for (; loop != null; loop = loop.getParent()) {
640: if (loop instanceof XMLConsumer) {
641: this .currentConsumer = (XMLConsumer) loop;
642: return;
643: }
644: }
645:
646: this .currentConsumer = this .xmlConsumer;
647: }
648:
649: private static Method getWriteMethod(Class type, String propertyName)
650: throws IntrospectionException {
651: Map map = getWriteMethodMap(type);
652: Method method = (Method) map.get(propertyName);
653: if (method == null) {
654: throw new IntrospectionException("No such property: "
655: + propertyName);
656: }
657: return method;
658: }
659:
660: private static Map getWriteMethodMap(Class beanClass)
661: throws IntrospectionException {
662: Map map = (Map) TAG_PROPERTIES_MAP.get(beanClass);
663: if (map != null) {
664: return map;
665: }
666:
667: BeanInfo info = Introspector.getBeanInfo(beanClass);
668: if (info != null) {
669: PropertyDescriptor pds[] = info.getPropertyDescriptors();
670: map = new HashMap(pds.length * 4 / 3, 1);
671: for (int i = 0; i < pds.length; i++) {
672: PropertyDescriptor pd = pds[i];
673: String name = pd.getName();
674: Method method = pd.getWriteMethod();
675: Class type = pd.getPropertyType();
676: if (type != String.class) // only String properties
677: continue;
678: map.put(name, method);
679: }
680: }
681: TAG_PROPERTIES_MAP.put(beanClass, map);
682: return map;
683: }
684: }
|