Package org.exoplatform.services.jcr.impl.dataflow

Source Code of org.exoplatform.services.jcr.impl.dataflow.TransientValueData

/*
* 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;

import org.exoplatform.services.jcr.access.AccessControlEntry;
import org.exoplatform.services.jcr.datamodel.Identifier;
import org.exoplatform.services.jcr.datamodel.InternalQName;
import org.exoplatform.services.jcr.datamodel.QPath;
import org.exoplatform.services.jcr.impl.Constants;
import org.exoplatform.services.jcr.impl.util.JCRDateFormat;
import org.exoplatform.services.jcr.impl.util.io.FileCleaner;
import org.exoplatform.services.jcr.impl.util.io.SpoolFile;

import java.io.ByteArrayInputStream;
import java.io.Externalizable;
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.ObjectInput;
import java.io.ObjectOutput;
import java.io.OutputStream;
import java.io.UnsupportedEncodingException;
import java.nio.ByteBuffer;
import java.nio.MappedByteBuffer;
import java.nio.channels.Channels;
import java.nio.channels.FileChannel;
import java.nio.channels.WritableByteChannel;
import java.util.Arrays;
import java.util.Calendar;

import javax.jcr.RepositoryException;

/**
* Created by The eXo Platform SAS.<br/>
*
* @author Gennady Azarenkov
* @version $Id: TransientValueData.java 35209 2009-08-07 15:32:27Z pnedonosko $
*/
public class TransientValueData extends AbstractValueData implements Externalizable
{

   private static final long serialVersionUID = -5280857006905550884L;

   protected byte[] data;

   protected InputStream tmpStream;

   protected File spoolFile;

   protected final boolean closeTmpStream;

   /**
    * User for read(...) method
    */
   protected FileChannel spoolChannel;

   protected FileCleaner fileCleaner;

   protected int maxBufferSize;

   protected File tempDirectory;

   protected boolean spooled = false;

   protected boolean isTransient = true;

   private boolean deleteSpoolFile;

   /**
    * Convert String into bytes array using default encoding.
    */
   protected static byte[] stringToBytes(final String value)
   {
      try
      {
         return value.getBytes(Constants.DEFAULT_ENCODING);
      }
      catch (UnsupportedEncodingException e)
      {
         throw new RuntimeException("FATAL ERROR Charset " + Constants.DEFAULT_ENCODING + " is not supported!");
      }
   }

   /**
    * Creates TransientValueData with incoming byte array.
    *
    * @param value
    *          byte[]
    * @param orderNumber
    *          int
    */
   public TransientValueData(byte[] value, int orderNumber)
   {
      super(orderNumber);
      this.data = value;
      this.deleteSpoolFile = true;
      this.closeTmpStream = false;
   }

   /**
    * Creates TransientValueData with incoming input stream. the stream will be lazily spooled to
    * file or byte array depending on maxBufferSize.
    *
    * @param stream
    *          InputStream
    * @param orderNumber
    *          int
    */
   protected TransientValueData(InputStream stream, int orderNumber)
   {
      super(orderNumber);
      this.tmpStream = stream;
      this.deleteSpoolFile = true;
      this.closeTmpStream = false;
   }

   /**
    * TransientValueData constructor.
    *
    * @param orderNumber
    *          int
    * @param bytes
    *          byte[]
    * @param stream
    *          InputStream
    * @param spoolFile
    *          File
    * @param fileCleaner
    *          FileCleaner
    * @param maxBufferSize
    *          int
    * @param tempDirectory
    *          File
    * @param deleteSpoolFile
    *          boolean
    * @throws IOException
    *           if read error
    */
   public TransientValueData(int orderNumber, byte[] bytes, InputStream stream, File spoolFile,
      FileCleaner fileCleaner, int maxBufferSize, File tempDirectory, boolean deleteSpoolFile) throws IOException
   {

      super(orderNumber);
      this.data = bytes;
      this.tmpStream = stream;
      this.closeTmpStream = true;
      this.spoolFile = spoolFile;
      this.fileCleaner = fileCleaner;
      this.maxBufferSize = maxBufferSize;
      this.tempDirectory = tempDirectory;
      this.deleteSpoolFile = deleteSpoolFile;

      if (spoolFile != null)
      {
         if (spoolFile instanceof SpoolFile)
            ((SpoolFile)spoolFile).acquire(this);

         if (this.tmpStream != null)
         {
            this.tmpStream.close();
            this.tmpStream = null; // 05.02.2009 release stream if file exists
         }

         this.spooled = true;
      }
   }

