From 710662200b0c88ab064e71c5ff98504a0e7be37e Mon Sep 17 00:00:00 2001 From: Sanne Grinovero Date: Wed, 15 Aug 2018 17:56:12 +0100 Subject: [PATCH] HHH-12914 Avoid need to create a Proxy to implement org.hibernate.engine.jdbc.BlobProxy --- .../engine/jdbc/BlobImplementer.java | 4 +- .../org/hibernate/engine/jdbc/BlobProxy.java | 192 +++++++++--------- .../jdbc/internal/BinaryStreamImpl.java | 2 +- .../descriptor/java/BlobDescriptorTest.java | 2 +- 4 files changed, 101 insertions(+), 99 deletions(-) diff --git a/hibernate-core/src/main/java/org/hibernate/engine/jdbc/BlobImplementer.java b/hibernate-core/src/main/java/org/hibernate/engine/jdbc/BlobImplementer.java index f5b35c387d..0e38c320d4 100644 --- a/hibernate-core/src/main/java/org/hibernate/engine/jdbc/BlobImplementer.java +++ b/hibernate-core/src/main/java/org/hibernate/engine/jdbc/BlobImplementer.java @@ -6,6 +6,8 @@ */ package org.hibernate.engine.jdbc; +import java.sql.SQLException; + /** * Marker interface for non-contextually created {@link java.sql.Blob} instances.. * @@ -17,5 +19,5 @@ public interface BlobImplementer { * * @return Access to the underlying data. */ - public BinaryStream getUnderlyingStream(); + public BinaryStream getUnderlyingStream() throws SQLException; } diff --git a/hibernate-core/src/main/java/org/hibernate/engine/jdbc/BlobProxy.java b/hibernate-core/src/main/java/org/hibernate/engine/jdbc/BlobProxy.java index 24e73d63b1..36991a9d7a 100644 --- a/hibernate-core/src/main/java/org/hibernate/engine/jdbc/BlobProxy.java +++ b/hibernate-core/src/main/java/org/hibernate/engine/jdbc/BlobProxy.java @@ -8,9 +8,7 @@ package org.hibernate.engine.jdbc; import java.io.IOException; import java.io.InputStream; -import java.lang.reflect.InvocationHandler; -import java.lang.reflect.Method; -import java.lang.reflect.Proxy; +import java.io.OutputStream; import java.sql.Blob; import java.sql.SQLException; @@ -18,17 +16,21 @@ import org.hibernate.engine.jdbc.internal.BinaryStreamImpl; import org.hibernate.type.descriptor.java.DataHelper; /** - * Manages aspects of proxying {@link Blob} references for non-contextual creation, including proxy creation and - * handling proxy invocations. We use proxies here solely to avoid JDBC version incompatibilities. + * Manages aspects of representing {@link Blob} objects. + * + * In previous versions this used to be implemented by using a java.lang.reflect.Proxy to deal with + * incompatibilities across various JDBC versions, hence the class name, but using a real Proxy is no longer necessary. + * + * The class name could be updated to reflect this but that would break APIs, so this operation is deferred. * * @author Gavin King * @author Steve Ebersole * @author Gail Badner + * @author Sanne Grinovero */ -public class BlobProxy implements InvocationHandler { - private static final Class[] PROXY_INTERFACES = new Class[] { Blob.class, BlobImplementer.class }; +public final class BlobProxy implements Blob, BlobImplementer { - private BinaryStream binaryStream; + private final BinaryStream binaryStream; private boolean needsReset; /** @@ -52,15 +54,12 @@ public class BlobProxy implements InvocationHandler { this.binaryStream = new StreamBackedBinaryStream( stream, length ); } - private long getLength() { - return binaryStream.getLength(); - } private InputStream getStream() throws SQLException { return getUnderlyingStream().getInputStream(); } - private BinaryStream getUnderlyingStream() throws SQLException { + public BinaryStream getUnderlyingStream() throws SQLException { resetIfNeeded(); return binaryStream; } @@ -78,84 +77,14 @@ public class BlobProxy implements InvocationHandler { } /** - * {@inheritDoc} - * - * @throws UnsupportedOperationException if any methods other than - * {@link Blob#length}, {@link BlobImplementer#getUnderlyingStream}, - * {@link Blob#getBinaryStream}, {@link Blob#getBytes}, {@link Blob#free}, - * or toString/equals/hashCode are invoked. - */ - @Override - public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { - final String methodName = method.getName(); - final int argCount = method.getParameterCount(); - - if ( "length".equals( methodName ) && argCount == 0 ) { - return getLength(); - } - if ( "getUnderlyingStream".equals( methodName ) ) { - return getUnderlyingStream(); // Reset stream if needed. - } - if ( "getBinaryStream".equals( methodName ) ) { - if ( argCount == 0 ) { - return getStream(); - } - else if ( argCount == 2 ) { - final 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() + "]" ); - } - final 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 ( "getBytes".equals( methodName ) ) { - if ( argCount == 2 ) { - final long start = (Long) args[0]; - if ( start < 1 ) { - throw new SQLException( "Start position 1-based; must be 1 or more." ); - } - final 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 ) { - binaryStream.release(); - return null; - } - if ( "toString".equals( methodName ) && argCount == 0 ) { - return this.toString(); - } - if ( "equals".equals( methodName ) && argCount == 1 ) { - return proxy == args[0]; - } - if ( "hashCode".equals( methodName ) && argCount == 0 ) { - return this.hashCode(); - } - - throw new UnsupportedOperationException( "Blob may not be manipulated from creating session" ); - } - - /** - * Generates a BlobImpl proxy using byte data. + * Generates a BlobImpl using byte data. * * @param bytes The data to be created as a Blob. * - * @return The generated proxy. + * @return The BlobProxy instance to represent this data. */ public static Blob generateProxy(byte[] bytes) { - return (Blob) Proxy.newProxyInstance( getProxyClassLoader(), PROXY_INTERFACES, new BlobProxy( bytes ) ); + return new BlobProxy( bytes ); } /** @@ -164,26 +93,92 @@ public class BlobProxy implements InvocationHandler { * @param stream The input stream of bytes to be created as a Blob. * @param length The number of bytes from stream to be written to the Blob. * - * @return The generated proxy. + * @return The BlobProxy instance to represent this data. */ public static Blob generateProxy(InputStream stream, long length) { - return (Blob) Proxy.newProxyInstance( getProxyClassLoader(), PROXY_INTERFACES, new BlobProxy( stream, length ) ); + return new BlobProxy( stream, length ); } - /** - * Determines the appropriate class loader to which the generated proxy - * should be scoped. - * - * @return The class loader appropriate for proxy construction. - */ - private static ClassLoader getProxyClassLoader() { - return BlobImplementer.class.getClassLoader(); + @Override + public long length() throws SQLException { + return binaryStream.getLength(); + } + + @Override + public byte[] getBytes(final long start, final int length) throws SQLException { + if ( start < 1 ) { + throw new SQLException( "Start position 1-based; must be 1 or more." ); + } + if ( length < 0 ) { + throw new SQLException( "Length must be great-than-or-equal to zero." ); + } + return DataHelper.extractBytes( getStream(), start-1, length ); + } + + @Override + public InputStream getBinaryStream() throws SQLException { + return getStream(); + } + + @Override + public long position(byte[] pattern, long start) { + throw notSupported(); + } + + @Override + public long position(Blob pattern, long start) { + throw notSupported(); + } + + @Override + public int setBytes(long pos, byte[] bytes) { + throw notSupported(); + } + + @Override + public int setBytes(long pos, byte[] bytes, int offset, int len) { + throw notSupported(); + } + + @Override + public OutputStream setBinaryStream(long pos) { + throw notSupported(); + } + + @Override + public void truncate(long len) { + throw notSupported(); + } + + @Override + public void free() { + binaryStream.release(); + } + + @Override + public InputStream getBinaryStream(final long start, final long length) throws SQLException { + if ( start < 1 ) { + throw new SQLException( "Start position 1-based; must be 1 or more." ); + } + if ( start > length() ) { + throw new SQLException( "Start position [" + start + "] cannot exceed overall CLOB length [" + length() + "]" ); + } + if ( length > Integer.MAX_VALUE ) { + throw new SQLException( "Can't deal with Blobs larger than Integer.MAX_VALUE" ); + } + final int intLength = (int)length; + if ( intLength < 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, intLength ); } private static class StreamBackedBinaryStream implements BinaryStream { + private final InputStream stream; private final long length; - private byte[] bytes; private StreamBackedBinaryStream(InputStream stream, long length) { @@ -218,4 +213,9 @@ public class BlobProxy implements InvocationHandler { } } } + + private static UnsupportedOperationException notSupported() { + return new UnsupportedOperationException( "Blob may not be manipulated from creating session" ); + } + } diff --git a/hibernate-core/src/main/java/org/hibernate/engine/jdbc/internal/BinaryStreamImpl.java b/hibernate-core/src/main/java/org/hibernate/engine/jdbc/internal/BinaryStreamImpl.java index c04f8ba7aa..ce1077e730 100644 --- a/hibernate-core/src/main/java/org/hibernate/engine/jdbc/internal/BinaryStreamImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/engine/jdbc/internal/BinaryStreamImpl.java @@ -17,7 +17,7 @@ import org.hibernate.engine.jdbc.BinaryStream; * * @author Steve Ebersole */ -public class BinaryStreamImpl extends ByteArrayInputStream implements BinaryStream { +public final class BinaryStreamImpl extends ByteArrayInputStream implements BinaryStream { private final int length; /** diff --git a/hibernate-core/src/test/java/org/hibernate/test/type/descriptor/java/BlobDescriptorTest.java b/hibernate-core/src/test/java/org/hibernate/test/type/descriptor/java/BlobDescriptorTest.java index 099553d4f3..82548b83b7 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/type/descriptor/java/BlobDescriptorTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/type/descriptor/java/BlobDescriptorTest.java @@ -74,7 +74,7 @@ public class BlobDescriptorTest extends AbstractDescriptorTest { @Test @TestForIssue( jiraKey = "HHH-8193" ) - public void testStreamResetOnAccess() throws IOException { + public void testStreamResetOnAccess() throws IOException, SQLException { byte[] bytes = new byte[] { 1, 2, 3, 4 }; BlobImplementer blob = (BlobImplementer) BlobProxy.generateProxy( bytes ); int value = blob.getUnderlyingStream().getInputStream().read();