[hibernate-commits] Hibernate SVN: r20070 - in core/trunk: core/src/main/java/org/hibernate/type/descriptor and 3 other directories.

hibernate-commits at lists.jboss.org hibernate-commits at lists.jboss.org
Mon Jul 26 16:03:51 EDT 2010


Author: steve.ebersole at jboss.com
Date: 2010-07-26 16:03:49 -0400 (Mon, 26 Jul 2010)
New Revision: 20070

Modified:
   core/trunk/core/src/main/java/org/hibernate/engine/jdbc/BlobProxy.java
   core/trunk/core/src/main/java/org/hibernate/engine/jdbc/ClobProxy.java
   core/trunk/core/src/main/java/org/hibernate/type/descriptor/BinaryStream.java
   core/trunk/core/src/main/java/org/hibernate/type/descriptor/java/BinaryStreamImpl.java
   core/trunk/core/src/main/java/org/hibernate/type/descriptor/java/DataHelper.java
   core/trunk/core/src/main/java/org/hibernate/type/descriptor/sql/BlobTypeDescriptor.java
   core/trunk/jdbc3-testing/src/test/java/org/hibernate/engine/jdbc/jdbc3/JdbcSupportTest.java
Log:
HHH-5400 - Blob persistence fails with Hibernate 3.6.0-SNAPSHOT, works with 3.5.3-Final


Modified: core/trunk/core/src/main/java/org/hibernate/engine/jdbc/BlobProxy.java
===================================================================
--- core/trunk/core/src/main/java/org/hibernate/engine/jdbc/BlobProxy.java	2010-07-26 17:25:47 UTC (rev 20069)
+++ core/trunk/core/src/main/java/org/hibernate/engine/jdbc/BlobProxy.java	2010-07-26 20:03:49 UTC (rev 20070)
@@ -29,9 +29,11 @@
 import java.sql.Blob;
 import java.sql.SQLException;
 import java.io.InputStream;
-import java.io.ByteArrayInputStream;
 import java.io.IOException;
 
+import org.hibernate.type.descriptor.java.BinaryStreamImpl;
+import org.hibernate.type.descriptor.java.DataHelper;
+
 /**
  * Manages aspects of proxying {@link Blob Blobs} for non-contextual creation, including proxy creation and
  * handling proxy invocations.
@@ -48,18 +50,18 @@
 	private boolean needsReset = false;
 
 	/**
-	 * Ctor used to build {@link Blob} from byte array.
+	 * Constructor used to build {@link Blob} from byte array.
 	 *
 	 * @param bytes The byte array
 	 * @see #generateProxy(byte[])
 	 */
 	private BlobProxy(byte[] bytes) {
-		this.stream = new ByteArrayInputStream( bytes );
+		this.stream = new BinaryStreamImpl( bytes );
 		this.length = bytes.length;
 	}
 
 	/**
-	 * Ctor used to build {@link Blob} from a stream.
+	 * Constructor used to build {@link Blob} from a stream.
 	 *
 	 * @param stream The binary stream
 	 * @param length The length of the stream
@@ -93,26 +95,62 @@
 	 * @throws UnsupportedOperationException if any methods other than {@link Blob#length()}
 	 * or {@link Blob#getBinaryStream} are invoked.
 	 */
+	@SuppressWarnings({ "UnnecessaryBoxing" })
 	public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
