package org.exist.xmldb;
import java.io.BufferedOutputStream;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.zip.DataFormatException;
import java.util.zip.Inflater;
import org.apache.xmlrpc.XmlRpcException;
import org.exist.external.org.apache.commons.io.output.ByteArrayOutputStream;
import org.exist.security.Permission;
import org.exist.storage.serializers.EXistOutputKeys;
import org.exist.util.Compressor;
import org.exist.util.EXistInputSource;
import org.exist.util.VirtualTempFile;
import org.xml.sax.InputSource;
import org.xmldb.api.base.Collection;
import org.xmldb.api.base.ErrorCodes;
import org.xmldb.api.base.Resource;
import org.xmldb.api.base.XMLDBException;
public abstract class AbstractRemoteResource
implements EXistResource, ExtendedResource, Resource
{
protected XmldbURI path = null ;
protected String mimeType=null;
protected RemoteCollection parent;
protected VirtualTempFile vfile=null;
protected VirtualTempFile contentVFile=null;
protected InputSource inputSource = null;
protected boolean isLocal=false;
protected long contentLen = 0L;
protected Permission permissions = null;
protected Date dateCreated= null;
protected Date dateModified= null;
public AbstractRemoteResource(RemoteCollection parent,XmldbURI documentName)
throws XMLDBException
{
this.parent = parent;
if (documentName.numSegments()>1) {
this.path = documentName;
} else {
this.path = parent.getPathURI().append(documentName);
}
}
// @Override
protected void finalize()
throws Throwable
{
freeResources();
super.finalize();
}
// @Override
public void freeResources() {
vfile = null;
inputSource = null;
if(contentVFile!=null) {
contentVFile.delete();
contentVFile=null;
}
isLocal=true;
}
protected Properties getProperties() {
return parent.properties;
}
/* (non-Javadoc)
* @see org.xmldb.api.base.Resource#getContent()
*/
public Object getContent()
throws XMLDBException
{
Object res=getExtendedContent();
// Backward compatibility
if(isLocal) return res;
if(res!=null) {
if(res instanceof File) {
return readFile((File)res);
} else if(res instanceof InputSource) {
return readFile((InputSource)res);
}
}
return res;
}
/* (non-Javadoc)
* @see org.xmldb.api.base.Resource#getContent()
*/
// Backward compatibility
protected byte[] getData()
throws XMLDBException
{
Object res=getExtendedContent();
if(res!=null) {
if(res instanceof File) {
return readFile((File)res);
} else if(res instanceof InputSource) {
return readFile((InputSource)res);
} else if(res instanceof String) {
try {
return ((String)res).getBytes("UTF-8");
} catch(UnsupportedEncodingException uee) {
throw new XMLDBException(ErrorCodes.VENDOR_ERROR, uee.getMessage(), uee);
}
}
}
return (byte[])res;
}
public int getContentLength()
throws XMLDBException
{
return (int)contentLen;
}
/* (non-Javadoc)
* @see org.exist.xmldb.EXistResource#getCreationTime()
*/
public Date getCreationTime()
throws XMLDBException
{
return dateCreated;
}
public long getExtendedContentLength()
throws XMLDBException
{
return contentLen;
}
/* (non-Javadoc)
* @see org.exist.xmldb.EXistResource#getLastModificationTime()
*/
public Date getLastModificationTime()
throws XMLDBException
{
return dateModified;
}
/* (non-Javadoc)
* @see org.exist.xmldb.EXistResource#getMimeType()
*/
public String getMimeType() {
return mimeType;
}
/* (non-Javadoc)
* @see org.xmldb.api.base.Resource#getParentCollection()
*/
public Collection getParentCollection()
throws XMLDBException
{
return parent;
}
/* (non-Javadoc)
* @see org.exist.xmldb.EXistResource#getPermissions()
*/
public Permission getPermissions() {
return permissions;
}
protected boolean setContentInternal(Object value)
throws XMLDBException
{
freeResources();
boolean wasSet=false;
if(value instanceof VirtualTempFile) {
vfile = (VirtualTempFile)value;
// Assuring the virtual file is close state
try {
vfile.close();
} catch(IOException ioe) {
// IgnoreIT(R)
}
setExtendendContentLength(vfile.length());
wasSet=true;
} else if(value instanceof File) {
vfile = new VirtualTempFile((File) value);
setExtendendContentLength(vfile.length());
wasSet=true;
} else if (value instanceof InputSource) {
inputSource = (InputSource) value;
wasSet=true;
} else if(value instanceof byte[]) {
vfile = new VirtualTempFile((byte[])value);
setExtendendContentLength(vfile.length());
wasSet=true;
} else if(value instanceof String) {
try {
vfile = new VirtualTempFile(((String)value).getBytes("UTF-8"));
setExtendendContentLength(vfile.length());
wasSet=true;
} catch(UnsupportedEncodingException uee) {
throw new XMLDBException(ErrorCodes.INVALID_RESOURCE,"input value cannot be translated to UTF-8",uee);
}
}
return wasSet;
}
protected void setExtendendContentLength(long len) {
this.contentLen = len;
}
public void setContentLength(int len) {
this.contentLen = len;
}
/* (non-Javadoc)
* @see org.exist.xmldb.EXistResource#setMimeType(java.lang.String)
*/
public void setMimeType(String mime) {
this.mimeType = mime;
}
public void setPermissions(Permission perms) {
permissions = perms;
}
public void getContentIntoAFile(File localfile)
throws XMLDBException
{
FileOutputStream fos=null;
BufferedOutputStream bos=null;
try {
fos=new FileOutputStream(localfile);
bos=new BufferedOutputStream(fos);
getContentIntoAStream(bos);
} catch (IOException ioe) {
throw new XMLDBException(ErrorCodes.VENDOR_ERROR, ioe.getMessage(), ioe);
} finally {
if(bos!=null) {
try {
bos.close();
} catch(IOException ioe) {
// IgnoreIT(R)
}
}
if(fos!=null) {
try {
fos.close();
} catch(IOException ioe) {
// IgnoreIT(R)
}
}
}
}
/**
* Fallback method to retrieve data from eXist versions < 1.5.
*
* @param handle
* @param pos
* @throws XMLDBException
*/
protected void getRemoteContentFallback(int handle, int pos) throws XMLDBException {
Properties properties = parent.getProperties();
List params = new ArrayList(1);
params.add(new Integer(handle));
params.add(new Integer(pos));
params.add(properties);
byte[] data = null;
try {
data = (byte[]) parent.getClient().execute("retrieve", params);
} catch (XmlRpcException xre) {
throw new XMLDBException(ErrorCodes.INVALID_RESOURCE, xre.getMessage(), xre);
}
if (properties.getProperty(EXistOutputKeys.COMPRESS_OUTPUT, "no").equals("yes")) {
try {
data = Compressor.uncompress(data);
} catch (IOException e) {
throw new XMLDBException(ErrorCodes.VENDOR_ERROR, e.getMessage(), e);
}
}
setContent(data);
}
protected void getRemoteContentIntoLocalFile(OutputStream os, boolean isRetrieve, int handle, int pos)
throws XMLDBException
{
Properties properties = getProperties();
String command = null;
List params = new ArrayList();
if(isRetrieve) {
command = "retrieveFirstChunk";
params.add(new Integer(handle));
params.add(new Integer(pos));
} else {
command = "getDocumentData";
params.add(path.toString());
}
if (properties == null)
properties = new Properties();
params.add(properties);
try {
VirtualTempFile vtmpfile = new VirtualTempFile();
vtmpfile.setTempPrefix("eXistARR");
vtmpfile.setTempPostfix(getResourceType().equals("XMLResource")?".xml":".bin");
Map table = (Map) parent.getClient().execute(command, params);
String method;
boolean useLongOffset;
if(table.containsKey("supports-long-offset") && (Boolean)(table.get("supports-long-offset"))) {
useLongOffset=true;
method="getNextExtendedChunk";
} else {
useLongOffset=false;
method="getNextChunk";
}
long offset = ((Integer)table.get("offset")).intValue();
byte[] data = (byte[])table.get("data");
boolean isCompressed=properties.getProperty(EXistOutputKeys.COMPRESS_OUTPUT, "no").equals("yes");
// One for the local cached file
Inflater dec = null;
byte[] decResult = null;
int decLength = 0;
if(isCompressed) {
dec = new Inflater();
decResult = new byte[65536];
dec.setInput(data);
do {
decLength = dec.inflate(decResult);
vtmpfile.write(decResult,0,decLength);
// And other for the stream where we want to save it!
if(os!=null)
os.write(decResult,0,decLength);
} while(decLength==decResult.length || !dec.needsInput());
} else {
vtmpfile.write(data);
// And other for the stream where we want to save it!
if(os!=null)
os.write(data);
}
while(offset > 0) {
params.clear();
params.add(table.get("handle"));
params.add(useLongOffset?Long.toString(offset):new Integer((int)offset));
table = (Map) parent.getClient().execute(method, params);
offset = useLongOffset?new Long((String)table.get("offset")).longValue():((Integer)table.get("offset")).longValue();
data = (byte[])table.get("data");
// One for the local cached file
if(isCompressed) {
dec.setInput(data);
do {
decLength = dec.inflate(decResult);
vtmpfile.write(decResult,0,decLength);
// And other for the stream where we want to save it!
if(os!=null)
os.write(decResult,0,decLength);
} while(decLength==decResult.length || !dec.needsInput());
} else {
vtmpfile.write(data);
// And other for the stream where we want to save it!
if(os!=null)
os.write(data);
}
}
if(dec!=null)
dec.end();
isLocal=false;
contentVFile=vtmpfile;
} catch (XmlRpcException xre) {
// backwards compatibility with eXist versions < 1.4
getRemoteContentFallback(handle, pos);
} catch (IOException ioe) {
throw new XMLDBException(ErrorCodes.VENDOR_ERROR, ioe.getMessage(), ioe);
} catch (DataFormatException dfe) {
throw new XMLDBException(ErrorCodes.VENDOR_ERROR, dfe.getMessage(), dfe);
} finally {
if(contentVFile!=null) {
try {
contentVFile.close();
} catch(IOException ioe) {
//IgnoreIT(R)
}
}
}
}
protected static InputStream getAnyStream(Object obj)
throws XMLDBException
{
InputStream bis=null;
if(obj instanceof String) {
try {
bis=new ByteArrayInputStream(((String)obj).getBytes("UTF-8"));
} catch(UnsupportedEncodingException uee) {
throw new XMLDBException(ErrorCodes.VENDOR_ERROR,uee.getMessage(),uee);
}
} else if(obj instanceof byte[]) {
bis=new ByteArrayInputStream((byte[])obj);
} else {
throw new XMLDBException(ErrorCodes.VENDOR_ERROR,"don't know how to handle value of type " + obj.getClass().getName());
}
return bis;
}
protected void getContentIntoAStreamInternal(OutputStream os, Object obj, boolean isRetrieve, int handle, int pos)
throws XMLDBException
{
if(vfile!=null || contentVFile!=null || inputSource!=null || obj!=null) {
InputStream bis=null;
try {
// First, the local content, then the remote one!!!!
if(vfile!=null) {
bis=vfile.getByteStream();
} else if(inputSource!=null) {
bis=inputSource.getByteStream();
} else if(obj!=null) {
bis=getAnyStream(obj);
} else {
bis=contentVFile.getByteStream();
}
int readed;
byte buffer[]=new byte[65536];
while((readed=bis.read(buffer))!=-1) {
os.write(buffer,0,readed);
}
} catch(IOException ioe) {
throw new XMLDBException(ErrorCodes.VENDOR_ERROR,ioe.getMessage(),ioe);
} finally {
if(inputSource!=null) {
if(bis!=null) {
// As it comes from an input source, we cannot blindly close it,
// but at least let's reset it! (if it is possible)
if(bis.markSupported()) {
try {
bis.reset();
} catch(IOException ioe) {
//IgnoreIT(R)
}
}
}
} else {
if(bis!=null) {
try {
bis.close();
} catch(IOException ioe) {
//IgnoreIT(R)
}
}
}
}
} else {
// Let's fetch it, and save just in time!!!
getRemoteContentIntoLocalFile(os,isRetrieve,handle,pos);
}
}
protected Object getExtendedContentInternal(Object obj, boolean isRetrieve, int handle, int pos)
throws XMLDBException
{
if(obj != null)
return obj;
if(vfile!=null)
return vfile.getContent();
if(inputSource!=null)
return inputSource;
if(contentVFile==null)
getRemoteContentIntoLocalFile(null,isRetrieve,handle,pos);
// vfile may have been set by getRemoteContentIntoLocalFile
if(vfile != null)
return vfile.getContent();
return contentVFile.getContent();
}
protected InputStream getStreamContentInternal(Object obj, boolean isRetrieve, int handle, int pos)
throws XMLDBException
{
InputStream retval=null;
if(vfile!=null) {
try {
retval=vfile.getByteStream();
} catch(IOException fnfe) {
throw new XMLDBException(ErrorCodes.VENDOR_ERROR, fnfe.getMessage(), fnfe);
}
} else if(inputSource!=null) {
retval=inputSource.getByteStream();
} else if(obj!=null) {
retval=getAnyStream(obj);
} else {
// At least one value, please!!!
if(contentVFile==null)
getRemoteContentIntoLocalFile(null,isRetrieve,handle,pos);
try {
retval=contentVFile.getByteStream();
} catch(IOException fnfe) {
throw new XMLDBException(ErrorCodes.VENDOR_ERROR, fnfe.getMessage(), fnfe);
}
}
return retval;
}
protected long getStreamLengthInternal(Object obj)
throws XMLDBException
{
long retval=-1;
if(vfile!=null) {
retval=vfile.length();
} else if(inputSource!=null && inputSource instanceof EXistInputSource) {
retval=((EXistInputSource)inputSource).getByteStreamLength();
} else if(obj!=null) {
if(obj instanceof String) {
try {
retval=((String)obj).getBytes("UTF-8").length;
} catch(UnsupportedEncodingException uee) {
throw new XMLDBException(ErrorCodes.VENDOR_ERROR,uee.getMessage(),uee);
}
} else if(obj instanceof byte[]) {
retval=((byte[])obj).length;
} else {
throw new XMLDBException(ErrorCodes.VENDOR_ERROR,"don't know how to handle value of type " + obj.getClass().getName());
}
} else if(contentVFile!=null) {
retval=contentVFile.length();
} else {
Properties properties = getProperties();
List params = new ArrayList();
params.add(path.toString());
params.add(properties);
try {
Map table = (Map) parent.getClient().execute("describeResource", params);
retval=((Integer)table.get("content-length")).intValue();
} catch (XmlRpcException xre) {
throw new XMLDBException(ErrorCodes.INVALID_RESOURCE, xre.getMessage(), xre);
}
}
return retval;
}
private static byte[] readFile(File file)
throws XMLDBException
{
String errmsg="file "+ file.getAbsolutePath();
try {
return readFile(new FileInputStream(file),errmsg);
} catch (FileNotFoundException e) {
throw new XMLDBException(ErrorCodes.VENDOR_ERROR,
errmsg + " could not be found", e);
}
}
private static byte[] readFile(InputSource is)
throws XMLDBException
{
String retval="<streamunknown>";
if(is instanceof EXistInputSource) {
retval=((EXistInputSource)is).getSymbolicPath();
}
return readFile(is.getByteStream(),"input source "+retval);
}
private static byte[] readFile(InputStream is,String errmsg)
throws XMLDBException
{
if(errmsg==null)
errmsg="stream";
try {
ByteArrayOutputStream bos = new ByteArrayOutputStream(2048);
byte[] temp = new byte[1024];
int count = 0;
while((count = is.read(temp)) > -1) {
bos.write(temp, 0, count);
}
return bos.toByteArray();
} catch (IOException e) {
throw new XMLDBException(ErrorCodes.VENDOR_ERROR,
"IO exception while reading " + errmsg, e);
}
}
protected void setDateCreated(Date dateCreated) {
this.dateCreated = dateCreated;
}
protected void setDateModified(Date dateModified) {
this.dateModified = dateModified;
}
}