| /* |
| * Copyright 1999-2004 The Apache Software Foundation. |
| * |
| * Licensed 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.components.source.impl; |
| |
| |
| import java.io.InputStream; |
| import java.io.IOException; |
| import java.io.OutputStream; |
| import java.io.ByteArrayOutputStream; |
| import java.io.ByteArrayInputStream; |
| import java.net.MalformedURLException; |
| import java.util.Map; |
| |
| import org.apache.avalon.framework.configuration.ConfigurationException; |
| import org.apache.avalon.framework.logger.Logger; |
| import org.apache.avalon.framework.service.ServiceException; |
| import org.apache.avalon.framework.service.ServiceManager; |
| import org.apache.avalon.framework.service.ServiceSelector; |
| |
| import org.apache.excalibur.source.ModifiableSource; |
| import org.apache.excalibur.source.SourceException; |
| import org.apache.excalibur.source.impl.AbstractSource; |
| import org.apache.excalibur.xml.sax.SAXParser; |
| import org.apache.excalibur.xml.sax.XMLizable; |
| |
| import org.apache.cocoon.components.modules.input.InputModule; |
| import org.apache.cocoon.components.modules.output.OutputModule; |
| import org.apache.cocoon.serialization.XMLSerializer; |
| import org.apache.cocoon.util.jxpath.DOMFactory; |
| import org.apache.cocoon.xml.dom.DOMBuilder; |
| import org.apache.cocoon.xml.dom.DOMStreamer; |
| |
| import org.apache.commons.jxpath.JXPathContext; |
| |
| import org.w3c.dom.Document; |
| import org.w3c.dom.Node; |
| |
| import org.xml.sax.ContentHandler; |
| import org.xml.sax.InputSource; |
| import org.xml.sax.SAXException; |
| |
| /** |
| * A <code>ModifiableSource</code> that takes its content from a |
| * module. |
| * <p>The URI syntax is |
| * "xmodule:[<input-module>|<output-module>]:attribute-name[#XPath]", |
| * where : |
| * <ul> |
| * <li>an input-module name is used for finding an input-module for reading data from</li>, |
| * <li>an output-module name is used for finding an output-module for writing data to</li>, |
| * <li>"attribute-name" is the name of the attribute found in the module</li>, |
| * <li>"XPath" is an XPath that is aplied on the object in the |
| * attribute, by using JXPath.</li> |
| * </ul> |
| * </p> |
| * |
| */ |
| public class XModuleSource |
| extends AbstractSource |
| implements ModifiableSource, XMLizable, DOMBuilder.Listener { |
| |
| private final static String SCHEME = "xmodule"; |
| private String attributeType; |
| private String attributeName; |
| private String xPath; |
| private ServiceManager manager; |
| private Map objectModel; |
| private Logger logger; |
| |
| /** |
| * Create a xmodule source from a 'xmodule:' uri and a the object model. |
| * <p>The uri is of the form "xmodule:/attribute-type/attribute-name/xpath</p> |
| */ |
| public XModuleSource( Map objectModel, String uri, |
| ServiceManager manager, Logger logger ) |
| throws MalformedURLException { |
| |
| this.objectModel = objectModel; |
| this.manager = manager; |
| this.logger = logger; |
| |
| setSystemId( uri ); |
| |
| // Scheme |
| int start = 0; |
| int end = uri.indexOf( ':' ); |
| if ( end == -1 ) |
| throw new MalformedURLException("Malformed uri for xmodule source (cannot find scheme) : " + uri); |
| |
| String scheme = uri.substring( start, end ); |
| if ( !SCHEME.equals( scheme ) ) |
| throw new MalformedURLException("Malformed uri for a xmodule source : " + uri); |
| |
| setScheme( scheme ); |
| |
| // Attribute type |
| start = end + 1; |
| end = uri.indexOf( ':', start ); |
| if ( end == -1 ) { |
| throw new MalformedURLException("Malformed uri for xmodule source (cannot find attribute type) : " + uri); |
| } |
| this.attributeType = uri.substring( start, end ); |
| |
| // Attribute name |
| start = end + 1; |
| end = uri.indexOf( '#', start ); |
| |
| if ( end == -1 ) |
| end = uri.length(); |
| |
| if ( end == start ) |
| throw new MalformedURLException("Malformed uri for xmodule source (cannot find attribute name) : " + uri); |
| |
| this.attributeName = uri.substring( start, end ); |
| |
| // xpath |
| start = end + 1; |
| this.xPath = start < uri.length() ? uri.substring( start ) : ""; |
| } |
| |
| /** |
| * Implement this method to obtain SAX events. |
| * |
| */ |
| |
| public void toSAX(ContentHandler handler) |
| throws SAXException { |
| |
| Object obj = getInputAttribute( this.attributeType, this.attributeName ); |
| if ( obj == null ) |
| throw new SAXException( " The attribute: " + this.attributeName + |
| " is empty" ); |
| |
| if ( !(this.xPath.length() == 0 || this.xPath.equals( "/" )) ) { |
| JXPathContext context = JXPathContext.newContext( obj ); |
| |
| obj = context.getPointer( this.xPath ).getNode(); |
| |
| if ( obj == null ) |
| throw new SAXException( "the xpath: " + this.xPath + |
| " applied on the attribute: " + |
| this.attributeName + |
| " returns null"); |
| } |
| |
| if ( obj instanceof Document ) { |
| DOMStreamer domStreamer = new DOMStreamer( handler ); |
| domStreamer.stream( (Document)obj ); |
| } else if ( obj instanceof Node ) { |
| DOMStreamer domStreamer = new DOMStreamer( handler ); |
| handler.startDocument(); |
| domStreamer.stream( (Node)obj ); |
| handler.endDocument(); |
| } else if ( obj instanceof XMLizable ) { |
| ((XMLizable)obj).toSAX( handler ); |
| } else { |
| throw new SAXException( "The object type: " + obj.getClass() + |
| " could not be serialized to XML: " + obj ); |
| } |
| } |
| |
| /** |
| * Return an <code>InputStream</code> object to read from the source. |
| * |
| * @throws IOException if I/O error occured. |
| */ |
| // Stolen from QDoxSource |
| public InputStream getInputStream() throws IOException, SourceException { |
| if ( this.logger.isDebugEnabled() ) { |
| this.logger.debug( "Getting InputStream for " + getURI() ); |
| } |
| |
| // Serialize the SAX events to the XMLSerializer: |
| |
| XMLSerializer serializer = new XMLSerializer(); |
| ByteArrayInputStream inputStream = null; |
| |
| try { |
| ByteArrayOutputStream outputStream = new ByteArrayOutputStream( 2048 ); |
| serializer.setOutputStream( outputStream ); |
| toSAX( serializer ); |
| inputStream = new ByteArrayInputStream( outputStream.toByteArray() ); |
| } catch ( SAXException se ) { |
| logger.error( "SAX exception!", se ); |
| throw new SourceException( "Serializing SAX to a ByteArray failed!", se ); |
| } |
| |
| return inputStream; |
| } |
| |
| /** |
| * Does this source actually exist ? |
| * |
| * @return true if the resource exists. |
| * |
| */ |
| public boolean exists() { |
| boolean exists = false; |
| try { |
| exists = getInputAttribute( this.attributeType, this.attributeName ) != null; |
| } catch ( SAXException e ) { |
| exists = false; |
| } |
| return exists; |
| } |
| |
| /** |
| * Get an <code>InputStream</code> where raw bytes can be written to. |
| * The signification of these bytes is implementation-dependent and |
| * is not restricted to a serialized XML document. |
| * |
| * @return a stream to write to |
| */ |
| public OutputStream getOutputStream() throws IOException { |
| return new DOMOutputStream(); |
| } |
| |
| /** |
| * Delete the source |
| */ |
| public void delete() throws SourceException { |
| if ( !(this.xPath.length() == 0 || this.xPath.equals( "/" )) ) { |
| Object value; |
| try { |
| value = getInputAttribute( this.attributeType, this.attributeName ); |
| } catch ( SAXException e ) { |
| throw new SourceException( "delete: ", e ); |
| } |
| if ( value == null ) |
| throw new SourceException( " The attribute: " + this.attributeName + |
| " is empty" ); |
| |
| JXPathContext context = JXPathContext.newContext( value ); |
| context.removeAll( this.xPath ); |
| } else { |
| try { |
| setOutputAttribute( this.attributeType, this.attributeName, null ); |
| } catch ( SAXException e ) { |
| throw new SourceException( "delete: ", e ); |
| } |
| } |
| } |
| |
| /** |
| * FIXME |
| * delete is an operator in java script, this method is for |
| * testing puposes in java script only |
| */ |
| public void deleteTest() throws SourceException { |
| delete(); |
| } |
| |
| /** |
| * Can the data sent to an <code>OutputStream</code> returned by |
| * {@link #getOutputStream()} be cancelled ? |
| * |
| * @return true if the stream can be cancelled |
| */ |
| public boolean canCancel( OutputStream stream ) { return false; } |
| |
| /** |
| * Cancel the data sent to an <code>OutputStream</code> returned by |
| * {@link #getOutputStream()}. |
| * <p> |
| * After cancel, the stream should no more be used. |
| */ |
| public void cancel(OutputStream stream) throws IOException {} |
| |
| /** |
| * Get a <code>ContentHandler</code> where an XML document can |
| * be written using SAX events. |
| * <p> |
| * Care should be taken that the returned handler can actually |
| * be a {@link org.apache.cocoon.xml.XMLConsumer} supporting also |
| * lexical events such as comments. |
| * |
| * @return a handler for SAX events |
| */ |
| public ContentHandler getContentHandler() { |
| return new DOMBuilder( this ); |
| } |
| |
| public void notify( Document insertDoc ) throws SAXException { |
| |
| // handle xpaths, we are only handling inserts, i.e. if there is no |
| // attribute of the given name and type the operation will fail |
| if ( !(this.xPath.length() == 0 || this.xPath.equals( "/" )) ) { |
| |
| Object value = getInputAttribute( this.attributeType, this.attributeName ); |
| if ( value == null ) |
| throw new SAXException( " The attribute: " + this.attributeName + |
| " is empty" ); |
| |
| JXPathContext context = JXPathContext.newContext( value ); |
| |
| if ( value instanceof Document ) { |
| // If the attribute contains a dom document we |
| // create the elements in the given xpath if |
| // necesary, import the input document and put it |
| // in the place described by the xpath. |
| Document doc = (Document)value; |
| |
| Node importedNode = |
| doc.importNode( insertDoc.getDocumentElement(), true ); |
| |
| context.setLenient( true ); |
| context.setFactory( new DOMFactory() ); |
| context.createPathAndSetValue( this.xPath, importedNode ); |
| } else { |
| // Otherwise just try to put a the input document in |
| // the place pointed to by the xpath |
| context.setValue( this.xPath, insertDoc ); |
| } |
| |
| } else { |
| setOutputAttribute( this.attributeType, this.attributeName, insertDoc ); |
| } |
| } |
| |
| private class DOMOutputStream extends ByteArrayOutputStream { |
| public void close() throws IOException { |
| SAXParser parser = null; |
| try { |
| parser = (SAXParser)XModuleSource.this.manager.lookup( SAXParser.ROLE ); |
| |
| parser.parse( new InputSource( new ByteArrayInputStream( super.toByteArray() ) ), |
| XModuleSource.this.getContentHandler()); |
| } catch (Exception e){ |
| throw new IOException("Exception during processing of " + |
| XModuleSource.super.getURI() + |
| e.getMessage()); |
| } finally { |
| if (parser != null) XModuleSource.this.manager.release( parser ); |
| } |
| super.close(); |
| } |
| } |
| |
| |
| private Object getInputAttribute( String inputModuleName, String attributeName ) |
| throws SAXException { |
| Object obj; |
| ServiceSelector selector = null; |
| InputModule inputModule = null; |
| try { |
| selector = (ServiceSelector) this.manager.lookup( InputModule.ROLE + "Selector" ); |
| inputModule = (InputModule) selector.select( inputModuleName ); |
| obj = inputModule.getAttribute( attributeName, null, this.objectModel ); |
| |
| } catch ( ServiceException e ) { |
| throw new SAXException( "Could not find an InputModule of the type " + |
| inputModuleName , e ); |
| } catch ( ConfigurationException e ) { |
| throw new SAXException( "Could not find an attribute: " + attributeName + |
| " from the InputModule " + inputModuleName, e ); |
| } finally { |
| if ( inputModule != null ) selector.release( inputModule ); |
| this.manager.release( selector ); |
| } |
| |
| return obj; |
| } |
| |
| private void setOutputAttribute( String outputModuleName, |
| String attributeName, Object value ) |
| throws SAXException{ |
| ServiceSelector selector = null; |
| OutputModule outputModule = null; |
| try { |
| selector = (ServiceSelector) this.manager.lookup( OutputModule.ROLE + "Selector" ); |
| outputModule = (OutputModule) selector.select( outputModuleName ); |
| outputModule.setAttribute( null, this.objectModel, attributeName, value ); |
| outputModule.commit( null, this.objectModel ); |
| |
| } catch ( ServiceException e ) { |
| throw new SAXException( "Could not find an OutputModule of the type " + |
| outputModuleName , e ); |
| } finally { |
| if ( outputModule != null ) selector.release( outputModule ); |
| this.manager.release( selector ); |
| } |
| } |
| } |