   /**
    * TransientValueData constructor.
    *
    * @param stream
    *          InputStream
    */
   public TransientValueData(InputStream stream)
   {
      this(stream, 0);
   }

   /**
    * Constructor for String value data.
    *
    * @param value
    *          String
    */
   public TransientValueData(String value)
   {
      this(stringToBytes(value), 0);
   }

   /**
    * Constructor for boolean value data.
    *
    * @param value
    *          boolean
    */
   public TransientValueData(boolean value)
   {
      this(Boolean.valueOf(value).toString().getBytes(), 0);
   }

   /**
    * Constructor for Calendar value data.
    *
    * @param value
    *          Calendar
    */
   public TransientValueData(Calendar value)
   {
      this(new JCRDateFormat().serialize(value), 0);
   }

   /**
    * Constructor for double value data.
    *
    * @param value
    *          double
    */
   public TransientValueData(double value)
   {
      this(Double.valueOf(value).toString().getBytes(), 0);
   }

   /**
    * Constructor for long value data.
    *
    * @param value
    *          long
    */
   public TransientValueData(long value)
   {
      this(Long.valueOf(value).toString().getBytes(), 0);
   }

   /**
    * Constructor for Name value data.
    *
    * @param value
    *          InternalQName
    */
   public TransientValueData(InternalQName value)
   {
      this(stringToBytes(value.getAsString()), 0);
   }

   /**
    * Constructor for Path value data.
    *
    * @param value
    *          QPath
    */
   public TransientValueData(QPath value)
   {
      this(stringToBytes(value.getAsString()), 0);
   }

   /**
    * Constructor for Reference value data.
    *
    * @param value
    *          Identifier
    */
   public TransientValueData(Identifier value)
   {
      this(value.getString().getBytes(), 0);
   }

   /**
    * Constructor for Permission value data.
    *
    * @param value
    *          AccessControlEntry
    */
   public TransientValueData(AccessControlEntry value)
   {
      this(stringToBytes(value.getAsString()), 0);
   }

   /**
    * {@inheritDoc}
    */
   public byte[] getAsByteArray() throws IOException
   {
      if (isByteArrayAfterSpool())
      {
         // TODO JCR-992 don't copy bytes
         // byte[] bytes = new byte[data.length];
         // System.arraycopy(data, 0, bytes, 0, data.length);
         return data;
      }
      else
      {
         return fileToByteArray();
      }
   }

   /**
    * Get input stream.
    *
    * @param needSpool
    *          spool input stream if true
    * @return input stream
    * @throws IOException
    *           if any Exception is occurred
    */
   public InputStream getAsStream(boolean needSpool) throws IOException
   {
      if (needSpool)
      {
         if (isByteArrayAfterSpool())
         {
            return new ByteArrayInputStream(data); // from bytes
         }
         else
         {
            if (spoolFile != null)
            {
               return new FileInputStream(spoolFile); // from spool file
            }
            else
            {
               throw new NullPointerException("Stream already consumed");
            }
         }
      }
      else
      {
         if (data != null)
         {
            return new ByteArrayInputStream(data); // from bytes
         }
         else if (spoolFile != null)
         {
            return new FileInputStream(spoolFile); // from spool file if initialized
         }
         else if (tmpStream != null)
         {
            return tmpStream;
         }
         else
         {
            throw new NullPointerException("Null Stream data ");
         }
      }
   }

   /**
    * {@inheritDoc}
    */
   public InputStream getAsStream() throws IOException
   {
      return getAsStream(true);
   }

   /**
    * {@inheritDoc}
    */
   public long getLength()
   {
      if (isByteArrayAfterSpool())
      {
         if (log.isDebugEnabled())
            log.debug("getLength data : " + data.length);
         return data.length;
      }
      else
      {
         if (log.isDebugEnabled())
            log.debug("getLength spoolFile : " + spoolFile.length());
         return spoolFile.length();
      }
   }

   /**
    * {@inheritDoc}
    */
   public boolean isByteArray()
   {
      return data != null;
   }

