com.sequoiadb.base.DBLobImpl Maven / Gradle / Ivy
Show all versions of sequoiadb-driver Show documentation
/*
* Copyright 2018 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;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.ByteBuffer;
import org.bson.BSONObject;
import org.bson.BasicBSONObject;
import org.bson.types.ObjectId;
import com.sequoiadb.exception.BaseException;
import com.sequoiadb.exception.SDBError;
import com.sequoiadb.message.ResultSet;
import com.sequoiadb.message.request.LobCloseRequest;
import com.sequoiadb.message.request.LobLockRequest;
import com.sequoiadb.message.request.LobOpenRequest;
import com.sequoiadb.message.request.LobReadRequest;
import com.sequoiadb.message.request.LobRuntimeDetailRequest;
import com.sequoiadb.message.request.LobWriteRequest;
import com.sequoiadb.message.response.LobOpenResponse;
import com.sequoiadb.message.response.LobReadResponse;
import com.sequoiadb.message.response.SdbReply;
import com.sequoiadb.util.Helper;
class DBLobImpl implements DBLob {
final static String FIELD_NAME_LOB_OPEN_MODE = "Mode";
final static String FIELD_NAME_LOB_OID = "Oid";
final static String FIELD_NAME_LOB_SIZE = "Size";
final static String FIELD_NAME_LOB_CREATE_TIME = "CreateTime";
final static String FIELD_NAME_LOB_MODIFICATION_TIME = "ModificationTime";
final static String FIELD_NAME_LOB_PAGESIZE = "LobPageSize";
final static String FIELD_NAME_LOB_LENGTH = "Length";
final static int SDB_LOB_CREATEONLY = 0x00000001;
// the max lob data size to send for one message
private final static int SDB_LOB_MAX_WRITE_DATA_LENGTH = 2097152; // 2M;
private final static int SDB_LOB_WRITE_DATA_LENGTH = 524288; // 512k;
private final static int SDB_LOB_READ_DATA_LENGTH = 65536; // 64k;
private final static int SDB_LOB_ALIGNED_LEN = 524288; // 512k;
private final static int FLG_LOBOPEN_WITH_RETURNDATA = 0X00000002;
private Sequoiadb _sdb;
private DBCollection _cl;
private ObjectId _id;
private int _mode;
private int _pageSize;
private long _lobSize;
private long _createTime;
private long _modificationTime;
private long _currentOffset = 0;
private long _cachedOffset = -1;
private ByteBuffer _cachedDataBuff = null;
private ByteBuffer _receivedBuff = null;
private boolean _isOpened = false;
private boolean _isOldVersionLobServer = false;
/*
* when first open/create DBLob, sequoiadb return the contextID for the further
* reading/writing/close
*/
private long _contextID;
private byte[] _tmpWriteBuf = null;
private byte[] _tmpReadBuf = null;
/**
* @param cl The instance of DBCollection
* @throws BaseException If error happens.
*/
DBLobImpl(DBCollection cl) throws BaseException {
if (cl == null) {
throw new BaseException(SDBError.SDB_INVALIDARG, "cl is null");
}
_cl = cl;
_sdb = cl.getSequoiadb();
}
public static boolean isReadOnlyMode(int mode) {
return mode == DBLob.SDB_LOB_READ || mode == DBLob.SDB_LOB_SHAREREAD;
}
public static boolean isReadWriteMode(int mode) {
return (mode == (DBLob.SDB_LOB_SHAREREAD | DBLob.SDB_LOB_WRITE));
}
public static boolean hasWriteMode(int mode) {
return (mode == DBLob.SDB_LOB_WRITE || isReadWriteMode(mode));
}
/**
* Create a lob, lob's id will auto generate in this function.
*
* @param id the lob's id
* @throws BaseException If error happens. public void open() { open(null, SDB_LOB_CREATEONLY); }
*
* /** Open an existing lob with id.
* @throws BaseException If error happens.
*/
public void open(ObjectId id) {
open(id, SDB_LOB_READ);
}
/**
* Open an existing 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
* @throws BaseException If error happens.
*/
public void open(ObjectId id, int mode) throws BaseException {
if (_isOpened) {
throw new BaseException(SDBError.SDB_INVALIDARG, "lob have opened: id = " + _id);
}
if (mode != SDB_LOB_CREATEONLY && !hasWriteMode(mode) && !isReadOnlyMode(mode)) {
throw new BaseException(SDBError.SDB_INVALIDARG, "mode is unsupported: " + mode);
}
if (mode != SDB_LOB_CREATEONLY) {
if (id == null) {
throw new BaseException(SDBError.SDB_INVALIDARG,
"id must be specify" + " in mode:" + mode);
}
}
_mode = mode;
_currentOffset = 0;
_id = id;
// going to read and write lob
if (_mode != SDB_LOB_CREATEONLY) {
_open();
_isOpened = true;
return;
}
// when _sdb tell us clearly that the remote engine is old(older than v3.2.4),
// we just need to create lob by the original way which we need to make
// sure _id is not empty.
if (_sdb.getIsOldVersionLobServer()) {
// deal with old version server. oid should be generated in client.
if (_id == null) {
_id = ObjectId.get();
}
_open();
_isOpened = true;
return;
}
// however, when we come here, we still do not know whether the remote engine
// is old or not. we assume it is new, so we won't force _id must has value,
// for the new engine will create one when _id is null(while the old engine will not,
// and when _id is null, the old engine will return -6).
try {
_isOldVersionLobServer = false;
_open();
_isOpened = true;
return;
} catch (BaseException e) {
if (!_isOldVersionLobServer) {
throw e;
}
}
// when we come here, we must had got an -6 error from the engine for
// not offering _id. so, _id must be null. and the remote engine must
// be an old engine, in this case, oid should be generated in client
// for the old engine.
_id = ObjectId.get();
_open();
_sdb.setIsOldVersionLobServer(true);
_isOpened = true;
}
private void _open() throws BaseException {
BSONObject openLob = new BasicBSONObject();
openLob.put(SdbConstants.FIELD_COLLECTION, _cl.getFullName());
if (_id != null) {
openLob.put(FIELD_NAME_LOB_OID, _id);
}
openLob.put(FIELD_NAME_LOB_OPEN_MODE, _mode);
int flags = (_mode == SDB_LOB_READ) ? FLG_LOBOPEN_WITH_RETURNDATA : 0;
LobOpenRequest request = new LobOpenRequest(openLob, flags);
LobOpenResponse response = _sdb.requestAndResponse(request, LobOpenResponse.class, _receivedBuff);
if (response.getFlag() == SDBError.SDB_INVALIDARG.getErrorCode() && _id == null
&& _mode == SDB_LOB_CREATEONLY) {
_isOldVersionLobServer = true;
}
_sdb.throwIfError(response, openLob);
BSONObject obj = response.getMetaInfo();
if (_id == null && obj.containsField(FIELD_NAME_LOB_OID) && _mode == SDB_LOB_CREATEONLY) {
_id = (ObjectId) obj.get(FIELD_NAME_LOB_OID);
}
_lobSize = (Long) obj.get(FIELD_NAME_LOB_SIZE);
_createTime = (Long) obj.get(FIELD_NAME_LOB_CREATE_TIME);
if (obj.containsField(FIELD_NAME_LOB_MODIFICATION_TIME)) {
_modificationTime = (Long) obj.get(FIELD_NAME_LOB_MODIFICATION_TIME);
} else {
_modificationTime = _createTime;
}
_pageSize = (Integer) obj.get(FIELD_NAME_LOB_PAGESIZE);
// refresh _receivedBuff
_receivedBuff = response.getData();
// get return data
_cachedDataBuff = _receivedBuff;
if (_cachedDataBuff != null) {
_currentOffset = 0;
_cachedOffset = _currentOffset;
}
_contextID = response.getContextId();
}
/**
* Get the lob's id.
*
* @return the lob's id
*/
@Override
public ObjectId getID() {
return _id;
}
/**
* Get the size of lob.
*
* @return the lob's size
*/
@Override
public long getSize() {
return _lobSize;
}
/**
* Get the create time of lob.
*
* @return the lob's create time
*/
@Override
public long getCreateTime() {
return _createTime;
}
/**
* Get the last modification time of lob.
*
* @return the lob's last modification time
*/
@Override
public long getModificationTime() {
return _modificationTime;
}
/**
* Close the lob.
*
* @throws BaseException If error happens.
*/
@Override
public void close() throws BaseException {
if (!_isOpened) {
return;
}
// clean buff
_sdb.cleanRequestBuff();
_receivedBuff = null;
_cachedDataBuff = null;
_tmpReadBuf = null;
_tmpWriteBuf = null;
LobCloseRequest request = new LobCloseRequest(_contextID);
SdbReply response = _sdb.requestAndResponse(request);
_sdb.throwIfError(response);
_isOpened = false;
if (response.getReturnedNum() > 0) {
ResultSet resultSet = response.getResultSet();
BSONObject obj = resultSet.getNext();
if (obj != null && obj.containsField(FIELD_NAME_LOB_MODIFICATION_TIME)) {
_modificationTime = (Long) obj.get(FIELD_NAME_LOB_MODIFICATION_TIME);
}
}
}
/**
* Write bytes from the input stream to this lob.
*
* @param in the input stream.
* @throws BaseException If error happens.
*/
@Override
public void write(InputStream in) throws BaseException {
if (!_isOpened) {
throw new BaseException(SDBError.SDB_LOB_NOT_OPEN, "lob is not open");
}
if (in == null) {
throw new BaseException(SDBError.SDB_INVALIDARG, "input is null");
}
// get data from input stream
int readNum = 0;
if (_tmpWriteBuf == null){
_tmpWriteBuf = new byte[SDB_LOB_WRITE_DATA_LENGTH];
}
try {
while (-1 < (readNum = in.read(_tmpWriteBuf))) {
write(_tmpWriteBuf, 0, readNum);
}
} catch (IOException e) {
throw new BaseException(SDBError.SDB_SYS, e);
}
}
/**
* Write b.length
bytes from the specified byte array to this lob.
*
* @param b the data.
* @throws BaseException If error happens.
*/
@Override
public void write(byte[] b) throws BaseException {
write(b, 0, b.length);
}
/**
* Write len
bytes from the specified byte array starting at offset
* off
to this lob.
*
* @param b the data.
* @param off the start offset in the data.
* @param len the number of bytes to write.
* @throws BaseException If error happens.
*/
@Override
public void write(byte[] b, int off, int len) throws BaseException {
if (!_isOpened) {
throw new BaseException(SDBError.SDB_LOB_NOT_OPEN, "lob is not open");
}
if (b == null) {
throw new BaseException(SDBError.SDB_INVALIDARG, "input is null");
}
if (len < 0 || len > b.length) {
throw new BaseException(SDBError.SDB_INVALIDARG, "invalid len");
}
if (off < 0 || off > b.length) {
throw new BaseException(SDBError.SDB_INVALIDARG, "invalid off");
}
if (off + len > b.length) {
throw new BaseException(SDBError.SDB_INVALIDARG, "off + len is great than b.length");
}
if ( SDB_LOB_CREATEONLY != _mode && !hasWriteMode(_mode) ){
throw new BaseException(SDBError.SDB_INVALIDARG, "invalid mode for writing");
}
if (isReadWriteMode(_mode)) {
//clean the read cache
_cachedOffset = -1;
_cachedDataBuff = null;
}
int offset = off;
int leftLen = len;
int writeLen = 0;
while (leftLen > 0) {
writeLen = (leftLen < SDB_LOB_MAX_WRITE_DATA_LENGTH) ? leftLen
: SDB_LOB_MAX_WRITE_DATA_LENGTH;
_write(b, offset, writeLen);
leftLen -= writeLen;
offset += writeLen;
}
}
/**
* Read data from this lob into the output stream.
*
* @param out the output stream.
* @throws BaseException If error happens.
*/
@Override
public void read(OutputStream out) throws BaseException {
if (!_isOpened) {
throw new BaseException(SDBError.SDB_LOB_NOT_OPEN, "lob is not open");
}
if (out == null) {
throw new BaseException(SDBError.SDB_INVALIDARG, "output stream is null");
}
// read data to output stream
int readNum = 0;
if (_tmpReadBuf == null){
_tmpReadBuf = new byte[SDB_LOB_READ_DATA_LENGTH];
}
while (-1 < (readNum = read(_tmpReadBuf, 0, _tmpReadBuf.length))) {
try {
out.write(_tmpReadBuf, 0, readNum);
} catch (IOException e) {
throw new BaseException(SDBError.SDB_SYS, e);
}
}
}
/**
* Read 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.
* @throws BaseException If error happens.
*/
@Override
public int read(byte[] b) throws BaseException {
return read(b, 0, b.length);
}
/**
* Read up to len
bytes of data from this lob into an array of bytes.
*
* @param b the buffer into which the data is read.
* @param off the start offset in the destination array b
.
* @param len the maximum number of bytes 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
* len
is Zero.
* @throws BaseException If error happens.
*/
@Override
public int read(byte[] b, int off, int len) throws BaseException {
if (!_isOpened) {
throw new BaseException(SDBError.SDB_LOB_NOT_OPEN, "lob is not open");
}
if (b == null) {
throw new BaseException(SDBError.SDB_INVALIDARG, "b is null");
}
if (len < 0 || len > b.length) {
throw new BaseException(SDBError.SDB_INVALIDARG, "invalid len");
}
if (off < 0 || off > b.length) {
throw new BaseException(SDBError.SDB_INVALIDARG, "invalid off");
}
if (off + len > b.length) {
throw new BaseException(SDBError.SDB_INVALIDARG, "off + len is great than b.length");
}
if (b.length == 0) {
return 0;
}
if (!isReadOnlyMode(_mode) && !isReadWriteMode(_mode)){
throw new BaseException(SDBError.SDB_INVALIDARG, "invalid mode for reading");
}
return _read(b, off, len);
}
/**
* Change the read or write 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
* @throws BaseException If error happens.
*/
@Override
public void seek(long size, int seekType) throws BaseException {
if (!_isOpened) {
throw new BaseException(SDBError.SDB_LOB_NOT_OPEN, "lob is not open");
}
if (!DBLobImpl.hasWriteMode(_mode) && !DBLobImpl.isReadOnlyMode(_mode)
&& _mode != SDB_LOB_CREATEONLY) {
throw new BaseException(SDBError.SDB_OPTION_NOT_SUPPORT,
"seek() is not supported" + " in mode=" + _mode);
}
if (SDB_LOB_SEEK_SET == seekType) {
if (size < 0 || (size >= _lobSize && _mode == SDB_LOB_READ)) {
throw new BaseException(SDBError.SDB_INVALIDARG,
"out of bound, lobSize=" + _lobSize);
}
_currentOffset = size;
} else if (SDB_LOB_SEEK_CUR == seekType) {
if ((_currentOffset + size >= _lobSize && _mode == SDB_LOB_READ)
|| (_currentOffset + size < 0)) {
throw new BaseException(SDBError.SDB_INVALIDARG,
"out of bound, _currentOffset=" + _currentOffset + ", lobSize=" + _lobSize);
}
_currentOffset += size;
} else if (SDB_LOB_SEEK_END == seekType) {
if (size < 0 || (size > _lobSize && _mode == SDB_LOB_READ)) {
throw new BaseException(SDBError.SDB_INVALIDARG,
"out of bound, lobSize=" + _lobSize);
}
_currentOffset = _lobSize - size;
} else {
throw new BaseException(SDBError.SDB_INVALIDARG, "unreconigzed seekType: " + seekType);
}
}
/**
* Lock LOB section for writing.
*
* @param offset lock start position
* @param length lock length, -1 means lock to the end of lob
* @throws BaseException If error happens..
*/
@Override
public void lock(long offset, long length) throws BaseException {
if (!_isOpened) {
throw new BaseException(SDBError.SDB_LOB_NOT_OPEN, "lob is not open");
}
if (offset < 0 || length < -1 || length == 0) {
throw new BaseException(SDBError.SDB_INVALIDARG,
"out of bound, offset=" + offset + ", length=" + length);
}
if (!hasWriteMode(_mode) && _mode != SDB_LOB_SHAREREAD) {
return;
}
LobLockRequest request = new LobLockRequest(_contextID, offset, length);
SdbReply response = _sdb.requestAndResponse(request);
_sdb.throwIfError(response);
}
/**
* Lock LOB section for writing and seek to the offset position.
*
* @param offset lock start position
* @param length lock length, -1 means lock to the end of lob
* @throws BaseException If error happens..
*/
@Override
public void lockAndSeek(long offset, long length) throws BaseException {
lock(offset, length);
seek(offset, SDB_LOB_SEEK_SET);
}
/**
* Check whether current offset has reached to the max size of current lob.
*
* @return Return true if yes while false for not.
*/
@Override
public boolean isEof() {
return _currentOffset >= _lobSize;
}
private int _reviseReadLen(int needLen) {
int mod = (int) (_currentOffset & (_pageSize - 1));
// when "needLen" is great than (2^31 - 1) - 3,
// alignedLen" will be less than 0, but we should not worry
// about this, because before we finish using the cached data,
// we won't come here, at that moment, "alignedLen" will be not be less
// than "needLen"
int alignedLen = Helper.alignedSize(needLen, SDB_LOB_ALIGNED_LEN);
if (alignedLen < needLen) {
alignedLen = SDB_LOB_ALIGNED_LEN;
}
alignedLen -= mod;
if (alignedLen < SDB_LOB_ALIGNED_LEN) {
alignedLen += SDB_LOB_ALIGNED_LEN;
}
return alignedLen;
}
private boolean _hasDataCached() {
int remaining = (_cachedDataBuff != null) ? _cachedDataBuff.remaining() : 0;
return (_cachedDataBuff != null && 0 < remaining && 0 <= _cachedOffset
&& _cachedOffset <= _currentOffset && _currentOffset < (_cachedOffset + remaining));
}
private int _readInCache(byte[] buf, int off, int needRead) {
if (needRead > buf.length - off) {
throw new BaseException(SDBError.SDB_SYS, "buf size is to small");
}
int readInCache = (int) (_cachedOffset + _cachedDataBuff.remaining() - _currentOffset);
readInCache = readInCache <= needRead ? readInCache : needRead;
// if we had used "lobSeek" to adjust "_currentOffset",
// let's adjust the right place to copy data
if (_currentOffset > _cachedOffset) {
int currentPos = _cachedDataBuff.position();
int newPos = currentPos + (int) (_currentOffset - _cachedOffset);
_cachedDataBuff.position(newPos);
}
// copy the data from cache out to the buf for user
_cachedDataBuff.get(buf, off, readInCache);
if (_cachedDataBuff.remaining() == 0) {
_cachedDataBuff = null;
} else {
_cachedOffset = _currentOffset + readInCache;
}
return readInCache;
}
private int _onceRead(byte[] buf, int off, int len) {
int needRead = len;
int totalRead = 0;
int onceRead = 0;
int alignedLen = 0;
// try to get data from local cache
if (_hasDataCached()) {
onceRead = _readInCache(buf, off, needRead);
totalRead += onceRead;
needRead -= onceRead;
_currentOffset += onceRead;
return totalRead;
}
// get data from database
_cachedOffset = -1;
_cachedDataBuff = null;
// page align
alignedLen = _reviseReadLen(needRead);
LobReadRequest request = new LobReadRequest(_contextID, alignedLen, _currentOffset);
LobReadResponse response = _sdb.requestAndResponse(request, LobReadResponse.class, _receivedBuff);
int rc = response.getFlag();
if (rc == SDBError.SDB_EOF.getErrorCode()) {
return -1; // meet the end of the lob
} else if (rc != 0) {
_sdb.throwIfError(response);
}
long offsetInEngine = response.getOffset();
if (_currentOffset != offsetInEngine) {
throw new BaseException(SDBError.SDB_SYS, "local read offset(" + _currentOffset
+ ") is not equal with what we expect(" + offsetInEngine + ")");
}
int retLobLen = response.getLobLen();
// refresh _receivedBuff
_receivedBuff = response.getData();
// get return data
_cachedDataBuff = _receivedBuff;
if (_cachedDataBuff == null){
throw new BaseException(SDBError.SDB_SYS, "The returned data is empty");
}
// sanity check
int remainLen = _cachedDataBuff.remaining();
if (remainLen != retLobLen) {
throw new BaseException(SDBError.SDB_SYS, "the remaining in buffer(" + remainLen
+ ") is not equal with what we expect(" + retLobLen + ")");
}
// if what we got is more than what we expect,
// let's cache some for next reading request.
if (needRead < retLobLen) {
_cachedDataBuff.get(buf, off, needRead);
totalRead += needRead;
_currentOffset += needRead;
_cachedOffset = _currentOffset;
} else {
_cachedDataBuff.get(buf, off, retLobLen);
totalRead += retLobLen;
_currentOffset += retLobLen;
_cachedOffset = -1;
_cachedDataBuff = null;
}
return totalRead;
}
private int _read(byte[] b, int off, int len) {
int offset = off;
int needRead = len;
int onceRead = 0;
int totalRead = 0;
// when no data for return
if (_currentOffset == _lobSize) {
return -1;
}
while (needRead > 0 && _currentOffset < _lobSize) {
onceRead = _onceRead(b, offset, needRead);
if (onceRead == -1) {
if (totalRead == 0) {
totalRead = -1;
}
// when we finish read, let's stop
break;
}
offset += onceRead;
needRead -= onceRead;
totalRead += onceRead;
onceRead = 0;
}
return totalRead;
}
private void _write(byte[] input, int off, int len) throws BaseException {
if (len == 0) {
return;
}
LobWriteRequest request = new LobWriteRequest(_contextID, input, off, len, _currentOffset);
SdbReply response = _sdb.requestAndResponse(request);
_sdb.throwIfError(response);
_currentOffset += len;
_lobSize = Math.max(_lobSize, _currentOffset);
}
@Override
public BSONObject getRunTimeDetail() throws BaseException {
LobRuntimeDetailRequest request = new LobRuntimeDetailRequest(_contextID);
SdbReply response = _sdb.requestAndResponse(request);
_sdb.throwIfError(response);
ResultSet r = response.getResultSet();
if (!r.hasNext()) {
throw new BaseException(SDBError.SDB_INVALIDARG, "Response must have obj");
}
BSONObject o = r.getNext();
return o;
}
}