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:
parent
9dff718744
commit
84c52c457d
|
@ -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" );
|
||||
}
|
||||
|
||||
|
|
|
@ -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" );
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
*
|
||||
|
|
|
@ -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() {
|
||||
|
|
|
@ -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 ) );
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 ) );
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue