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

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

/*
* 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.impl.util.io.FileCleaner;

import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.RandomAccessFile;
import java.nio.ByteBuffer;
import java.nio.MappedByteBuffer;
import java.nio.channels.Channels;
import java.nio.channels.FileChannel;
import java.nio.channels.ReadableByteChannel;

import javax.jcr.RepositoryException;

public class EditableValueData extends TransientValueData
{

   protected final int maxIOBuffSize;

   public EditableValueData(byte[] bytes, int orderNumber, FileCleaner fileCleaner, int maxBufferSize,
      File tempDirectory) throws IOException
   {

      // send bytes to super.<init>
      super(orderNumber, bytes, null, null, fileCleaner, maxBufferSize, tempDirectory, true);

      this.maxIOBuffSize = calcMaxIOSize();

      this.spooled = true;
   }

   // TODO use InputStream instead of spoolFile and use Channel.transferFrom.
   public EditableValueData(File spoolFile, int orderNumber, FileCleaner fileCleaner, int maxBufferSize,
      File tempDirectory) throws IOException
   {

      // don't send any data there (no stream, no bytes)
      super(orderNumber, null, null, null, fileCleaner, maxBufferSize, tempDirectory, true);

      this.maxIOBuffSize = calcMaxIOSize();

      File sf = null;
      FileChannel sch = null;
      try
      {
         sf = File.createTempFile("jcrvdedit", null, tempDirectory);

         sch = new RandomAccessFile(sf, "rw").getChannel();

         FileChannel sourceCh = new FileInputStream(spoolFile).getChannel();
         try
         {
            sch.transferFrom(sourceCh, 0, sourceCh.size());
         }
         finally
         {
            sourceCh.close();
         }
      }
      catch (final IOException e)
      {
         try
         {
            sch.close();
            sf.delete();
         }
         catch (Exception e1)
         {
         }
         throw new IOException("init error " + e.getMessage())
         {
            @Override
            public Throwable getCause()
            {
               return e;
            }
         };
      }

      this.data = null;

      this.spoolFile = sf;
      this.spoolChannel = sch;

      this.spooled = true;
   }

   protected int calcMaxIOSize()
   {
      return maxBufferSize < 1024 ? 1024 : maxBufferSize < (250 * 1024) ? maxBufferSize : 250 * 1024;
   }

   protected int calcBuffSize(long length)
   {
      int buffSize = (int)(length > maxIOBuffSize ? maxIOBuffSize : length / 4);
      buffSize = buffSize < 1024 ? 256 : buffSize;
      return buffSize;
   }

   public TransientValueData createTransientCopy() throws RepositoryException
   {
      if (isByteArray())
      {
         // bytes, make a copy of real data
         byte[] newBytes = new byte[data.length];
         System.arraycopy(data, 0, newBytes, 0, newBytes.length);
         return new TransientValueData(newBytes, orderNumber);
      }
      else
      {
         // stream, make a copy
         try
         {
            // force changes made to the file
            spoolChannel.force(false);

            InputStream thisStream = getAsStream();
            try
            {
               TransientValueData copy =
                  new TransientValueData(orderNumber, null, thisStream, null, fileCleaner, maxBufferSize,
                     tempDirectory, true);
               copy.spoolInputStream(); // force spool - read now, till the source isn't changed
               return copy;
            }
            finally
            {
               thisStream.close();
            }
         }
         catch (IOException e)
         {
            throw new RepositoryException("Create transient copy error. " + e, e);
         }
      }
   }

   /**
    * Update with <code>length</code> bytes from the specified <code>stream</code> to this value data
    * at <code>position</code>.
    *
    * If <code>position</code> is lower 0 the IOException exception will be thrown.
    *
    * If <code>position</code> is higher of current Value length the Value length will be increased
    * before to <code>position</code> size and <code>length</code> bytes will be added after the
    * <code>position</code>.
    *
    * @param stream
    *          the data.
    * @param length
    *          the number of bytes from buffer to write.
    * @param position
    *          position in file to write data
    *
    * @throws IOException
    */
   public void update(InputStream 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 (isByteArray())
      {
         // edit bytes
         // ...check length
         long updateSize = position + length;

         long newSize = updateSize > data.length ? updateSize : data.length;
         if ((newSize <= maxBufferSize && newSize <= Integer.MAX_VALUE) || maxBufferSize <= 0 || tempDirectory == null)
         {
            // bytes
            byte[] newBytes = new byte[(int)newSize];

            int newIndex = 0; // first pos to write

            if ((newIndex = (int)position) > 0)
            {
               // begin from the existed bytes
               System.arraycopy(data, 0, newBytes, 0, newIndex < data.length ? newIndex : data.length);
            }

            // write new data
            int i = -1;
            boolean doRead = true;
            byte[] buff = new byte[calcBuffSize(length)];
            while (doRead && (i = stream.read(buff)) >= 0)
            {
               if (newIndex + i > newBytes.length)
               {
                  // given length reached
                  i = newBytes.length - newIndex;
                  doRead = false;
               }
               System.arraycopy(buff, 0, newBytes, newIndex, i);
               newIndex += i;
            }

            if (newIndex < data.length)
               // write the rest of existed data
               System.arraycopy(data, newIndex, newBytes, newIndex, data.length - newIndex);

            this.data = newBytes;

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

         }
         else
         {

            // switch from bytes to file/channel
            File chf = null;
            FileChannel chch = null;
            long newIndex = 0; // first pos to write

            try
            {
               chf = File.createTempFile("jcrvdedit", null, tempDirectory);
               chch = new RandomAccessFile(chf, "rw").getChannel();

               // allocate the space for whole file
               MappedByteBuffer bb = chch.map(FileChannel.MapMode.READ_WRITE, position + length, 0);
               bb.force();
               bb = null;

               ReadableByteChannel bch = Channels.newChannel(new ByteArrayInputStream(this.data));

               if ((newIndex = (int)position) > 0)
               {
                  // begin from the existed bytes
                  chch.transferFrom(bch, 0, newIndex < data.length ? newIndex : data.length);
                  bch.close();
               }

               // write update data
               // TODO don't use Channels.newChannel in Java5
               ReadableByteChannel sch = Channels.newChannel(stream);
               chch.transferFrom(sch, newIndex, length);
               sch.close();
               newIndex += length;

               if (newIndex < data.length)
                  // write the rest of existed data
                  chch.transferFrom(bch, newIndex, data.length - newIndex);

               bch.close();
            }
            catch (final IOException e)
            {
               try
               {
                  chch.close();
                  chf.delete();
               }
               catch (Exception e1)
               {
               }
               throw new IOException("update error " + e.getMessage())
               {
                  @Override
                  public Throwable getCause()
                  {
                     return e;
                  }
               };
            }
            this.spoolFile = chf;
            this.spoolChannel = chch;
            this.data = null;
         }
      }
      else
      {
         MappedByteBuffer bb = spoolChannel.map(FileChannel.MapMode.READ_WRITE, position, length);

         ReadableByteChannel ch = Channels.newChannel(stream);
         ch.read(bb);
         ch.close();

         bb.force();
      }
   }

   /**
    * Set length of the Value in bytes to the specified <code>size</code>.
    *
    * If <code>size</code> is lower 0 the IOException exception will be thrown.
    *
    * This operation can be used both for extend and for truncate the Value size.
    *
    * This method used internally in update operation in case of extending the size to the given
    * position.
    *
    * @param size
    * @throws IOException
    */
   public void setLength(long size) throws IOException
   {

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

      if (isByteArray())
      {
         if (size < maxBufferSize || maxBufferSize <= 0 || tempDirectory == null)
         {
            // use bytes
            byte[] newBytes = new byte[(int)size];
            System.arraycopy(data, 0, newBytes, 0, (data.length < newBytes.length) ? data.length : newBytes.length);
            this.data = newBytes;
         }
         else
         {
            // switch from bytes to file/channel
            File chf = null;
            FileChannel chch = null;
            try
            {
               chf = File.createTempFile("jcrvdedit", null, tempDirectory);
               chch = new RandomAccessFile(chf, "rw").getChannel();

               ReadableByteChannel bch = Channels.newChannel(new ByteArrayInputStream(this.data));
               chch.transferFrom(bch, 0, this.data.length); // get all
               bch.close();

               if (chch.size() < size)
               {
                  // extend length
                  MappedByteBuffer bb = chch.map(FileChannel.MapMode.READ_WRITE, size, 0);
                  bb.force();
               }
            }
            catch (final IOException e)
            {
               try
               {
                  chch.close();
                  chf.delete();
               }
               catch (Exception e1)
               {
               }
               throw new IOException("setLength(" + size + ") error. " + e.getMessage())
               {
                  @Override
                  public Throwable getCause()
                  {
                     return e;
                  }
               };
            }
            this.spoolFile = chf;
            this.spoolChannel = chch;
            this.data = null;
         }
      }
      else if (size < maxBufferSize)
      {
         // switch to bytes
         ByteBuffer bb = ByteBuffer.allocate((int)size);
         spoolChannel.force(false);
         spoolChannel.position(0);
         spoolChannel.read(bb);

         byte[] tmpb = null;

         if (bb.hasArray())
         {
            tmpb = bb.array();
         }
         else
         {
            // impossible code in most cases, as we use heap backed buffer
            tmpb = new byte[bb.capacity()];
            bb.get(tmpb);
         }

         spoolChannel.close();

         // delete file
         if (!spoolFile.delete())
         {
            if (fileCleaner != null)
            {
               log.info("Could not remove file. Add to fileCleaner " + spoolFile);
               fileCleaner.addFile(spoolFile);
            }
            else
            {
               log.warn("Could not remove temporary file on switch to bytes, fileCleaner not found. "
                  + spoolFile.getAbsolutePath());
            }
         }

         data = tmpb;
         spoolChannel = null;
         spoolFile = null;
      }
      else
      {
         if (spoolChannel.size() < size)
         {
            // extend file
            MappedByteBuffer bb = spoolChannel.map(FileChannel.MapMode.READ_WRITE, size, 0);
            bb.force();
         }
         else
         {
            // truncate file
            spoolChannel.truncate(size);
         }
      }
   }
}
TOP

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

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.