   /**
    * {@inheritDoc}
    */
   @Override
   public TransientValueData createTransientCopy() throws RepositoryException
   {
      if (isByteArray())
      {
         // bytes, make a copy of real data
         // TODO JCR-992 don't copy bytes
         // byte[] newBytes = new byte[data.length];
         // System.arraycopy(data, 0, newBytes, 0, newBytes.length);

         try
         {
            return new TransientValueData(orderNumber, data, // TODO JCR-992
               null, null, fileCleaner, maxBufferSize, tempDirectory, deleteSpoolFile);
         }
         catch (IOException e)
         {
            throw new RepositoryException(e);
         }
      }
      else
      {
         // stream (or file) based , i.e. shared across sessions
         return this;
      }
   }

   /**
    * Create editable ValueData copy.
    *
    * @return EditableValueData
    * @throws RepositoryException
    *           if error occurs
    */
   public EditableValueData createEditableCopy() throws RepositoryException
   {
      if (isByteArrayAfterSpool())
      {
         // bytes, make a copy of real data
         byte[] newBytes = new byte[data.length];
         System.arraycopy(data, 0, newBytes, 0, newBytes.length);

         try
         {
            return new EditableValueData(newBytes, orderNumber, fileCleaner, maxBufferSize, tempDirectory);
         }
         catch (IOException e)
         {
            throw new RepositoryException(e);
         }
      }
      else
      {
         // edited BLOB file, make a copy
         try
         {
            EditableValueData copy =
               new EditableValueData(spoolFile, orderNumber, fileCleaner, maxBufferSize, tempDirectory);
            return copy;
         }
         catch (FileNotFoundException e)
         {
            throw new RepositoryException("Create transient copy error. " + e, e);
         }
         catch (IOException e)
         {
            throw new RepositoryException("Create transient copy error. " + e, e);
         }
      }
   }

   /**
    * Read <code>length</code> bytes from the binary value at <code>position</code> to the
    * <code>stream</code>.
    *
    * @param stream
    *          - destenation OutputStream
    * @param length
    *          - data length to be read
    * @param position
    *          - position in value data from which the read will be performed
    * @return - The number of bytes, possibly zero, that were actually transferred
    * @throws IOException
    *           if read/write error occurs
    */
   public long read(OutputStream stream, long length, long position) throws IOException
   {

      if (position < 0)
         throw new IOException("Position must be higher or equals 0. But given " + position);

      if (length < 0)
         throw new IOException("Length must be higher or equals 0. But given " + length);

      if (isByteArrayAfterSpool())
      {
         // validation
         if (position >= data.length && position > 0)
            throw new IOException("Position " + position + " out of value size " + data.length);

         if (position + length >= data.length)
            length = data.length - position;

         stream.write(data, (int)position, (int)length);

         return length;
      }
      else
      {
         if (spoolChannel == null)
            spoolChannel = new FileInputStream(spoolFile).getChannel();

         // validation
         if (position >= spoolChannel.size() && position > 0)
            throw new IOException("Position " + position + " out of value size " + spoolChannel.size());

         if (position + length >= spoolChannel.size())
            length = spoolChannel.size() - position;

         MappedByteBuffer bb = spoolChannel.map(FileChannel.MapMode.READ_ONLY, position, length);

         WritableByteChannel ch = Channels.newChannel(stream); // TODO don't use Channels.newChannel
         ch.write(bb);
         ch.close();

         return length;
      }
   }

   /**
    * Return spool file. Actual for BLOBs only.
    *
    * @return File spool file or null
    */
   public File getSpoolFile()
   {
      return spoolFile;
   }

   /**
    * Set spool as persisted file. It's means ValueData has its data stored to a External Value
    * Storage. And it's a file with the content which cannot be deleted or moved outside Value
    * Storage.
    *
    * @param persistedFile
    *          File
    * @throws IOException
    *           if any Exception is occurred
    */
   public void setPersistedFile(File persistedFile) throws IOException
   {
      if (isTransient())
      {
         deleteCurrentSpoolFile();
      }

      this.spoolFile = persistedFile;
      this.deleteSpoolFile = false;
      this.spooled = true;

      this.tmpStream = null;
      this.data = null;

      this.isTransient = false;
   }

