/*
* eXist Open Source Native XML Database
* Copyright (C) 2001-04 The eXist Project
* http://exist-db.org
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*
* $Id: SerializeToFile.java 14691 2011-06-14 12:29:57Z deliriumsky $
*/
package org.exist.xquery.modules.file;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.Writer;
import java.util.Properties;
import javax.xml.transform.OutputKeys;
import org.apache.log4j.Logger;
import org.exist.dom.QName;
import org.exist.storage.serializers.Serializer;
import org.exist.util.serializer.SAXSerializer;
import org.exist.util.serializer.SerializerPool;
import org.exist.xquery.BasicFunction;
import org.exist.xquery.Cardinality;
import org.exist.xquery.FunctionSignature;
import org.exist.xquery.Option;
import org.exist.xquery.XPathException;
import org.exist.xquery.XQueryContext;
import org.exist.xquery.value.Base64Binary;
import org.exist.xquery.value.BooleanValue;
import org.exist.xquery.value.FunctionParameterSequenceType;
import org.exist.xquery.value.FunctionReturnSequenceType;
import org.exist.xquery.value.NodeValue;
import org.exist.xquery.value.Sequence;
import org.exist.xquery.value.SequenceIterator;
import org.exist.xquery.value.SequenceType;
import org.exist.xquery.value.Type;
import org.xml.sax.SAXException;
public class SerializeToFile extends BasicFunction
{
private final static Logger logger = Logger.getLogger(SerializeToFile.class);
private final static String FN_SERIALIZE_LN = "serialize";
private final static String FN_SERIALIZE_BINARY_LN = "serialize-binary";
public final static FunctionSignature signatures[] = {
new FunctionSignature(
new QName( FN_SERIALIZE_LN, FileModule.NAMESPACE_URI, FileModule.PREFIX ),
"Writes the node set into a file on the file system. $parameters contains a " +
"sequence of zero or more serialization parameters specified as key=value pairs. The " +
"serialization options are the same as those recognized by \"declare option exist:serialize\". " +
"The function does NOT automatically inherit the serialization options of the XQuery it is " +
"called from. This method is only available to the DBA role.",
new SequenceType[] {
new FunctionParameterSequenceType( "node-set", Type.NODE, Cardinality.ZERO_OR_MORE, "The contents to write to the file system." ),
new FunctionParameterSequenceType( "filepath", Type.STRING, Cardinality.EXACTLY_ONE, "The full path to the file" ),
new FunctionParameterSequenceType( "parameters", Type.STRING, Cardinality.ZERO_OR_MORE, "The serialization parameters specified as key-value pairs" )
},
new FunctionReturnSequenceType( Type.BOOLEAN, Cardinality.ZERO_OR_ONE, "true on success - false if the specified file can not be created or is not writable. The empty sequence is returned if the argument sequence is empty." )
),
new FunctionSignature(
new QName(FN_SERIALIZE_BINARY_LN, FileModule.NAMESPACE_URI, FileModule.PREFIX),
"Writes binary data into a file on the file system. This method is only available to the DBA role.",
new SequenceType[]{
new FunctionParameterSequenceType("binarydata", Type.BASE64_BINARY, Cardinality.EXACTLY_ONE, "The contents to write to the file system."),
new FunctionParameterSequenceType("filepath", Type.STRING, Cardinality.EXACTLY_ONE, "The full path to the file")
},
new FunctionReturnSequenceType(Type.BOOLEAN, Cardinality.EXACTLY_ONE, "true on success - false if the specified file can not be created or is not writable")
)
};
public SerializeToFile( XQueryContext context, FunctionSignature signature )
{
super( context, signature );
}
public Sequence eval( Sequence[] args, Sequence contextSequence ) throws XPathException
{
if(args[0].isEmpty()) {
return Sequence.EMPTY_SEQUENCE;
}
if (!context.getUser().hasDbaRole()) {
XPathException xPathException = new XPathException(this, "Permission denied, calling user '" + context.getUser().getName() + "' must be a DBA to call this function.");
logger.error("Invalid user", xPathException);
throw xPathException;
}
//check the file output path
String inputPath = args[1].getStringValue();
File file = FileModuleHelper.getFile(inputPath);
if(file.isDirectory())
{
logger.debug("Cannot serialize file. Output file is a directory: " + file.getAbsolutePath());
return BooleanValue.FALSE;
}
if(file.exists() && !file.canWrite())
{
logger.debug("Cannot serialize file. Cannot write to file " + file.getAbsolutePath() );
return BooleanValue.FALSE;
}
if(isCalledAs(FN_SERIALIZE_LN))
{
//parse serialization options from third argument to function
Properties outputProperties = parseXMLSerializationOptions( args[2].iterate() );
//do the serialization
serializeXML(args[0].iterate(), outputProperties, file);
}
else if(isCalledAs(FN_SERIALIZE_BINARY_LN))
{
serializeBinary((Base64Binary)args[0].itemAt(0), file);
}
else
{
throw new XPathException(this, "Unknown function name");
}
return BooleanValue.TRUE;
}
private Properties parseXMLSerializationOptions( SequenceIterator siSerializeParams ) throws XPathException
{
//parse serialization options
Properties outputProperties = new Properties();
outputProperties.setProperty( OutputKeys.INDENT, "yes" );
outputProperties.setProperty( OutputKeys.OMIT_XML_DECLARATION, "yes" );
while(siSerializeParams.hasNext()) {
String serializeParam = siSerializeParams.nextItem().getStringValue();
String opt[] = Option.parseKeyValuePair(serializeParam);
if(opt != null && opt.length == 2) {
outputProperties.setProperty( opt[0], opt[1] );
}
}
return( outputProperties );
}
private void serializeXML( SequenceIterator siNode, Properties outputProperties, File file ) throws XPathException
{
// serialize the node set
SAXSerializer sax = (SAXSerializer)SerializerPool.getInstance().borrowObject( SAXSerializer.class );
try {
OutputStream os = new FileOutputStream(file);
String encoding = outputProperties.getProperty( OutputKeys.ENCODING, "UTF-8" );
Writer writer = new OutputStreamWriter( os, encoding );
sax.setOutput( writer, outputProperties );
Serializer serializer = context.getBroker().getSerializer();
serializer.reset();
serializer.setProperties( outputProperties );
serializer.setReceiver( sax );
sax.startDocument();
while( siNode.hasNext() ) {
NodeValue next = (NodeValue)siNode.nextItem();
serializer.toSAX( next );
}
sax.endDocument();
writer.close();
}
catch( SAXException e ) {
throw( new XPathException( this, "Cannot serialize file. A problem ocurred while serializing the node set: " + e.getMessage(), e ) );
}
catch ( IOException e ) {
throw( new XPathException(this, "Cannot serialize file. A problem ocurred while serializing the node set: " + e.getMessage(), e ) );
}
finally {
SerializerPool.getInstance().returnObject( sax );
}
}
private void serializeBinary(Base64Binary binary, File file) throws XPathException
{
try
{
OutputStream fos = new BufferedOutputStream(new FileOutputStream(file));
fos.write(binary.getBinaryData());
fos.flush();
fos.close();
}
catch(FileNotFoundException fnfe)
{
throw new XPathException(this, "Cannot serialize file. A problem ocurred while serializing the binary data: " + fnfe.getMessage(), fnfe);
}
catch(IOException ioe)
{
throw new XPathException(this, "Cannot serialize file. A problem ocurred while serializing the binary data: " + ioe.getMessage(), ioe);
}
}
}