/* * 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 org.apache.avalon.excalibur.pool.Recyclable; import org.apache.avalon.framework.activity.Disposable; import org.apache.avalon.framework.component.ComponentSelector; import org.apache.avalon.framework.configuration.Configurable; import org.apache.avalon.framework.configuration.Configuration; import org.apache.avalon.framework.configuration.ConfigurationException; import org.apache.avalon.framework.parameters.Parameters; import org.apache.avalon.framework.service.ServiceException; import org.apache.avalon.framework.service.ServiceManager; import org.apache.avalon.framework.service.ServiceSelector; import org.apache.avalon.framework.service.Serviceable; import org.apache.cocoon.components.sax.XMLDeserializer; import org.apache.cocoon.components.sax.XMLSerializer; import org.apache.cocoon.environment.SourceResolver; import org.apache.cocoon.taglib.IterationTag; import org.apache.cocoon.taglib.Tag; import org.apache.cocoon.taglib.BodyTag; import org.apache.cocoon.taglib.BodyContent; import org.apache.cocoon.xml.AbstractXMLProducer; import org.apache.cocoon.xml.XMLConsumer; import org.apache.cocoon.xml.XMLProducer; import org.apache.cocoon.xml.SaxBuffer; import org.apache.commons.collections.ArrayStack; import org.apache.commons.collections.map.StaticBucketMap; import org.xml.sax.Attributes; import org.xml.sax.SAXException; import java.beans.BeanInfo; import java.beans.IntrospectionException; import java.beans.Introspector; import java.beans.PropertyDescriptor; import java.io.IOException; import java.lang.reflect.Method; import java.util.HashMap; import java.util.Map; /** * Transformer which implements the taglib functionalty. * * <p>Transformer processes incoming SAX events and for each element it tries to * find {@link Tag} component with matching namespace and tag name. * * @author <a href="mailto:volker.schmitt@basf-it-services.com">Volker Schmitt</a> * @version CVS $Id$ */ public class TagTransformer extends AbstractXMLProducer implements Transformer, Serviceable, Configurable, Disposable, Recyclable { private int recordingLevel; private int skipLevel; private String transformerHint; private ServiceSelector transformerSelector; private final ArrayStack tagStack = new ArrayStack(); private final ArrayStack tagSelectorStack = new ArrayStack(); private final ArrayStack tagTransformerStack = new ArrayStack(); private ServiceSelector tagNamespaceSelector; private Tag currentTag; /** current SAX Event Consumer */ private XMLConsumer currentConsumer; /** backup of currentConsumer while recording */ private XMLConsumer currentConsumerBackup; private XMLSerializer xmlSerializer; /** The SourceResolver for this request */ private SourceResolver resolver; /** The current objectModel of the environment */ private Map objectModel; /** The parameters specified in the sitemap */ private Parameters parameters; /** The Avalon ServiceManager */ private ServiceManager manager; /** Array for dynamic calling of Tag set property methods */ private final String[] paramArray = new String[1]; /** Map for caching Tag Introspection */ private static Map TAG_PROPERTIES_MAP = new StaticBucketMap(); // // Component Lifecycle Methods // /** * Avalon Serviceable Interface * @param manager The Avalon Service Manager */ public void service(ServiceManager manager) throws ServiceException { this.manager = manager; this.tagNamespaceSelector = (ServiceSelector) manager.lookup(Tag.ROLE + "Selector"); } /** * Avalon Configurable Interface */ public void configure(Configuration conf) throws ConfigurationException { this.transformerHint = conf.getChild("transformer-hint").getValue(null); if (this.transformerHint != null) { try { this.transformerSelector = (ServiceSelector) manager.lookup(Transformer.ROLE + "Selector"); } catch (ServiceException e) { String message = "Can't lookup transformer selector"; if (getLogger().isDebugEnabled()) { getLogger().debug(message, e); } throw new ConfigurationException(message, e); } } } /** * Set the <code>EntityResolver</code>, objectModel <code>Map</code>, * the source and sitemap <code>Parameters</code> used to process the request. */ public void setup(SourceResolver resolver, Map objectModel, String source, Parameters parameters) throws IOException, SAXException { this.resolver = resolver; this.objectModel = objectModel; this.parameters = parameters; } /** * Recycle this component. */ public void recycle() { this.recordingLevel = 0; this.skipLevel = 0; this.resolver = null; this.objectModel = null; this.parameters = null; this.currentTag = null; this.currentConsumer = null; this.currentConsumerBackup = null; // can happen if there was a error in the pipeline if (xmlSerializer != null) { manager.release(xmlSerializer); xmlSerializer = null; } while (!tagStack.isEmpty()) { Tag tag = (Tag) tagStack.pop(); if (tag == null) continue; ComponentSelector tagSelector = (ComponentSelector)tagSelectorStack.pop(); tagSelector.release(tag); tagNamespaceSelector.release(tagSelector); } while (!tagTransformerStack.isEmpty()) { Transformer transformer = (Transformer) tagTransformerStack.pop(); transformerSelector.release(transformer); } if (!tagSelectorStack.isEmpty()) { getLogger().fatalError("recycle: internal Error, tagSelectorStack not empty"); tagSelectorStack.clear(); } super.recycle(); } /** * Dispose this component. */ public void dispose() { this.manager.release(tagNamespaceSelector); tagNamespaceSelector = null; if (transformerSelector != null) { this.manager.release(transformerSelector); transformerSelector = null; } } /* * @see XMLProducer#setConsumer(XMLConsumer) */ public void setConsumer(XMLConsumer consumer) { this.currentConsumer = consumer; super.setConsumer(consumer); } // // SAX Events Methods // public void setDocumentLocator(org.xml.sax.Locator locator) { // If we are skipping the body of a tag, ignore this... if (this.skipLevel > 0) { return; } this.currentConsumer.setDocumentLocator(locator); } public void startDocument() throws SAXException { this.currentConsumer.startDocument(); } public void endDocument() throws SAXException { this.currentConsumer.endDocument(); } public void processingInstruction(String target, String data) throws SAXException { // If we are skipping the body of a tag, ignore this... if (this.skipLevel > 0) { return; } this.currentConsumer.processingInstruction(target, data); } public void startDTD(String name, String publicId, String systemId) throws SAXException { // If we are skipping the body of a tag, ignore this... if (this.skipLevel > 0) { return; } this.currentConsumer.startDTD(name, publicId, systemId); } public void endDTD() throws SAXException { // If we are skipping the body of a tag, ignore this... if (this.skipLevel > 0) { return; } this.currentConsumer.endDTD(); } public void startPrefixMapping(String prefix, String uri) throws SAXException { // If we are skipping the body of a tag, ignore this... if (this.skipLevel > 0) { return; } this.currentConsumer.startPrefixMapping(prefix, uri); } public void endPrefixMapping(String prefix) throws SAXException { // If we are skipping the body of a tag, ignore this... if (this.skipLevel > 0) { return; } this.currentConsumer.endPrefixMapping(prefix); } public void startCDATA() throws SAXException { // If we are skipping the body of a tag, ignore this... if (this.skipLevel > 0) { return; } this.currentConsumer.startCDATA(); } public void endCDATA() throws SAXException { // If we are skipping the body of a tag, ignore this... if (this.skipLevel > 0) { return; } this.currentConsumer.endCDATA(); } public void startElement(String namespaceURI, String localName, String qName, Attributes atts) throws SAXException { // Are we recording for iteration ? if (this.recordingLevel > 0) { this.recordingLevel ++; this.currentConsumer.startElement(namespaceURI, localName, qName, atts); return; } // If we are skipping the body of a Tag if (this.skipLevel > 0) { // Remember to skip one more end element this.skipLevel ++; // and ignore this start element return; } Tag tag = null; if (namespaceURI != null && namespaceURI.length() > 0) { // Try to find Tag corresponding to this element ComponentSelector tagSelector = null; try { tagSelector = (ComponentSelector) tagNamespaceSelector.select(namespaceURI); tagSelectorStack.push(tagSelector); // namespace matches tag library, lookup tag now. tag = (Tag) tagSelector.select(localName); if (getLogger().isDebugEnabled()) { getLogger().debug("startElement: Got tag " + qName); } setupTag(tag, qName, atts); } catch (SAXException e) { throw e; } catch (Exception ignore) { // No namespace or tag found, process it as normal element (tag == null) } } tagStack.push(tag); if (tag == null) { currentConsumer.startElement(namespaceURI, localName, qName, atts); return; } // Execute Tag int eval = tag.doStartTag(namespaceURI, localName, qName, atts); switch (eval) { case Tag.EVAL_BODY : skipLevel = 0; if (tag instanceof IterationTag) { // start recording for IterationTag startRecording(); } break; case Tag.SKIP_BODY : skipLevel = 1; break; default : String tagName = tag.getClass().getName(); getLogger().warn("Bad return value from doStartTag(" + tagName + "): " + eval); break; } } public void endElement(String namespaceURI, String localName, String qName) throws SAXException { Object saxFragment = null; // Are we recording? if (recordingLevel > 0) { if (--recordingLevel > 0) { currentConsumer.endElement(namespaceURI, localName, qName); return; } // Recording finished saxFragment = endRecording(); } if (skipLevel > 0) { if (--skipLevel > 0) { return; } } Tag tag = (Tag) tagStack.pop(); if (tag != null) { ComponentSelector tagSelector = (ComponentSelector)tagSelectorStack.pop(); try { if (saxFragment != null) { // Start Iteration IterationTag iterTag = (IterationTag) tag; XMLDeserializer xmlDeserializer = null; try { xmlDeserializer = (XMLDeserializer) manager.lookup(XMLDeserializer.ROLE); xmlDeserializer.setConsumer(this); // BodyTag Support XMLConsumer backup = this.currentConsumer; if (tag instanceof BodyTag) { SaxBuffer content = new SaxBuffer(); this.currentConsumer = content; ((BodyTag)tag).setBodyContent(new BodyContent(content, backup)); ((BodyTag)tag).doInitBody(); } do { xmlDeserializer.deserialize(saxFragment); } while (iterTag.doAfterBody() != Tag.SKIP_BODY); // BodyTag Support if (tag instanceof BodyTag) { this.currentConsumer = backup; } } catch (ServiceException e) { throw new SAXException("Can't obtain XMLDeserializer", e); } finally { if (xmlDeserializer != null) { manager.release(xmlDeserializer); } } } tag.doEndTag(namespaceURI, localName, qName); currentTag = tag.getParent(); if (tag == this.currentConsumer) { popConsumer(); } } finally { if (getLogger().isDebugEnabled()) { getLogger().debug("endElement: Release tag " + qName); } tagSelector.release(tag); tagNamespaceSelector.release(tagSelector); if (transformerSelector != null && tag instanceof XMLProducer) { getLogger().debug("endElement: Release transformer"); Transformer transformer = (Transformer) tagTransformerStack.pop(); transformerSelector.release(transformer); } } } else { this.currentConsumer.endElement(namespaceURI, localName, qName); } } public void startEntity(String name) throws SAXException { // If we are skipping the body of a tag, ignore this... if (this.skipLevel > 0) { return; } this.currentConsumer.startEntity(name); } public void endEntity(String name) throws SAXException { // If we are skipping the body of a tag, ignore this... if (this.skipLevel > 0) { return; } this.currentConsumer.endEntity(name); } public void skippedEntity(String name) throws SAXException { // If we are skipping the body of a tag, ignore this... if (this.skipLevel > 0) { return; } this.currentConsumer.skippedEntity(name); } public void characters(char[] ch, int start, int length) throws SAXException { // If we are skipping the body of a tag, ignore this... if (this.skipLevel > 0) { return; } this.currentConsumer.characters(ch, start, length); } public void comment(char[] ch, int start, int length) throws SAXException { // If we are skipping the body of a tag, ignore this... if (this.skipLevel > 0) { return; } this.currentConsumer.comment(ch, start, length); } public void ignorableWhitespace(char[] ch, int start, int length) throws SAXException { // If we are skipping the body of a tag, ignore this... if (this.skipLevel > 0) { return; } this.currentConsumer.ignorableWhitespace(ch, start, length); } // // Internal Implementation Methods // private void setupTag(Tag tag, String name, Attributes atts) throws SAXException { // Set Tag Parent tag.setParent(this.currentTag); // Set Tag XML Consumer if (tag instanceof XMLProducer) { XMLConsumer tagConsumer; if (transformerSelector != null) { Transformer tagTransformer = null; try { // Add additional (Tag)Transformer to the output of the Tag tagTransformer = (Transformer) transformerSelector.select(transformerHint); tagTransformerStack.push(tagTransformer); tagTransformer.setConsumer(currentConsumer); tagTransformer.setup(this.resolver, this.objectModel, null, this.parameters); } catch (SAXException e) { throw e; } catch (Exception e) { throw new SAXException("Failed to setup tag transformer " + transformerHint, e); } tagConsumer = tagTransformer; } else { tagConsumer = this.currentConsumer; } ((XMLProducer) tag).setConsumer(tagConsumer); } // Setup Tag try { tag.setup(this.resolver, this.objectModel, this.parameters); } catch (IOException e) { throw new SAXException("Could not set up tag " + name, e); } if (tag instanceof XMLConsumer) { this.currentConsumer = (XMLConsumer) tag; } this.currentTag = tag; // Set Tag-Attributes, Attributes are mapped to the coresponding Tag method for (int i = 0; i < atts.getLength(); i++) { String attributeName = atts.getLocalName(i); String attributeValue = atts.getValue(i); this.paramArray[0] = attributeValue; try { Method method = getWriteMethod(tag.getClass(), attributeName); method.invoke(tag, this.paramArray); } catch (Throwable e) { if (getLogger().isInfoEnabled()) { getLogger().info("Tag " + name + " attribute " + attributeName + " not set", e); } } } } /** * Start recording for the iterator tag. */ private void startRecording() throws SAXException { try { this.xmlSerializer = (XMLSerializer) manager.lookup(XMLSerializer.ROLE); } catch (ServiceException e) { throw new SAXException("Can't lookup XMLSerializer", e); } this.currentConsumerBackup = this.currentConsumer; this.currentConsumer = this.xmlSerializer; this.recordingLevel = 1; } /** * End recording for the iterator tag and returns recorded XML fragment. */ private Object endRecording() { // Restore XML Consumer this.currentConsumer = this.currentConsumerBackup; this.currentConsumerBackup = null; // Get XML Fragment Object saxFragment = this.xmlSerializer.getSAXFragment(); // Release Serializer this.manager.release(this.xmlSerializer); this.xmlSerializer = null; return saxFragment; } /** * Find previous XML consumer when processing of current consumer * is complete. */ private void popConsumer() { Tag loop = this.currentTag; for (; loop != null; loop = loop.getParent()) { if (loop instanceof XMLConsumer) { this.currentConsumer = (XMLConsumer) loop; return; } } this.currentConsumer = this.xmlConsumer; } private static Method getWriteMethod(Class type, String propertyName) throws IntrospectionException { Map map = getWriteMethodMap(type); Method method = (Method) map.get(propertyName); if (method == null) { throw new IntrospectionException("No such property: " + propertyName); } return method; } private static Map getWriteMethodMap(Class beanClass) throws IntrospectionException { Map map = (Map) TAG_PROPERTIES_MAP.get(beanClass); if (map != null) { return map; } BeanInfo info = Introspector.getBeanInfo(beanClass); if (info != null) { PropertyDescriptor pds[] = info.getPropertyDescriptors(); map = new HashMap(pds.length * 4 / 3, 1); for (int i = 0; i < pds.length; i++) { PropertyDescriptor pd = pds[i]; String name = pd.getName(); Method method = pd.getWriteMethod(); Class type = pd.getPropertyType(); if (type != String.class) // only String properties continue; map.put(name, method); } } TAG_PROPERTIES_MAP.put(beanClass, map); return map; } }