All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.sequoiadb.base.DBLob Maven / Gradle / Ivy

There is a newer version: 5.10
Show newest version
/**
 *      Copyright (C) 2012 SequoiaDB Inc.
 *
 *   Licensed 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 com.sequoiadb.base;
 * @brief SequoiaDB Driver for Java
 * @author YouBin Lin
 */

package com.sequoiadb.base;

import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

import org.bson.BSONObject;
import org.bson.BasicBSONObject;
import org.bson.types.ObjectId;

import com.sequoiadb.base.SequoiadbConstants.Operation;
import com.sequoiadb.exception.BaseException;
import com.sequoiadb.util.Helper;
import com.sequoiadb.util.SDBMessageHelper;

/**
 * @class DBLob
 * @brief Operation interfaces of DBLob.
 */
public interface DBLob {
    /**
     * @memberof SDB_LOB_SEEK_SET 0
     * @brief Change the position from the beginning of lob 
     */
    public final static int SDB_LOB_SEEK_SET   = 0;
    
    /**
     * @memberof SDB_LOB_SEEK_CUR 1
     * @brief Change the position from the current position of lob 
     */
    public final static int SDB_LOB_SEEK_CUR   = 1;
    
    /**
     * @memberof SDB_LOB_SEEK_END 2
     * @brief Change the position from the end of lob 
     */
    public final static int SDB_LOB_SEEK_END   = 2;
    
    /**
     * @fn          ObjectId getID()
     * @brief       get the lob's id
     * @return      the lob's id
     */
    public ObjectId getID();

    /**
     * @fn          long getSize()
     * @brief       get the size of lob
     * @return      the lob's size
     */
    public long getSize();
    
    /**
     * @fn          long getCreateTime()
     * @brief       get the create time of lob
     * @return      the lob's create time
     */
    public long getCreateTime();
    
    /**
     * @fn          write( byte[] b )
     * @brief       Writes b.length bytes from the specified 
     *              byte array to this lob. 
     * @param       b   the data.
     * @exception   com.sequoiadb.exception.BaseException
     */
    public void write( byte[] b ) throws BaseException;
    
    /**
     * @fn          int read( byte[] b )
     * @brief       Reads up to b.length bytes of data from this lob into 
     *              an array of bytes. 
     * @param       b   the buffer into which the data is read.
     * @return      the total number of bytes read into the buffer, or-1 if 
     *              there is no more data because the end of the file has been 
     *              reached, or 0 if b.length is Zero.
     * @exception   com.sequoiadb.exception.BaseException
     */
    public int read( byte[] b ) throws BaseException;
    
    /**
     * @fn          seek( long size, int seekType )
     * @brief       change the read position of the lob. The new position is 
     *              obtained by adding size to the position specified by 
     *              seekType. If seekType is set to SDB_LOB_SEEK_SET, 
     *              SDB_LOB_SEEK_CUR, or SDB_LOB_SEEK_END, the offset is 
     *              relative to the start of the lob, the current position 
     *              of lob, or the end of lob.
     * @param       size the adding size.
     * @param       seekType  SDB_LOB_SEEK_SET/SDB_LOB_SEEK_CUR/SDB_LOB_SEEK_END
     * @exception   com.sequoiadb.exception.BaseException.
     */
    public void seek( long size, int seekType ) throws BaseException;
    
    /**
     * @fn          close()
     * @brief       close the lob
     * @exception   com.sequoiadb.exception.BaseException
     */
    public void close() throws BaseException;
}

class DBLobConcrete implements DBLob {
    public final static int SDB_LOB_CREATEONLY = 0x00000001;
    public final static int SDB_LOB_READ       = 0x00000004;
    
    // the max lob data size to send for one message
    private final static int SDB_LOB_MAX_DATA_LENGTH  = 1024 * 1024;
//    private final static int SDB_LOB_MAX_DATA_LENGTH = 1024 * 1024;
    
    private final static long SDB_LOB_DEFAULT_OFFSET  = -1;
    private final static int SDB_LOB_DEFAULT_SEQ      = 0;
    
    private DBCollection _cl;
    private ObjectId     _id;
    private int          _mode;
    private long         _size;
    private long         _createTime;
    private long         _readOffset = 0;
    private boolean      _endianConvert;
    private boolean      _isOpen = false;
    
    /* when first open/create DBLob, sequoiadb return the contextID for the
     * further reading/writing/close
    */
    private long _contextID ;
    