   /**
    * Helper method to simplify operations that requires stringified data.
    *
    * @return String
    * @throws IOException
    *           if read error
    */
   public String getString() throws IOException
   {
      if (log.isDebugEnabled())
         log.debug("getString");

      return new String(getAsByteArray(), Constants.DEFAULT_ENCODING);
   }

   // ///////////////////////////////////
   /**
    * Make sense for stream storage only.
    *
    * @param cleaner
    *          FileCleaner
    */
   public void setFileCleaner(FileCleaner cleaner)
   {
      this.fileCleaner = cleaner;
   }

   /**
    * @param tempDirectory
    */
   public void setTempDirectory(File tempDirectory)
   {
      this.tempDirectory = tempDirectory;
   }

   /**
    * @param maxBufferSize
    */
   public void setMaxBufferSize(int maxBufferSize)
   {
      this.maxBufferSize = maxBufferSize;
   }

   /**
    * {@inheritDoc}
    */
   protected void finalize() throws Throwable
   {
      deleteCurrentSpoolFile();
   }

   /**
    * {@inheritDoc}
    */
   public boolean equals(Object obj)
   {

      if (this == obj)
      {
         return true;
      }

      if (obj instanceof TransientValueData)
      {

         TransientValueData other = (TransientValueData)obj;
         if (isByteArray() != other.isByteArray())
            return false;
         try
         {
            if (isByteArray())
            {
               return Arrays.equals(getAsByteArray(), other.getAsByteArray());
            }
            else
               return getSpoolFile().equals(other.getSpoolFile());
         }
         catch (IOException e)
         {
            log.error("Read error", e);
            return false;
         }
      }
      return false;
   }

   // ///////////////////////////////////

   /**
    * Spool ValueData InputStream to a temp File.
    */
   protected void spoolInputStreamAlways()
   {

      if (spooled || tmpStream == null) // already spooled
         return;

      byte[] tmpBuff = new byte[2048];
      OutputStream sfout = null;
      int read = 0;

      try
      {
         SpoolFile sf = SpoolFile.createTempFile("jcrvd", null, tempDirectory);
         sf.acquire(this);
         sfout = new FileOutputStream(sf);

         while ((read = tmpStream.read(tmpBuff)) >= 0)
            sfout.write(tmpBuff, 0, read);

         this.spoolChannel = null;
         this.spoolFile = sf;

         this.data = null;
         this.spooled = true;
      }
      catch (IOException e)
      {
         throw new IllegalStateException(e);
      }
      finally
      {
         try
         {
            if (sfout != null)
               sfout.close();
         }
         catch (IOException e)
         {
            log.error("Error of spool output close.", e);
         }

         if (this.closeTmpStream)
            try
            {
               this.tmpStream.close();
            }
            catch (IOException e)
            {
               log.error("Error of source input close.", e);
            }
         this.tmpStream = null;
      }
   }

   /**
    * Tell is this Value backed by bytes array before or after spooling.
    */
   private boolean isByteArrayAfterSpool()
   {
      if (data != null)
      {
         return true;
      }
      else
      {
         spoolInputStream();

         return data != null;
      }
   }

   /**
    * Spool ValueData temp InputStream to a temp File.
    */
   protected void spoolInputStream()
   {

      if (spooled || tmpStream == null) // already spooled
         return;

      byte[] buffer = new byte[0];
      byte[] tmpBuff = new byte[2048];
      int read = 0;
      int len = 0;
      SpoolFile sf = null;
      OutputStream sfout = null;

      try
      {
         while ((read = tmpStream.read(tmpBuff)) >= 0)
         {
            if (sfout != null)
            {
               // spool to temp file
               sfout.write(tmpBuff, 0, read);
               len += read;
            }
            else if (len + read > maxBufferSize && fileCleaner != null)
            {
               // threshold for keeping data in memory exceeded,
               // if have a fileCleaner create temp file and spool buffer contents.
               sf = SpoolFile.createTempFile("jcrvd", null, tempDirectory);
               sf.acquire(this);

               sfout = new FileOutputStream(sf);
               sfout.write(buffer, 0, len);
               sfout.write(tmpBuff, 0, read);
               buffer = null;
               len += read;
            }
            else
            {
               // reallocate new buffer and spool old buffer contents
               byte[] newBuffer = new byte[len + read];
               System.arraycopy(buffer, 0, newBuffer, 0, len);
               System.arraycopy(tmpBuff, 0, newBuffer, len, read);
               buffer = newBuffer;
               len += read;
            }
         }

         if (sf != null)
         {
            // spooled to file
            this.spoolChannel = null;
            this.spoolFile = sf;
            this.data = null;
         }
         else
         {
            // ...bytes
            this.spoolChannel = null;
            this.spoolFile = null;
            this.data = buffer;
         }

         this.spooled = true;
      }
      catch (IOException e)
      {
         throw new IllegalStateException(e);
      }
      finally
      {
         try
         {
            if (sfout != null)
               sfout.close();
         }
         catch (IOException e)
         {
            log.error("Error of spool output close.", e);
         }

         if (this.closeTmpStream)
            try
            {
               this.tmpStream.close();
            }
            catch (IOException e)
            {
               log.error("Error of source input close.", e);
            }
         this.tmpStream = null;
      }
   }

   /**
    * Convert File to byte array. <br/>
    * WARNING: Potential lack of memory due to call getAsByteArray() on stream data.
    *
    * @return byte[] bytes array
    */
   private byte[] fileToByteArray() throws IOException
   {
      FileChannel fch = new FileInputStream(spoolFile).getChannel();

      if (log.isDebugEnabled() && fch.size() > maxBufferSize)
      {
         log.debug("Potential lack of memory due to call getAsByteArray() on stream data exceeded " + fch.size()
            + " bytes");
      }

      try
      {
         ByteBuffer bb = ByteBuffer.allocate((int)fch.size());
         fch.read(bb);
         if (bb.hasArray())
         {
            return bb.array();
         }
         else
         {
            // impossible code in most cases, as we use heap backed buffer
            byte[] tmpb = new byte[bb.capacity()];
            bb.get(tmpb);
            return tmpb;
         }
      }
      finally
      {
         fch.close();
      }
   }

   /**
    * Delete current spool file.
    *
    * @throws IOException
    *           if error
    */
   private void deleteCurrentSpoolFile() throws IOException
   {
      if (spoolChannel != null)
         spoolChannel.close();

      if (spoolFile != null)
      {

         if (spoolFile instanceof SpoolFile)
            ((SpoolFile)spoolFile).release(this);

         if (deleteSpoolFile && spoolFile.exists())
         {
            if (!spoolFile.delete())
            {
               if (fileCleaner != null)
               {
                  fileCleaner.addFile(spoolFile);

                  if (log.isDebugEnabled())
                  {
                     log.debug("Could not remove file. Add to fileCleaner " + spoolFile.getAbsolutePath());
                  }
               }
               else
               {
                  log.warn("Could not remove temporary file on finalize " + spoolFile.getAbsolutePath());
               }
            }
         }
      }
   }

   // ------------- Serializable

   /**
    * TransientValueData empty constructor. Used for Replication serialization (java).
    *
    */
   public TransientValueData()
   {
      super(0);
      this.deleteSpoolFile = true;
      this.closeTmpStream = true;
   }

   /**
    * {@inheritDoc}
    */
   public void writeExternal(ObjectOutput out) throws IOException
   {
      if (this.isByteArray())
      {
         out.writeInt(1);
         int f = data.length;
         out.writeInt(f);
         out.write(data);
      }
      else
      {
         out.writeInt(2);
      }
      out.writeInt(orderNumber);
      out.writeInt(maxBufferSize);
   }

   /**
    * {@inheritDoc}
    */
   public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException
   {
      int type = in.readInt();

      if (type == 1)
      {
         data = new byte[in.readInt()];
         in.readFully(data);
      }
      orderNumber = in.readInt();
      maxBufferSize = in.readInt();
   }

   /**
    * Set data Stream from outside. FOR Synchronouis replicatiojn only!
    *
    * @param in
    *          InputStream
    */
   public void setStream(InputStream in)
   {
      this.spooled = false;
      this.tmpStream = in;

      this.data = null;

      this.spoolFile = null;
      this.spoolChannel = null;
   }

   /**
    * {@inheritDoc}
    */
   public boolean isTransient()
   {
      return isTransient;
   }
}
TOP

Related Classes of org.exoplatform.services.jcr.impl.dataflow.TransientValueData

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.