HHH-8193 - Reset input stream before binding

Conflicts:
	hibernate-core/src/main/java/org/hibernate/engine/jdbc/BlobProxy.java
	hibernate-core/src/test/java/org/hibernate/test/type/descriptor/java/BlobDescriptorTest.java
This commit is contained in:
Lukasz Antoniak 2013-07-10 13:26:57 -04:00 committed by Brett Meyer
parent 2ecde32811
commit 6c2937e3c8
6 changed files with 240 additions and 19 deletions

View File

@ -75,24 +75,36 @@ public class BlobProxy implements InvocationHandler {
}
private InputStream getStream() throws SQLException {
final InputStream stream = binaryStream.getInputStream();
return getUnderlyingStream().getInputStream();
}
private BinaryStream getUnderlyingStream() throws SQLException {
resetIfNeeded();
return binaryStream;
}
private void resetIfNeeded() throws SQLException {
try {
if ( needsReset ) {
stream.reset();
binaryStream.getInputStream().reset();
}
}
catch ( IOException ioe) {
throw new SQLException("could not reset reader");
}
needsReset = true;
return stream;
}
/**
* {@inheritDoc}
*
* @throws UnsupportedOperationException if any methods other than
<<<<<<< HEAD
* {@link Blob#length}, {@link Blob#getBinaryStream}, {@link Blob#getBytes}, {@link Blob#free},
=======
* {@link Blob#length}, {@link BlobImplementer#getUnderlyingStream},
* {@link Blob#getBinaryStream}, {@link Blob#getBytes}, {@link Blob#free},
>>>>>>> d7dae6c... HHH-8193 - Reset input stream before binding
* or toString/equals/hashCode are invoked.
*/
@Override
@ -105,7 +117,7 @@ public class BlobProxy implements InvocationHandler {
return Long.valueOf( getLength() );
}
if ( "getUnderlyingStream".equals( methodName ) ) {
return binaryStream;
return getUnderlyingStream(); // Reset stream if needed.
}
if ( "getBinaryStream".equals( methodName ) ) {
if ( argCount == 0 ) {

View File

@ -76,13 +76,16 @@ public class ClobProxy implements InvocationHandler {
}
protected InputStream getAsciiStream() throws SQLException {
resetIfNeeded();
return new ReaderInputStream( characterStream.asReader() );
return new ReaderInputStream( getCharacterStream() );
}
protected Reader getCharacterStream() throws SQLException {
return getUnderlyingStream().asReader();
}
protected CharacterStream getUnderlyingStream() throws SQLException {
resetIfNeeded();
return characterStream.asReader();
return characterStream;
}
protected String getSubString(long start, int length) {
@ -95,8 +98,10 @@ public class ClobProxy implements InvocationHandler {
/**
* {@inheritDoc}
*
* @throws UnsupportedOperationException if any methods other than {@link Clob#length()},
* {@link Clob#getAsciiStream()}, or {@link Clob#getCharacterStream()} are invoked.
* @throws UnsupportedOperationException if any methods other than {@link Clob#length},
* {@link Clob#getAsciiStream}, {@link Clob#getCharacterStream},
* {@link ClobImplementer#getUnderlyingStream}, {@link Clob#getSubString},
* {@link Clob#free}, or toString/equals/hashCode are invoked.
*/
@Override
@SuppressWarnings({ "UnnecessaryBoxing" })
@ -108,7 +113,7 @@ public class ClobProxy implements InvocationHandler {
return Long.valueOf( getLength() );
}
if ( "getUnderlyingStream".equals( methodName ) ) {
return characterStream;
return getUnderlyingStream(); // Reset stream if needed.
}
if ( "getAsciiStream".equals( methodName ) && argCount == 0 ) {
return getAsciiStream();

View File

@ -0,0 +1,87 @@
/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* Copyright (c) 2009 by Red Hat Inc and/or its affiliates or by
* third-party contributors as indicated by either @author tags or express
* copyright attribution statements applied by the authors. All
* third-party contributions are distributed under license by Red Hat Inc.
*
* This copyrighted material is made available to anyone wishing to use, modify,
* copy, or redistribute it subject to the terms and conditions of the GNU
* Lesser General Public License, as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
* for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this distribution; if not, write to:
* Free Software Foundation, Inc.
* 51 Franklin Street, Fifth Floor
* Boston, MA 02110-1301 USA
*/
package org.hibernate.test.annotations.lob.locator;
import java.sql.Blob;
import java.sql.Clob;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
/**
* @author Lukasz Antoniak (lukasz dot antoniak at gmail dot com)
*/
@Entity
public class LobHolder {
@Id
@GeneratedValue
private Long id;
private Clob clobLocator;
private Blob blobLocator;
private Integer counter;
public LobHolder() {
}
public LobHolder(Blob blobLocator, Clob clobLocator, Integer counter) {
this.blobLocator = blobLocator;
this.clobLocator = clobLocator;
this.counter = counter;
}
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public Clob getClobLocator() {
return clobLocator;
}
public void setClobLocator(Clob clobLocator) {
this.clobLocator = clobLocator;
}
public Blob getBlobLocator() {
return blobLocator;
}
public void setBlobLocator(Blob blobLocator) {
this.blobLocator = blobLocator;
}
public Integer getCounter() {
return counter;
}
public void setCounter(Integer counter) {
this.counter = counter;
}
}

View File

@ -0,0 +1,97 @@
/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* Copyright (c) 2009 by Red Hat Inc and/or its affiliates or by
* third-party contributors as indicated by either @author tags or express
* copyright attribution statements applied by the authors. All
* third-party contributions are distributed under license by Red Hat Inc.
*
* This copyrighted material is made available to anyone wishing to use, modify,
* copy, or redistribute it subject to the terms and conditions of the GNU
* Lesser General Public License, as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
* for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this distribution; if not, write to:
* Free Software Foundation, Inc.
* 51 Franklin Street, Fifth Floor
* Boston, MA 02110-1301 USA
*/
package org.hibernate.test.annotations.lob.locator;
import java.sql.SQLException;
import org.junit.Assert;
import org.junit.Test;
import org.hibernate.Session;
import org.hibernate.testing.DialectChecks;
import org.hibernate.testing.RequiresDialectFeature;
import org.hibernate.testing.TestForIssue;
import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase;
import org.hibernate.type.descriptor.java.DataHelper;
/**
* @author Lukasz Antoniak (lukasz dot antoniak at gmail dot com)
*/
public class LobLocatorTest extends BaseCoreFunctionalTestCase {
@Override
protected Class<?>[] getAnnotatedClasses() {
return new Class<?>[] { LobHolder.class };
}
/**
* Specific JDBC drivers (e.g. SQL Server) may not automatically rewind bound input stream
* during statement execution. Such behavior results in error message similar to:
* {@literal The stream value is not the specified length. The specified length was 4, the actual length is 0.}
*/
@Test
@TestForIssue(jiraKey = "HHH-8193")
@RequiresDialectFeature(DialectChecks.UsesInputStreamToInsertBlob.class)
public void testStreamResetBeforeParameterBinding() throws SQLException {
final Session session = openSession();
session.getTransaction().begin();
LobHolder entity = new LobHolder(
session.getLobHelper().createBlob( "blob".getBytes() ),
session.getLobHelper().createClob( "clob" ), 0
);
session.persist( entity );
session.getTransaction().commit();
final Integer updatesLimit = 3;
for ( int i = 1; i <= updatesLimit; ++i ) {
session.getTransaction().begin();
entity = (LobHolder) session.get( LobHolder.class, entity.getId() );
entity.setCounter( i );
entity = (LobHolder) session.merge( entity );
session.getTransaction().commit();
}
session.getTransaction().begin();
entity = (LobHolder) session.get( LobHolder.class, entity.getId() );
entity.setBlobLocator( session.getLobHelper().createBlob( "updated blob".getBytes() ) );
entity.setClobLocator( session.getLobHelper().createClob( "updated clob" ) );
entity = (LobHolder) session.merge( entity );
session.getTransaction().commit();
session.clear();
session.getTransaction().begin();
checkState( "updated blob".getBytes(), "updated clob", updatesLimit, (LobHolder) session.get( LobHolder.class, entity.getId() ) );
session.getTransaction().commit();
session.close();
}
private void checkState(byte[] blob, String clob, Integer counter, LobHolder entity) throws SQLException {
Assert.assertEquals( counter, entity.getCounter() );
Assert.assertArrayEquals( blob, DataHelper.extractBytes( entity.getBlobLocator().getBinaryStream() ) );
Assert.assertEquals( clob, DataHelper.extractString( entity.getClobLocator() ) );
}
}

View File

@ -22,20 +22,24 @@
* Boston, MA 02110-1301 USA
*/
package org.hibernate.test.type.descriptor.java;
import java.sql.Blob;
import java.sql.SQLException;
import org.junit.Test;
import org.hibernate.engine.jdbc.BlobProxy;
import org.hibernate.type.descriptor.java.BlobTypeDescriptor;
import org.hibernate.type.descriptor.java.DataHelper;
import org.hibernate.type.descriptor.java.PrimitiveByteArrayTypeDescriptor;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import java.io.IOException;
import java.sql.Blob;
import java.sql.SQLException;
import org.hibernate.engine.jdbc.BlobImplementer;
import org.hibernate.engine.jdbc.BlobProxy;
import org.hibernate.testing.TestForIssue;
import org.hibernate.type.descriptor.java.BlobTypeDescriptor;
import org.hibernate.type.descriptor.java.DataHelper;
import org.hibernate.type.descriptor.java.PrimitiveByteArrayTypeDescriptor;
import org.junit.Test;
/**
* @author Steve Ebersole
*/
@ -84,4 +88,14 @@ public class BlobDescriptorTest extends AbstractDescriptorTest<Blob> {
fail( "SQLException accessing blob : " + e.getMessage() );
}
}
@Test
@TestForIssue( jiraKey = "HHH-8193" )
public void testStreamResetOnAccess() throws IOException {
byte[] bytes = new byte[] { 1, 2, 3, 4 };
BlobImplementer blob = (BlobImplementer) BlobProxy.generateProxy( bytes );
int value = blob.getUnderlyingStream().getInputStream().read();
// Call to BlobImplementer#getUnderlyingStream() should mark input stream for reset.
assertEquals( bytes.length, blob.getUnderlyingStream().getInputStream().available() );
}
}

View File

@ -44,6 +44,12 @@ abstract public class DialectChecks {
}
}
public static class UsesInputStreamToInsertBlob implements DialectCheck {
public boolean isMatch(Dialect dialect) {
return dialect.useInputStreamToInsertBlob();
}
}
public static class SupportsIdentityColumns implements DialectCheck {
public boolean isMatch(Dialect dialect) {
return dialect.supportsIdentityColumns();