RemoteDirtyShareManager.java
/*
** Module : RemoteDirtyShareManager.java
** Abstract : DirtyShareManager implementation which delegates to a remote server
**
** Copyright (c) 2004-2024, Golden Code Development Corporation.
**
** -#- -I- --Date-- --JPRM-- ----------------------------------Description-----------------------------------
** 001 SVL 20080605 @38557 Created initial version. DirtyShareManager
** implementation which delegates to a remote
** server.
** 002 ECF 20080612 @38728 Replaced isEntityDirty() with
** getEntityDirtyState(). New method returns
** state information (is entity dirty with
** changes and/or deletes), rather than a simple
** boolean.
** 003 CA 20080716 @39115 Renamed method lockUniqueIndexes to
** lockAllIndexes - this will lock all the
** indexes for a certain entity. Modified the
** delete(...) and rollbackInsert(...) methods
** so the index locking can be disabled.
** 004 ECF 20080815 @39548 Added support for global change events. These
** are events that represent index-changing
** inserts, updates, or deletes, which are
** processed by sessions other than the session
** in which the event originated.
** 005 ECF 20090513 @42172 Modified variant of update() method. Added lock
** parameter to make index locking optional.
** 006 ECF 20090601 @42573 Added support for Progress isolation leak quirk.
** Added updateSnapshots() method.
** 007 ECF 20090609 @42647 Modified cleanupChanges(). The list of entities
** which have had their uncommitted changes leaked
** is now passed in, allowing proper cleanup.
** 008 ECF 20090720 @43312 Changed delete() method signature. Now it throws
** PersistenceException.
** 009 ECF 20131028 Import change.
** 010 ECF 20140825 Track unvalidated inserts.
** 011 OM 20160307 Added support for EXTENTs in update() methods. Updated signature of
** getDirtyInfo().
** 012 ECF 20200901 New ORM implementation.
** 013 SVL 20230516 registerForGlobalEvents can return null now.
** 014 AL2 20230822 Removed readOnly parameter.
** 015 TJD 20240123 Java 17 compatibility updates
** 016 TJD 20230509 Extend interface to allow dirty DMO leaks and to update shared snapshots
** TJD 20240724 Extend indexState parameter meaning for insert method
** 017 AL2 20240903 Added integration with DirtyData.
*/
/*
** This program is free software: you can redistribute it and/or modify
** it under the terms of the GNU Affero General Public License as
** published by the Free Software Foundation, either version 3 of the
** License, or (at your option) any later version.
**
** 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 Affero General Public License for more details.
**
** You may find a copy of the GNU Affero GPL version 3 at the following
** location: https://www.gnu.org/licenses/agpl-3.0.en.html
**
** Additional terms under GNU Affero GPL version 3 section 7:
**
** Under Section 7 of the GNU Affero GPL version 3, the following additional
** terms apply to the works covered under the License. These additional terms
** are non-permissive additional terms allowed under Section 7 of the GNU
** Affero GPL version 3 and may not be removed by you.
**
** 0. Attribution Requirement.
**
** You must preserve all legal notices or author attributions in the covered
** work or Appropriate Legal Notices displayed by works containing the covered
** work. You may not remove from the covered work any author or developer
** credit already included within the covered work.
**
** 1. No License To Use Trademarks.
**
** This license does not grant any license or rights to use the trademarks
** Golden Code, FWD, any Golden Code or FWD logo, or any other trademarks
** of Golden Code Development Corporation. You are not authorized to use the
** name Golden Code, FWD, or the names of any author or contributor, for
** publicity purposes without written authorization.
**
** 2. No Misrepresentation of Affiliation.
**
** You may not represent yourself as Golden Code Development Corporation or FWD.
**
** You may not represent yourself for publicity purposes as associated with
** Golden Code Development Corporation, FWD, or any author or contributor to
** the covered work, without written authorization.
**
** 3. No Misrepresentation of Source or Origin.
**
** You may not represent the covered work as solely your work. All modified
** versions of the covered work must be marked in a reasonable way to make it
** clear that the modified work is not originating from Golden Code Development
** Corporation or FWD. All modified versions must contain the notices of
** attribution required in this license.
*/
package com.goldencode.p2j.persist.remote;
import java.util.*;
import com.goldencode.p2j.persist.*;
import com.goldencode.p2j.persist.Record;
import com.goldencode.p2j.persist.dirty.*;
import com.goldencode.p2j.persist.event.*;
import com.goldencode.p2j.persist.lock.*;
import com.goldencode.p2j.persist.orm.*;
import com.goldencode.p2j.util.*;
/**
* A specialized implementation of {@link DirtyShareManager} which delegates
* requests for dirty share services to the remote P2J server which is
* authoritative for the target database. This is necessary in cases where
* business logic dynamically connects to a remote database which is not
* managed by the current P2J server instance, since actions of management of
* the information about uncommitted modifications to database records must be
* serialized through a dirty share manager which is authoritative for the
* target database.
* <p>
* An instance of this class works with a local proxy for a remote instance of
* {@link DirtyShareMultiplexer} residing at the target server. The
* remote object may manage multiple databases, each of which is assigned a
* unique database identifier. When the database is {@link
* #setDatabase(Database) set} into a {@code RemoteDirtyShareManager}
* instance, the associated identifier is requested from the remote
* multiplexer. The ID returned by the multiplexer is used on all subsequent
* service requests sent by the {@code DirtyShareManager} instance.
*
* @see RemotePersistence
* @author SVL
*/
public final class RemoteDirtyShareManager
implements DirtyShareManager
{
/** Dirty share manager multiplexer which will service remote requests */
private final DirtyShareMultiplexer multiplexer;
/** Multiplex ID for the remote database associated with this object */
private int mpID = -1;
/**
* Constructor which stores a proxy to the remote object which will
* service dirty share manager requests for the remote database.
*
* @param multiplexer
* Dirty share manager multiplexer which will service remote
* requests.
*/
public RemoteDirtyShareManager(DirtyShareMultiplexer multiplexer)
{
this.multiplexer = multiplexer;
}
/**
* Set the name of the physical database with which this dirty share
* manager is associated.
*
* @param database
* Database information object.
*/
public void setDatabase(Database database)
{
if (mpID >= 0)
{
throw new IllegalStateException("Database can only be set once");
}
mpID = multiplexer.getMultiplexID(database.getName());
}
/**
* Register with a global event clearinghouse an interest in insert,
* delete, and update events which affect the given DMO entity.
*
* @param entity
* DMO entity for which the caller is interested in tracking
* change.
*
* @return A unique identifier for the registrant, assigned by the event
* clearinghouse, used later to collect events and deregister or
* <code>null</code> if global events are disabled.
*
* @see #getGlobalEvents(long, long)
* @see #deregisterForGlobalEvents(long)
*/
public Long registerForGlobalEvents(String entity)
{
return multiplexer.registerForGlobalEvents(mpID, entity);
}
/**
* Deregister with a global event clearinghouse an interest in insert,
* delete, and update events which affect a particular DMO type.
*
* @param registerID
* A unique identifier for the registrant being deregistered.
*
* @see #getGlobalEvents(long, long)
* @see #registerForGlobalEvents(String)
*/
public void deregisterForGlobalEvents(long registerID)
{
multiplexer.deregisterForGlobalEvents(mpID, registerID);
}
/**
* Retrieve any new events that have been collected by a global event
* clearinghouse, representing inserts, deletes, and updates made to
* record types for which the caller previously has registered interest.
*
* @param registerID
* A unique identifier for an object which has registered interest
* in DMO changing events.
* @param lastID
* A unique identifier representing the last event retrieved by
* the caller. If less than 0, it is assumed no events have yet
* been retrieved since the caller registered interest in these
* events.
*
* @return Array of applicable, new events, or <code>null</code> if no
* such events have been collected.
*
* @throws EventRegistrationException
* if <code>registerID</code> is invalid or has expired.
*/
public GlobalChangeEvent[] getGlobalEvents(long registerID, long lastID)
throws EventRegistrationException
{
return multiplexer.getGlobalEvents(mpID, registerID, lastID);
}
/**
* Lock or unlock all indexes associated with the given DMO entity.
*
* @param entity
* Name of an entity, which is the name of the DMO implementation
* class associated with a table being tracked for uncommitted
* changes.
* @param lockType
* Type of lock to acquire, or <code>NONE</code> to release
* existing locks. <code>NO_WAIT</code> variants will be treated
* as regular, blocking variants.
*
* @throws PersistenceException
* if there is an error querying JDBC metadata while retrieving
* index information.
*/
public void lockAllIndexes(String entity, LockType lockType)
throws PersistenceException
{
int type = LockType.toInt(lockType);
multiplexer.lockAllIndexes(mpID, entity, type);
}
/**
* Begin tracking a newly inserted record in the dirty database.
* <p>
* This record is removed when the current transaction is committed or rolled back.
*
* @param entity
* Name of an entity, which is the name of the DMO implementation class associated
* with a table being tracked for uncommitted changes.
* @param dmo
* Record to be tracked.
* @param indexState
* The index update state of the record at the time of insertion.
* If null it means all indexes are complete
* @param lock
* <code>true</code> to acquire write locks to insert the record; <code>false</code>
* to assume write locks are acquired by calling code.
* @param dirtyData
* The data that was changed and caused this update. This is explicitly sent
* because sometimes we can't infer the dirty props from the DMO anymore.
*
* @throws PersistenceException
* if there is any database error.
*/
@Override
public void insert(String entity, Record dmo, IndexState indexState, boolean lock, DirtyData dirtyData)
throws PersistenceException
{
multiplexer.insert(mpID, entity, dmo, indexState, lock, dirtyData);
}
/**
* Rollback the insert of a record previously introduced with the {@link
* #insert(String, Record, IndexState, boolean, DirtyData)} method.
*
* @param ident
* Identifier for the target record, which encapsulates its entity
* name and primary key.
* @param lock
* <code>true</code> to acquire write locks;
* <code>false</code> to assume write locks are acquired by
* calling code.
*
* @throws PersistenceException
* if there is any database error.
*/
public void rollbackInsert(RecordIdentifier<String> ident, boolean lock)
throws PersistenceException
{
multiplexer.rollbackInsert(mpID, ident, lock);
}
/**
* Indicate whether the record specified by the given entity and primary
* key is being tracked for changes within an uncommitted transaction.
* This includes records which have been added to the dirty database due to
* an insert or an update, not due to a delete.
*
* @param entity
* Name of an entity, which is the name of the DMO implementation
* class associated with a table being tracked for uncommitted
* changes.
* @param id
* Primary key of the record to be checked.
*
* @return <code>true</code> to indicate the target record is being
* tracked for changes, else <code>false</code>.
*/
public boolean isTracked(String entity, Long id)
{
return multiplexer.isTracked(mpID, entity, id);
}
/**
* Begin tracking a DMO for changes, based on the updates indicated by the
* {@code properties}, {@code extIndexes} and {@code values} arguments.
* <p>
* Make a copy of the given record, apply the specified updates to that
* copy, and store the copy in the dirty database.
* <p>
* This record is removed when the adding context's current transaction is
* committed or rolled back.
*
* @param entity
* Name of an entity, which is the name of the DMO implementation
* class associated with a table being tracked for uncommitted changes.
* @param copy
* Record to be tracked.
* @param lock
* <code>true</code> to acquire write locks to update the record;
* <code>false</code> to assume write locks are acquired by calling code.
* @param dirtyData
* The data that was changed and caused this update. This is explicitly sent
* because sometimes we can't infer the dirty props from the DMO anymore.
*
* @throws PersistenceException
* if there is any database error.
*/
public void update(String entity,
Record copy,
boolean lock,
DirtyData dirtyData)
throws PersistenceException
{
multiplexer.update(mpID, entity, copy, lock, dirtyData);
}
/**
* Retrieve a DMO which is being tracked in the dirty database, and apply
* the specified updates to it, as provided by the <code>properties</code>
* and <code>values</code> arguments. It is assumed the DMO already exists
* in the database, from a previous call to {@link
* #update(String, Record, boolean, DirtyData)}.
*
* @param entity
* Name of an entity, which is the name of the DMO implementation
* class associated with a table being tracked for uncommitted changes.
* @param id
* Primary key id of the record to be retrieved and updated.
* @param validatedTransientIndex
* {@code true} if the update was caused by an index validation of a transient record.
* @param lock
* {@code true} to acquire write locks to update the record;
* {@code false} to assume write locks are acquired by calling code.
* @param dirtyData
* The data that was changed and caused this update. This is explicitly sent
* because sometimes we can't infer the dirty props from the DMO anymore.
*
* @throws PersistenceException
* if there is any database error.
*/
public void update(String entity,
Long id,
boolean validatedTransientIndex,
boolean lock,
DirtyData dirtyData)
throws PersistenceException
{
multiplexer.update(mpID, entity, id, lock, dirtyData);
}
/**
* Indicate whether any uncommitted modification is being tracked for the
* database table associated with the specified entity. A modification is
* the insertion, deletion, or update of a record. Insertions and updates
* considered the same for purposes of this method.
*
* @param entity
* Name of an entity, which is the name of the DMO implementation
* class associated with a table being tracked for uncommitted
* changes.
*
* @return Bitfield indicating dirty status of entity, using constants
* from {@link DirtyInfo}. Zero return indicates entity is not
* dirty. Only <code>CHANGE</code> and <code>DELETE</code> are
* detected, not <code>INSERT</code>.
*/
public int getEntityDirtyState(String entity)
{
return multiplexer.getEntityDirtyState(mpID, entity);
}
/**
* Indicate whether the record specified by the given entity and primary
* key has been deleted within an uncommitted transaction in <i>any</i>
* context.
*
* @param entity
* Name of an entity, which is the name of the DMO implementation
* class associated with a table being tracked for uncommitted
* changes.
* @param id
* Primary key of the record to be checked.
*
* @return <code>true</code> if the specified record has been deleted,
* else <code>false</code>.
*/
public boolean isDeleted(String entity, Long id)
{
return multiplexer.isDeleted(mpID, entity, id);
}
/**
* Mark a record as having been deleted in an uncommitted transaction, or
* unmark a record which previously was so marked.
* <p>
* A record is unmarked when the adding context's current transaction is
* committed or rolled back.
*
* @param entity
* Name of an entity, which is the name of the DMO implementation
* class associated with a table being tracked for uncommitted
* changes.
* @param id
* Primary key of deleted record.
* @param rollback
* If <code>false</code>, the target record is marked as deleted;
* if <code>true</code>, it is unmarked.
* @param lock
* <code>true</code> to acquire write locks to delete the record;
* <code>false</code> to assume write locks are acquired by
* calling code.
*
* @throws PersistenceException
* if there was an error deleting the record from the database.
*/
public void delete(String entity, Long id, boolean rollback, boolean lock)
throws PersistenceException
{
multiplexer.delete(mpID, entity, id, rollback, lock);
}
/**
* Retrieve from the dirty database the record specified by the given
* entity and primary key, if it exists.
*
* @param entity
* Name of an entity, which is the name of the DMO implementation
* class associated with a table being tracked for uncommitted
* changes.
* @param id
* Primary key of the record to be retrieved.
*
* @return Target record if it exists in the dirty database, else
* <code>null</code>.
*
* @throws PersistenceException
* if there is any database error.
*/
public Record getDirtyDMO(String entity, Long id)
throws PersistenceException
{
return multiplexer.getDirtyDMO(mpID, entity, id);
}
/**
* Given certain search criteria and possibly the primary key of a potential DMO match in the
* primary database, report any significant information currently being tracked for uncommitted
* transactions, which might override data (or the lack thereof) found in the primary database.
* <p>
* If a {@link DirtyInfo} object is returned by this method, it will contain overriding
* information, such as a dirty DMO which matched the search criteria, whether that record was
* newly inserted, or the fact that the candidate record found in the primary search has been
* deleted or modified in an uncommitted transaction. A {@code null} return indicates no
* overriding information was available.
*
* @param entity
* Name of an entity, which is the name of the DMO implementation class associated
* with a table being tracked for uncommitted changes.
* @param candidateID
* Primary key ID of DMO, if any, found in the primary database, which matches the
* current query criteria.
* @param index
* Name of index which governs the ordering of data in the current query.
* @param statements
* One or more HQL statements to be executed against the dirty database to find a
* record which matches the current query criteria. The list is in order of most
* specific to least specific search criteria.
* @param params
* Query substitution parameters which match placeholders in the most specific HQL
* statement.
* @param rowidLookup
* The predicate of the query does a lookup for ROWID/RECID of the table. In case of
* transient records they are accessible without the need of having a full matched
* index.
*
* @return An object which summarizes the findings, or {@code null} if no uncommitted change
* information was available for the given arguments.
*
* @throws PersistenceException
* if there was any database error.
*/
@Override
public DirtyInfo getDirtyInfo(String entity,
Long candidateID,
int index,
List<String> statements,
Object[] params,
boolean rowidLookup)
throws PersistenceException
{
return multiplexer.getDirtyInfo(mpID, entity, candidateID, index, statements, params, rowidLookup);
}
/**
* Execute an HQL query and return the results as a list. If the query returns multiple results
* per row, each element in the list will be an array of {@code Object}s.
* <p>
* No locking is attempted.
*
* @param fql
* FQL query statement. This method makes no assumptions as to the types of object(s) returned.
* @param params
* Substitution values for the query. If none, this should be an empty array.
* @param maxResults
* The maximum number of elements to be returned in the list.
* If this value is non-positive, no upper limit is applied.
* @param startOffset
* The 0-based offset of the first record to retrieve. If this
* value is non-positive, an offset of 0 is used by default.
*
* @return A list of results, or {@code null} if no result was found.
*
* @throws PersistenceException
* if there was an error executing the query.
*/
@Override
public <T> List<T> list(String fql, Object[] params, int maxResults, int startOffset)
throws PersistenceException
{
return multiplexer.list(mpID, fql, params, maxResults, startOffset);
}
/**
* Stop tracking uncommitted changes to the the subset of records
* represented by <code>subChanges</code>, and remove from the master set
* of uncommitted, inserted records the subset of records represented by
* <code>subInserts</code>.
*
* @param subChanges
* Sets of primary keys representing those records to be removed
* from uncommitted change tracking, mapped by entity name.
* @param subInserts
* A set of identifiers of those records to be removed from the
* master set of uncommitted inserts.
*
* @throws PersistenceException
* if there is any database error.
*/
public void cleanupChanges(Map<String, Set<Long>> subChanges, Set<RecordIdentifier<String>> subInserts)
throws PersistenceException
{
multiplexer.cleanupChanges(mpID, subChanges, subInserts);
}
/**
* Remove from the master set of uncommitted, deleted records the subset of
* records represented by <code>subDeletes</code>.
*
* @param subDeletes
* Sets of primary keys representing those records to be removed
* from uncommitted delete tracking, mapped by entity name.
*/
public void cleanupDeletes(Map<String, Set<Long>> subDeletes)
{
multiplexer.cleanupDeletes(mpID, subDeletes);
}
/**
* Update DMO snapshot to be leaked to other sessions
*
* @param database
* Value that uniquely identifies the target database on the
* remote server.
* @param entity
* Name of an entity, which is the name of the DMO implementation
* class associated with the table being created.
* @param dmos
* DMOs to be leaked, not NULL
* @throws PersistenceException
* if there is any database error.
*/
@Override
public void updateSnapshots(String entity, Set<Record> dmos)
throws PersistenceException
{
multiplexer.updateSnaphots(mpID, entity, dmos);
}
}