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

src.com.ibm.as400.access.AS400JDBCClobLocator Maven / Gradle / Ivy

There is a newer version: 11.1
Show newest version
///////////////////////////////////////////////////////////////////////////////
//                                                                             
// 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(); 
  }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy