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

com.microsoft.sqlserver.jdbc.PLPInputStream Maven / Gradle / Ivy

There is a newer version: 12.8.1.jre11
Show newest version
//---------------------------------------------------------------------------------------------------------------------------------
// File: PLPInputStream.java
//
//
// Microsoft JDBC Driver for SQL Server
// Copyright(c) Microsoft Corporation
// All rights reserved.
// MIT License
// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files(the ""Software""), 
//  to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, 
//  and / or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions :
// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
// THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 
//  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 
//  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 
//  IN THE SOFTWARE.
//---------------------------------------------------------------------------------------------------------------------------------
 
 
package com.microsoft.sqlserver.jdbc;

import java.io.*;

/**
* PLPInputStream is an InputStream implementation that reads from a TDS PLP stream.
* 
* Note PLP stands for Partially Length-prefixed Bytes.  TDS 7.2 introduced this new streaming
* format for streaming of large types such as varchar(max), nvarchar(max), varbinary(max) and XML.
* 
* See TDS specification, 6.3.3 Datatype Dependant Data Streams: Partially Length-prefixed Bytes 
* for more details on the PLP format.
*/

class PLPInputStream extends BaseInputStream 
{
    static final long PLP_NULL        = 0xFFFFFFFFFFFFFFFFL;
    static final long UNKNOWN_PLP_LEN = 0xFFFFFFFFFFFFFFFEL;
    static final int PLP_TERMINATOR = 0x00000000;
    private final static byte[] EMPTY_PLP_BYTES = new byte[0];
	

    // Stated length of the PLP stream payload; -1 if unknown length.
    int payloadLength;

    private static final int PLP_EOS = -1;
    private int currentChunkRemain;

    private int markedChunkRemain;
    private int leftOverReadLimit =0;

    private byte[] oneByteArray = new byte[1];
		
    /**
     * Non-destructive method for checking whether a PLP value at the current TDSReader location is null.
     */
    final static boolean isNull(TDSReader tdsReader) throws SQLServerException
    {
    	 TDSReaderMark mark = tdsReader.mark();
         try
         {
         	PLPInputStream tempPLP = PLPInputStream.makeTempStream(tdsReader, false, null);
         	try
 			{
 				if(null != tempPLP)
 				{
 					tempPLP.close();
 					return false;
 				}
 			}
 			catch (IOException e)
 			{
 				tdsReader.getConnection().terminate(SQLServerException.DRIVER_ERROR_IO_FAILED, e.getMessage());
 			}
         	return true;
         }
         finally
         {
       	  if( null != tdsReader )
       		  tdsReader.reset(mark);
         }
    }

    /**
    * Create a new input stream.
    * @param tdsReader TDS reader pointing at the start of the PLP data
	* @param discardValue
	* @param dtv
    */
    final static PLPInputStream makeTempStream(TDSReader tdsReader, boolean discardValue, ServerDTVImpl dtv) throws SQLServerException
    {
        return makeStream(tdsReader, discardValue, discardValue, dtv);
    }

    final static PLPInputStream makeStream(TDSReader tdsReader, InputStreamGetterArgs getterArgs, ServerDTVImpl dtv) throws SQLServerException
    {
        PLPInputStream is = makeStream(tdsReader, getterArgs.isAdaptive, getterArgs.isStreaming, dtv);
        if (null != is)
            is.setLoggingInfo(getterArgs.logContext);
        return is;
    }

    private final static PLPInputStream makeStream(TDSReader tdsReader, boolean isAdaptive, boolean isStreaming, ServerDTVImpl dtv) throws SQLServerException
    {
        // Read total length of PLP stream.
        long payloadLength = tdsReader.readLong();

        // If length is PLP_NULL, then return a null PLP value.
        if (PLP_NULL == payloadLength)
            return null;

        return new PLPInputStream(tdsReader, payloadLength, isAdaptive, isStreaming, dtv);
    }


    /**
    * Initializes the input stream.
    */	
    PLPInputStream(TDSReader tdsReader, long statedPayloadLength, boolean isAdaptive, boolean isStreaming, ServerDTVImpl dtv) throws SQLServerException
    {
        super(tdsReader, isAdaptive, isStreaming, dtv);
        this.payloadLength = (UNKNOWN_PLP_LEN != statedPayloadLength) ? ((int) statedPayloadLength) : -1;
        this.currentChunkRemain = this.markedChunkRemain = 0;
    }

