/* * 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.io.OutputStream; import java.util.Map; 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.cocoon.ProcessingException; import org.apache.cocoon.components.source.SourceUtil; import org.apache.cocoon.environment.SourceResolver; import org.apache.cocoon.serialization.Serializer; import org.apache.cocoon.xml.XMLUtils; import org.apache.cocoon.xml.dom.DOMStreamer; import org.apache.cocoon.xml.dom.DOMUtil; import org.apache.excalibur.source.ModifiableSource; import org.apache.excalibur.source.Source; import org.apache.excalibur.source.SourceException; import org.apache.excalibur.xml.dom.DOMParser; import org.apache.excalibur.xml.xpath.XPathProcessor; import org.w3c.dom.DOMException; import org.w3c.dom.Document; import org.w3c.dom.DocumentFragment; import org.w3c.dom.Node; import org.w3c.dom.NodeList; import org.xml.sax.Attributes; import org.xml.sax.SAXException; /** * @cocoon.sitemap.component.documentation * This transformer allows you to output to a ModifiableSource. * * @cocoon.sitemap.component.name sourcewriting * @cocoon.sitemap.component.logger sitemap.transformer.write-source * * This transformer allows you to output to a ModifiableSource. * * <p>Definition:</p> * <pre> * <map:transformer name="tofile" src="org.apache.cocoon.transformation.SourceWritingTransformer"> * <!-- 'xml' is the default Serializer (if your Source needs one, like for instance FileSource) --> * <map:parameter name="serializer" value="xml"/> * </map:transformer/> * </pre> * * <p>Invocation:</p> * <pre> * <map:transform type="tofile"> * <map:parameter name="serializer" value="xml"/> <!-- you can optionally override the serializer here --> * </map:transform> * </pre> * * <p>The Tags:</p> * <pre> * <source:write create="[true]|false"> - replaces the entire content of an existing asset, if @create is 'true' (default), a new asset will be created if one does not already exist. * <source:source>The System ID of the asset to be written to</source:source> - eg: "docs/blah.xml" or "context://blah.xml" etc. * <source:path>[Optional] XPath to specify how your content is wrapped</source:path> - eg: "doc" (your content is placed inside a <doc/> root tag). NOTE: if this value is omitted, your content MUST have only ONE top-level node. * <source:fragment>The XML Fragment to be written</source:fragment> - eg: "<foo><bar id="dogcow"/></foo>" or "<foo/><bar><dogcow/><bar/>" etc. NOTE: the second example type, can only be used when the <source:path/> tag has been specified. * <source:write> * * <source:insert create="[true]|false" overwrite="[true]|false"> - inserts content into an existing asset, if @create is 'true' (default), a new asset will be created if one does not already exist. If @overwrite is set to 'true' the data is only inserted if the node specified by the 'replacePath' does not exists. * <source:source>The System ID of the asset to be written to</source:source> - eg: "docs/blah.xml" or "context://blah.xml" etc. * <source:path>XPath specifying the node into which the content is inserted</source:path> - eg: "doc" (your content is appended as the last child of the <doc/> root tag), or "doc/section[3]". NOTE: this tag is required in <source:insert/> unlike <source:write/> where it is optional. * <source:replace>[Optional] XPath (relative to <source:path/>) to the node that is replaced by your new content</source:replace> - eg: "foo/bar/dogcow/@status='cut'" (is equivalent to this in XSLT: select="foo[bar/dogcow/@status='cut']"). * <source:reinsert>[Optional] The XPath (relative to <source:replace/>) to backup the overwritten node to</source:reinsert> - eg: "foo/versions" or "/doc/versions/foo". NOTE: If specified and a node is replaced, all children of this replaced node will be reinserted at the given path. * <source:fragment>The XML Fragment to be written</source:fragment> - eg: "<foo><bar id="dogcow"/></foo>" or "<foo/><bar><dogcow/><bar/>" etc. * <source:insert> * * <source:delete > - deletes an existing asset. * <source:source>The System ID of the asset to be deleted</source:source> - eg: "docs/blah.xml" or "context://blah.xml" etc. * <source:path>[Ignored] XPath to specify how your content is wrapped</source:path> * <source:fragment>[Ignored]The XML Fragment to be written</source:fragment> * <source:delete> * </pre> * * * <p>Input XML document example (write):</p> * <pre> * <page> * ... * <source:write xmlns:source="http://apache.org/cocoon/source/1.0"> * <source:source>context://doc/editable/my.xml</source:source> * <source:fragment><page> * <title>Hello World</title> * <content> * <p>This is my first paragraph.</p> * </content> * </page></source:fragment> * </source:write> * ... * </page> * </pre> * * <p>Input XML document example (insert at end):</p> * <pre> * <page> * ... * <source:insert xmlns:source="http://apache.org/cocoon/source/1.0"> * <source:source>context://doc/editable/my.xml</source:source> * <source:path>page/content</source:path> * <source:fragment> * <p>This paragraph gets <emp>inserted</emp>.</p> * <p>With this one, at the end of the content.</p> * </source:fragment> * </source:insert> * ... * </page> * </pre> * * <p>Input XML document example (insert at beginning):</p> * <pre> * <page> * ... * <source:insert> * <source:source>context://doc/editable/my.xml</source:source> * <source:path>page</source:path> * <source:replace>content</source:replace> * <source:reinsert>content</source:reinsert> * <source:fragment> * <content> * <p>This new paragraph gets inserted <emp>before</emp> the other ones.</p> * </content> * </source:fragment> * <source:insert> * ... * </page> * </pre> * * <p>Input XML document example (replace):</p> * <pre> * <page> * ... * <source:insert xmlns:source="http://apache.org/cocoon/source/1.0"> * <source:source>context://doc/editable/my.xml"</source:source> * <source:path>page/content</source:path> * <source:replace>p[1]</source:replace> * <source:fragment> * <p>This paragraph <emp>replaces</emp> the first paragraph.</p> * </source:fragment> * </source:insert> * ... * </page> * </pre> * * <p>Output XML document example:</p> * <pre> * <page> * ... * <sourceResult xmlns:source="http://apache.org/cocoon/source/1.0"> * <action>new|overwritten|none</action> * <behaviour>write|insert<behaviour> * <execution>success|failure</execution> * <serializer>xml</serializer> * <source>file:/source/specific/path/to/context/doc/editable/my.xml</source> * </sourceResult> * ... * </page> * </pre> * * * The XPath specification is very complicated. So here is an example for the sitemap: * <pre> * <page xmlns:source="http://apache.org/cocoon/source/1.0"> * ... * <source:insert> * <source:source>sitemap.xmap</source:source> * <source:path>/*[namespace-uri()="http://apache.org/cocoon/sitemap/1.0" and local-name()="sitemap"]/*[namespace-uri()="http://apache.org/cocoon/sitemap/1.0" and local-name()="components"]/*[namespace-uri()="http://apache.org/cocoon/sitemap/1.0" and local-name()="generators"]</source:path> * <source:fragment> * <generator name="file" xmln="http://apache.org/cocoon/sitemap/1.0"> * <test/> * </generator> * </source:fragment> * <source:replace>*[namespace-uri()="http://apache.org/cocoon/sitemap/1.0" and local-name()="generator" and attribute::name="file"]</source:replace> * </source:insert> * ... * </page> * </pre> * * <p>This insert replaces (if it exists) the file generator definition with a new one. * As the sitemap uses namespaces the XPath for the generator is rather complicated. * Due to this it is necessary that the node specified by path exists if namespaces * are used! Otherwise a node with the name * would be created...</p> * * <p>The create attribute of insert. If this is set * to true (default is true), the file is created if it does not exists. * If it is set to false, it is not created, making insert a real insert. * create is only usable for files!</p> * <p>In addition the overwrite attribute is used to check if replacing is allowed. * If overwrite is true (the default) the node is replaced. If it is false * the node is not inserted if the replace node is available.</p> * * <p>[JQ] - the way I understand this, looking at the code: * <pre> * if 'replace' is not specified, your 'fragment' is appended as a child of 'path'. * if 'replace' is specified and it exists and 'overwrite' is true, your 'fragment' is inserted in 'path', before 'replace' and then 'replace' is deleted. * if 'replace' is specified and it exists and 'overwrite' is false, no action occurs. * if 'replace' is specified and it does not exist and 'overwrite' is true, your 'fragment' is appended as a child of 'path'. * if 'replace' is specified and it does not exist and 'overwrite' is false, your 'fragment' is appended as a child of 'path'. * if 'reinsert' is specified and it does not exist, no action occurs. * </pre></p> * * The <source:reinsert> option can be used to * reinsert a replaced node at a given path in the new fragment. * * <b> * TODO: Use the serializer instead of the XMLUtils for inserting of fragments<br/> * TODO: Add a <source:before/> tag. * </b> * * @author <a href="mailto:cziegeler@s-und-n.de">Carsten Ziegeler</a> * @author <a href="mailto:jeremy@apache.org">Jeremy Quinn</a> * @author <a href="mailto:gianugo@apache.org">Gianugo Rabellino</a> * @version $Id$ */ public class SourceWritingTransformer extends AbstractSAXTransformer { public static final String SWT_URI = "http://apache.org/cocoon/source/1.0"; public static final String DEFAULT_SERIALIZER = "xml"; /** incoming elements */ public static final String WRITE_ELEMENT = "write"; public static final String INSERT_ELEMENT = "insert"; public static final String PATH_ELEMENT = "path"; public static final String FRAGMENT_ELEMENT = "fragment"; public static final String REPLACE_ELEMENT = "replace"; public static final String DELETE_ELEMENT = "delete"; public static final String SOURCE_ELEMENT = "source"; public static final String REINSERT_ELEMENT = "reinsert"; /** outgoing elements */ public static final String RESULT_ELEMENT = "sourceResult"; public static final String EXECUTION_ELEMENT = "execution"; public static final String BEHAVIOUR_ELEMENT = "behaviour"; public static final String ACTION_ELEMENT = "action"; public static final String MESSAGE_ELEMENT = "message"; public static final String SERIALIZER_ELEMENT = "serializer"; /** main (write or insert) tag attributes */ public static final String SERIALIZER_ATTRIBUTE = "serializer"; public static final String CREATE_ATTRIBUTE = "create"; public static final String OVERWRITE_ATTRIBUTE = "overwrite"; /** results */ public static final String RESULT_FAILED = "failed"; public static final String RESULT_SUCCESS = "success"; public static final String ACTION_NONE = "none"; public static final String ACTION_NEW = "new"; public static final String ACTION_OVER = "overwritten"; public static final String ACTION_DELETE = "deleted"; /** The current state */ private static final int STATE_OUTSIDE = 0; private static final int STATE_INSERT = 1; private static final int STATE_PATH = 3; private static final int STATE_FRAGMENT = 4; private static final int STATE_REPLACE = 5; private static final int STATE_FILE = 6; private static final int STATE_REINSERT = 7; private static final int STATE_WRITE = 8; private static final int STATE_DELETE = 9; private int state; private int parent_state; /** The configured serializer name */ protected String configuredSerializerName; /** The XPath processor */ protected XPathProcessor xpathProcessor; /** * Constructor. Set the namespace. */ public SourceWritingTransformer() { this.defaultNamespaceURI = SWT_URI; } /** * Get the current <code>Configuration</code> instance used by this * <code>Configurable</code>. */ public void configure(Configuration configuration) throws ConfigurationException { super.configure(configuration); this.configuredSerializerName = configuration.getChild(SERIALIZER_ATTRIBUTE).getValue(DEFAULT_SERIALIZER); } /** * Get the <code>Parameter</code> called "serializer" from the * <code>Transformer</code> invocation. */ public void setup(SourceResolver resolver, Map objectModel, String src, Parameters par) throws ProcessingException, SAXException, IOException { super.setup(resolver, objectModel, src, par); this.configuredSerializerName = par.getParameter(SERIALIZER_ATTRIBUTE, this.configuredSerializerName); this.state = STATE_OUTSIDE; } /** * Receive notification of the beginning of an element. * * @param uri The Namespace URI, or the empty string if the element has no * Namespace URI or if Namespace * processing is not being performed. * @param name The local name (without prefix), or the empty string if * Namespace processing is not being performed. * @param raw The raw XML 1.0 name (with prefix), or the empty string if * raw names are not available. * @param attr The attributes attached to the element. If there are no * attributes, it shall be an empty Attributes object. */ public void startTransformingElement(String uri, String name, String raw, Attributes attr) throws SAXException, IOException, ProcessingException { if (getLogger().isDebugEnabled()) { getLogger().debug("Start transforming element. uri=" + uri + ", name=" + name + ", raw=" + raw + ", attr=" + attr); } // Element: insert if (this.state == STATE_OUTSIDE && (name.equals(INSERT_ELEMENT) || name.equals(WRITE_ELEMENT))) { this.state = (name.equals(INSERT_ELEMENT) ? STATE_INSERT : STATE_WRITE); this.parent_state = this.state; if (attr.getValue(CREATE_ATTRIBUTE) != null && attr.getValue(CREATE_ATTRIBUTE).equals("false")) { this.stack.push("false"); } else { this.stack.push("true"); // default value } if (attr.getValue(OVERWRITE_ATTRIBUTE) != null && attr.getValue(OVERWRITE_ATTRIBUTE).equals("false")) { this.stack.push("false"); } else { this.stack.push("true"); // default value } this.stack.push(attr.getValue(SERIALIZER_ATTRIBUTE)); this.stack.push("END"); // Element: delete } else if (this.state == STATE_OUTSIDE && name.equals(DELETE_ELEMENT)) { this.state = STATE_DELETE; this.parent_state = state; this.stack.push("END"); // Element: file } else if (name.equals(SOURCE_ELEMENT) && (this.state == STATE_INSERT || this.state == STATE_WRITE || this.state == STATE_DELETE)) { this.state = STATE_FILE; this.startTextRecording(); // Element: path } else if (name.equals(PATH_ELEMENT) && (this.state == STATE_INSERT || this.state == STATE_WRITE || this.state == STATE_DELETE)) { this.state = STATE_PATH; this.startTextRecording(); // Element: replace } else if (name.equals(REPLACE_ELEMENT) && this.state == STATE_INSERT) { this.state = STATE_REPLACE; this.startTextRecording(); // Element: fragment } else if (name.equals(FRAGMENT_ELEMENT) && (this.state == STATE_INSERT || this.state == STATE_WRITE || this.state == STATE_DELETE)) { this.state = STATE_FRAGMENT; this.startRecording(); // Element: reinsert } else if (name.equals(REINSERT_ELEMENT) && this.state == STATE_INSERT) { this.state = STATE_REINSERT; this.startTextRecording(); } else { super.startTransformingElement(uri, name, raw, attr); } } /** * Receive notification of the end of an element. * * @param uri The Namespace URI, or the empty string if the element has no * Namespace URI or if Namespace * processing is not being performed. * @param name The local name (without prefix), or the empty string if * Namespace processing is not being performed. * @param raw The raw XML 1.0 name (with prefix), or the empty string if * raw names are not available. */ public void endTransformingElement(String uri, String name, String raw) throws SAXException, IOException, ProcessingException { if (getLogger().isDebugEnabled()) { getLogger().debug("End transforming element. uri=" + uri + ", name=" + name + ", raw=" + raw); } if ((name.equals(INSERT_ELEMENT) && this.state == STATE_INSERT) || (name.equals(WRITE_ELEMENT) && this.state == STATE_WRITE)) { // get the information from the stack DocumentFragment fragment = null; String tag; String sourceName = null; String path = (this.state == STATE_INSERT ? null : "/"); // source:write's path can be empty String replacePath = null; String reinsert = null; do { tag = (String)this.stack.pop(); if (tag.equals("PATH")) { path = (String)this.stack.pop(); } else if (tag.equals("FILE")) { sourceName = (String)this.stack.pop(); } else if (tag.equals("FRAGMENT")) { fragment = (DocumentFragment)this.stack.pop(); } else if (tag.equals("REPLACE")) { replacePath = (String)this.stack.pop(); } else if (tag.equals("REINSERT")) { reinsert = (String)this.stack.pop(); } } while ( !tag.equals("END") ); final String localSerializer = (String)this.stack.pop(); final boolean overwrite = this.stack.pop().equals("true"); final boolean create = this.stack.pop().equals("true"); this.insertFragment(sourceName, path, fragment, replacePath, create, overwrite, reinsert, localSerializer, name); this.state = STATE_OUTSIDE; // Element: delete } else if (name.equals(DELETE_ELEMENT) && this.state == STATE_DELETE) { String sourceName = null; String tag; do { tag = (String)this.stack.pop(); if (tag.equals("FILE")) { sourceName = (String)this.stack.pop(); } else if (tag.equals("FRAGMENT")) { //Get rid of it this.stack.pop(); } } while ( !tag.equals("END")); this.deleteSource(sourceName); this.state = STATE_OUTSIDE; // Element: file } else if (name.equals(SOURCE_ELEMENT) && this.state == STATE_FILE) { this.state = this.parent_state; this.stack.push(this.endTextRecording()); this.stack.push("FILE"); // Element: path } else if (name.equals(PATH_ELEMENT) && this.state == STATE_PATH) { this.state = this.parent_state; this.stack.push(this.endTextRecording()); this.stack.push("PATH"); // Element: replace } else if (name.equals(REPLACE_ELEMENT) && this.state == STATE_REPLACE) { this.state = this.parent_state; this.stack.push(this.endTextRecording()); this.stack.push("REPLACE"); // Element: fragment } else if (name.equals(FRAGMENT_ELEMENT) && this.state == STATE_FRAGMENT) { this.state = this.parent_state; this.stack.push(this.endRecording()); this.stack.push("FRAGMENT"); // Element: reinsert } else if (name.equals(REINSERT_ELEMENT) && this.state == STATE_REINSERT) { this.state = this.parent_state; this.stack.push(this.endTextRecording()); this.stack.push("REINSERT"); // default } else { super.endTransformingElement(uri, name, raw); } } /** * Deletes a source * @param systemID */ private void deleteSource(String systemID) throws ProcessingException, IOException, SAXException { Source source = null; try { source = resolver.resolveURI(systemID); if (!(source instanceof ModifiableSource)) { throw new ProcessingException("Source '" + systemID + "' is not writeable."); } ((ModifiableSource)source).delete(); reportResult("none", "delete", "source deleted successfully", systemID, RESULT_SUCCESS, ACTION_DELETE); } catch (SourceException se) { if (getLogger().isDebugEnabled()) { getLogger().debug("FAIL exception: " + se, se); } reportResult("none", "delete", "unable to delete source: " + se.getMessage(), systemID, RESULT_FAILED, ACTION_DELETE); } finally { resolver.release(source); } } /** * Insert a fragment into a file. * The file is loaded by the resource connector. * * @param systemID The name of the xml file. * @param path The XPath specifying the node under which the data is inserted * @param fragment The data to be inserted. * @param replacePath Optional XPath relative to <CODE>path</CODE>. This path * can specify a node which will be removed if it exists. * So insertFragment can be used as a replace utility. * @param create If the file does not exists and this is set to * <CODE>false</CODE> nothing is inserted. If it is set * to <CODE>true</CODE> the file is created and the data * is inserted. * @param overwrite If this is set to <CODE>true</CODE> the data is only * inserted if the node specified by the <CODE>replacePath</CODE> * does not exists. * @param reinsertPath If specified and a node is replaced , all children of * this replaced node will be reinserted at the given path. * @param localSerializer The serializer used to serialize the XML * @param tagname The name of the tag that triggered me 'insert' or 'write' */ protected void insertFragment(String systemID, String path, DocumentFragment fragment, String replacePath, boolean create, boolean overwrite, String reinsertPath, String localSerializer, String tagname) throws SAXException, IOException, ProcessingException { // no sync req if (getLogger().isDebugEnabled()) { getLogger().debug("Insert fragment. systemID=" + systemID + ", path=" + path + ", replace=" + replacePath + ", create=" + create + ", overwrite=" + overwrite + ", reinsert=" + reinsertPath + ", fragment=" + (fragment == null ? "null" : XMLUtils.serializeNode(fragment))); } // test parameter if (systemID == null) { throw new ProcessingException("insertFragment: systemID is required."); } if (path == null) { throw new ProcessingException("insertFragment: path is required."); } if (path.startsWith("/")) { path = path.substring(1); } if (fragment == null) { throw new ProcessingException("insertFragment: fragment is required."); } // first: read the source as a DOM Source source = null; Document resource = null; boolean failed = true; boolean exists = false; String message = ""; String target = systemID; try { source = this.resolver.resolveURI(systemID); if (! (source instanceof ModifiableSource)) { throw new ProcessingException("Source '" + systemID + "' is not writeable."); } ModifiableSource ws = (ModifiableSource) source; exists = ws.exists(); target = source.getURI(); // Insert? if (exists && this.state == STATE_INSERT) { message = "content inserted at: " + path; resource = SourceUtil.toDOM(source); // import the fragment Node importNode = resource.importNode(fragment, true); // get the node Node parent = DOMUtil.selectSingleNode(resource, path, this.xpathProcessor); // replace? if (replacePath != null) { try { Node replaceNode = DOMUtil.getSingleNode(parent, replacePath, this.xpathProcessor); // now get the parent of this node until it is the parent node for insertion while (replaceNode != null && !replaceNode.getParentNode().equals(parent)) { replaceNode = replaceNode.getParentNode(); } if (replaceNode != null) { if (overwrite) { if (parent.getNodeType() == Node.DOCUMENT_NODE) { // replacing of the document element is not allowed resource = newDocument(); resource.appendChild(resource.importNode(importNode, true)); parent = resource; replaceNode = resource.importNode(replaceNode, true); } else { parent.replaceChild(importNode, replaceNode); } message += ", replacing: " + replacePath; if (reinsertPath != null) { Node insertAt = DOMUtil.getSingleNode(parent, reinsertPath, this.xpathProcessor); if (insertAt != null) { while (replaceNode.hasChildNodes()) { insertAt.appendChild(replaceNode.getFirstChild()); } } else { // reinsert point null message = "replace failed, could not find your reinsert path: " + reinsertPath; resource = null; } } } else { // overwrite was false message = "replace failed, no overwrite allowed."; resource = null; } } else { // specified replaceNode was not found parent.appendChild(importNode); } } catch (javax.xml.transform.TransformerException sax) { throw new ProcessingException("TransformerException: " + sax, sax); } } else { // no replace path, just do an insert at end parent.appendChild(importNode); } // Create? } else if (create) { // Create new document resource = newDocument(); // Import the fragment Node importNode = resource.importNode(fragment, true); if (path.length() == 0) { // Parent node is document itself NodeList nodes = importNode.getChildNodes(); for (int i = 0; i < nodes.getLength();) { Node node = nodes.item(i); switch (node.getNodeType()) { case Node.ELEMENT_NODE: // May throw exception if fragment has more than one element resource.appendChild(node); break; case Node.DOCUMENT_TYPE_NODE: case Node.PROCESSING_INSTRUCTION_NODE: case Node.COMMENT_NODE: resource.appendChild(node); break; default: // Ignore all other nodes i++; break; } } message = "entire source overwritten"; } else { // Get the parent node Node parent = DOMUtil.selectSingleNode(resource, path, this.xpathProcessor); // Add a fragment parent.appendChild(importNode); message = "content appended to: " + path; } // Oops: Document does not exist and create is not allowed. } else { message = "create not allowed"; resource = null;/**/ } // Write source if (resource != null) { resource.normalize(); // use serializer if (localSerializer == null) { localSerializer = this.configuredSerializerName; } if (localSerializer != null) { // Lookup the Serializer ServiceSelector selector = null; Serializer serializer = null; OutputStream oStream = null; try { selector = (ServiceSelector)manager.lookup(Serializer.ROLE + "Selector"); serializer = (Serializer)selector.select(localSerializer); oStream = ws.getOutputStream(); serializer.setOutputStream(oStream); DOMStreamer streamer = new DOMStreamer(serializer); streamer.stream(resource); } finally { if (oStream != null) { oStream.flush(); try { oStream.close(); failed = false; } catch (Throwable t) { if (getLogger().isDebugEnabled()) { getLogger().debug("FAIL (oStream.close) exception"+t, t); } throw new ProcessingException("Could not process your document.", t); } finally { if (selector != null) { selector.release(serializer); this.manager.release(selector); } } } } } else { if (getLogger().isDebugEnabled()) { getLogger().debug("ERROR: No serializer"); } //throw new ProcessingException("No serializer specified for writing to source " + systemID); message = "That source requires a serializer, please add the appropirate tag to your code."; } } } catch (DOMException de) { if (getLogger().isDebugEnabled()) { getLogger().debug("FAIL exception: "+de, de); } message = "There was a problem manipulating your document: " + de; } catch (ServiceException ce) { if (getLogger().isDebugEnabled()) { getLogger().debug("FAIL exception: "+ce, ce); } message = "There was a problem looking up a component: " + ce; } catch (SourceException se) { if (getLogger().isDebugEnabled()) { getLogger().debug("FAIL exception: "+se, se); } message = "There was a problem resolving that source: [" + systemID + "] : " + se; } finally { this.resolver.release(source); } // Report result String result = (failed) ? RESULT_FAILED : RESULT_SUCCESS; String action = ACTION_NONE; if (!failed) { action = (exists) ? ACTION_OVER : ACTION_NEW; } reportResult(localSerializer, tagname, message, target, result, action); } private void reportResult(String localSerializer, String tagname, String message, String target, String result, String action) throws SAXException { sendStartElementEvent(RESULT_ELEMENT); sendStartElementEvent(EXECUTION_ELEMENT); sendTextEvent(result); sendEndElementEvent(EXECUTION_ELEMENT); sendStartElementEvent(MESSAGE_ELEMENT); sendTextEvent(message); sendEndElementEvent(MESSAGE_ELEMENT); sendStartElementEvent(BEHAVIOUR_ELEMENT); sendTextEvent(tagname); sendEndElementEvent(BEHAVIOUR_ELEMENT); sendStartElementEvent(ACTION_ELEMENT); sendTextEvent(action); sendEndElementEvent(ACTION_ELEMENT); sendStartElementEvent(SOURCE_ELEMENT); sendTextEvent(target); sendEndElementEvent(SOURCE_ELEMENT); if (localSerializer != null) { sendStartElementEvent(SERIALIZER_ELEMENT); sendTextEvent(localSerializer); sendEndElementEvent(SERIALIZER_ELEMENT); } sendEndElementEvent(RESULT_ELEMENT); } private Document newDocument() throws SAXException, ServiceException { DOMParser parser = (DOMParser) this.manager.lookup(DOMParser.ROLE); try { return parser.createDocument(); } finally { this.manager.release(parser); } } /* (non-Javadoc) * @see org.apache.avalon.framework.service.Serviceable#service(ServiceManager) */ public void service(ServiceManager manager) throws ServiceException { super.service(manager); this.xpathProcessor = (XPathProcessor) this.manager.lookup(XPathProcessor.ROLE); } /* (non-Javadoc) * @see org.apache.avalon.framework.activity.Disposable#dispose() */ public void dispose() { if (this.manager != null) { this.manager.release(this.xpathProcessor); this.xpathProcessor = null; } super.dispose(); } }