/*
* Copyright (C) 2009 eXo Platform SAS.
*
* This 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.1 of
* the License, or (at your option) any later version.
*
* This software 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 software; if not, write to the Free
* Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
* 02110-1301 USA, or see the FSF site: http://www.fsf.org.
*/
package org.exoplatform.services.jcr.impl.dataflow.persistent;
import org.exoplatform.commons.utils.PrivilegedFileHelper;
import org.exoplatform.commons.utils.SecurityHelper;
import org.exoplatform.services.jcr.datamodel.ValueData;
import org.exoplatform.services.jcr.impl.dataflow.SpoolConfig;
import org.exoplatform.services.jcr.impl.util.io.SpoolFile;
import org.exoplatform.services.jcr.impl.util.io.SwapFile;
import org.exoplatform.services.jcr.storage.value.ValueStorageURLStreamHandler;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.ObjectInput;
import java.io.ObjectOutput;
import java.io.OutputStream;
import java.net.URL;
import java.net.URLConnection;
import java.security.PrivilegedExceptionAction;
import java.util.concurrent.atomic.AtomicLong;
/**
* Created by The eXo Platform SAS.
*
* @author <a href="mailto:peter.nedonosko@exoplatform.com">Peter Nedonosko</a>
* @version $Id$
*/
public class StreamPersistedValueData extends FilePersistedValueData
{
private static final AtomicLong SEQUENCE = new AtomicLong();
/**
* Original stream.
*/
protected InputStream stream;
/**
* Reserved file to spool.
*/
protected SpoolFile tempFile;
/**
* The URL to the resource
*/
protected URL url;
/**
* Indicates whether or not the content should always be spooled
*/
protected boolean spoolContent;
/**
* StreamPersistedValueData constructor for stream data.
*/
public StreamPersistedValueData(int orderNumber, InputStream stream, SpoolConfig spoolConfig) throws IOException
{
this(orderNumber, stream, null, spoolConfig);
}
/**
* StreamPersistedValueData constructor for data spooled to temp file.
*/
public StreamPersistedValueData(int orderNumber, SpoolFile tempFile, SpoolConfig spoolConfig) throws IOException
{
this(orderNumber, tempFile, null, spoolConfig);
}
/**
* StreamPersistedValueData constructor for stream data with know destination file.
* <br>
* Destination file reserved for use in internal impl.
*/
public StreamPersistedValueData(int orderNumber, InputStream stream, File destFile, SpoolConfig spoolConfig)
throws IOException
{
super(orderNumber, destFile, spoolConfig);
this.tempFile = null;
this.stream = stream;
}
/**
* StreamPersistedValueData constructor for data spooled to temp file with know destination file.
* <br>
* Destination file reserved for use in internal impl.
*
* @param orderNumber int
* @param tempFile File
*/
public StreamPersistedValueData(int orderNumber, SpoolFile tempFile, File destFile, SpoolConfig spoolConfig)
throws IOException
{
super(orderNumber, destFile, spoolConfig);
this.tempFile = tempFile;
this.stream = null;
if (tempFile != null)
{
tempFile.acquire(this);
}
}
/**
* StreamPersistedValueData constructor.
*/
public StreamPersistedValueData(int orderNumber, URL url, SpoolFile tempFile, SpoolConfig spoolConfig) throws IOException
{
this(orderNumber, tempFile, null, spoolConfig);
this.url = url;
}
/**
* StreamPersistedValueData empty constructor for serialization.
*/
public StreamPersistedValueData() throws IOException
{
super();
}
/**
* Return original data stream or null. <br>
* For persistent transformation from non-spooled TransientValueData to persistent layer.<br>
* WARN: after the stream will be consumed it will not contains data anymore.
*
* @return InputStream data stream or null
* @throws IOException if error occurs
*/
public InputStream getStream() throws IOException
{
return stream;
}
/**
* Return temp file or null. For transport from spooled TransientValueData to persistent layer. <br>
* WARN: after the save the temp file will be removed. So, temp file actual only during the save from transient state.
*
* @return File temporary file or null
*/
public SpoolFile getTempFile()
{
return tempFile;
}
/**
* Sets persistent file. Will reset (null) temp file and stream. This method should be called only from
* persistent layer (Value storage).
*
* @param file File
* @throws FileNotFoundException
*/
public void setPersistedFile(File file) throws FileNotFoundException
{
if (file instanceof SwapFile)
{
((SwapFile)file).acquire(this);
}
this.file = file;
// JCR-2326 Release the current ValueData from tempFile users before
// setting its reference to null so it will be garbage collected.
if (this.tempFile != null)
{
this.tempFile.release(this);
this.tempFile = null;
}
this.stream = null;
}
/**
* Sets persistent URL. Will reset (null) the stream and will spool the content of the stream if <code>spoolContent</code>
* has been set to <code>true</code>.
* This method should be called only from persistent layer (Value storage).
*
* @param url the url to which the data has been persisted
* @param spoolContent Indicates whether or not the content should always be spooled
*/
public InputStream setPersistedURL(URL url, boolean spoolContent) throws IOException
{
InputStream result = null;
this.url = url;
this.spoolContent = spoolContent;
// JCR-2326 Release the current ValueData from tempFile users before
// setting its reference to null so it will be garbage collected.
if (!spoolContent && this.tempFile != null)
{
this.tempFile.release(this);
this.tempFile = null;
}
else if (spoolContent && tempFile == null && stream != null)
{
spoolContent(stream);
result = new FileInputStream(tempFile);
}
this.stream = null;
return result;
}
/**
* Return status of persisted state.
*
* @return boolean, true if the ValueData was persisted to a storage, false otherwise.
*/
public boolean isPersisted()
{
return file != null || url != null;
}
/**
* {@inheritDoc}
*/
@Override
public long getLength()
{
if (file != null)
{
return PrivilegedFileHelper.length(file);
}
else if (tempFile != null)
{
return PrivilegedFileHelper.length(tempFile);
}
else if (stream instanceof FileInputStream)
{
try
{
return ((FileInputStream)stream).getChannel().size();
}
catch (IOException e)
{
return -1;
}
}
else if (url != null)
{
try
{
URLConnection connection = url.openConnection();
return connection.getContentLength();
}
catch (IOException e)
{
return -1;
}
}
else
{
try
{
return stream.available();
}
catch (IOException e)
{
return -1;
}
}
}
/**
* {@inheritDoc}
*/
@Override
protected void finalize() throws Throwable
{
try
{
if (file instanceof SwapFile)
{
((SwapFile)file).release(this);
}
if (tempFile != null)
{
tempFile.release(this);
}
}
finally
{
super.finalize();
}
}
/**
* {@inheritDoc}
*/
@Override
protected boolean internalEquals(ValueData another)
{
if (another instanceof StreamPersistedValueData)
{
StreamPersistedValueData streamValue = (StreamPersistedValueData)another;
if (file != null && file.equals(streamValue.file))
{
return true;
}
else if (tempFile != null && tempFile.equals(streamValue.tempFile))
{
return true;
}
else if (stream != null && stream == streamValue.stream)
{
return true;
}
else if (url != null && streamValue.url != null && url.getFile().equals(streamValue.url.getFile()))
{
return true;
}
}
return false;
}
/**
* {@inheritDoc}
*/
@Override
public InputStream getAsStream() throws IOException
{
if (url != null)
{
if (tempFile != null)
{
return PrivilegedFileHelper.fileInputStream(tempFile);
}
else if (spoolContent)
{
spoolContent();
return PrivilegedFileHelper.fileInputStream(tempFile);
}
return url.openStream();
}
return super.getAsStream();
}
/**
* Spools the content extracted from the URL
*/
private void spoolContent() throws IOException, FileNotFoundException
{
spoolContent(url.openStream());
}
/**
* Spools the content extracted from the URL
*/
private void spoolContent(InputStream is) throws IOException, FileNotFoundException
{
SwapFile swapFile =
SwapFile.get(spoolConfig.tempDirectory, System.currentTimeMillis() + "_" + SEQUENCE.incrementAndGet(),
spoolConfig.fileCleaner);
try
{
OutputStream os = PrivilegedFileHelper.fileOutputStream(swapFile);
try
{
byte[] bytes = new byte[1024];
int length;
while ((length = is.read(bytes)) != -1)
{
os.write(bytes, 0, length);
}
}
finally
{
os.close();
}
}
finally
{
swapFile.spoolDone();
is.close();
}
tempFile = swapFile;
}
/**
* {@inheritDoc}
*/
@Override
public byte[] getAsByteArray() throws IllegalStateException, IOException
{
if (url != null)
{
if (tempFile != null)
{
return fileToByteArray(tempFile);
}
else if (spoolContent)
{
spoolContent();
return fileToByteArray(tempFile);
}
ByteArrayOutputStream baos = new ByteArrayOutputStream();
InputStream is = url.openStream();
try
{
byte[] bytes = new byte[1024];
int length;
while ((length = is.read(bytes)) != -1)
{
baos.write(bytes, 0, length);
}
return baos.toByteArray();
}
finally
{
is.close();
}
}
return super.getAsByteArray();
}
/**
* {@inheritDoc}
*/
@Override
public long read(OutputStream stream, long length, long position) throws IOException
{
if (url != null)
{
if (tempFile != null)
{
return readFromFile(stream, tempFile, length, position);
}
else if (spoolContent)
{
spoolContent();
return readFromFile(stream, tempFile, length, position);
}
InputStream is = url.openStream();
try
{
is.skip(position);
byte[] bytes = new byte[(int)length];
int lg = is.read(bytes);
if (lg > 0)
stream.write(bytes, 0, lg);
return lg;
}
finally
{
is.close();
stream.close();
}
}
return super.read(stream, length, position);
}
/**
* {@inheritDoc}
*/
public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException
{
orderNumber = in.readInt();
// read canonical file path
int size = in.readInt();
if (size > 0)
{
byte[] buf = new byte[size];
in.readFully(buf);
final String path = new String(buf, "UTF-8");
File f = new File(path);
// validate if exists
if (PrivilegedFileHelper.exists(f))
{
file = f;
}
else if (path.startsWith(ValueStorageURLStreamHandler.PROTOCOL + ":/"))
{
url = SecurityHelper.doPrivilegedMalformedURLExceptionAction(new PrivilegedExceptionAction<URL>()
{
public URL run() throws Exception
{
return new URL(null, path, ValueStorageURLStreamHandler.INSTANCE);
}
});
}
else
{
file = null;
url = null;
}
}
else
{
// should not occurs but since we have a way to recover, it should not be
// an issue
file = null;
url = null;
}
}
/**
* {@inheritDoc}
*/
public void writeExternal(ObjectOutput out) throws IOException
{
if (url == null)
{
super.writeExternal(out);
return;
}
out.writeInt(orderNumber);
// write the path
byte[] buf = url.toString().getBytes("UTF-8");
out.writeInt(buf.length);
out.write(buf);
}
/**
* {@inheritDoc}
*/
public PersistedValueData createPersistedCopy(int orderNumber) throws IOException
{
if (url != null)
return new StreamPersistedValueData(orderNumber, url, tempFile, spoolConfig);
return super.createPersistedCopy(orderNumber);
}
/**
* @return the url
*/
public URL getUrl()
{
return url;
}
}