    /**
    * Helper function to convert the entire PLP stream into a contiguous byte array.
    * This call is inefficient (in terms of memory usage and run time) for
    * very large PLPs.  Use it only if a contiguous byte array is required.
    */
    byte[] getBytes() throws SQLServerException
    {
        byte[] value;

        // The following 0-byte read just ensures that the number of bytes
        // remaining in the current chunk is known.
        readBytesInternal(null, 0, 0);

        if (PLP_EOS == currentChunkRemain)
        {
            value = EMPTY_PLP_BYTES;
        }
        else
        {
            // If the PLP payload length is known, allocate the final byte array now.
            // Otherwise, start with the size of the first chunk.  Additional chunks
            // will cause the array to be reallocated & copied.
            value = new byte[(-1 != payloadLength) ? payloadLength : currentChunkRemain];

            int bytesRead = 0;
            while (PLP_EOS != currentChunkRemain)
            {
                // If the current byte array isn't large enough to hold
                // the contents of the current chunk, then make it larger.
                if (value.length == bytesRead)
                {
                    byte[] newValue = new byte[bytesRead + currentChunkRemain];
                    System.arraycopy(value, 0, newValue, 0, bytesRead);
                    value = newValue;
                }

                bytesRead += readBytesInternal(value, bytesRead, currentChunkRemain);
            }
        }

        // Always close the stream after retrieving it
        try
        {
            close();
        }
        catch (IOException e)
        {
            SQLServerException.makeFromDriverError(
                null,
                null, 
                e.getMessage(),
                null,
                true);
        }

        return value;
    }

    /**
    * Skips over and discards n bytes of data from this input stream.
    * @param n the number of bytes to be skipped. 
    * @return the actual number of bytes skipped. 
    * @exception IOException if an I/O error occurs.
    */
    public long skip(long n) throws IOException
    {
        checkClosed();
        if (n < 0) return 0L;
        if(n >Integer.MAX_VALUE)
            n = Integer.MAX_VALUE;

        long bytesread = readBytes(null,0,(int)n);

        if(-1 == bytesread) 
            return 0;
        else
            return bytesread;
    }

    /**
     * Returns the number of bytes that can be read (or skipped over) from this 
     * input stream without blocking by the next caller of a method for 
     * this input stream. 
     * @return the actual number of bytes available. 
     * @exception IOException if an I/O error occurs.
    */	
    public int available() throws IOException 
    {
            checkClosed();
            try
            {

                // The following 0-byte read just ensures that the number of bytes
                // remaining in the current chunk is known.
                if (0 == currentChunkRemain)
                    readBytesInternal(null, 0, 0);

                if (PLP_EOS == currentChunkRemain)
                    return 0;

                // Return the lesser of the number of bytes available for reading
                // from the underlying TDSReader and the number of bytes left in
                // the current chunk.
                int available = tdsReader.available();
                if (available > currentChunkRemain)
                    available = currentChunkRemain;

                return available;
            }
            catch (SQLServerException e)
            {
                throw new IOException(e.getMessage());
            }


    }

    /**
    * Reads the next byte of data from the input stream.
    * @return the byte read or -1 meaning no more bytes. 
    * @exception IOException if an I/O error occurs.
    */		
    public int read() throws IOException 
    {
        checkClosed();

        if (-1 != readBytes(oneByteArray, 0, 1))
            return oneByteArray[0] & 0xFF;
        return -1;
    }

    /**
    * Reads available data into supplied byte array.
    * @param b array of bytes to fill.
    * @return the number of bytes read or 0 meaning no bytes read. 
    * @exception IOException if an I/O error occurs.
    */
    public int read(byte[] b) throws IOException
    {
        // If b is null, a NullPointerException is thrown. 
        if (null==b)
            throw new NullPointerException();

        checkClosed();

        return readBytes(b,0,b.length);
    }
    
    /**
    * Reads available data into supplied byte array.
    * @param b array of bytes to fill.
    * @param offset the offset into array b where to start writing.
    * @param maxBytes the max number of bytes to write into b.
    * @return the number of bytes read or 0 meaning no bytes read. 
    * @exception IOException if an I/O error occurs.
    */
    public int read(byte b[], int offset, int maxBytes) throws IOException
    {	
        // If b is null, a NullPointerException is thrown. 
        if (null==b)
            throw new NullPointerException();

        // Verify offset and maxBytes against target buffer if we're reading (as opposed to skipping).
        // If offset is negative, or maxBytes is negative, or offset+maxBytes
        // is greater than the length of the array b, then an IndexOutOfBoundsException is thrown.
        if (offset < 0 || maxBytes < 0 || offset + maxBytes > b.length)
            throw new IndexOutOfBoundsException();

        checkClosed();

        return readBytes(b,offset,maxBytes);
    }

    /**
    * Reads available data into supplied byte array b.
    * @param b array of bytes to fill.  If b is null, method will skip over data.
    * @param offset the offset into array b where to start writing.
    * @param maxBytes the max number of bytes to write into b.
    * @return the number of bytes read or 0 meaning no bytes read or -1 meaning EOS. 
    * @exception IOException if an I/O error occurs.
    */
    int readBytes(byte[] b, int offset, int maxBytes) throws IOException
    {
        // If maxBytes is zero, then no bytes are read and 0 is returned
        // This must be done here rather than in readBytesInternal since a 0-byte read
        // there may return -1 at EOS.
        if (0 == maxBytes)
            return 0;

        try
        {
            return readBytesInternal(b, offset, maxBytes);
        }
        catch (SQLServerException e)
        {
            throw new IOException(e.getMessage());
        }
    }

    private int readBytesInternal(byte b[], int offset, int maxBytes) throws SQLServerException
    {
        // If we're at EOS, say so.
        // Note: For back compat, this special case needs to always be handled
        // before checking user-supplied arguments below.
        if (PLP_EOS == currentChunkRemain)
            return -1;


        // Save off the current TDSReader position, wherever it is, and start reading
        // from where we left off last time.

        int bytesRead = 0;
        for (;;)
        {
            // Check that we have bytes left to read from the current chunk.
            // If not then figure out the size of the next chunk or
            // determine that we have reached the end of the stream.
            if (0 == currentChunkRemain)
            {
                currentChunkRemain = (int) tdsReader.readUnsignedInt();
                assert currentChunkRemain >=0;
                if (0 == currentChunkRemain)
                {
                    currentChunkRemain = PLP_EOS;
                    break;
                }
            }

            if (bytesRead == maxBytes)
                break;

            // Now we know there are bytes to be read in the current chunk.
            // Further limit the max number of bytes we can read to whatever
            // remains in the current chunk.
            int bytesToRead = maxBytes - bytesRead;
            if (bytesToRead > currentChunkRemain)
                bytesToRead = currentChunkRemain;

            // Skip/Read as many bytes as we can, given the constraints.
            if (null == b)
                tdsReader.skip(bytesToRead);
            else
                tdsReader.readBytes(b, offset + bytesRead, bytesToRead);

            bytesRead += bytesToRead;
            currentChunkRemain -= bytesToRead;
        }

        if (bytesRead > 0)
        {
            if(isReadLimitSet && leftOverReadLimit  >0)
            {
                leftOverReadLimit=  leftOverReadLimit - bytesRead;
                if (leftOverReadLimit <0)
                    clearCurrentMark();
            }
            return bytesRead;
        }

        if (PLP_EOS == currentChunkRemain)
            return -1;

        return 0;
    }

    /**
    * Marks the current position in this input stream.
    * @param readlimit the number of bytes to hold (this implementation ignores this).
    */	
    public void mark(int readLimit) 
    {
        // Save off current position and how much of the current chunk remains
        // cant throw if the tdsreader is null
        if(null != tdsReader && readLimit >0)
        {
            currentMark = tdsReader.mark();
            markedChunkRemain = currentChunkRemain;
            leftOverReadLimit =readLimit;
            setReadLimit(readLimit);
         }
    }
    /**
    * Closes the stream releasing all resources held.
    */	
    public void close() throws IOException
    {
        if (null==tdsReader)
            return;

        while (skip(tdsReader.getConnection().getTDSPacketSize()) !=0)
            ;
        // Release ref to tdsReader and parentRS here, shut down stream state.
        closeHelper();
    }

    /**
    * Resets stream to saved mark position. 
    * @exception IOException if an I/O error occurs.
    */	
    public void reset() throws IOException
    {
        resetHelper();
        leftOverReadLimit = readLimit;
        currentChunkRemain = markedChunkRemain;
    }
}

/**
 * Implements an XML binary stream with BOM header.
 * 
 * Class extends a normal PLPInputStream class and prepends the XML BOM (0xFFFE) token
 * then steps out of the way and forwards the rest of the InputStream calls to the 
 * super class PLPInputStream.
 */
final class PLPXMLInputStream extends PLPInputStream
{
    // XML BOM header (the first two header bytes sent to caller).
    private final static byte [] xmlBOM = {(byte)0xFF, (byte)0xFE};
    private final ByteArrayInputStream bomStream = new ByteArrayInputStream(xmlBOM);

    final static PLPXMLInputStream makeXMLStream(TDSReader tdsReader, InputStreamGetterArgs getterArgs, ServerDTVImpl dtv) throws SQLServerException
    {
        // Read total length of PLP stream.
        long payloadLength = tdsReader.readLong();

        // If length is PLP_NULL, then return a null PLP value.
        if (PLP_NULL == payloadLength)
            return null;

        PLPXMLInputStream is = new PLPXMLInputStream(tdsReader, payloadLength, getterArgs, dtv);
        if (null != is)
            is.setLoggingInfo(getterArgs.logContext);

        return is;
    }

    PLPXMLInputStream(
        TDSReader tdsReader,
        long statedPayloadLength,
        InputStreamGetterArgs getterArgs,
        ServerDTVImpl dtv) throws SQLServerException
    {
        super(tdsReader, statedPayloadLength, getterArgs.isAdaptive, getterArgs.isStreaming, dtv);
    }

    public void close()  throws IOException
    {
        super.close();
    }

    int readBytes(byte[] b, int offset, int maxBytes) throws IOException
    {
        assert offset >= 0;
        assert maxBytes >= 0;
        // If maxBytes is zero, then no bytes are read and 0 is returned.
        if (0 == maxBytes)
            return 0;

        int bytesRead = 0;
        int xmlBytesRead = 0;

        // Read/Skip BOM bytes first.  When all BOM bytes have been consumed ...
        if (null == b)
        {
            for (int bomBytesSkipped = 0;
                 bytesRead < maxBytes && 0 != (bomBytesSkipped = (int) bomStream.skip(maxBytes - bytesRead));
                 bytesRead += bomBytesSkipped)
                ;
        }
        else
        {
            for (int bomBytesRead = 0;
                 bytesRead < maxBytes && -1 != (bomBytesRead = bomStream.read(b, offset + bytesRead, maxBytes - bytesRead));
                 bytesRead += bomBytesRead)
                ;
        }

        // ... then read/skip bytes from the underlying PLPInputStream
        for (;
             bytesRead < maxBytes && -1 != (xmlBytesRead = super.readBytes(b, offset + bytesRead, maxBytes - bytesRead));
             bytesRead += xmlBytesRead)
            ;

        if (bytesRead > 0)
            return bytesRead;

        // No bytes read - should have been EOF since 0-byte reads are handled above
        assert -1 == xmlBytesRead;
        return -1;
    }

    public void mark(int readLimit)
    {
        bomStream.mark(xmlBOM.length);
        super.mark(readLimit);
    }

    public void reset() throws IOException
    {
        bomStream.reset();
        super.reset();
    }

    /**
     * Helper function to convert the entire PLP stream into a contiguous byte array.
     * This call is inefficient (in terms of memory usage and run time) for
     * very large PLPs.  Use it only if a contiguous byte array is required.
     */
    byte[] getBytes() throws SQLServerException
    {
        // Look to see if the BOM has been read
        byte [] bom = new byte[2];
        try
        {
            int bytesread = bomStream.read(bom);
            byte[] valueWithoutBOM = super.getBytes();

            if(bytesread >0)
            {
                assert 2 == bytesread;
                byte[] valueWithBOM = new byte[valueWithoutBOM.length + bytesread];
                System.arraycopy(bom, 0, valueWithBOM, 0, bytesread);
                System.arraycopy(valueWithoutBOM, 0, valueWithBOM, bytesread, valueWithoutBOM.length);
                return valueWithBOM;
             }
            else
                return valueWithoutBOM;
        }catch (IOException e)
        {
            SQLServerException.makeFromDriverError(
            null,
            null, 
            e.getMessage(),
            null,
            true);
        }
                
        return null;
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy