Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@
public final class BlobProxy implements Blob, BlobImplementer {

private final BinaryStream binaryStream;
private final int markBytes;
private boolean resetAllowed;
private boolean needsReset;

/**
Expand All @@ -41,6 +43,8 @@ public final class BlobProxy implements Blob, BlobImplementer {
*/
private BlobProxy(byte[] bytes) {
binaryStream = new BinaryStreamImpl( bytes );
markBytes = bytes.length + 1;
setStreamMark();
}

/**
Expand All @@ -52,6 +56,18 @@ private BlobProxy(byte[] bytes) {
*/
private BlobProxy(InputStream stream, long length) {
this.binaryStream = new StreamBackedBinaryStream( stream, length );
this.markBytes = (int) length + 1;
setStreamMark();
}

private void setStreamMark() {
if ( binaryStream.getInputStream().markSupported() ) {
binaryStream.getInputStream().mark( markBytes );
resetAllowed = true;
}
else {
resetAllowed = false;
}
}


Expand All @@ -67,7 +83,12 @@ public BinaryStream getUnderlyingStream() throws SQLException {
private void resetIfNeeded() throws SQLException {
try {
if ( needsReset ) {
if ( !resetAllowed ) {
throw new SQLException( "Underlying stream does not allow reset" );
}

binaryStream.getInputStream().reset();
setStreamMark();
}
}
catch ( IOException ioe) {
Expand All @@ -90,6 +111,11 @@ public static Blob generateProxy(byte[] bytes) {
/**
* Generates a BlobImpl proxy using a given number of bytes from an InputStream.
*
* Be aware that certain database drivers will automatically close the provided InputStream after the
* contents have been written to the database. This may cause unintended side effects if the entity
* is also audited by Envers. In this case, it's recommended to use {@link #generateProxy(byte[])}
* instead as it isn't affected by this non-standard behavior.
*
* @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.
*
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* License: GNU Lesser General Public License (LGPL), version 2.1 or later.
* See the lgpl.txt file in the root directory or <http://www.gnu.org/licenses/lgpl-2.1.html>.
*/
package org.hibernate.orm.test.envers.integration.blob;

import static org.hamcrest.MatcherAssert.assertThat;
import static org.hibernate.testing.transaction.TransactionUtil.doInJPA;
import static org.junit.Assert.fail;

import java.io.BufferedInputStream;
import java.io.InputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.sql.Blob;

import org.hamcrest.Matchers;
import org.hibernate.engine.jdbc.BlobProxy;
import org.hibernate.envers.Audited;
import org.hibernate.orm.test.envers.BaseEnversJPAFunctionalTestCase;
import org.hibernate.orm.test.envers.Priority;
import org.junit.Test;

import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.Id;

/**
* @author Chris Cranford
*/
public class BasicBlobTest extends BaseEnversJPAFunctionalTestCase {

@Override
protected Class<?>[] getAnnotatedClasses() {
return new Class<?>[] { Asset.class };
}

@Test
@Priority(10)
public void initData() {
final Path path = Path.of( getClass().getResource( "./blob.txt" ).getPath() );

Check warning

Code scanning / CodeQL

Unsafe use of getResource

The idiom getClass().getResource() is unsafe for classes that may be extended.
doInJPA( this::entityManagerFactory, entityManager -> {
try {
final Asset asset = new Asset();
asset.setFileName( "blob.txt" );

final InputStream stream = new BufferedInputStream( Files.newInputStream( path ) );

Check warning

Code scanning / CodeQL

Potential input resource leak

This BufferedInputStream is not always closed on method exit.
assertThat( stream.markSupported(), Matchers.is( true ) );

// We use the method readAllBytes instead of passing the raw stream to the proxy
// since this is the only guaranteed way that will work across all dialects in a
// deterministic way. Postgres and Sybase will automatically close the stream
// after the blob has been written by the driver, which prevents Envers from
// then writing the contents of the stream to the audit table.
//
// If the driver and dialect are known not to close the input stream after the
// contents have been written by the driver, then it's safe to pass the stream
// here instead and the stream will be automatically marked and reset so that
// Envers can serialize the data after Hibernate has done so. Dialects like
// H2, MySQL, Oracle, SQL Server work this way.
//
//
Blob blob = BlobProxy.generateProxy( stream.readAllBytes() );

asset.setData( blob );
entityManager.persist( asset );
}
catch (Exception e) {
e.printStackTrace();
fail( "Failed to persist the entity" );
}
} );
}

@Audited
@Entity(name = "Asset")
public static class Asset {
@Id
@GeneratedValue
private Integer id;
private String fileName;
private Blob data;

public Integer getId() {
return id;
}

public void setId(Integer id) {
this.id = id;
}

public String getFileName() {
return fileName;
}

public void setFileName(String fileName) {
this.fileName = fileName;
}

public Blob getData() {
return data;
}

public void setData(Blob data) {
this.data = data;
}
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
<?xml version='1.0' encoding='utf-8'?>
<!--
~ Hibernate, Relational Persistence for Idiomatic Java
~
~ License: GNU Lesser General Public License (LGPL), version 2.1 or later.
~ See the lgpl.txt file in the root directory or <http://www.gnu.org/licenses/lgpl-2.1.html>.
-->
<!DOCTYPE hibernate-configuration PUBLIC
"-//Hibernate/Hibernate Configuration DTD//EN"
"http://www.hibernate.org/dtd/hibernate-configuration-3.0.dtd">

<hibernate-configuration>
<session-factory>
<property name="hbm2ddl.auto">create-drop</property>

<property name="show_sql">false</property>
<property name="format_sql">false</property>

<property name="dialect">org.hibernate.dialect.H2Dialect</property>
<property name="connection.url">jdbc:h2:mem:envers</property>
<property name="connection.driver_class">org.h2.Driver</property>
<property name="connection.username">sa</property>
<property name="connection.password"></property>

<!--<property name="dialect">org.hibernate.dialect.MySQL5Dialect</property>-->
<!--<property name="connection.url">jdbc:mysql:///hibernate_tests?useUnicode=true&amp;characterEncoding=UTF-8</property>-->
<!--<property name="connection.driver_class">com.mysql.jdbc.Driver</property>-->
<!--<property name="connection.username">root</property>-->
<!--<property name="connection.password"></property>-->

<!--<property name="hibernate.jdbc.batch_size">100</property>-->
</session-factory>
</hibernate-configuration>