HHH-5400 - Blob persistence fails with Hibernate 3.6.0-SNAPSHOT, works with 3.5.3-Final

git-svn-id: https://svn.jboss.org/repos/hibernate/core/trunk@20070 1b8cb986-b30d-0410-93ca-fae66ebed9b2
This commit is contained in:
Steve Ebersole 2010-07-26 20:03:49 +00:00
parent 9dff718744
commit 84c52c457d
7 changed files with 228 additions and 38 deletions

View File

@ -29,9 +29,11 @@ import java.lang.reflect.Proxy;
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 @@ public class BlobProxy implements InvocationHandler {
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 @@ public class BlobProxy implements InvocationHandler {
* @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" );
}

View File

@ -33,6 +33,8 @@ import java.io.StringReader;
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 @@ public class ClobProxy implements InvocationHandler {
/**
* 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 @@ public class ClobProxy implements InvocationHandler {
}
/**
* 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 @@ public class ClobProxy implements InvocationHandler {
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 @@ public class ClobProxy implements InvocationHandler {
*/
@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" );
}

View File

@ -38,6 +38,13 @@ public interface BinaryStream {
*/
public InputStream getInputStream();
/**
* Access to the bytes.
*
* @return The bytes.
*/
public byte[] getBytes();
/**
* Retrieve the length of the input stream
*

View File

@ -33,17 +33,21 @@ import org.hibernate.type.descriptor.BinaryStream;
*
* @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() {

View File

@ -27,11 +27,13 @@ import java.io.ByteArrayOutputStream;
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 @@ public class DataHelper {
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 @@ public class DataHelper {
}
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 ) );
}
}

View File

@ -56,6 +56,11 @@ public class BlobTypeDescriptor implements SqlTypeDescriptor {
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 ) );
}

View File

@ -45,14 +45,16 @@ import org.hibernate.engine.jdbc.NClobImplementer;
* @author Steve Ebersole
*/
public class JdbcSupportTest extends TestCase {
public void testLobCreator() throws ClassNotFoundException, SQLException {
final LobCreationContext lobCreationContext = new LobCreationContext() {
public Object execute(Callback callback) {
fail( "Unexpeted call to getConnection" );
return null;
}
};
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 {
LobCreator lobCreator = JdbcSupportLoader.loadJdbcSupport( null ).getLobCreator( lobCreationContext );
Blob blob = lobCreator.createBlob( new byte[] {} );
@ -69,6 +71,20 @@ public class JdbcSupportTest extends TestCase {
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();
}
}