    /**
     * @fn          DBLob( DBCollection cl )
     * @brief       Constructor
     * @param       cl   The instance of DBCollection 
     * @exception   com.sequoiadb.exception.BaseException
     */
    public DBLobConcrete( DBCollection cl ) throws BaseException {
        if ( cl == null ) {
            throw new BaseException( "SDB_INVALIDARG", "cl is null" );
        }
        
        _cl            = cl;
        _endianConvert = cl.getSequoiadb().endianConvert;
    }
    
    /**
     * @fn          open()
     * @brief       create a lob, lob's id will auto generate in this function
     * @exception   com.sequoiadb.exception.BaseException.
     */
    public void open() {
        open( null, SDB_LOB_CREATEONLY );
    }

    /**
     * @fn          open( ObjectId id )
     * @brief       open an exist lob with id
     * @param       id   the lob's id
     * @exception   com.sequoiadb.exception.BaseException.
     */
    public void open( ObjectId id ) {
        open( id, SDB_LOB_READ );
    }
    
    /**
     * @fn          open( ObjectId id, int mode )
     * @brief       open an exist lob, or create a lob
     * @param       id   the lob's id
     * @param       mode available mode is SDB_LOB_CREATEONLY or SDB_LOB_READ.
     *              SDB_LOB_CREATEONLY 
     *                  create a new lob with given id, if id is null, it will 
     *                  be generated in this function;
     *              SDB_LOB_READ
     *                  read an exist lob
     * @exception   com.sequoiadb.exception.BaseException.
     */
    public void open( ObjectId id, int mode ) throws BaseException {
        if ( _isOpen ) {
            throw new BaseException( "SDB_INVALIDARG", "lob have opened:id="
                    + _id );
        }
        
        if ( SDB_LOB_CREATEONLY != mode && SDB_LOB_READ != mode ) {
            throw new BaseException( "SDB_INVALIDARG", "mode is unsupported:" 
                            + mode );
        }
        
        if ( SDB_LOB_READ == mode ) {
            if ( null == id ) {
                throw new BaseException( "SDB_INVALIDARG", "id must be specify"
                        + " in mode:" + mode );
            }
        }
        
        _id = id;
        if ( SDB_LOB_CREATEONLY == mode ) {
            if ( null == _id ) {
                _id = ObjectId.get();
            }
        }

        _mode       = mode;
        _readOffset = 0;

        _open();
        _isOpen = true;
    }
    
    private void _open() throws BaseException {
        BSONObject openLob = new BasicBSONObject();
        openLob.put( SequoiadbConstants.FIELD_COLLECTION, _cl.getFullName() );
        openLob.put( SequoiadbConstants.FIELD_NAME_LOB_OID, _id );
        openLob.put( SequoiadbConstants.FIELD_NAME_LOB_OPEN_MODE, _mode );
        
        byte[] request = generateOpenLobRequest( openLob );
        ByteBuffer res = sendRequest( request, request.length );
        
        SDBMessage resMessage = SDBMessageHelper.msgExtractLobOpenReply( res );
        displayResponse( resMessage );
        if ( resMessage.getOperationCode() != Operation.MSG_BS_LOB_OPEN_RES) {
            throw new BaseException("SDB_UNKNOWN_MESSAGE", 
                    resMessage.getOperationCode());
        }
        int flag = resMessage.getFlags();
        if ( 0 != flag ) {
            throw new BaseException( flag, openLob );
        }
        
        List objList = resMessage.getObjectList();
        if ( objList.size() != 1 ) {
            throw new BaseException( "SDB_NET_BROKEN_MSG", 
                    "objList.size()="+objList.size() );
        }
        
        BSONObject obj =  objList.get(0);
        _size = (Long) obj.get( SequoiadbConstants.FIELD_NAME_LOB_SIZE );
        _createTime = (Long) obj.get( 
                                SequoiadbConstants.FIELD_NAME_LOB_CREATTIME );
        _contextID = resMessage.getContextIDList().get(0);
    }
    
    private ByteBuffer sendRequest( byte[] request, int length )
            throws BaseException {
        if ( request == null ) {
            throw new BaseException( "SDB_INVALIDARG", "request can't be null" );
        }
        
        _cl.getConnection().sendMessage( request, length );
        return _cl.getConnection().receiveMessage(_endianConvert);
    }
    
    /**
     * @fn          getID()
     * @brief       get the lob's id
     * @return      the lob's id
     */
    public ObjectId getID() {
        return _id;
    }

    /**
     * @fn          getSize()
     * @brief       get the size of lob
     * @return      the lob's size
     */
    public long getSize() {
        return _size;
    }
    
    /**
     * @fn          getCreateTime()
     * @brief       get the create time of lob
     * @return      the lob's create time
     */
    public long getCreateTime() {
        return _createTime;
    }
    
    /**
     * @fn          close()
     * @brief       close the lob
     * @exception   com.sequoiadb.exception.BaseException
     */
    public void close() throws BaseException {
        if ( !_isOpen ) {
            return;
        }
        
        byte[] request = generateCloseLobRequest();
        ByteBuffer res = sendRequest( request, request.length );
        
        SDBMessage resMessage = SDBMessageHelper.msgExtractReply( res );
        displayResponse( resMessage );
        if ( resMessage.getOperationCode() != Operation.MSG_BS_LOB_CLOSE_RES) {
            throw new BaseException("SDB_UNKNOWN_MESSAGE", 
                    resMessage.getOperationCode());
        }
        int flag = resMessage.getFlags();
        if ( 0 != flag ) {
            throw new BaseException( flag );
        }
        
        _isOpen = false;
    }
    
    /**
     * @fn          write( byte[] b )
     * @brief       Writes b.length bytes from the specified 
     *              byte array to this lob. 
     * @param       b   the data.
     * @exception   com.sequoiadb.exception.BaseException.
     */
    public void write( byte[] b ) throws BaseException {
        if ( !_isOpen ) {
            throw new BaseException( "SDB_LOB_NOT_OPEN", "lob is not open" );
        }

        if ( b == null ) {
            throw new BaseException( "SDB_INVALIDARG", "input is null" );
        }
        
        if ( b.length <= SDB_LOB_MAX_DATA_LENGTH ) {
            _write( b );
            return;
        }
        
        /* if b.length is more then SDB_LOB_MAX_DATA_LENGTH. we will split 
         * the data to pieces with length=SDB_LOB_MAX_DATA_LENGTH. 
         * besides, data copy is a must in this case.
         */
        int offset = 0;
        while ( b.length > offset ) {
            int leftLen  = b.length - offset;
            int writeLen = leftLen > SDB_LOB_MAX_DATA_LENGTH ? 
                                             SDB_LOB_MAX_DATA_LENGTH : leftLen;
            
            //_write( Arrays.copyOfRange( b, offset, offset + writeLen ) );
            // just for compatible jdk 5
            byte[] tempb = new byte[writeLen];
            int j=0;
            for (int i=offset; i<(offset + writeLen); ++i)
            {
            	tempb[j] = b[i];
            	j++;
            }
            _write (tempb);
            
            offset += writeLen;
        }
    }
    
    /**
     * @fn          read( byte[] b )
     * @brief       Reads up to b.length bytes of data from this 
     *              lob into an array of bytes. 
     * @param       b   the buffer into which the data is read.
     * @return      the total number of bytes read into the buffer, or
     *              -1 if there is no more data because the end of
     *              the file has been reached, or 0 if 
     *              b.length is Zero.
     * @exception   com.sequoiadb.exception.BaseException.
     */
    public int read( byte[] b ) throws BaseException {
        if ( !_isOpen ) {
            throw new BaseException( "SDB_LOB_NOT_OPEN", "lob is not open" );
        }
        
        if ( b == null ) {
            throw new BaseException( "SDB_INVALIDARG", "b is null" );
        }
        
        if ( b.length == 0 ) {
            return 0;
        }

        return _read( b );
    }
    
    /**
     * @fn          seek( long size, int seekType )
     * @brief       change the read position of the lob. The new position is 
     *              obtained by adding size to the position 
     *              specified by seekType. If seekType 
     *              is set to SDB_LOB_SEEK_SET, SDB_LOB_SEEK_CUR, or SDB_LOB_SEEK_END, 
     *              the offset is relative to the start of the lob, the current 
     *              position of lob, or the end of lob.
     * @param       size the adding size.
     * @param       seekType  SDB_LOB_SEEK_SET/SDB_LOB_SEEK_CUR/SDB_LOB_SEEK_END
     * @exception   com.sequoiadb.exception.BaseException.
     */
    public void seek( long size, int seekType ) throws BaseException {
        if ( !_isOpen ) {
            throw new BaseException( "SDB_LOB_NOT_OPEN", "lob is not open" );
        }

        if ( _mode != SDB_LOB_READ ) {
            throw new BaseException( "SDB_INVALIDARG", "seek() is not supported"
                    + "in mode=" + _mode );
        }
        
        if ( SDB_LOB_SEEK_SET == seekType ) {
            if ( size < 0 || size > _size ) {
                throw new BaseException( "SDB_INVALIDARG", "out of bound" );
            }
            
            _readOffset = size;
        }
        else if ( SDB_LOB_SEEK_CUR == seekType ) {
            if ( ( _size < _readOffset + size ) 
                    || ( _readOffset + size < 0 ) ) {
                throw new BaseException( "SDB_INVALIDARG", "out of bound" );
            }
            
            _readOffset += size;
        }
        else if ( SDB_LOB_SEEK_END == seekType ) {
            if ( size < 0 || size > _size ) {
                throw new BaseException( "SDB_INVALIDARG", "out of bound" );
            }
            
            _readOffset = _size - size;
        }
        else {
            throw new BaseException( "SDB_INVALIDARG", "unreconigzed seekType:"
                    + seekType );
        }
    }
    
    private int _read( byte[] b ) {
        byte[] request = generateReadLobRequest( b.length );
        ByteBuffer res = sendRequest( request, request.length );
        
        SDBMessage resMessage = SDBMessageHelper.msgExtractLobReadReply( res );
        displayResponse( resMessage );
        if ( resMessage.getOperationCode() != Operation.MSG_BS_LOB_READ_RES) {
            throw new BaseException("SDB_UNKNOWN_MESSAGE", 
                    resMessage.getOperationCode());
        }
        int flag = resMessage.getFlags();
        
        // meet the end of the lob
        if ( SequoiadbConstants.SDB_EOF == flag ) {
            return -1;
        }
        
        if ( 0 != flag ) {
            throw new BaseException( flag );
        }
        
        byte[] lobData = resMessage.getLobBuff();
        for ( int i = 0; i < lobData.length; i++ ) {
            b[i] = lobData[i];
        }
        
        _readOffset += lobData.length;
        return lobData.length;
    }

    private byte[] generateReadLobRequest( int length ) {
        int totalLen = SDBMessageHelper.MESSAGE_OPLOB_LENGTH 
                + SDBMessageHelper.MESSAGE_LOBTUPLE_LENGTH;
        
        // add _MsgOpLob into buff with convert(db.endianConvert)
        ByteBuffer buff = ByteBuffer.allocate( 
                                SDBMessageHelper.MESSAGE_OPLOB_LENGTH
                                + SDBMessageHelper.MESSAGE_LOBTUPLE_LENGTH );
        if ( _endianConvert ) {
            buff.order( ByteOrder.LITTLE_ENDIAN );
        } else {
            buff.order( ByteOrder.BIG_ENDIAN );
        }
        
        //*******************MsgHeader*******************
        SDBMessageHelper.addLobMsgHeader( buff, totalLen, 
                Operation.MSG_BS_LOB_READ_REQ.getOperationCode(), 
                SequoiadbConstants.ZERO_NODEID, 0 );
        
        //*******************_MsgOpLob**********************
        SDBMessageHelper.addLobOpMsg( buff, SequoiadbConstants.DEFAULT_VERSION, 
                   SequoiadbConstants.DEFAULT_W, (short)0, 
                   SequoiadbConstants.DEFAULT_FLAGS, _contextID, 0 );
        
        //*******************_MsgLobTuple*******************
        addMsgTuple( buff, length, SDB_LOB_DEFAULT_SEQ,
                _readOffset );
        
        return buff.array();
    }

    private void _write( byte[] input ) throws BaseException {
        byte[] request = generateWriteLobRequest( input );
        ByteBuffer res = sendRequest( request, request.length );
        
        SDBMessage resMessage = SDBMessageHelper.msgExtractReply( res );
        displayResponse( resMessage );
        if ( resMessage.getOperationCode() != Operation.MSG_BS_LOB_WRITE_RES) {
            throw new BaseException("SDB_UNKNOWN_MESSAGE", 
                    resMessage.getOperationCode());
        }
        int flag = resMessage.getFlags();
        if ( 0 != flag ) {
            throw new BaseException( flag );
        }
        
        _size += input.length;
    }
    
