Package org.apache.derby.impl.jdbc

Source Code of org.apache.derby.impl.jdbc.LOBStreamControl

/*

   Derby - Class org.apache.derby.impl.jdbc.LOBStreamControl

   Licensed to the Apache Software Foundation (ASF) under one
   or more contributor license agreements.  See the NOTICE file
   distributed with this work for additional information
   regarding copyright ownership.  The ASF licenses this file
   to you under the Apache License, Version 2.0 (the
   "License"); you may not use this file except in compliance
   with the License.  You may obtain a copy of the License at

     http://www.apache.org/licenses/LICENSE-2.0

   Unless required by applicable law or agreed to in writing,
   software distributed under the License is distributed on an
   "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
   KIND, either express or implied.  See the License for the
   specific language governing permissions and limitations
   under the License.

*/
package org.apache.derby.impl.jdbc;

import java.io.EOFException;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.security.AccessController;
import java.security.PrivilegedActionException;
import java.security.PrivilegedExceptionAction;
import org.apache.derby.iapi.error.StandardException;
import org.apache.derby.iapi.reference.Property;
import org.apache.derby.iapi.reference.SQLState;
import org.apache.derby.iapi.services.monitor.Monitor;
import org.apache.derby.iapi.store.raw.data.DataFactory;
import org.apache.derby.io.StorageFile;
import org.apache.derby.shared.common.error.ExceptionUtil;

/**
* This class acts as a layer of blob/clob repository (in memory or file).
* The max bytes of data stored in memory depends on the way this
* class is created. If the class is created with initial data, the buffer
* size is set to the size of the byte array supplied. If no initial data
* is supplied or if the initial data size is less than DEFAULT_MAX_BUF_SIZE,
* The buffer size is set to DEFAULT_MAX_BUF_SIZE.
* When write increases the data beyond this value a temporary file is created
* and data is moved into that. If truncate reduces the size of the file below
* initial buffer size (max of DEFAULT_MAX_BUF_SIZE and initial byte array size)
* the data moved into memory.
*
* This class also creates InputStream and OutputStream which can be used to access
* blob data irrespective of if its in memory or in file.
*/

class LOBStreamControl {
    private LOBFile tmpFile;
    private StorageFile lobFile;
    private byte [] dataBytes = new byte [0];
    private boolean isBytes = true;
    private final int bufferSize;
    private String dbName;
    private long updateCount;
    private static final int DEFAULT_MAX_BUF_SIZE = 4096;

    /**
     * Creates an empty LOBStreamControl.
     * @param dbName database name
     */
    LOBStreamControl (String dbName) {
        this.dbName = dbName;
        updateCount = 0;
        //default buffer size
        bufferSize = DEFAULT_MAX_BUF_SIZE;
    }

    /**
     * Creates a LOBStreamControl and initializes with a bytes array.
     * @param dbName database name
     * @param data initial value
     */
    LOBStreamControl (String dbName, byte [] data)
            throws IOException, StandardException {
        this.dbName = dbName;
        updateCount = 0;
        bufferSize = Math.max (DEFAULT_MAX_BUF_SIZE, data.length);
        write (data, 0, data.length, 0);
    }

    private void init(byte [] b, long len)
            throws IOException, StandardException {
        try {
            AccessController.doPrivileged (new PrivilegedExceptionAction() {
                public Object run() throws IOException, StandardException {
                    Object monitor = Monitor.findService(
                            Property.DATABASE_MODULE, dbName);
                    DataFactory df =  (DataFactory) Monitor.findServiceModule(
                            monitor, DataFactory.MODULE);
                    //create a temporary file
                    lobFile =
                        df.getStorageFactory().createTemporaryFile("lob", null);
                    if (df.databaseEncrypted()) {
                        tmpFile = new EncryptedLOBFile (lobFile, df);
                    }
                    else
                        tmpFile = new LOBFile (lobFile);
                    return null;
                }
            });
        }
        catch (PrivilegedActionException pae) {
            Exception e = pae.getException();
            if (e instanceof StandardException)
                throw (StandardException)e;
            if (e instanceof IOException)
                throw (IOException) e;
            throw Util.newIOException(e);
        }
        isBytes = false;
        //now this call will write into the file
        if (len != 0)
            write(b, 0, (int) len, 0);
        dataBytes = null;
    }

    private long updateData(byte[] bytes, int offset, int len, long pos)
            throws StandardException {
        if (dataBytes == null) {
            if ((int) pos == 0) {
                dataBytes = new byte [len];
                System.arraycopy(bytes, offset, dataBytes, (int) pos, len);
                return len;
            }
            else {
                //invalid postion
                throw StandardException.newException(
                        SQLState.BLOB_POSITION_TOO_LARGE, new Long(pos));
            }
        }
        else {
            if (pos > dataBytes.length) {
                //invalid postion
                throw StandardException.newException(
                        SQLState.BLOB_POSITION_TOO_LARGE, new Long(pos));
            }
            else {
                if (pos + len < dataBytes.length) {
                    System.arraycopy(bytes, offset, dataBytes, (int) pos, len);
                }
                else {
                    byte [] tmpBytes = new byte [len + (int) pos];
                    System.arraycopy(dataBytes, 0 , tmpBytes, 0, (int) pos);
                    System.arraycopy(bytes, offset, tmpBytes, (int) pos, len);
                    dataBytes = tmpBytes;
                }
            }
            return pos + len;
        }
    }

