Author: steve.ebersole(a)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();
}
}