org.firebirdsql.gds.ng.AbstractFbBlob Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of jaybird-jdk18 Show documentation
Show all versions of jaybird-jdk18 Show documentation
JDBC Driver for the Firebird RDBMS
/*
* $Id$
*
* Firebird Open Source JavaEE Connector - JDBC Driver
*
* Distributable under LGPL license.
* You may obtain a copy of the License at http://www.gnu.org/copyleft/lgpl.html
*
* 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
* LGPL License for more details.
*
* This file was created by members of the firebird development team.
* All individual contributions remain the Copyright (C) of those
* individuals. Contributors to this file are either listed here or
* can be obtained from a source control history command.
*
* All rights reserved.
*/
package org.firebirdsql.gds.ng;
import org.firebirdsql.gds.BlobParameterBuffer;
import org.firebirdsql.gds.ISCConstants;
import org.firebirdsql.gds.ng.listeners.DatabaseListener;
import org.firebirdsql.gds.ng.listeners.ExceptionListener;
import org.firebirdsql.gds.ng.listeners.ExceptionListenerDispatcher;
import org.firebirdsql.gds.ng.listeners.TransactionListener;
import org.firebirdsql.logging.Logger;
import org.firebirdsql.logging.LoggerFactory;
import java.sql.SQLException;
import java.sql.SQLWarning;
/**
* @author Mark Rotteveel
* @since 3.0
*/
public abstract class AbstractFbBlob implements FbBlob, TransactionListener, DatabaseListener {
private static final Logger log = LoggerFactory.getLogger(AbstractFbBlob.class);
private final Object syncObject;
protected final ExceptionListenerDispatcher exceptionListenerDispatcher = new ExceptionListenerDispatcher(this);
private final BlobParameterBuffer blobParameterBuffer;
private FbTransaction transaction;
private FbDatabase database;
private boolean open;
private boolean eof;
protected AbstractFbBlob(FbDatabase database, FbTransaction transaction, BlobParameterBuffer blobParameterBuffer) {
this.syncObject = database.getSynchronizationObject();
this.database = database;
this.transaction = transaction;
this.blobParameterBuffer = blobParameterBuffer;
transaction.addWeakTransactionListener(this);
}
@Override
public final boolean isOpen() {
synchronized (getSynchronizationObject()) {
return open;
}
}
@Override
public final boolean isEof() {
synchronized (getSynchronizationObject()) {
return eof || !isOpen();
}
}
/**
* Marks this blob as EOF (End of file).
*
* For an output blob this is a no-op (as those are never end of file, unless explicitly closed)
*
*/
protected final void setEof() {
if (isOutput()) return;
synchronized (getSynchronizationObject()) {
// TODO Can stream blobs be 'reopened' using seek?
eof = true;
}
}
/**
* Resets the eof state of the blob to false (not eof).
*
* This method should only be called by sub-classes of this class.
*
*/
protected final void resetEof() {
synchronized (getSynchronizationObject()) {
eof = false;
}
}
/**
* Sets the open state of the blob to the specified value.
*
* This method should only be called by sub-classes of this class.
*
*
* @param open New value of open.
*/
protected final void setOpen(boolean open) {
synchronized (getSynchronizationObject()) {
final FbDatabase database = this.database;
if (open) {
database.addWeakDatabaseListener(this);
} else {
database.removeDatabaseListener(this);
final FbTransaction transaction = this.transaction;
if (transaction != null) {
transaction.removeTransactionListener(this);
}
}
this.open = open;
}
}
@Override
public final void close() throws SQLException {
try {
synchronized (getSynchronizationObject()) {
if (!isOpen()) return;
try {
if (isEndingTransaction()) {
releaseResources();
} else {
checkDatabaseAttached();
checkTransactionActive();
closeImpl();
}
} finally {
setOpen(false);
}
}
} catch (SQLException e) {
exceptionListenerDispatcher.errorOccurred(e);
throw e;
} finally {
exceptionListenerDispatcher.shutdown();
}
}
/**
* Internal implementation of {@link #close()}. The implementation does not need
* to check for attached database and active transaction, nor does it need to mark this blob as closed.
*/
protected abstract void closeImpl() throws SQLException;
@Override
public final void cancel() throws SQLException {
try {
synchronized (getSynchronizationObject()) {
try {
if (isEndingTransaction()) {
releaseResources();
} else {
checkDatabaseAttached();
checkTransactionActive();
cancelImpl();
}
} finally {
setOpen(false);
}
}
} catch (SQLException e) {
exceptionListenerDispatcher.errorOccurred(e);
throw e;
}
}
/**
* Internal implementation of {@link #cancel()}. The implementation does not need
* to check for attached database and active transaction, nor does it need to mark this blob as closed.
*/
protected abstract void cancelImpl() throws SQLException;
/**
* Release Java resources held. This should not communicate with the Firebird server.
*/
protected abstract void releaseResources();
@Override
public final Object getSynchronizationObject() {
return syncObject;
}
@Override
public void transactionStateChanged(FbTransaction transaction, TransactionState newState,
TransactionState previousState) {
if (getTransaction() != transaction) {
transaction.removeTransactionListener(this);
return;
}
switch (newState) {
case COMMITTING:
case ROLLING_BACK:
case PREPARING:
try {
close();
} catch (SQLException e) {
log.error("Exception while closing blob during transaction end", e);
}
case COMMITTED:
case ROLLED_BACK:
synchronized (getSynchronizationObject()) {
clearTransaction();
setOpen(false);
releaseResources();
}
break;
default:
// Do nothing
break;
}
// TODO Need additional handling for other transitions?
}
@Override
public void detaching(FbDatabase database) {
if (this.database != database) {
database.removeDatabaseListener(this);
return;
}
synchronized (getSynchronizationObject()) {
if (isOpen()) {
log.debug(String.format("blob with blobId %d still open on database detach", getBlobId()));
try {
close();
} catch (SQLException e) {
log.error("Blob close in detaching event failed", e);
}
}
}
}
@Override
public void detached(FbDatabase database) {
synchronized (getSynchronizationObject()) {
if (this.database == database) {
open = false;
clearDatabase();
clearTransaction();
releaseResources();
}
}
database.removeDatabaseListener(this);
}
@Override
public void warningReceived(FbDatabase database, SQLWarning warning) {
// Do nothing
}
/**
* @return {@code true} if the transaction is committing, rolling back or preparing
*/
protected final boolean isEndingTransaction() {
FbTransaction transaction = getTransaction();
if (transaction != null) {
TransactionState transactionState = transaction.getState();
return transactionState == TransactionState.COMMITTING
|| transactionState == TransactionState.ROLLING_BACK
|| transactionState == TransactionState.PREPARING;
}
return false;
}
/**
* @throws java.sql.SQLException
* When no transaction is set, or the transaction state is not {@link TransactionState#ACTIVE}
*/
protected final void checkTransactionActive() throws SQLException {
TransactionHelper.checkTransactionActive(getTransaction(), ISCConstants.isc_segstr_no_trans);
}
/**
* @throws SQLException
* When no database is set, or the database is not attached
*/
protected void checkDatabaseAttached() throws SQLException {
synchronized (getSynchronizationObject()) {
if (database == null || !database.isAttached()) {
throw new FbExceptionBuilder().nonTransientException(ISCConstants.isc_segstr_wrong_db).toSQLException();
}
}
}
/**
* @throws SQLException
* When the blob is closed.
*/
protected void checkBlobOpen() throws SQLException {
if (!isOpen()) {
// TODO Use more specific exception message?
throw new FbExceptionBuilder().nonTransientException(ISCConstants.isc_bad_segstr_handle).toSQLException();
}
}
/**
* @throws SQLException
* When the blob is open.
*/
protected void checkBlobClosed() throws SQLException {
if (isOpen()) {
throw new FbExceptionBuilder().nonTransientException(ISCConstants.isc_no_segstr_close).toSQLException();
}
}
protected FbTransaction getTransaction() {
synchronized (getSynchronizationObject()) {
return transaction;
}
}
protected final void clearTransaction() {
final FbTransaction transaction;
synchronized (getSynchronizationObject()) {
transaction = this.transaction;
this.transaction = null;
}
if (transaction != null) {
transaction.removeTransactionListener(this);
}
}
@Override
public FbDatabase getDatabase() {
synchronized (getSynchronizationObject()) {
return database;
}
}
@Override
public T getBlobInfo(final byte[] requestItems, final int bufferLength, final InfoProcessor infoProcessor)
throws SQLException {
final byte[] blobInfo = getBlobInfo(requestItems, bufferLength);
try {
return infoProcessor.process(blobInfo);
} catch (SQLException e) {
exceptionListenerDispatcher.errorOccurred(e);
throw e;
}
}
@Override
public long length() throws SQLException {
try {
synchronized (getSynchronizationObject()) {
checkDatabaseAttached();
if (getBlobId() == FbBlob.NO_BLOB_ID) {
throw new FbExceptionBuilder().exception(ISCConstants.isc_bad_segstr_id).toSQLException();
}
final BlobLengthProcessor blobLengthProcessor = createBlobLengthProcessor();
return getBlobInfo(blobLengthProcessor.getBlobLengthItems(), 20, blobLengthProcessor);
}
} catch (SQLException e) {
exceptionListenerDispatcher.errorOccurred(e);
throw e;
}
}
@Override
public final void addExceptionListener(ExceptionListener listener) {
exceptionListenerDispatcher.addListener(listener);
}
@Override
public final void removeExceptionListener(ExceptionListener listener) {
exceptionListenerDispatcher.removeListener(listener);
}
protected final void clearDatabase() {
final FbDatabase database;
synchronized (getSynchronizationObject()) {
database = this.database;
this.database = null;
}
if (database != null) {
database.removeDatabaseListener(this);
}
}
/**
* @return The blob parameter buffer of this blob.
*/
protected BlobParameterBuffer getBlobParameterBuffer() {
return blobParameterBuffer;
}
/**
* @return New instance of {@link BlobLengthProcessor} (or subclass) for this blob.
*/
protected BlobLengthProcessor createBlobLengthProcessor() {
return new BlobLengthProcessor(this);
}
@Override
public int getMaximumSegmentSize() {
// TODO Max size in FB 3 is 2^16, not 2^15 - 1, is that for all versions, or only for newer protocols?
return Short.MAX_VALUE - 2;
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy