/*
* Copyright (C) 2012 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;
import org.exoplatform.commons.utils.PrivilegedFileHelper;
import org.exoplatform.services.jcr.access.AccessControlEntry;
import org.exoplatform.services.jcr.core.ExtendedPropertyType;
import org.exoplatform.services.jcr.datamodel.Identifier;
import org.exoplatform.services.jcr.datamodel.IllegalNameException;
import org.exoplatform.services.jcr.datamodel.IllegalPathException;
import org.exoplatform.services.jcr.datamodel.InternalQName;
import org.exoplatform.services.jcr.datamodel.QPath;
import org.exoplatform.services.jcr.datamodel.ValueData;
import org.exoplatform.services.jcr.impl.Constants;
import org.exoplatform.services.jcr.impl.dataflow.persistent.BooleanPersistedValueData;
import org.exoplatform.services.jcr.impl.dataflow.persistent.ByteArrayPersistedValueData;
import org.exoplatform.services.jcr.impl.dataflow.persistent.CalendarPersistedValueData;
import org.exoplatform.services.jcr.impl.dataflow.persistent.CleanableFilePersistedValueData;
import org.exoplatform.services.jcr.impl.dataflow.persistent.DoublePersistedValueData;
import org.exoplatform.services.jcr.impl.dataflow.persistent.FilePersistedValueData;
import org.exoplatform.services.jcr.impl.dataflow.persistent.LongPersistedValueData;
import org.exoplatform.services.jcr.impl.dataflow.persistent.PersistedValueData;
import org.exoplatform.services.jcr.impl.dataflow.persistent.StringPersistedValueData;
import org.exoplatform.services.jcr.impl.storage.value.fs.operations.ValueFileIOHelper;
import org.exoplatform.services.jcr.impl.util.JCRDateFormat;
import org.exoplatform.services.jcr.impl.util.io.SwapFile;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.UnsupportedEncodingException;
import java.util.Calendar;
import javax.jcr.PropertyType;
import javax.jcr.RepositoryException;
import javax.jcr.ValueFormatException;
/**
* Utility class for managing value data.
*
* @author <a href="abazko@exoplatform.com">Anatoliy Bazko</a>
* @version $Id: PersistedValueDataFactory.java 34360 2009-07-22 23:58:59Z tolusha $
*/
public class ValueDataUtil
{
/**
* Read value data from stream.
*
* @param cid
* property identifier
* @param type
* property type, {@link PropertyType}
* @param orderNumber
* value data order number
* @param version
* property persisted version
* @param content
* value data represented in stream
* @param spoolConfig
* contains threshold for spooling
* @return PersistedValueData
* @throws IOException
* if any error is occurred
*/
public static ValueDataWrapper readValueData(String cid, int type, int orderNumber, int version,
final InputStream content, SpoolConfig spoolConfig) throws IOException
{
ValueDataWrapper vdDataWrapper = new ValueDataWrapper();
byte[] buffer = new byte[0];
byte[] spoolBuffer = new byte[ValueFileIOHelper.IOBUFFER_SIZE];
int read;
int len = 0;
OutputStream out = null;
SwapFile swapFile = null;
try
{
// stream from database
if (content != null)
{
while ((read = content.read(spoolBuffer)) >= 0)
{
if (out != null)
{
// spool to temp file
out.write(spoolBuffer, 0, read);
}
else if (len + read > spoolConfig.maxBufferSize)
{
// threshold for keeping data in memory exceeded;
// create temp file and spool buffer contents
swapFile = SwapFile.get(spoolConfig.tempDirectory, cid + orderNumber + "." + version,spoolConfig.fileCleaner);
if (swapFile.isSpooled())
{
// break, value already spooled
buffer = null;
break;
}
out = PrivilegedFileHelper.fileOutputStream(swapFile);
out.write(buffer, 0, len);
out.write(spoolBuffer, 0, read);
buffer = null;
}
else
{
// reallocate new buffer and spool old buffer contents
byte[] newBuffer = new byte[len + read];
System.arraycopy(buffer, 0, newBuffer, 0, len);
System.arraycopy(spoolBuffer, 0, newBuffer, len, read);
buffer = newBuffer;
}
len += read;
}
}
}
finally
{
if (out != null)
{
out.close();
swapFile.spoolDone();
}
}
vdDataWrapper.size = len;
if (swapFile != null)
{
vdDataWrapper.value = new CleanableFilePersistedValueData(orderNumber, swapFile, spoolConfig);
}
else
{
vdDataWrapper.value = createValueData(type, orderNumber, buffer);
}
return vdDataWrapper;
}
/**
* Read value data from file.
*
* @param type
* property type, {@link PropertyType}
* @param file
* File
* @param orderNumber
* value data order number
* @param spoolConfig
* contains threshold for spooling
* @return PersistedValueData
* @throws IOException
* if any error is occurred
*/
public static ValueDataWrapper readValueData(int type, int orderNumber, File file, SpoolConfig spoolConfig)
throws IOException
{
ValueDataWrapper vdDataWrapper = new ValueDataWrapper();
long fileSize = file.length();
vdDataWrapper.size = fileSize;
if (fileSize > spoolConfig.maxBufferSize)
{
vdDataWrapper.value = new FilePersistedValueData(orderNumber, file, spoolConfig);
}
else
{
FileInputStream is = new FileInputStream(file);
try
{
byte[] data = new byte[(int)fileSize];
byte[] buff =
new byte[ValueFileIOHelper.IOBUFFER_SIZE > fileSize ? ValueFileIOHelper.IOBUFFER_SIZE : (int)fileSize];
int rpos = 0;
int read;
while ((read = is.read(buff)) >= 0)
{
System.arraycopy(buff, 0, data, rpos, read);
rpos += read;
}
vdDataWrapper.value = createValueData(type, orderNumber, data);
}
finally
{
is.close();
}
}
return vdDataWrapper;
}
/**
* Creates value data depending on its type. It avoids storing unnecessary bytes in memory
* every time.
*
* @param type
* property data type, can be either {@link PropertyType} or {@link ExtendedPropertyType}
* @param orderNumber
* value data order number
* @param data
* value data represented in array of bytes
*/
public static PersistedValueData createValueData(int type, int orderNumber, byte[] data) throws IOException
{
switch (type)
{
case PropertyType.BINARY :
case PropertyType.UNDEFINED :
return new ByteArrayPersistedValueData(orderNumber, data);
case PropertyType.BOOLEAN :
return new BooleanPersistedValueData(orderNumber, Boolean.valueOf(getString(data)));
case PropertyType.DATE :
try
{
return new CalendarPersistedValueData(orderNumber, JCRDateFormat.parse(getString(data)));
}
catch (ValueFormatException e)
{
throw new IOException("Can't create Calendar value", e);
}
case PropertyType.DOUBLE :
return new DoublePersistedValueData(orderNumber, Double.valueOf(getString(data)));
case PropertyType.LONG :
return new LongPersistedValueData(orderNumber, Long.valueOf(getString(data)));
case PropertyType.NAME :
try
{
return new NamePersistedValueData(orderNumber, InternalQName.parse(getString(data)));
}
catch (IllegalNameException e)
{
throw new IOException(e.getMessage(), e);
}
case PropertyType.PATH :
try
{
return new PathPersistedValueData(orderNumber, QPath.parse(getString(data)));
}
catch (IllegalPathException e)
{
throw new IOException(e.getMessage(), e);
}
case PropertyType.REFERENCE :
return new ReferencePersistedValueData(orderNumber, new Identifier(data));
case PropertyType.STRING :
return new StringPersistedValueData(orderNumber, getString(data));
case ExtendedPropertyType.PERMISSION :
return new PermissionPersistedValueData(orderNumber, AccessControlEntry.parse(getString(data)));
default :
throw new IllegalStateException("Unknown property type " + type);
}
}
/**
* {@link AbstractValueData#createTransientCopy(int)}
*/
public static TransientValueData createTransientCopy(ValueData valueData) throws IOException
{
return createTransientCopy(valueData, 0);
}
/**
* {@link AbstractValueData#createTransientCopy(int)}
*/
public static TransientValueData createTransientCopy(ValueData valueData, int orderNumber) throws IOException
{
if (valueData instanceof TransientValueData)
{
return createTransientCopy(((TransientValueData)valueData).delegate, orderNumber);
}
else if (valueData instanceof AbstractValueData)
{
return ((AbstractValueData)valueData).createTransientCopy(orderNumber);
}
else
{
return new TransientValueData(orderNumber, valueData.getAsByteArray());
}
}
/**
* Returns <code>Long</code> value.
*/
public static Long getLong(ValueData valueData) throws RepositoryException
{
if (valueData instanceof TransientValueData)
{
return getLong(((TransientValueData)valueData).delegate);
}
return ((AbstractValueData) valueData).getLong();
}
/**
* Returns <code>Double</code> value.
*/
public static Double getDouble(ValueData valueData) throws RepositoryException
{
if (valueData instanceof TransientValueData)
{
return getDouble(((TransientValueData)valueData).delegate);
}
return ((AbstractValueData)valueData).getDouble();
}
/**
* Returns <code>Date</code> value.
*/
public static Calendar getDate(ValueData valueData) throws RepositoryException
{
if (valueData instanceof TransientValueData)
{
return getDate(((TransientValueData)valueData).delegate);
}
return ((AbstractValueData)valueData).getDate();
}
/**
* Returns <code>Boolean</code> value.
*/
public static Boolean getBoolean(ValueData valueData) throws RepositoryException
{
if (valueData instanceof TransientValueData)
{
return getBoolean(((TransientValueData)valueData).delegate);
}
return ((AbstractValueData)valueData).getBoolean();
}
/**
* Returns <code>String</code> value.
*/
public static String getString(ValueData valueData) throws RepositoryException
{
if (valueData instanceof TransientValueData)
{
return getString(((TransientValueData)valueData).delegate);
}
return ((AbstractValueData)valueData).getString();
}
/**
* Returns <code>String</code> value.
*/
public static InternalQName getName(ValueData valueData) throws RepositoryException
{
if (valueData instanceof TransientValueData)
{
return getName(((TransientValueData)valueData).delegate);
}
try
{
return ((AbstractValueData)valueData).getName();
}
catch (IllegalNameException e)
{
throw new RepositoryException(e.getMessage(), e);
}
}
/**
* Returns <code>String</code> value.
*/
public static QPath getPath(ValueData valueData) throws RepositoryException
{
if (valueData instanceof TransientValueData)
{
return getPath(((TransientValueData)valueData).delegate);
}
return ((AbstractValueData)valueData).getPath();
}
/**
* Returns <code>Reference</code> value.
*/
public static String getReference(ValueData valueData) throws RepositoryException
{
if (valueData instanceof TransientValueData)
{
return getReference(((TransientValueData)valueData).delegate);
}
return ((AbstractValueData)valueData).getReference();
}
/**
* Returns <code>AccessControlEntry</code> value.
*/
public static AccessControlEntry getPermission(ValueData valueData) throws RepositoryException
{
if (valueData instanceof TransientValueData)
{
return getPermission(((TransientValueData)valueData).delegate);
}
return ((AbstractValueData)valueData).getPermission();
}
/**
* Returns String data represented in array of bytes.
*/
private static String getString(byte[] data) throws UnsupportedEncodingException
{
return new String(data, Constants.DEFAULT_ENCODING);
}
/**
* Simply wraps {@link ValueData} and its size at storage.
*/
public static class ValueDataWrapper
{
public long size;
public PersistedValueData value;
private ValueDataWrapper()
{
}
}
}