3030import org .firebirdsql .gds .ng .listeners .TransactionListener ;
3131import org .firebirdsql .gds .ng .wire .InlineBlob ;
3232import org .firebirdsql .jaybird .props .DatabaseConnectionProperties ;
33+ import org .firebirdsql .jaybird .props .PropertyConstants ;
3334import org .firebirdsql .jaybird .util .SQLExceptionChainBuilder ;
35+ import org .firebirdsql .jdbc .FBObjectListener .BlobListener ;
3436import org .firebirdsql .util .InternalApi ;
3537import org .jspecify .annotations .NullMarked ;
38+ import org .jspecify .annotations .Nullable ;
3639
3740import java .io .*;
3841import java .sql .Blob ;
4144import java .util .ArrayList ;
4245import java .util .Arrays ;
4346import java .util .Collection ;
44- import java .util .Collections ;
4547import java .util .HashSet ;
4648
4749import static java .util .Objects .requireNonNull ;
6264 * </p>
6365 */
6466@ InternalApi
67+ @ NullMarked
6568public final class FBBlob implements FirebirdBlob , TransactionListener {
6669
6770 private static final System .Logger logger = System .getLogger (FBBlob .class .getName ());
6871
6972 private boolean isNew ;
7073 private long blobId ;
7174 @ SuppressWarnings ("java:S3077" )
72- private volatile GDSHelper gdsHelper ;
73- private FBObjectListener . BlobListener blobListener ;
75+ private volatile @ Nullable GDSHelper gdsHelper ;
76+ private BlobListener blobListener ;
7477 private final Config config ;
7578
76- private final Collection <FBBlobInputStream > inputStreams = Collections . synchronizedSet ( new HashSet <>() );
77- private FBBlobOutputStream blobOut ;
78- private InlineBlob cachedInlineBlob ;
79+ private final Collection <FBBlobInputStream > inputStreams = new HashSet <>();
80+ private @ Nullable FBBlobOutputStream blobOut ;
81+ private @ Nullable InlineBlob cachedInlineBlob ;
7982
80- private FBBlob (GDSHelper c , boolean isNew , FBObjectListener . BlobListener blobListener , Config config ) {
83+ private FBBlob (GDSHelper c , boolean isNew , @ Nullable BlobListener blobListener , Config config ) {
8184 gdsHelper = c ;
8285 this .isNew = isNew ;
8386 this .blobListener = blobListener != null ? blobListener : FBObjectListener .NoActionBlobListener .instance ();
@@ -95,7 +98,7 @@ private FBBlob(GDSHelper c, boolean isNew, FBObjectListener.BlobListener blobLis
9598 * blob configuration (cannot be {@code null})
9699 * @since 5
97100 */
98- public FBBlob (GDSHelper c , FBObjectListener . BlobListener blobListener , Config config ) {
101+ public FBBlob (GDSHelper c , @ Nullable BlobListener blobListener , Config config ) {
99102 this (c , true , blobListener , config );
100103 }
101104
@@ -112,7 +115,7 @@ public FBBlob(GDSHelper c, FBObjectListener.BlobListener blobListener, Config co
112115 * blob configuration (cannot be {@code null})
113116 * @since 5
114117 */
115- public FBBlob (GDSHelper c , long blobId , FBObjectListener . BlobListener blobListener , Config config ) {
118+ public FBBlob (GDSHelper c , long blobId , @ Nullable BlobListener blobListener , Config config ) {
116119 this (c , false , blobListener , config );
117120 this .blobId = blobId ;
118121 }
@@ -135,7 +138,7 @@ Config config() {
135138 */
136139 FbBlob openBlob () throws SQLException {
137140 try (var ignored = withLock ()) {
138- checkClosed ();
141+ GDSHelper gdsHelper = requireOpenGDSHelper ();
139142 if (isNew ) {
140143 throw new SQLException ("No Blob ID is available in new Blob object" ,
141144 SQLStateConstants .SQL_STATE_GENERAL_ERROR );
@@ -164,8 +167,8 @@ FbBlob openBlob() throws SQLException {
164167 * @since 5
165168 */
166169 FbBlob createBlob () throws SQLException {
167- try (LockCloseable ignored = withLock ()) {
168- checkClosed ();
170+ try (var ignored = withLock ()) {
171+ GDSHelper gdsHelper = requireOpenGDSHelper ();
169172 // For historic reasons we allow creating an output blob even if this is not a new blob. This may need to
170173 // be reconsidered in the future
171174 return gdsHelper .createBlob (config );
@@ -231,8 +234,8 @@ public InputStream getBinaryStream(long pos, long length) throws SQLException {
231234 * if something went wrong
232235 */
233236 public byte [] getInfo (byte [] items , int bufferLength ) throws SQLException {
234- try (LockCloseable ignored = withLock ()) {
235- checkClosed ();
237+ try (var ignored = withLock ()) {
238+ GDSHelper gdsHelper = requireOpenGDSHelper ();
236239 blobListener .executionStarted (this );
237240 // TODO Does it make sense to close blob here?
238241 try (FbBlob blob = gdsHelper .openBlob (blobId , config )) {
@@ -246,7 +249,7 @@ public byte[] getInfo(byte[] items, int bufferLength) throws SQLException {
246249 @ Override
247250 public long length () throws SQLException {
248251 try (var ignored = withLock ()) {
249- checkClosed ();
252+ checkOpen ();
250253 blobListener .executionStarted (this );
251254 try (FbBlob blob = openBlob ()) {
252255 return blob .length ();
@@ -269,7 +272,7 @@ public boolean isSegmented() throws SQLException {
269272 @ Override
270273 public FBBlob detach () throws SQLException {
271274 try (var ignored = withLock ()) {
272- checkClosed ();
275+ GDSHelper gdsHelper = requireOpenGDSHelper ();
273276 var blobCopy = new FBBlob (gdsHelper , blobId , blobListener , config );
274277 if (cachedInlineBlob != null ) {
275278 blobCopy .cachedInlineBlob = cachedInlineBlob .copy ();
@@ -343,8 +346,8 @@ public byte[] getBytes() throws SQLException {
343346
344347 @ Override
345348 public InputStream getBinaryStream () throws SQLException {
346- try (LockCloseable ignored = withLock ()) {
347- checkClosed ();
349+ try (var ignored = withLock ()) {
350+ checkOpen ();
348351 FBBlobInputStream blobstream = new FBBlobInputStream (this );
349352 inputStreams .add (blobstream );
350353 return blobstream ;
@@ -384,8 +387,8 @@ public int setBytes(long pos, byte[] bytes, int offset, int len) throws SQLExcep
384387
385388 @ Override
386389 public OutputStream setBinaryStream (long pos ) throws SQLException {
387- try (LockCloseable ignored = withLock ()) {
388- checkClosed ();
390+ try (var ignored = withLock ()) {
391+ checkOpen ();
389392 blobListener .executionStarted (this );
390393
391394 if (blobOut != null ) {
@@ -430,6 +433,7 @@ public long getBlobId() throws SQLException {
430433 void setBlobId (long blobId ) {
431434 try (LockCloseable ignored = withLock ()) {
432435 this .blobId = blobId ;
436+ GDSHelper gdsHelper = this .gdsHelper ;
433437 if (isNew && gdsHelper != null ) {
434438 FbTransaction currentTransaction = gdsHelper .getCurrentTransaction ();
435439 if (currentTransaction != null ) {
@@ -456,7 +460,7 @@ public void copyBytes(byte[] bytes, int pos, int len) throws SQLException {
456460 * @return The buffer length
457461 */
458462 int getBufferLength () {
459- return config .blobBufferSize ();
463+ return Math . max ( PropertyConstants . MIN_BLOB_BUFFER_SIZE , config .blobBufferSize () );
460464 }
461465
462466 /**
@@ -466,7 +470,9 @@ int getBufferLength() {
466470 * InputStream that has been closed.
467471 */
468472 void notifyClosed (FBBlobInputStream stream ) {
469- inputStreams .remove (stream );
473+ try (var ignored = withLock ()) {
474+ inputStreams .remove (stream );
475+ }
470476 }
471477
472478 /**
@@ -647,18 +653,36 @@ public void copyCharacterStream(Reader reader) throws SQLException {
647653 public void transactionStateChanged (FbTransaction transaction , TransactionState newState ,
648654 TransactionState previousState ) {
649655 if (newState == TransactionState .COMMITTED || newState == TransactionState .ROLLED_BACK ) {
650- try ( LockCloseable ignored = withLock ()) {
656+ try {
651657 free ();
652658 } catch (SQLException e ) {
653659 logger .log (System .Logger .Level .ERROR , "Error calling free on blob during transaction end" , e );
654660 }
655661 }
656662 }
657663
658- private void checkClosed () throws SQLException {
664+ /**
665+ * Checks if this blob is open.
666+ * <p>
667+ * If the method also requires use of the {@code GDSHelper} object, use {@link #requireOpenGDSHelper()} instead.
668+ * </p>
669+ *
670+ * @throws SQLException
671+ * if this blob is currently not open (does not have a {@code GDSHelper}).
672+ * @see #requireOpenGDSHelper()
673+ */
674+ private void checkOpen () throws SQLException {
675+ if (gdsHelper == null ) {
676+ throw FbExceptionBuilder .toException (JaybirdErrorCodes .jb_blobClosed );
677+ }
678+ }
679+
680+ private GDSHelper requireOpenGDSHelper () throws SQLException {
681+ GDSHelper gdsHelper = this .gdsHelper ;
659682 if (gdsHelper == null ) {
660683 throw FbExceptionBuilder .toException (JaybirdErrorCodes .jb_blobClosed );
661684 }
685+ return gdsHelper ;
662686 }
663687
664688 /**
0 commit comments