blob: 044e882911d33ca4224734cc6734e944beb10b2c [file] [log] [blame]
/*
* 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.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.Serializer;
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>
*
* @author <a href="mailto:danielf@nada.kth.se">Daniel Fagerstom</a>
*/
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;
protected ServiceManager manager;
private Map objectModel;
private Logger logger;
// TODO: make this actually configurable
private String configuredSerializerName = "xml";
/**
* 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.
*/
public InputStream getInputStream() throws IOException, SourceException {
if ( this.logger.isDebugEnabled() ) {
this.logger.debug( "Getting InputStream for " + getURI() );
}
// Serialize the SAX events to the XMLSerializer
ByteArrayInputStream inputStream = null;
ServiceSelector selector = null;
Serializer serializer = null;
try {
selector = (ServiceSelector)this.manager.lookup(Serializer.ROLE + "Selector");
serializer = (Serializer)selector.select(this.configuredSerializerName);
ByteArrayOutputStream outputStream = new ByteArrayOutputStream(2048);
serializer.setOutputStream(outputStream);
toSAX(serializer);
inputStream = new ByteArrayInputStream(outputStream.toByteArray());
} catch (SAXException e) {
throw new SourceException("Serializing SAX to a ByteArray failed!", e);
} catch (ServiceException e) {
throw new SourceException("Retrieving serializer failed.", e);
} finally {
if (selector != null) {
selector.release(serializer);
this.manager.release(selector);
}
}
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.this.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 ( selector != 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 ( selector != null ) {
selector.release( outputModule );
this.manager.release( selector );
}
}
}
}