    private byte[] generateWriteLobRequest( byte[] input ) {
        int totalLen = SDBMessageHelper.MESSAGE_OPLOB_LENGTH 
                + SDBMessageHelper.MESSAGE_LOBTUPLE_LENGTH 
                + Helper.roundToMultipleXLength( input.length, 4 );
        
        // add _MsgOpLob into buff with convert(db.endianConvert)
        ByteBuffer buff = ByteBuffer.allocate( 
                                SDBMessageHelper.MESSAGE_OPLOB_LENGTH
                                + SDBMessageHelper.MESSAGE_LOBTUPLE_LENGTH );
        if ( _endianConvert ) {
            buff.order( ByteOrder.LITTLE_ENDIAN );
        } else {
            buff.order( ByteOrder.BIG_ENDIAN );
        }
        
        //*******************MsgHeader*******************
        SDBMessageHelper.addLobMsgHeader( buff, totalLen, 
                Operation.MSG_BS_LOB_WRITE_REQ.getOperationCode(), 
                SequoiadbConstants.ZERO_NODEID, 0 );
        
        //*******************_MsgOpLob**********************
        SDBMessageHelper.addLobOpMsg( buff, SequoiadbConstants.DEFAULT_VERSION, 
                   SequoiadbConstants.DEFAULT_W, (short)0, 
                   SequoiadbConstants.DEFAULT_FLAGS, _contextID, 0 );
        
        //*******************_MsgLobTuple*******************
        addMsgTuple( buff, input.length, SDB_LOB_DEFAULT_SEQ,
                SDB_LOB_DEFAULT_OFFSET );
        
        List buffList = new ArrayList();
        buffList.add( buff.array() );
        buffList.add( Helper.roundToMultipleX( input, 4 ) );
        
        return Helper.concatByteArray( buffList );
    }

    private void addMsgTuple( ByteBuffer buff, int length, int sequence,
            long offset ) {
        
        buff.putInt( length );
        buff.putInt( sequence );
        buff.putLong( offset );
    }

    private byte[] generateCloseLobRequest() {
        int totalLen = SDBMessageHelper.MESSAGE_OPLOB_LENGTH;
        
        // add _MsgOpLob into buff with convert(db.endianConvert)
        ByteBuffer buff = ByteBuffer.allocate( 
                                SDBMessageHelper.MESSAGE_OPLOB_LENGTH );
        if ( _endianConvert ) {
            buff.order( ByteOrder.LITTLE_ENDIAN );
        } else {
            buff.order( ByteOrder.BIG_ENDIAN );
        }
        
        //*******************MsgHeader*******************
        SDBMessageHelper.addLobMsgHeader( buff, totalLen, 
                Operation.MSG_BS_LOB_CLOSE_REQ.getOperationCode(), 
                SequoiadbConstants.ZERO_NODEID, 0 );
        
        //*******************_MsgOpLob**********************
        SDBMessageHelper.addLobOpMsg( buff, SequoiadbConstants.DEFAULT_VERSION, 
                   SequoiadbConstants.DEFAULT_W, (short)0, 
                   SequoiadbConstants.DEFAULT_FLAGS, _contextID, 0 );
        
        return buff.array();
    }
    
    private byte[] generateOpenLobRequest( BSONObject openLob ) {
        
        byte bOpenLob[] = SDBMessageHelper.bsonObjectToByteArray( openLob );
        int totalLen = SDBMessageHelper.MESSAGE_OPLOB_LENGTH 
                        + Helper.roundToMultipleXLength( bOpenLob.length, 4 );
        
        // convert the openLob's buff
        if ( !_endianConvert ) {
            SDBMessageHelper.bsonEndianConvert( bOpenLob, 0, bOpenLob.length, 
                    true );
        }
        
        // add _MsgOpLob into buff with convert(db.endianConvert)
        ByteBuffer buff = ByteBuffer.allocate( 
                                SDBMessageHelper.MESSAGE_OPLOB_LENGTH );
        if ( _endianConvert ) {
            buff.order( ByteOrder.LITTLE_ENDIAN );
        } else {
            buff.order( ByteOrder.BIG_ENDIAN );
        }
        
        //*******************MsgHeader*******************
        SDBMessageHelper.addLobMsgHeader( buff, totalLen, 
                Operation.MSG_BS_LOB_OPEN_REQ.getOperationCode(), 
                SequoiadbConstants.ZERO_NODEID, 0 );
        
        //*******************_MsgOpLob**********************
        SDBMessageHelper.addLobOpMsg( buff, SequoiadbConstants.DEFAULT_VERSION, 
                   SequoiadbConstants.DEFAULT_W, (short)0, 
                   SequoiadbConstants.DEFAULT_FLAGS, 
                   SequoiadbConstants.DEFAULT_CONTEXTID, bOpenLob.length );
        
        List buffList = new ArrayList();
        buffList.add( buff.array() );
        buffList.add( Helper.roundToMultipleX( bOpenLob, 4 ) );

        return Helper.concatByteArray( buffList );
    }
    
    private void displayResponse( SDBMessage resMessage ) {
//        int flag = resMessage.getFlags();
//        System.out.println( "flags=" + flag );
//        List objList = resMessage.getObjectList();
//        if ( objList != null ) {
//            for ( int i = 0; i < objList.size(); i++ ) {
//                BSONObject obj = objList.get( i );
//                System.out.println( "obj " + i + ":" + obj.toString() );
//            }
//        }
    }
}