src.com.ibm.as400.access.AS400JDBCClobLocator Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of jt400-jdk8 Show documentation
Show all versions of jt400-jdk8 Show documentation
The Open Source version of the IBM Toolbox for Java
///////////////////////////////////////////////////////////////////////////////
//
// JTOpen (IBM Toolbox for Java - OSS version)
//
// Filename: AS400JDBCClobLocator.java
//
// The source code contained herein is licensed under the IBM Public License
// Version 1.0, which has been approved by the Open Source Initiative.
// Copyright (C) 1997-2006 International Business Machines Corporation and
// others. All rights reserved.
//
///////////////////////////////////////////////////////////////////////////////
package com.ibm.as400.access;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.Reader;
import java.io.UnsupportedEncodingException;
import java.io.Writer;
import java.sql.Clob;
import java.sql.SQLException;
// Note: This code in this class requires understanding of bit manipulation
// and sign extension. Do not attempt to rework this code if you do not
// have a grasp of these concepts.
// Currently, the database host server only supports 2 GB LOBs. Therefore,
// we validate any long parameters to make sure they are not greater than
// the maximum positive value for a 4-byte int (2 GB). This has the added
// bonus of being able to cast the long down to an int without worrying
// about sign extension. There are some cases where we could allow the
// user to pass in a long greater than 2 GB, but for consistency, we will
// throw an exception.
// Offset refers to a 0-based index. Position refers to a 1-based index.
// In the event that the column in the database is a DBCLOB, know that
// JDLobLocator knows that it is graphic and will correctly convert
// the byte offsets and lengths into character offsets and lengths.
// For the case where the column has a CCSID with variable size
// characters, all the data will be needed to be retrieved and
// translated for request requiring the length of the lob, as
// well as for requests requiring a character offset into the lob.
/**
The AS400JDBCClobLocator class provides access to character large
objects. The data is valid only within the current
transaction.
**/
public class AS400JDBCClobLocator implements Clob
{
protected ConvTable converter_; //@pdc jdbc40
JDLobLocator locator_;
Object savedObject_; // This is our InputStream or byte[] or whatever that needs to be written if we are batching.
int savedScale_; // This is our length that goes with our savedObject_.
private char[] cache_;
private int cacheOffset_;
private static final char[] INIT_CACHE = new char[0];
//private int truncate_ = -1;
protected int maxLength_; // The max length in LOB-characters. See JDLobLocator. //@pdc jdbc40
private boolean isXML_ = false; //@xml3 true if this data originated from a native XML column type
/**
Constructs an AS400JDBCClobLocator object. The data for the
CLOB will be retrieved as requested, directly from the
IBM i system, using the locator handle.
@param locator The locator.
@param converter The text converter.
**/
AS400JDBCClobLocator(JDLobLocator locator, ConvTable converter, Object savedObject, int savedScale)
{
locator_ = locator;
converter_ = converter;
savedObject_ = savedObject;
savedScale_ = savedScale;
maxLength_ = locator_.getMaxLength();
}
//@xml3 new constructor
/**
Constructs an AS400JDBCClobLocator object. The data for the
CLOB will be retrieved as requested, directly from the
IBM i system, using the locator handle.
If this clob has a source of a columne of type XML, then any getX method that returns xml as string will trim the xml declaration.
@param locator The locator.
@param converter The text converter.
@param savedObject Input data
@param savedScale Inpuat scale of data
@param isXML Flag that stream is from an XML column type (needed to strip xml declaration)
**/
AS400JDBCClobLocator(JDLobLocator locator, ConvTable converter, Object savedObject, int savedScale, boolean isXML)
{
this(locator, converter, savedObject, savedScale);
isXML_ = isXML;
}
/**
Returns the entire CLOB as a stream of ASCII characters.
@return The stream.
@exception SQLException If an error occurs.
**/
public InputStream getAsciiStream() throws SQLException
{
synchronized(this)
{
//Following Native, throw HY010 after free() has been called. Note: NullPointerException if synchronized(null-ref)
if(locator_ == null)//@free
JDError.throwSQLException(this, JDError.EXC_FUNCTION_SEQUENCE); //@free
try
{
//@xml3 if xml column, remove xml declaration via ConvTableReader
return new ReaderInputStream(new ConvTableReader(new AS400JDBCInputStream(locator_), converter_.getCcsid(), converter_.bidiStringType_, isXML_), 819); // ISO 8859-1. //@xml3
}
catch (UnsupportedEncodingException e)
{
JDError.throwSQLException(this, JDError.EXC_INTERNAL, e);
return null;
}
}
}
/**
Returns the entire CLOB as a character stream.
@return The stream.
@exception SQLException If an error occurs.
**/
public Reader getCharacterStream() throws SQLException
{
synchronized(this)
{
if(locator_ == null)//@free
JDError.throwSQLException(this, JDError.EXC_FUNCTION_SEQUENCE); //@free
try
{
//@xml3 if xml column, remove xml declaration via ConvTableReader
return new ConvTableReader(new AS400JDBCInputStream(locator_), converter_.getCcsid(), converter_.bidiStringType_, isXML_); //@xml3
}
catch (UnsupportedEncodingException e)
{
JDError.throwSQLException(this, JDError.EXC_INTERNAL, e);
return null;
}
}
}
/**
Returns the handle to this CLOB locator in the database.
@return The handle to this locator in the databaes.
**/
int getHandle()throws SQLException //@free called from rs.updateValue(), which in turn will throw exc back to rs.updateX() caller
{
if(locator_ == null)//@free
JDError.throwSQLException(this, JDError.EXC_FUNCTION_SEQUENCE); //@free
return locator_.getHandle();
}
/**
Returns part of the contents of the CLOB.
@param position The position within the CLOB (1-based).
@param length The number of characters to return.
@return The contents.
@exception SQLException If the position is not valid,
if the length is not valid,
or an error occurs.
**/
public String getSubString(long position, int length) throws SQLException
{
synchronized(this)
{
if(locator_ == null)//@free
JDError.throwSQLException(this, JDError.EXC_FUNCTION_SEQUENCE); //@free
int offset = (int)position-1;
if (offset < 0 || length < 0 || (offset > length()) )
{
JDError.throwSQLException(this, JDError.EXC_ATTRIBUTE_VALUE_INVALID);
}
int lengthToUse = (int)length() - offset;
if (lengthToUse < 0) return "";
if (lengthToUse > length) lengthToUse = length;
//@xml4 if xml column, remove xml declaration via ConvTableReader
if(isXML_)
{
ConvTableReader r = null;
try{
r = new ConvTableReader(new AS400JDBCInputStream( locator_), converter_.getCcsid(), converter_.bidiStringType_, isXML_); //@xml4
r.skip(offset); //@xml4 ConvTableReader will already have skipped XML header if column is XML type
return r.read(lengthToUse); //@xml4
}
catch ( Exception e)
{
JDError.throwSQLException(this, JDError.EXC_INTERNAL, e);
return null;
}
finally{
try{
if (r != null ) r.close();
}catch(Exception ee){
JDTrace.logException(this, "getSubString r.close() threw exception", ee);
}
}
}
DBLobData data = locator_.retrieveData(offset, lengthToUse);
int actualLength = data.getLength();
return converter_.byteArrayToString(data.getRawBytes(), data.getOffset(), actualLength);
}
}
/**
Returns the length of the current contents of the CLOB in characters.
@return The length of the CLOB in characters.
@exception SQLException If an error occurs.
**/
public long length() throws SQLException
{
synchronized(this)
{
if(locator_ == null)//@free
JDError.throwSQLException(this, JDError.EXC_FUNCTION_SEQUENCE); //@free
return locator_.getLength();
}
}
// Used for position().
private void initCache()
{
cacheOffset_ = 0;
cache_ = INIT_CACHE;
}
// Used for position().
private int getCachedChar(int index) throws SQLException
{
int realIndex = index - cacheOffset_;
if (realIndex >= cache_.length)
{
int blockSize = AS400JDBCPreparedStatement.LOB_BLOCK_SIZE;
int len = (int)length();
if (len < 0) len = 0x7FFFFFFF;
if ((blockSize+index) > len) blockSize = len-index;
cache_ = getSubString(index+1, blockSize).toCharArray();
cacheOffset_ = index;
realIndex = 0;
}
if (cache_.length == 0) return -1;
return cache_[realIndex];
}
/**
Returns the position at which a pattern is found in the CLOB.
This method is not supported.
@param pattern The pattern.
@param position The position within the CLOB to begin
searching (1-based).
@return The position in the CLOB at which the pattern was found,
or -1 if the pattern was not found.
@exception SQLException If the pattern is null,
the position is not valid,
or an error occurs.
**/
public long position(String pattern, long position) throws SQLException
{
synchronized(this)
{
if(locator_ == null)//@free
JDError.throwSQLException(this, JDError.EXC_FUNCTION_SEQUENCE); //@free
int offset = (int)position-1;
if (pattern == null || offset < 0 || offset >= length())
{
throw JDError.throwSQLException(this, JDError.EXC_ATTRIBUTE_VALUE_INVALID);
}
char[] charPattern = pattern.toCharArray();
int end = (int)length() - charPattern.length;
// We use a cache of chars so we don't have to read in the entire
// contents of the CLOB.
initCache();
for (int i=offset; i<=end; ++i)
{
int j = 0;
int cachedChar = getCachedChar(i+j);
while (j < charPattern.length && cachedChar != -1 && charPattern[j] == cachedChar)
{
++j;
cachedChar = getCachedChar(i+j);
}
if (j == charPattern.length) return i+1;
}
return -1;
}
}
/**
Returns the position at which a pattern is found in the CLOB.
This method is not supported.
@param pattern The pattern.
@param position The position within the CLOB to begin
searching (1-based).
@return The position in the CLOB at which the pattern was found,
or -1 if the pattern was not found.
@exception SQLException If the pattern is null,
the position is not valid,
or an error occurs.
**/
public long position(Clob pattern, long position) throws SQLException
{
synchronized(this)
{
if(locator_ == null)//@free
JDError.throwSQLException(this, JDError.EXC_FUNCTION_SEQUENCE); //@free
int offset = (int)position-1;
if (pattern == null || offset < 0 || offset >= length())
{
throw JDError.throwSQLException(this, JDError.EXC_ATTRIBUTE_VALUE_INVALID);
}
int patternLength = (int)pattern.length();
int locatorLength = (int)length();
if (patternLength > locatorLength || patternLength < 0) return -1;
int end = locatorLength - patternLength;
char[] charPattern = pattern.getSubString(1L, patternLength).toCharArray(); //@CRS - Get all chars for now, improve this later.
// We use a cache of chars so we don't have to read in the entire
// contents of the CLOB.
initCache();
for (int i=offset; i<=end; ++i)
{
int j = 0;
int cachedChar = getCachedChar(i+j);
while (j < patternLength && cachedChar != -1 && charPattern[j] == cachedChar)
{
++j;
cachedChar = getCachedChar(i+j);
}
if (j == patternLength) return i+1;
}
return -1;
}
}
/**
Returns a stream that an application can use to write Ascii characters to this CLOB.
The stream begins at position position, and the CLOB will be truncated
after the last character of the write.
@param position The position (1-based) in the CLOB where writes should start.
@return An OutputStream object to which data can be written by an application.
@exception SQLException If there is an error accessing the CLOB or if the position
specified is greater than the length of the CLOB.
**/
public OutputStream setAsciiStream(long position) throws SQLException
{
if(locator_ == null)//@free
JDError.throwSQLException(this, JDError.EXC_FUNCTION_SEQUENCE); //@free
if (position <= 0 || position > maxLength_)
{
JDError.throwSQLException (this, JDError.EXC_ATTRIBUTE_VALUE_INVALID);
}
try
{
return new AS400JDBCClobLocatorOutputStream(this, position, ConvTable.getTable(819, null));
}
catch (UnsupportedEncodingException e)
{
// Should never happen.
JDError.throwSQLException(JDError.EXC_INTERNAL, e);
return null;
}
}
/**
Returns a stream that an application can use to write a stream of Unicode characters to
this CLOB. The stream begins at position position, and the CLOB will
be truncated after the last character of the write.
@param position The position (1-based) in the CLOB where writes should start.
@return An OutputStream object to which data can be written by an application.
@exception SQLException If there is an error accessing the CLOB or if the position
specified is greater than the length of the CLOB.
**/
public Writer setCharacterStream(long position) throws SQLException
{
if(locator_ == null)//@free
JDError.throwSQLException(this, JDError.EXC_FUNCTION_SEQUENCE); //@free
if (position <= 0 || position > maxLength_)
{
JDError.throwSQLException(this, JDError.EXC_ATTRIBUTE_VALUE_INVALID);
}
return new AS400JDBCWriter(this, position);
}
/**
Writes a String to this CLOB, starting at position position. The CLOB
will be truncated after the last character written.
@param position The position (1-based) in the CLOB where writes should start.
@param stringToWrite The string that will be written to the CLOB.
@return The number of characters that were written.
@exception SQLException If there is an error accessing the CLOB or if the position
specified is greater than the length of the CLOB.
**/
public int setString(long position, String stringToWrite) throws SQLException
{
synchronized(this)
{
if(locator_ == null)//@free
JDError.throwSQLException(this, JDError.EXC_FUNCTION_SEQUENCE); //@free
int offset = (int)position-1;
if (offset < 0 || offset >= maxLength_ || stringToWrite == null)
{
throw JDError.throwSQLException(this, JDError.EXC_ATTRIBUTE_VALUE_INVALID);
}
// We will write as many chars as we can. If our internal char array
// would overflow past the 2 GB boundary, we don't throw an error, we just
// return the number of chars that were set.
char[] charsToWrite = stringToWrite.toCharArray();
int newSize = offset + charsToWrite.length;
if (newSize < 0) newSize = 0x7FFFFFFF; // In case the addition resulted in overflow.
int numChars = newSize - offset;
if (numChars != charsToWrite.length)
{
char[] temp = charsToWrite;
charsToWrite = new char[newSize];
System.arraycopy(temp, 0, charsToWrite, 0, numChars);
}
// We don't really know if all of these chars can be written until we go to
// the system, so we just return the char[] length as the number written.
byte[] bytesToWrite = converter_.stringToByteArray(charsToWrite, 0, charsToWrite.length);
locator_.writeData((long)offset, bytesToWrite, false); //@K1A
return charsToWrite.length;
}
}
/**
Writes a String to this CLOB, starting at position position in the CLOB.
The CLOB will be truncated after the last character written. The lengthOfWrite
characters written will start from offset in the string that was provided by the
application.
@param position The position (1-based) in the CLOB where writes should start.
@param string The string that will be written to the CLOB.
@param offset The offset into string to start reading characters (0-based).
@param lengthOfWrite The number of characters to write.
@return The number of characters written.
@exception SQLException If there is an error accessing the CLOB value or if the position
specified is greater than the length of the CLOB.
**/
public int setString(long position, String string, int offset, int lengthOfWrite) throws SQLException
{
synchronized(this)
{
if(locator_ == null)//@free
JDError.throwSQLException(this, JDError.EXC_FUNCTION_SEQUENCE); //@free
int clobOffset = (int)position-1;
if (clobOffset < 0 || clobOffset >= maxLength_ ||
string == null || offset < 0 || lengthOfWrite < 0 || (offset+lengthOfWrite) > string.length() ||
(clobOffset+lengthOfWrite) > maxLength_)
{
throw JDError.throwSQLException(this, JDError.EXC_ATTRIBUTE_VALUE_INVALID);
}
// We will write as many chars as we can. If our internal char array
// would overflow past the 2 GB boundary, we don't throw an error, we just
// return the number of chars that were set.
int newSize = clobOffset + lengthOfWrite;
if (newSize < 0) newSize = 0x7FFFFFFF; // In case the addition resulted in overflow.
int numChars = newSize - clobOffset;
int realLength = (numChars < lengthOfWrite ? numChars : lengthOfWrite);
char[] charsToWrite = new char[realLength];
string.getChars(offset, offset + numChars, charsToWrite, 0); //@K2C
// We don't really know if all of these chars can be written until we go to
// the system, so we just return the char[] length as the number written.
byte[] bytesToWrite = converter_.stringToByteArray(charsToWrite, 0, charsToWrite.length);
locator_.writeData((long)clobOffset, bytesToWrite, false); //@k1A
return charsToWrite.length;
}
}
/**
Truncates this CLOB to a length of lengthOfCLOB characters.
@param lengthOfCLOB The length, in characters, that this CLOB should be after
truncation.
@exception SQLException If there is an error accessing the CLOB or if the length
specified is greater than the length of the CLOB.
**/
public void truncate(long lengthOfCLOB) throws SQLException
{
synchronized(this)
{
if(locator_ == null)//@free
JDError.throwSQLException(this, JDError.EXC_FUNCTION_SEQUENCE); //@free
int length = (int)lengthOfCLOB;
if (length < 0 || length > maxLength_)
{
JDError.throwSQLException(this, JDError.EXC_ATTRIBUTE_VALUE_INVALID);
}
//truncate_ = length;
// The host server does not currently provide a way for us
// to truncate the temp space used to hold the locator data,
// so we just keep track of it ourselves. This should work,
// since the temp space on the system should only be valid
// within the scope of our transaction/connection. That means
// there's no reason to go to the system to update the data,
// since no other process can get at it.
locator_.writeData(length, new byte[0], 0, 0, true); //@k1A
}
}
//@PDA 550
/**
* This method frees the Clob
object and releases the
* resources that it holds. The object is invalid once the
* free
method is called. If free
is called
* multiple times, the subsequent calls to free
are treated
* as a no-op.
*
* @throws SQLException If a database error occurs.
* if an error occurs releasing the Clob's resources
*/
public void free() throws SQLException //@sync
{
synchronized(this) //@sync
{
if(locator_ == null)
return; //no-op
locator_.free();
locator_ = null; //@pda make objects available for GC
converter_ = null;
savedObject_ = null;
cache_ = null;
}
}
// @PDA jdbc40
/**
* Returns a Reader
object that contains a partial
* Clob
value, starting with the character specified by pos,
* which is length characters in length.
*
* @param pos
* the offset to the first character of the partial value to be
* retrieved. The first character in the Clob is at position 1.
* @param length
* the length in characters of the partial value to be retrieved.
* @return Reader
through which the partial Clob
* value can be read.
* @throws SQLException If a database error occurs.
* if pos is less than 1 or if pos is greater than the number of
* characters in the Clob
or if pos + length is
* greater than the number of characters in the
* Clob
*/
public Reader getCharacterStream(long pos, long length) throws SQLException //@sync
{
synchronized(this) //@sync
{
if(locator_ == null)//@free
JDError.throwSQLException(this, JDError.EXC_FUNCTION_SEQUENCE); //@free
if (pos < 1 || (pos - 1 + length) > locator_.getMaxLength() || length < 0 ) //@pdc change parm check like getSubString
{
JDError.throwSQLException(this, JDError.EXC_ATTRIBUTE_VALUE_INVALID);
}
Reader r = null;
try
{
//@xml3 if xml column, remove xml declaration via ConvTableReader
r = new ConvTableReader(new AS400JDBCInputStream( locator_), converter_.getCcsid(), converter_.bidiStringType_, isXML_); //@xml3
r.skip(pos);
return r;
}
catch (UnsupportedEncodingException e)
{
JDError.throwSQLException(this, JDError.EXC_INTERNAL, e);
return null;
}
catch (IOException e)
{
JDError.throwSQLException(this, JDError.EXC_INTERNAL, e);
return null;
}
}
}
/** Get the locator handle corresponding to this ClobLocator
* @return locator handle
* @throws SQLException If a database error occurs.
*
*/
public int getLocator() throws SQLException {
if(locator_ == null)//@free
JDError.throwSQLException(this, JDError.EXC_FUNCTION_SEQUENCE); //@free
return locator_.getHandle();
}
}