vgritsenko 02/01/09 21:40:35 Added: src/scratchpad/src/org/apache/cocoon/transformation XMLDBTransformer.java Log: XMLDB transformer allows to: - create - delete - update (using XUpdate) resources in XML:DB database Revision Changes Path 1.1 xml-cocoon2/src/scratchpad/src/org/apache/cocoon/transformation/XMLDBTransformer.java Index: XMLDBTransformer.java =================================================================== /***************************************************************************** * Copyright (C) The Apache Software Foundation. All rights reserved. * * ------------------------------------------------------------------------- * * This software is published under the terms of the Apache Software License * * version 1.1, a copy of which has been included with this distribution in * * the LICENSE file. * *****************************************************************************/ package org.apache.cocoon.transformation; import org.apache.avalon.excalibur.pool.Poolable; import org.apache.avalon.framework.activity.Disposable; import org.apache.avalon.framework.activity.Initializable; import org.apache.avalon.framework.parameters.Parameters; import org.apache.avalon.framework.configuration.Configurable; import org.apache.avalon.framework.configuration.Configuration; import org.apache.avalon.framework.configuration.ConfigurationException; import org.apache.cocoon.util.TraxErrorHandler; import org.apache.cocoon.caching.CacheValidity; import org.apache.cocoon.caching.Cacheable; import org.apache.cocoon.environment.SourceResolver; import org.apache.cocoon.ProcessingException; import org.apache.cocoon.ResourceNotFoundException; import org.xml.sax.Attributes; import org.xml.sax.SAXException; import org.xml.sax.helpers.AttributesImpl; import org.xmldb.api.base.Database; import org.xmldb.api.base.Resource; import org.xmldb.api.base.Collection; import org.xmldb.api.base.XMLDBException; import org.xmldb.api.DatabaseManager; import org.xmldb.api.modules.XUpdateQueryService; import javax.xml.transform.sax.TransformerHandler; import javax.xml.transform.sax.SAXTransformerFactory; import javax.xml.transform.stream.StreamResult; import javax.xml.transform.TransformerFactory; import javax.xml.transform.OutputKeys; import javax.xml.transform.TransformerConfigurationException; import java.io.IOException; import java.io.StringWriter; import java.util.HashMap; import java.util.Iterator; import java.util.Map; import java.util.Properties; /** * This transformer allows to perform resource creation, deletion, and * XUpdate command execution in XML:DB. * * <p>Definition:</p> * <pre> * <map:transformer name="xmldb" src="www.XMLDBTransformer"> * <driver>org.apache.xindice.client.xmldb.DatabaseImpl</driver> * <base>xmldb:xindice:///db/collection</base> * </map:transformer> * </pre> * * <p>Invokation:</p> * <pre> * <map:transform type="xmldb"> * <map:parameter name="base" value="xmldb:xindice:///db/collection"/> * </map:transform> * </pre> * * <p>Input XML document example:</p> * <pre> * <page xmlns:xindice="http://apache.org/cocoon/xmldb/1.0"> * ... * <xindice:query type="create" oid="xmldb-object-id"> * <page> * XML Object body * </page> * </xindice:query> * * <xindice:query type="delete" oid="xmldb-object-id"/> * * <xindice:query type="update" oid="xmldb-object-id"> * <xu:modifications version="1.0" xmlns:xu="http://www.xmldb.org/xupdate"> * <xu:remove select="/person/phone[@type = 'home']"/> * <xu:update select="/person/phone[@type = 'work']"> * 480-300-3003 * </xu:update> * </xu:modifications> * </xindice:query> * ... * </page> * </pre> * * <p>Output XML document example:</p> * <pre> * <page xmlns:xindice="http://apache.org/cocoon/xmldb/1.0"> * ... * <xindice:query type="create" oid="xmldb-object-id" result="success"/> * * <xindice:query type="delete" oid="xmldb-object-id" result="success"/> * * <xindice:query type="update" oid="xmldb-object-id" result="failure"> * Resource xmldb-object-id is not found * </xindice:query> * ... * </page> * </pre> * * <p>Known bugs and limitations:</p> * <ul> * <li>XUpdate is not tested</li> * <li>No namespaces with Xalan (see AbstractTextSerializer)</li> * </ul> * * @author <a href="mailto:[EMAIL PROTECTED]">Vadim Gritsenko</a> * @version CVS $Revision: 1.1 $ $Date: 2002/01/10 05:40:35 $ */ public class XMLDBTransformer extends AbstractTransformer implements Disposable, Cacheable, Poolable, Configurable, Initializable { private static String XMLDB_URI = "http://apache.org/cocoon/xmldb/1.0"; private static String XMLDB_QUERY_ELEMENT = "query"; private static String XMLDB_QUERY_TYPE_ATTRIBUTE = "type"; private static String XMLDB_QUERY_OID_ATTRIBUTE = "oid"; private static String XMLDB_QUERY_RESULT_ATTRIBUTE = "result"; /** The trax <code>TransformerFactory</code> used by this transformer. */ private SAXTransformerFactory tfactory = null; private Properties format = new Properties(); /** The map of namespace prefixes. */ private Map prefixMap = new HashMap(); /** XML:DB driver class name. */ private String driver = null; /** Default collection name. */ private String default_base = null; /** Current collection name. */ private String base = null; /** Current collection. */ private Collection collection; /** Operation. One of: create, delete, update. */ private String operation; /** Document ID. Can be null if update is performed on collection. */ private String key; private StringWriter queryWriter; private TransformerHandler queryHandler; /** True when inside <query> element. */ private boolean processing; public void XMLDBTransformer() { format.put(OutputKeys.ENCODING, "utf-8"); format.put(OutputKeys.INDENT, "no"); format.put(OutputKeys.OMIT_XML_DECLARATION, "yes"); } public void configure(Configuration configuration) throws ConfigurationException { Configuration driver = configuration.getChild("driver"); if (driver == null || driver.getValue() == null) throw new ConfigurationException("Required driver parameter is missing."); this.driver = driver.getValue(); Configuration default_base = configuration.getChild("base"); if (default_base != null) this.default_base = default_base.getValue(); } /** * Initializes XML:DB database instance using specified driver class. */ public void initialize() throws Exception { Class c = Class.forName(driver); Database database = (Database)c.newInstance(); DatabaseManager.registerDatabase(database); } /** Setup the transformer. */ public void setup(SourceResolver resolver, Map objectModel, String src, Parameters par) throws ProcessingException, SAXException, IOException { this.base = par.getParameter("base", this.default_base); if (this.base == null) throw new ProcessingException("Required base parameter is missing. Syntax is: xmldb:xindice:///db/collection"); try { this.collection = DatabaseManager.getCollection(base); } catch (XMLDBException e) { throw new ProcessingException("Could not get collection " + base + ": " + e.errorCode, e); } if(this.collection == null) throw new ResourceNotFoundException("Collection " + base + " does not exist"); } /** * Helper for TransformerFactory. */ protected SAXTransformerFactory getTransformerFactory() { if(tfactory == null) { tfactory = (SAXTransformerFactory) TransformerFactory.newInstance(); tfactory.setErrorListener(new TraxErrorHandler(getLogger())); } return tfactory; } /** * Generate the unique key. * This key must be unique inside the space of this component. * * @return The generated key hashes the src */ public long generateKey() { return 1; } /** * Generate the validity object. * * @return The generated validity object or <code>null</code> if the * component is currently not cacheable. */ public CacheValidity generateValidity() { return null; } /** * Receive notification of the beginning of a document. */ public void startDocument() throws SAXException { super.startDocument(); } /** * Receive notification of the end of a document. */ public void endDocument() throws SAXException { super.endDocument(); } /** * Begin the scope of a prefix-URI Namespace mapping. * * @param prefix The Namespace prefix being declared. * @param uri The Namespace URI the prefix is mapped to. */ public void startPrefixMapping(String prefix, String uri) throws SAXException { if (!processing) { super.startPrefixMapping(prefix,uri); prefixMap.put(prefix,uri); } else if (this.queryHandler != null){ this.queryHandler.startPrefixMapping(prefix, uri); } } /** * End the scope of a prefix-URI mapping. * * @param prefix The prefix that was being mapping. */ public void endPrefixMapping(String prefix) throws SAXException { if (!processing) { super.endPrefixMapping(prefix); prefixMap.remove(prefix); } else if (this.queryHandler != null){ this.queryHandler.endPrefixMapping(prefix); } } /** * 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 loc 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 a The attributes attached to the element. If there are no * attributes, it shall be an empty Attributes object. */ public void startElement(String uri, String loc, String raw, Attributes a) throws SAXException { if (!processing) { if (XMLDB_URI.equals(uri) && XMLDB_QUERY_ELEMENT.equals(loc)){ this.operation = a.getValue(XMLDB_QUERY_TYPE_ATTRIBUTE); if(!"create".equals(operation) && !"delete".equals(operation) && !"update".equals(operation)) { throw new SAXException("Supported operation types are: create, delete"); } this.key = a.getValue(XMLDB_QUERY_OID_ATTRIBUTE); if(!"update".equals(operation) && this.key == null) { throw new SAXException("Object ID is missing in xmldb element"); } processing = true; if (!"delete".equals(operation)) { // Prepare SAX query writer queryWriter = new StringWriter(256); try { this.queryHandler = getTransformerFactory().newTransformerHandler(); this.queryHandler.setResult(new StreamResult(queryWriter)); this.queryHandler.getTransformer().setOutputProperties(format); } catch (TransformerConfigurationException e) { throw new SAXException("Failed to get transformer handler", e); } // Start query document this.queryHandler.startDocument(); Iterator itt = prefixMap.entrySet().iterator(); while ( itt.hasNext() ) { Map.Entry entry = (Map.Entry)itt.next(); this.queryHandler.startPrefixMapping((String)entry.getKey(), (String)entry.getValue()); } } } else { super.startElement(uri,loc,raw,a); } } else if (this.queryHandler != null){ this.queryHandler.startElement(uri,loc,raw,a); } } /** * 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 loc 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 endElement(String uri, String loc, String raw) throws SAXException { if (!processing) { super.endElement(uri,loc,raw); } else { if (XMLDB_URI.equals(uri) && XMLDB_QUERY_ELEMENT.equals(loc)){ processing = false; String document = null; if (this.queryHandler != null){ // Finish building query. Remove existing prefix mappings. Iterator itt = prefixMap.entrySet().iterator(); while ( itt.hasNext() ) { Map.Entry entry = (Map.Entry) itt.next(); this.queryHandler.endPrefixMapping((String)entry.getKey()); } this.queryHandler.endDocument(); document = this.queryWriter.toString(); } // Perform operation String result = "failure"; String message = null; if("create".equals(operation)) { try { System.out.println("XI: Creating document: " + this.key); Resource resource = collection.createResource(key, "XMLResource"); resource.setContent(document); collection.storeResource(resource); result = "success"; } catch (XMLDBException e) { message = "Failed to create resource " + key + ": " + e.errorCode; getLogger().debug(message, e); } } else if("delete".equals(operation)) { try { System.out.println("XI: Deleting document: " + this.key); Resource resource = collection.getResource(this.key); if (resource == null) { message = "Resource " + this.key + " does not exist"; getLogger().debug(message); } else { collection.removeResource(resource); } result = "success"; } catch (XMLDBException e) { message = "Failed to delete resource " + key + ": " + e.errorCode; getLogger().debug(message, e); } } else if("update".equals(operation)) { try { XUpdateQueryService service = (XUpdateQueryService) collection.getService("XUpdateQueryService", "1.0"); long count = (this.key == null)? service.update(document) : service.updateResource(this.key, document); message = count + " entries updated."; System.out.println("XI: " + message); result = "success"; } catch (XMLDBException e) { message = "Failed to update resource " + key + ": " + e.errorCode; getLogger().debug(message, e); } } // Report result AttributesImpl attrs = new AttributesImpl(); attrs.addAttribute(null, XMLDB_QUERY_OID_ATTRIBUTE, XMLDB_QUERY_OID_ATTRIBUTE, "CDATA", this.key); attrs.addAttribute(null, XMLDB_QUERY_TYPE_ATTRIBUTE, XMLDB_QUERY_TYPE_ATTRIBUTE, "CDATA", this.operation); if (message == null) attrs.addAttribute(null, XMLDB_QUERY_RESULT_ATTRIBUTE, XMLDB_QUERY_RESULT_ATTRIBUTE, "CDATA", "success"); else attrs.addAttribute(null, XMLDB_QUERY_RESULT_ATTRIBUTE, XMLDB_QUERY_RESULT_ATTRIBUTE, "CDATA", "failure"); super.startElement(uri, loc, raw, attrs); if (message != null) { super.characters(message.toCharArray(), 0, message.length()); } super.endElement(uri, loc, raw); } else if (this.queryHandler != null){ this.queryHandler.endElement(uri, loc, raw); } } } /** * Receive notification of character data. * * @param c The characters from the XML document. * @param start The start position in the array. * @param len The number of characters to read from the array. */ public void characters(char c[], int start, int len) throws SAXException { if (!processing) { super.characters(c,start,len); } else if (this.queryHandler != null){ this.queryHandler.characters(c,start,len); } } /** * Receive notification of ignorable whitespace in element content. * * @param c The characters from the XML document. * @param start The start position in the array. * @param len The number of characters to read from the array. */ public void ignorableWhitespace(char c[], int start, int len) throws SAXException { if (!processing) { super.ignorableWhitespace(c,start,len); } else if (this.queryHandler != null){ this.queryHandler.ignorableWhitespace(c,start,len); } } /** * Receive notification of a processing instruction. * * @param target The processing instruction target. * @param data The processing instruction data, or null if none was * supplied. */ public void processingInstruction(String target, String data) throws SAXException { if (!processing) { super.processingInstruction(target,data); } else if (this.queryHandler != null){ this.queryHandler.processingInstruction(target,data); } } /** * Receive notification of a skipped entity. * * @param name The name of the skipped entity. If it is a parameter * entity, the name will begin with '%'. */ public void skippedEntity(String name) throws SAXException { if (!processing) { super.skippedEntity(name); } else if (this.queryHandler != null){ this.queryHandler.skippedEntity(name); } } /** * Report the start of DTD declarations, if any. * * @param name The document type name. * @param publicId The declared public identifier for the external DTD * subset, or null if none was declared. * @param systemId The declared system identifier for the external DTD * subset, or null if none was declared. */ public void startDTD(String name, String publicId, String systemId) throws SAXException { if (!processing) { super.startDTD(name,publicId,systemId); } else { throw new SAXException( "Recieved startDTD after beginning SVG extraction process." ); } } /** * Report the end of DTD declarations. */ public void endDTD() throws SAXException { if (!processing) { super.endDTD(); } else { throw new SAXException("Recieved endDTD after xmldb element."); } } /** * Report the beginning of an entity. * * @param name The name of the entity. If it is a parameter entity, the * name will begin with '%'. */ public void startEntity(String name) throws SAXException { if (!processing) { super.startEntity(name); } else if (this.queryHandler != null){ this.queryHandler.startEntity(name); } } /** * Report the end of an entity. * * @param name The name of the entity that is ending. */ public void endEntity(String name) throws SAXException { if (!processing) { super.endEntity(name); } else if (this.queryHandler != null){ this.queryHandler.endEntity(name); } } /** * Report the start of a CDATA section. */ public void startCDATA() throws SAXException { if (!processing) { super.startCDATA(); } else if (this.queryHandler != null){ this.queryHandler.startCDATA(); } } /** * Report the end of a CDATA section. */ public void endCDATA() throws SAXException { if (!processing) { super.endCDATA(); } else if (this.queryHandler != null){ this.queryHandler.endCDATA(); } } /** * Report an XML comment anywhere in the document. * * @param ch An array holding the characters in the comment. * @param start The starting position in the array. * @param len The number of characters to use from the array. */ public void comment(char ch[], int start, int len) throws SAXException { if (!processing) { super.comment(ch,start,len); } else if (this.queryHandler != null){ this.queryHandler.comment(ch,start,len); } } public void recycle() { this.prefixMap.clear(); this.queryHandler = null; this.queryWriter = null; try { if (collection != null) collection.close(); } catch (XMLDBException e) { getLogger().error("Failed to close collection " + base + ". Error " + e.errorCode, e); } collection = null; } /** * dispose */ public void dispose() { } }
---------------------------------------------------------------------- In case of troubles, e-mail: [EMAIL PROTECTED] To unsubscribe, e-mail: [EMAIL PROTECTED] For additional commands, e-mail: [EMAIL PROTECTED]