/*
* Copyright (C) 2005-2011 Alfresco Software Limited.
*
* This file is part of Alfresco
*
* Alfresco 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 3 of the License, or
* (at your option) any later version.
*
* Alfresco 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 Alfresco. If not, see
* Only the instance need be constructed. The required mimetype, encoding, etc * will be copied across by this class. * * @return Returns a reader onto the location referenced by this instance. * The instance must always be a new instance. * @throws ContentIOException */ protected abstract ContentReader createReader() throws ContentIOException; /** * Performs checks and copies required reader attributes */ public final ContentReader getReader() throws ContentIOException { ContentReader reader = createReader(); if (reader == null) { throw new AlfrescoRuntimeException("ContentReader failed to create new reader: \n" + " reader: " + this); } else if (reader.getContentUrl() == null || !reader.getContentUrl().equals(getContentUrl())) { throw new AlfrescoRuntimeException("ContentReader has different URL: \n" + " reader: " + this + "\n" + " new reader: " + reader); } // copy across common attributes reader.setMimetype(this.getMimetype()); reader.setEncoding(this.getEncoding()); reader.setLocale(this.getLocale()); // done if (logger.isDebugEnabled()) { logger.debug("Reader spawned new reader: \n" + " reader: " + this + "\n" + " new reader: " + reader); } return reader; } /** * An automatically created listener sets the flag */ public synchronized final boolean isClosed() { if (channel != null) { return !channel.isOpen(); } else { return false; } } public synchronized boolean isChannelOpen() { if (channel != null) { return channel.isOpen(); } else { return false; } } /** * Provides low-level access to read content from the repository. *
* This is the only of the content reading methods that needs to be implemented
* by derived classes. All other content access methods make use of this in their
* underlying implementations.
*
* @return Returns a channel from which content can be read
* @throws ContentIOException if the channel could not be opened or the underlying content
* has disappeared
*/
protected abstract ReadableByteChannel getDirectReadableChannel() throws ContentIOException;
/**
* Create a channel that performs callbacks to the given listeners.
*
* @param directChannel the result of {@link #getDirectReadableChannel()}
* @param listeners the listeners to call
* @return Returns a channel
* @throws ContentIOException
*/
private ReadableByteChannel getCallbackReadableChannel(
ReadableByteChannel directChannel,
List
* All the content is streamed into memory. So, like the interface said,
* be careful with this method.
*
* @see ContentAccessor#getEncoding()
*/
public final String getContentString() throws ContentIOException
{
try
{
// read from the stream into a byte[]
InputStream is = getContentInputStream();
ByteArrayOutputStream os = new ByteArrayOutputStream();
FileCopyUtils.copy(is, os); // both streams are closed
byte[] bytes = os.toByteArray();
// get the encoding for the string
String encoding = getEncoding();
// create the string from the byte[] using encoding if necessary
String content = (encoding == null) ? new String(bytes) : new String(bytes, encoding);
// done
return content;
}
catch (IOException e)
{
throw new ContentIOException("Failed to copy content to string: \n" +
" accessor: " + this,
e);
}
}
/**
* Does a comparison of the binaries associated with two readers. Several shortcuts are assumed to be valid:OutputStream
*/
public final void getContent(OutputStream os) throws ContentIOException
{
try
{
InputStream is = getContentInputStream();
FileCopyUtils.copy(is, os); // both streams are closed
// done
}
catch (IOException e)
{
throw new ContentIOException("Failed to copy content to output stream: \n" +
" accessor: " + this,
e);
}
}
public final void getContent(File file) throws ContentIOException
{
try
{
InputStream is = getContentInputStream();
FileOutputStream os = new FileOutputStream(file);
FileCopyUtils.copy(is, os); // both streams are closed
// done
}
catch (IOException e)
{
throw new ContentIOException("Failed to copy content to file: \n" +
" accessor: " + this + "\n" +
" file: " + file,
e);
}
}
public final String getContentString(int length) throws ContentIOException
{
if (length < 0 || length > Integer.MAX_VALUE)
{
throw new IllegalArgumentException("Character count must be positive and within range");
}
Reader reader = null;
try
{
// just create buffer of the required size
char[] buffer = new char[length];
String encoding = getEncoding();
// create a reader from the input stream
if (encoding == null)
{
reader = new InputStreamReader(getContentInputStream());
}
else
{
reader = new InputStreamReader(getContentInputStream(), encoding);
}
// read it all, if possible
int count = reader.read(buffer, 0, length);
// there may have been fewer characters - create a new string as the result
return (count != -1 ? new String(buffer, 0, count) : "");
}
catch (IOException e)
{
throw new ContentIOException("Failed to copy content to string: \n" +
" accessor: " + this + "\n" +
" length: " + length,
e);
}
finally
{
if (reader != null)
{
try { reader.close(); } catch (Throwable e) { logger.error(e); }
}
}
}
/**
* Makes use of the encoding, if available, to convert bytes to a string.
*
* - if the readers are the same instance, then the binaries are the same
* - if the size field is different, then the binaries are different
* Otherwise the binaries are {@link EqualsHelper#binaryStreamEquals(InputStream, InputStream) compared}.
*
* @return Returns true if the underlying binaries are the same
* @throws ContentIOException
*/
public static boolean compareContentReaders(ContentReader left, ContentReader right) throws ContentIOException
{
if (left == right)
{
return true;
}
else if (left == null || right == null)
{
return false;
}
else if (left.getSize() != right.getSize())
{
return false;
}
InputStream leftIs = left.getContentInputStream();
InputStream rightIs = right.getContentInputStream();
try
{
return EqualsHelper.binaryStreamEquals(leftIs, rightIs);
}
catch (IOException e)
{
throw new ContentIOException(
"Failed to compare content reader streams: \n" +
" Left: " + left + "\n" +
" right: " + right);
}
}
/**
* InputStream that wraps another InputStream to terminate early after a timeout
* or after reading a number of bytes. It terminates by either returning end of file
* (-1) or throwing an IOException.
* @author Alan Davis
*/
private class TimeSizeRestrictedInputStream extends InputStream
{
private final AtomicBoolean timeoutFlag = new AtomicBoolean(false);
private final InputStream is;
private final long timeoutMs;
private final long readLimitBytes;
private final Action timeoutAction;
private final Action readLimitAction;
private final TransformerDebug transformerDebug;
private long readCount = 0;
public TimeSizeRestrictedInputStream(InputStream is,
long timeoutMs, Action timeoutAction,
long readLimitBytes, Action readLimitAction,
TransformerDebug transformerDebug)
{
this.is = useBufferedInputStream ? new BufferedInputStream(is) : is;
this.timeoutMs = timeoutMs;
this.timeoutAction = timeoutAction;
this.readLimitBytes = readLimitBytes;
this.readLimitAction = readLimitAction;
this.transformerDebug = transformerDebug;
if (timeoutMs > 0)
{
timer.schedule(new TimerTask()
{
@Override
public void run()
{
timeoutFlag.set(true);
}
}, timeoutMs);
}
}
@Override
public int read() throws IOException
{
// Throws exception or return true to indicate EOF
if (hitTimeout() || hitReadLimit())
{
return -1;
}
int n = is.read();
if (n > 0)
{
readCount++;
}
return n;
}
private boolean hitTimeout() throws IOException
{
if (timeoutMs > 0 && timeoutFlag.get())
{
timeoutAction.throwIOExceptionIfRequired(
"Transformation has taken too long ("+
(timeoutMs/1000)+" seconds)", transformerDebug);
return true;
}
return false;
}
private boolean hitReadLimit() throws IOException
{
if (readLimitBytes > 0 && readCount >= readLimitBytes)
{
readLimitAction.throwIOExceptionIfRequired(
"Transformation has read too many bytes ("+
(readLimitBytes/1024)+"K)", transformerDebug);
return true;
}
return false;
}
@Override
public void close() throws IOException
{
try
{
is.close();
}
finally
{
super.close();
}
}
};
}