    private void isValidPostion(long pos)
            throws IOException, StandardException {
        if (pos < 0)
            throw StandardException.newException(
                    SQLState.BLOB_NONPOSITIVE_LENGTH, new Long(pos + 1));
        if (pos > Integer.MAX_VALUE)
            throw StandardException.newException(
                    SQLState.BLOB_POSITION_TOO_LARGE, new Long(pos + 1));

        if (isBytes) {
            if (dataBytes == null) {
                if (pos != 0)
                    throw StandardException.newException(
                            SQLState.BLOB_POSITION_TOO_LARGE, new Long(pos + 1));
            } else if (dataBytes.length < pos)
                throw StandardException.newException(
                        SQLState.BLOB_POSITION_TOO_LARGE, new Long(pos + 1));
        } else {
            if (pos > tmpFile.length())
                throw StandardException.newException(
                        SQLState.BLOB_POSITION_TOO_LARGE, new Long(pos + 1));
        }
    }

    private void isValidOffset(int off, int length) throws StandardException {
        if (off < 0 || off > length)
            throw StandardException.newException(
                    SQLState.BLOB_INVALID_OFFSET, new Integer(off));
    }

    /**
     * Writes one byte.
     * @param b byte
     * @param pos
     * @return new postion
     * @throws IOException, StandardException
     */
    synchronized long write(int b, long pos)
            throws IOException, StandardException {
        isValidPostion(pos);
        updateCount++;
        if (isBytes) {
            if (pos < bufferSize) {
                byte [] bytes = {(byte) b};
                updateData(bytes, 0, 1, pos);
                return pos + 1;
            } else {
                init(dataBytes, pos);
            }
        }
        tmpFile.seek(pos);
        tmpFile.write(b);
        return tmpFile.getFilePointer();
    }

    /**
     * Writes part of the byte array.
     * @param b byte array
     * @param off offset from where to read from the byte array
     * @param len number of bytes to be copied
     * @param pos starting postion
     * @return new postion
     * @throws IOException, StandardException
     */
    synchronized long write(byte[] b, int off, int len, long pos)
            throws IOException, StandardException {
        isValidPostion(pos);
        try {
            isValidOffset(off, b.length);
        } catch (StandardException e) {
            if (e.getSQLState().equals(
                    ExceptionUtil.getSQLStateFromIdentifier(
                                  SQLState.BLOB_INVALID_OFFSET)))
                    throw new ArrayIndexOutOfBoundsException (e.getMessage());
            throw e;
        }
        updateCount++;
        if (isBytes) {
            if (pos + len <= bufferSize)
                return updateData(b, off, len, pos);
            else {
                init(dataBytes, pos);
            }
        }
        tmpFile.seek(pos);
        tmpFile.write(b, off, len);
        return tmpFile.getFilePointer();
    }

    /**
     * Reads one byte.
     * @param pos postion from where to read
     * @return byte
     * @throws IOException, StandardException
     */
    synchronized int read(long pos)
            throws IOException, StandardException {
        isValidPostion(pos);
        if (isBytes) {
            if (dataBytes.length == pos)
                return -1;
            return dataBytes [(int) pos] & 0xff;
        }
        if (tmpFile.getFilePointer() != pos)
            tmpFile.seek(pos);
        try {
            return tmpFile.readByte() & 0xff;
        }
        catch (EOFException eof) {
            return -1;
        }
    }

    private int readBytes(byte [] b, int off, int len, long pos) {
        if (pos >= dataBytes.length)
            return -1;
        int lengthFromPos = dataBytes.length - (int) pos;
        int actualLength = len > lengthFromPos ? lengthFromPos : len;
        System.arraycopy(dataBytes, (int) pos, b, off, actualLength);
        return actualLength;
    }

    /**
     * Reads bytes starting from 'position' into bytes array.
     * starting from 'offset'
     * @param buff array into the bytes will be copied
     * @param off offset from where the array has to be populated
     * @param len number of bytes to read
     * @param pos initial postion before reading
     * @return number new postion
     * @throws IOException, StandardException
     */
    synchronized int read(byte[] buff, int off, int len, long pos)
            throws IOException, StandardException {
        isValidPostion(pos);
        isValidOffset(off, buff.length);
        if (isBytes) {
            return readBytes(buff, off, len, pos);
        }
        tmpFile.seek(pos);
        return tmpFile.read (buff, off, len);
    }

    /**
     * returns input stream linked with this object.
     * @param pos initial postion
     * @return InputStream
     */
    InputStream getInputStream(long pos) {
        return new LOBInputStream(this, pos);
    }

    /**
     * returns output stream linked with this object
     * @param pos initial postion
     * @return OutputStream
     */
    OutputStream getOutputStream(long pos) {
        return new LOBOutputStream(this, pos);
    }

    /**
     * Returns length of data.
     * @return length
     * @throws IOException
     */
    long getLength() throws IOException {
        if (isBytes)
            return dataBytes.length;
        return tmpFile.length();
    }

    /**
     * Resets the size.
     * @param size new size should be smaller than exisiting size
     * @throws IOException
     */
    synchronized void truncate(long size)
            throws IOException, StandardException {
        isValidPostion(size);
        if (isBytes) {
            byte [] tmpByte = new byte [(int) size];
            System.arraycopy(dataBytes, 0, tmpByte, 0, (int) size);
            dataBytes = tmpByte;
        } else {
            if (size < bufferSize) {
                dataBytes = new byte [(int) size];
                read(dataBytes, 0, dataBytes.length, 0);
                isBytes = true;
                tmpFile.close();
                tmpFile = null;
            } else {
                tmpFile.setLength(size);
            }
        }
    }

    /**
     * Copies bytes from stream to local storage.
     * @param inStream
     * @param length length to be copied
     * @throws IOException, StandardException
     */
    synchronized void copyData(InputStream inStream, long length)
            throws IOException, StandardException {
        byte [] data = new byte [bufferSize];
        long sz = 0;
        while (sz < length) {
            int len = (int) Math.min (length - sz, bufferSize);
            len = inStream.read(data, 0, len);
            if (len < 0)
                throw new EOFException("Reached end-of-stream " +
                        "prematurely at " + sz);
            write(data, 0, len, sz);
            sz += len;
        }
    }

    protected void finalize() throws Throwable {
        free();
    }

    private void deleteFile (StorageFile file) throws IOException {
        try {
            final StorageFile sf = file;
            AccessController.doPrivileged(new PrivilegedExceptionAction() {
                public Object run() throws IOException {
                    sf.delete();
                    return null;
                }
            });
        } catch (PrivilegedActionException pae) {
            Exception e = pae.getException();
            if (e instanceof IOException)
                throw (IOException) e;
            if (e instanceof RuntimeException)
                throw (RuntimeException) e;
            throw Util.newIOException(e);
        }
    }
    /**
     * Invalidates all the variables and closes file handle if open.
     * @throws IOexception
     */
    void free() throws IOException {
        dataBytes = null;
        if (tmpFile != null) {
            tmpFile.close();
            deleteFile(lobFile);
            tmpFile = null;
        }
    }
   
    /**
     * Replaces a block of bytes in the middle of the LOB with a another block
     * of bytes, which may be of a different size.
     * <p>
     * The new byte array may not be be of same length as the original,
     * thus it may result in resizing the total lob.
     *
     * @param buf byte array which will be written inplace of old block
     * @param stPos inclusive starting position of current block
     * @param endPos exclusive end position of current block
     * @return Current position after write.
     * @throws IOExcepton if writing to temporary file fails
     * @throws StandardException
     */
    synchronized long replaceBytes (byte [] buf, long stPos, long endPos)
            throws IOException, StandardException {
        long length = getLength();
        long finalLength = length - endPos + stPos + buf.length;
        if (isBytes) {
            if (finalLength > bufferSize) {
                byte [] tmpBytes = dataBytes;
                init (tmpBytes, stPos);
                write (buf, 0, buf.length, getLength());
                if (endPos < length)
                    write (tmpBytes, (int) endPos,
                            (int) (length - endPos), getLength());
            }
            else {
                byte [] tmpByte = new byte [(int) finalLength];
                System.arraycopy (dataBytes, 0, tmpByte, 0, (int) stPos);
                System.arraycopy (buf, 0, tmpByte, (int) stPos, (int) buf.length);
                if (endPos < length)
                    System.arraycopy (dataBytes, (int) endPos, tmpByte,
                            (int) (stPos + buf.length), (int) (length - endPos));
                dataBytes = tmpByte;           
            }
        }
        else {
            //save over file handle and
            //create new file with 0 size
           
            byte tmp [] = new byte [0];
            LOBFile oldFile = tmpFile;
            StorageFile oldStoreFile = lobFile;
            init (tmp, 0);
            byte [] tmpByte = new byte [1024];
            long sz = stPos;
            oldFile.seek(0);
            while (sz != 0) {
                int readLen = (int) Math.min (1024, sz);               
                int actualLength = oldFile.read (tmpByte, 0, readLen);
                if (actualLength == -1)
                    break;
                tmpFile.write (tmpByte, 0, actualLength);
                sz -= actualLength;
            }
            tmpFile.write (buf);
            oldFile.seek (endPos);
            int rdLen;
            if (endPos < length) {
                do {
                    rdLen = oldFile.read (tmpByte, 0, 1024);
                    if (rdLen == -1)
                        break;
                    tmpFile.write (tmpByte, 0, rdLen);
                }while (true);
                oldFile.close();
                deleteFile(oldStoreFile);
            }           
        }
        updateCount++;
        return stPos + buf.length;
    }

    /**
     * Returns the running secquence number to check if the lob is updated since
     * last access.
     *
     * @return The current update sequence number.
     */
    long getUpdateCount() {
        return updateCount;
    }
}
TOP

Related Classes of org.apache.derby.impl.jdbc.LOBStreamControl

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.