-		if ( "length".equals( method.getName() ) ) {
-			return new Long( getLength() );
+		final String methodName = method.getName();
+		final int argCount = method.getParameterTypes().length;
+
+		if ( "length".equals( methodName ) && argCount == 0 ) {
+			return Long.valueOf( getLength() );
 		}
-		if ( "getBinaryStream".equals( method.getName() ) && method.getParameterTypes().length == 0 ) {
-			return getStream();
+		if ( "getBinaryStream".equals( methodName ) ) {
+			if ( argCount == 0 ) {
+				return getStream();
+			}
+			else if ( argCount == 2 ) {
+				long start = (Long) args[0];
+				if ( start < 1 ) {
+					throw new SQLException( "Start position 1-based; must be 1 or more." );
+				}
+				if ( start > getLength() ) {
+					throw new SQLException( "Start position [" + start + "] cannot exceed overall CLOB length [" + getLength() + "]" );
+				}
+				int length = (Integer) args[1];
+				if ( length < 0 ) {
+					// java docs specifically say for getBinaryStream(long,int) that the start+length must not exceed the
+					// total length, however that is at odds with the getBytes(long,int) behavior.
+					throw new SQLException( "Length must be great-than-or-equal to zero." );
+				}
+				return DataHelper.subStream( getStream(), start-1, length );
+			}
 		}
-		if ( "free".equals( method.getName() ) ) {
+		if ( "getBytes".equals( methodName ) ) {
+			if ( argCount == 2 ) {
+				long start = (Long) args[0];
+				if ( start < 1 ) {
+					throw new SQLException( "Start position 1-based; must be 1 or more." );
+				}
+				int length = (Integer) args[1];
+				if ( length < 0 ) {
+					throw new SQLException( "Length must be great-than-or-equal to zero." );
+				}
+				return DataHelper.extractBytes( getStream(), start-1, length );
+			}
+		}
+		if ( "free".equals( methodName ) && argCount == 0 ) {
 			stream.close();
 			return null;
 		}
-		if ( "toString".equals( method.getName() ) ) {
+		if ( "toString".equals( methodName ) && argCount == 0 ) {
 			return this.toString();
 		}
-		if ( "equals".equals( method.getName() ) ) {
+		if ( "equals".equals( methodName ) && argCount == 1 ) {
 			return Boolean.valueOf( proxy == args[0] );
 		}
-		if ( "hashCode".equals( method.getName() ) ) {
+		if ( "hashCode".equals( methodName ) && argCount == 0 ) {
 			return new Integer( this.hashCode() );
 		}
+
 		throw new UnsupportedOperationException( "Blob may not be manipulated from creating session" );
 	}
 

Modified: core/trunk/core/src/main/java/org/hibernate/engine/jdbc/ClobProxy.java
===================================================================
--- core/trunk/core/src/main/java/org/hibernate/engine/jdbc/ClobProxy.java	2010-07-26 17:25:47 UTC (rev 20069)
+++ core/trunk/core/src/main/java/org/hibernate/engine/jdbc/ClobProxy.java	2010-07-26 20:03:49 UTC (rev 20070)
@@ -33,6 +33,8 @@
 import java.io.InputStream;
 import java.io.IOException;
 
+import org.hibernate.type.descriptor.java.DataHelper;
+
 /**
  * Manages aspects of proxying {@link Clob Clobs} for non-contextual creation, including proxy creation and
  * handling proxy invocations.
@@ -51,7 +53,7 @@
 
 
 	/**
-	 * Ctor used to build {@link Clob} from string data.
+	 * Constructor used to build {@link Clob} from string data.
 	 *
 	 * @param string The byte array
 	 * @see #generateProxy(String)
@@ -63,7 +65,7 @@
 	}
 
 	/**
-	 * Ctor used to build {@link Clob} from a reader.
+	 * Constructor used to build {@link Clob} from a reader.
 	 *
 	 * @param reader The character reader.
 	 * @param length The length of the reader stream.
@@ -88,12 +90,13 @@
 		return reader;
 	}
 
-	protected String getSubString(long pos, int length) {
+	protected String getSubString(long start, int length) {
 		if ( string == null ) {
 			throw new UnsupportedOperationException( "Clob was not created from string; cannot substring" );
 		}
-		// naive impl...
-		return string.substring( (int)pos-1, (int)(pos+length-1) );
+		// semi-naive implementation
+		int endIndex = Math.min( ((int)start)+length, string.length() );
+		return string.substring( (int)start, endIndex );
 	}
 
 	/**
@@ -104,31 +107,64 @@
 	 */
 	@SuppressWarnings({ "UnnecessaryBoxing" })
 	public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
-		if ( "length".equals( method.getName() ) ) {
+		final String methodName = method.getName();
+		final int argCount = method.getParameterTypes().length;
+
+		if ( "length".equals( methodName ) && argCount == 0 ) {
 			return Long.valueOf( getLength() );
 		}
-		if ( "getAsciiStream".equals( method.getName() ) ) {
+		if ( "getAsciiStream".equals( methodName ) && argCount == 0 ) {
 			return getAsciiStream();
 		}
-		if ( "getCharacterStream".equals( method.getName() ) ) {
-			return getCharacterStream();
+		if ( "getCharacterStream".equals( methodName ) ) {
+			if ( argCount == 0 ) {
+				return getCharacterStream();
+			}
+			else if ( argCount == 2 ) {
+				long start = (Long) args[0];
+				if ( start < 1 ) {
+					throw new SQLException( "Start position 1-based; must be 1 or more." );
+				}
+				if ( start > getLength() ) {
+					throw new SQLException( "Start position [" + start + "] cannot exceed overall CLOB length [" + getLength() + "]" );
+				}
+				int length = (Integer) args[1];
+				if ( length < 0 ) {
+					// java docs specifically say for getCharacterStream(long,int) that the start+length must not exceed the
+					// total length, however that is at odds with the getSubString(long,int) behavior.
+					throw new SQLException( "Length must be great-than-or-equal to zero." );
+				}
+				return DataHelper.subStream( getCharacterStream(), start-1, length );
+			}
 		}
-		if ( "getSubString".equals( method.getName() ) ) {
-			return getSubString( (Long)args[0], (Integer)args[1] );
+		if ( "getSubString".equals( methodName ) && argCount == 2 ) {
+			long start = (Long) args[0];
+			if ( start < 1 ) {
+				throw new SQLException( "Start position 1-based; must be 1 or more." );
+			}
+			if ( start > getLength() ) {
+				throw new SQLException( "Start position [" + start + "] cannot exceed overall CLOB length [" + getLength() + "]" );
+			}
+			int length = (Integer) args[1];
+			if ( length < 0 ) {
+				throw new SQLException( "Length must be great-than-or-equal to zero." );
+			}
+			return getSubString( start-1, length );
 		}
-		if ( "free".equals( method.getName() ) ) {
+		if ( "free".equals( methodName ) && argCount == 0 ) {
 			reader.close();
 			return null;
 		}
-		if ( "toString".equals( method.getName() ) ) {
+		if ( "toString".equals( methodName ) && argCount == 0 ) {
 			return this.toString();
 		}
-		if ( "equals".equals( method.getName() ) ) {
+		if ( "equals".equals( methodName ) && argCount == 1 ) {
 			return Boolean.valueOf( proxy == args[0] );
 		}
-		if ( "hashCode".equals( method.getName() ) ) {
+		if ( "hashCode".equals( methodName ) && argCount == 0 ) {
 			return new Integer( this.hashCode() );
 		}
+
 		throw new UnsupportedOperationException( "Clob may not be manipulated from creating session" );
 	}
 

Modified: core/trunk/core/src/main/java/org/hibernate/type/descriptor/BinaryStream.java
===================================================================
--- core/trunk/core/src/main/java/org/hibernate/type/descriptor/BinaryStream.java	2010-07-26 17:25:47 UTC (rev 20069)
+++ core/trunk/core/src/main/java/org/hibernate/type/descriptor/BinaryStream.java	2010-07-26 20:03:49 UTC (rev 20070)
@@ -39,6 +39,13 @@
 	public InputStream getInputStream();
 
 	/**
+	 * Access to the bytes.
+	 *
+	 * @return The bytes.
+	 */
+	public byte[] getBytes();
+
+	/**
 	 * Retrieve the length of the input stream
 	 *
 	 * @return The input stream length

Modified: core/trunk/core/src/main/java/org/hibernate/type/descriptor/java/BinaryStreamImpl.java
===================================================================
--- core/trunk/core/src/main/java/org/hibernate/type/descriptor/java/BinaryStreamImpl.java	2010-07-26 17:25:47 UTC (rev 20069)
+++ core/trunk/core/src/main/java/org/hibernate/type/descriptor/java/BinaryStreamImpl.java	2010-07-26 20:03:49 UTC (rev 20070)
@@ -33,19 +33,23 @@
  *
  * @author Steve Ebersole
  */
-public class BinaryStreamImpl implements BinaryStream {
-	private final ByteArrayInputStream stream;
+public class BinaryStreamImpl extends ByteArrayInputStream implements BinaryStream {
 	private final int length;
 
 	public BinaryStreamImpl(byte[] bytes) {
-		this.stream = new ByteArrayInputStream( bytes );
+		super( bytes );
 		this.length = bytes.length;
 	}
 
 	public InputStream getInputStream() {
-		return stream;
+		return this;
 	}
 
+	public byte[] getBytes() {
+		// from ByteArrayInputStream
+		return buf;
+	}
+
 	public int getLength() {
 		return length;
 	}

Modified: core/trunk/core/src/main/java/org/hibernate/type/descriptor/java/DataHelper.java
===================================================================
--- core/trunk/core/src/main/java/org/hibernate/type/descriptor/java/DataHelper.java	2010-07-26 17:25:47 UTC (rev 20069)
+++ core/trunk/core/src/main/java/org/hibernate/type/descriptor/java/DataHelper.java	2010-07-26 20:03:49 UTC (rev 20070)
@@ -27,11 +27,13 @@
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.Reader;
+import java.io.StringReader;
 
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
 import org.hibernate.HibernateException;
+import org.hibernate.type.descriptor.BinaryStream;
 import org.hibernate.util.ReflectHelper;
 
 /**
@@ -84,7 +86,46 @@
 		return stringBuilder.toString();
 	}
 
+	private static String extractString(Reader characterStream, long start, int length) {
+		StringBuilder stringBuilder = new StringBuilder( length );
+		try {
+			long skipped = characterStream.skip( start - 1 );
+			if ( skipped != start - 1 ) {
+				throw new HibernateException( "Unable to skip needed bytes" );
+			}
+			char[] buffer = new char[2048];
+			int charsRead = 0;
+			while ( true ) {
+				int amountRead = characterStream.read( buffer, 0, buffer.length );
+				if ( amountRead == -1 ) {
+					break;
+				}
+				stringBuilder.append( buffer, 0, amountRead );
+				if ( amountRead < buffer.length ) {
+					// we have read up to the end of stream
+					break;
+				}
+				charsRead += amountRead;
+				if ( charsRead >= length ) {
+					break;
+				}
+			}
+		}
+		catch ( IOException ioe ) {
+			throw new HibernateException( "IOException occurred reading a binary value", ioe );
+		}
+		return stringBuilder.toString();
+	}
+
+	public static Object subStream(Reader characterStream, long start, int length) {
+		return new StringReader( extractString( characterStream, start, length ) );
+	}
+
 	public static byte[] extractBytes(InputStream inputStream) {
+		if ( BinaryStream.class.isInstance( inputStream ) ) {
+			return ( (BinaryStream ) inputStream ).getBytes();
+		}
+
 		// read the stream contents into a buffer and return the complete byte[]
 		ByteArrayOutputStream outputStream = new ByteArrayOutputStream(2048);
 		try {
@@ -116,4 +157,47 @@
 		}
 		return outputStream.toByteArray();
 	}
+
+	public static byte[] extractBytes(InputStream inputStream, long position, int length) {
+		if ( BinaryStream.class.isInstance( inputStream ) && Integer.MAX_VALUE > position ) {
+			byte[] data = ( (BinaryStream ) inputStream ).getBytes();
+			int size = Math.min( length, data.length );
+			byte[] result = new byte[size];
+			System.arraycopy( data, (int) position, result, 0, size );
+			return result;
+		}
+
+		ByteArrayOutputStream outputStream = new ByteArrayOutputStream( length );
+		try {
+			long skipped = inputStream.skip( position - 1 );
+			if ( skipped != position - 1 ) {
+				throw new HibernateException( "Unable to skip needed bytes" );
+			}
+			byte[] buffer = new byte[2048];
+			int bytesRead = 0;
+			while ( true ) {
+				int amountRead = inputStream.read( buffer );
+				if ( amountRead == -1 ) {
+					break;
+				}
+				outputStream.write( buffer, 0, amountRead );
+				if ( amountRead < buffer.length ) {
+					// we have read up to the end of stream
+					break;
+				}
+				bytesRead += amountRead;
+				if ( bytesRead >= length ) {
+					break;
+				}
+			}
+		}
+		catch ( IOException ioe ) {
+			throw new HibernateException( "IOException occurred reading a binary value", ioe );
+		}
+		return outputStream.toByteArray();
+	}
+
+	public static InputStream subStream(InputStream inputStream, long start, int length) {
+		return new BinaryStreamImpl( extractBytes( inputStream, start, length ) );
+	}
 }

Modified: core/trunk/core/src/main/java/org/hibernate/type/descriptor/sql/BlobTypeDescriptor.java
===================================================================
--- core/trunk/core/src/main/java/org/hibernate/type/descriptor/sql/BlobTypeDescriptor.java	2010-07-26 17:25:47 UTC (rev 20069)
+++ core/trunk/core/src/main/java/org/hibernate/type/descriptor/sql/BlobTypeDescriptor.java	2010-07-26 20:03:49 UTC (rev 20070)
@@ -56,6 +56,11 @@
 					final BinaryStream binaryStream = javaTypeDescriptor.unwrap( value, BinaryStream.class, options );
 					st.setBinaryStream( index, binaryStream.getInputStream(), binaryStream.getLength() );
 				}
+				else if ( byte[].class.isInstance( value ) ) {
+					// performance shortcut for binding BLOB data in byte[] format
+					final byte[] bytes = (byte[]) value;
+					st.setBytes( index, bytes );
+				}
 				else {
 					st.setBlob( index, javaTypeDescriptor.unwrap( value, Blob.class, options ) );
 				}

Modified: core/trunk/jdbc3-testing/src/test/java/org/hibernate/engine/jdbc/jdbc3/JdbcSupportTest.java
===================================================================
--- core/trunk/jdbc3-testing/src/test/java/org/hibernate/engine/jdbc/jdbc3/JdbcSupportTest.java	2010-07-26 17:25:47 UTC (rev 20069)
+++ core/trunk/jdbc3-testing/src/test/java/org/hibernate/engine/jdbc/jdbc3/JdbcSupportTest.java	2010-07-26 20:03:49 UTC (rev 20070)
@@ -45,14 +45,16 @@
  * @author Steve Ebersole
  */
 public class JdbcSupportTest extends TestCase {
+	private static class LobCreationContextImpl implements LobCreationContext {
+		public Object execute(Callback callback) {
+			fail( "Unexpected call to getConnection" );
+			return null;
+		}
+	}
+
+	private LobCreationContextImpl lobCreationContext = new LobCreationContextImpl();
+
 	public void testLobCreator() throws ClassNotFoundException, SQLException {
-		final LobCreationContext lobCreationContext = new LobCreationContext() {
-			public Object execute(Callback callback) {
-				fail( "Unexpeted call to getConnection" );
-				return null;
-			}
-		};
-
 		LobCreator lobCreator = JdbcSupportLoader.loadJdbcSupport( null ).getLobCreator( lobCreationContext );
 
 		Blob blob = lobCreator.createBlob( new byte[] {} );
@@ -69,6 +71,20 @@
 		assertTrue( nclob instanceof NClobImplementer );
 		nclob = lobCreator.wrap( nclob );
 		assertTrue( nclob instanceof WrappedClob );
+	}
 
+	public void testLobAccess() throws SQLException {
+		LobCreator lobCreator = JdbcSupportLoader.loadJdbcSupport( null ).getLobCreator( lobCreationContext );
+
+		Blob blob = lobCreator.createBlob( "Hi".getBytes() );
+		assertEquals( 2, blob.length() );
+		assertEquals( 2, blob.getBytes( 1, 5 ).length );
+		blob.getBinaryStream();
+
+		Clob clob = lobCreator.createClob( "Hi" );
+		assertEquals( 2, clob.length() );
+		assertEquals( 2, clob.getSubString( 1, 5 ).length() );
+		clob.getCharacterStream();
+		clob.getAsciiStream();
 	}
 }



More information about the hibernate-commits mailing list