All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.tentackle.dbms.ModificationLog Maven / Gradle / Ivy

There is a newer version: 21.16.1.0
Show newest version
/**
 * Tentackle - http://www.tentackle.org
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library 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
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */



package org.tentackle.dbms;

import org.tentackle.common.Freezable;
import org.tentackle.common.StringHelper;
import org.tentackle.common.Timestamp;
import org.tentackle.dbms.rmi.ModificationLogRemoteDelegate;
import org.tentackle.log.Logger;
import org.tentackle.log.LoggerFactory;
import org.tentackle.misc.Canonicalizer;
import org.tentackle.misc.DateHelper;
import org.tentackle.misc.ParameterString;
import org.tentackle.session.ClassId;
import org.tentackle.session.PersistenceException;
import org.tentackle.session.Persistent;
import org.tentackle.session.Session;
import org.tentackle.session.SessionUtilities;
import org.tentackle.session.TableName;
import org.tentackle.sql.Backend;

import java.io.Serializable;
import java.rmi.RemoteException;
import java.text.ParseException;
import java.util.ArrayList;
import java.util.List;



/**
 * @> $mapfile
 *
 * # modification table for async coupling
 * name := $classname
 * id := $classid
 * table := $tablename
 *
 * [remote]
 *
 * ## attributes
 * long           objectId         objectid         object id
 * int            objectClassId    classid          object class id
 * String(192)    objectClassName  classname        object classname (if classid == 0)
 * long           txId             txid             transaction id (optional)
 * String(64)     txName           txname           transaction name (optional) [TRIMWRITE]
 * char           modType          modtype          modification type
 * Timestamp      when             modtime          time of event
 * String(32)     user             moduser          name of user [TRIMWRITE]
 * String         message          message          optional informational or error message [NOMETHOD]
 * Timestamp      processed        processed        processing time [MAPNULL]
 *
 * ## indexes
 * index next := processed, id
 * index txid := txid
 * index object := processed, objectid, classid
 * index user := processed, moduser
 *
 * @<
 */



/**
 * Logging for object modifications.
* * Modifications to {@link AbstractDbObject}s can be logged to a so-called modification log (aka: modlog).
* Most applications will use the modlog for asynchroneous database coupling. *

* Note: the txId is only valid (> 0) if the db-connection has {@link Db#isLogModificationTxEnabled}, * i.e. begin and commit records are logged as well. If the {@link IdSource} of the modlog is * transaction-based, transactions will not overlap in the modlog because obtaining * the id for the modlog is part of the transaction. However, if the idsource is * remote (poolkeeper rmi-client, for example), transactions may overlap! * In such cases the txid is necessary to separate the modlog sequences into * discrete transactions. (see the PoolKeeper project) */ @ClassId(/**/2/**/) // @wurblet < Inject $classid @TableName(/**/"modlog"/**/) // @wurblet < Inject --string $tablename public class ModificationLog extends AbstractDbObject { private static final long serialVersionUID = 7997968053729155282L; /** * logger for this class. */ private static final Logger LOGGER = LoggerFactory.getLogger(ModificationLog.class); /** Variables common to all instances of {@link ModificationLog}. */ public static final DbObjectClassVariables CLASSVARIABLES = DbObjectClassVariables.create(ModificationLog.class); /** modification type: begin transaction **/ public static final char BEGIN = 'B'; /** modification type: commit transaction **/ public static final char COMMIT= 'C'; /** modification type: object inserted **/ public static final char INSERT= 'I'; /** modification type: object updated **/ public static final char UPDATE= 'U'; /** modification type: object deleted **/ public static final char DELETE= 'D'; // @wurblet fieldlenghts ColumnNames --model=$mapfile // //GEN-BEGIN:fieldlenghts /** database column name for 'objectId'. */ public static final String CN_OBJECTID = "objectid"; /** database column name for 'objectClassId'. */ public static final String CN_OBJECTCLASSID = "classid"; /** database column name for 'objectClassName'. */ public static final String CN_OBJECTCLASSNAME = "classname"; /** database column name for 'txId'. */ public static final String CN_TXID = "txid"; /** database column name for 'txName'. */ public static final String CN_TXNAME = "txname"; /** database column name for 'modType'. */ public static final String CN_MODTYPE = "modtype"; /** database column name for 'when'. */ public static final String CN_WHEN = "modtime"; /** database column name for 'user'. */ public static final String CN_USER = "moduser"; /** database column name for 'message'. */ public static final String CN_MESSAGE = "message"; /** database column name for 'processed'. */ public static final String CN_PROCESSED = "processed"; // //GEN-END:fieldlenghts // @wurblet fieldnames ColumnLengths --model=$mapfile // //GEN-BEGIN:fieldnames /** maximum number of characters for 'objectClassName'. */ int CL_OBJECTCLASSNAME = 192; /** maximum number of characters for 'txName'. */ int CL_TXNAME = 64; /** maximum number of characters for 'user'. */ int CL_USER = 32; // //GEN-END:fieldnames /** * The {@link AbstractDbObject} the log belongs to. null = unknown. Speeds up {@link #getObject} * in distributed applications (see the poolkeeper framework). *

* If the lazyObject is a PersistentObject, it's getPdo() method will hold the PDO. */ protected ModificationLoggable lazyObject; /** * Message parameters (lazy) */ protected ParameterString messageParameters; // @wurblet declare Declare --model=$mapfile // //GEN-BEGIN:declare /** object id. */ private long objectId; /** object class id. */ private int objectClassId; /** object classname (if classid == 0). */ private String objectClassName; /** transaction id (optional). */ private long txId; /** transaction name (optional). */ private String txName; /** modification type. */ private char modType; /** time of event. */ private Timestamp when; /** name of user. */ private String user; /** optional informational or error message. */ private String message; /** processing time. */ private Timestamp processed; // //GEN-END:declare /** * Creates an empty modification log for a given db. * Useful for reading the log or as an RMI-proxy. * * @param db the database connection */ public ModificationLog(Db db) { super(db); } /** * Creates a modification log for a given db and modification type.
* * @param db the database connection * @param modType is the modification type (BEGIN or COMMIT) */ public ModificationLog(Db db, char modType) { this(db); this.modType = modType; txName = db.getTxName(); txId = db.getLogModificationTxId(); when = DateHelper.now(); user = db.getSessionInfo().getUserName(); } /** * Creates a modification log from an object. * * @param object is the logged object * @param modType is the modification type (INSERT, UPDATE...) */ public ModificationLog(ModificationLoggable object, char modType) { this((Db) object.getSession(), modType); if (modType == BEGIN || modType == COMMIT) { throw new PersistenceException(this, "illegal BEGIN or COMMIT in object logging"); } objectId = object.getId(); /** * The modlog's serial should reflect the serial of the object. * The modlog is inserted _after_ the object has been modified in the db. * Because the modlog's serial will be incremented during save(), we need to subtract 1 * from the serial. However, during update, the serial will be incremented _after_ * creating the modlog (see AbstractDbObject.updateObject()), so we need to subtract 1 only * for the other modlog types. */ setSerial(object.getSerial() - (modType == UPDATE ? 0 : 1)); if (object instanceof AbstractDbObject) { objectClassId = ((AbstractDbObject) object).getClassId(); } if (objectClassId == 0) { objectClassName = object.getClass().getName(); } if (modType == INSERT || modType == UPDATE) { // keep object for RMI transfers (not DELETE as this will be loaded on the servers side) lazyObject = object; } } /** * Creates a modlog from another modlog, but a different type. * * @param template the modlog template * @param modType is the modification type (INSERT, UPDATE...) */ public ModificationLog(ModificationLog template, char modType) { super(template.getSession()); objectId = template.objectId; objectClassId = template.objectClassId; txId = template.txId; txName = template.txName; when = template.when; user = template.user; message = template.message; this.modType = modType; } /** * Creates an empty modlog. * Constructor only provided for {@link Class#newInstance}. */ public ModificationLog() { super(); } // @wurblet methods MethodsImpl --model=$mapfile --noif // //GEN-BEGIN:methods @Override public void getFields(ResultSetWrapper rs) { super.getFields(rs); if (rs.configureSection(CLASSVARIABLES)) { rs.configureColumn(CN_OBJECTID); rs.configureColumn(CN_OBJECTCLASSID); rs.configureColumn(CN_OBJECTCLASSNAME); rs.configureColumn(CN_TXID); rs.configureColumn(CN_TXNAME); rs.configureColumn(CN_MODTYPE); rs.configureColumn(CN_WHEN); rs.configureColumn(CN_USER); rs.configureColumn(CN_MESSAGE); rs.configureColumn(CN_PROCESSED); rs.configureColumn(CN_ID); rs.configureColumn(CN_SERIAL); } if (rs.getRow() <= 0) { throw new PersistenceException(getSession(), "no valid row"); } objectId = rs.getLong(); objectClassId = rs.getInt(); objectClassName = rs.getString(); txId = rs.getLong(); txName = rs.getString(); modType = rs.getChar(); when = rs.getTimestamp(); user = rs.getString(); message = rs.getString(); processed = rs.getTimestamp(true); setId(rs.getLong()); setSerial(rs.getLong()); } @Override public int setFields(PreparedStatementWrapper st) { int ndx = super.setFields(st); st.setLong(++ndx, objectId); st.setInt(++ndx, objectClassId); st.setString(++ndx, objectClassName); st.setLong(++ndx, txId); st.setString(++ndx, StringHelper.trim(txName, 64)); st.setChar(++ndx, modType); st.setTimestamp(++ndx, when); st.setString(++ndx, StringHelper.trim(user, 32)); st.setString(++ndx, message); st.setTimestamp(++ndx, processed, true); st.setLong(++ndx, getId()); st.setLong(++ndx, getSerial()); return ndx; } @Override public String createInsertSql() { return Backend.SQL_INSERT_INTO + getTableName() + Backend.SQL_LEFT_PARENTHESIS + CN_OBJECTID + Backend.SQL_COMMA + CN_OBJECTCLASSID + Backend.SQL_COMMA + CN_OBJECTCLASSNAME + Backend.SQL_COMMA + CN_TXID + Backend.SQL_COMMA + CN_TXNAME + Backend.SQL_COMMA + CN_MODTYPE + Backend.SQL_COMMA + CN_WHEN + Backend.SQL_COMMA + CN_USER + Backend.SQL_COMMA + CN_MESSAGE + Backend.SQL_COMMA + CN_PROCESSED + Backend.SQL_COMMA + CN_ID + Backend.SQL_COMMA + CN_SERIAL + Backend.SQL_INSERT_VALUES + Backend.SQL_PAR_COMMA + Backend.SQL_PAR_COMMA + Backend.SQL_PAR_COMMA + Backend.SQL_PAR_COMMA + Backend.SQL_PAR_COMMA + Backend.SQL_PAR_COMMA + Backend.SQL_PAR_COMMA + Backend.SQL_PAR_COMMA + Backend.SQL_PAR_COMMA + Backend.SQL_PAR_COMMA + Backend.SQL_PAR_COMMA + Backend.SQL_PAR + Backend.SQL_RIGHT_PARENTHESIS; } @Override public String createUpdateSql() { return Backend.SQL_UPDATE + getTableName() + Backend.SQL_SET + CN_OBJECTID + Backend.SQL_EQUAL_PAR_COMMA + CN_OBJECTCLASSID + Backend.SQL_EQUAL_PAR_COMMA + CN_OBJECTCLASSNAME + Backend.SQL_EQUAL_PAR_COMMA + CN_TXID + Backend.SQL_EQUAL_PAR_COMMA + CN_TXNAME + Backend.SQL_EQUAL_PAR_COMMA + CN_MODTYPE + Backend.SQL_EQUAL_PAR_COMMA + CN_WHEN + Backend.SQL_EQUAL_PAR_COMMA + CN_USER + Backend.SQL_EQUAL_PAR_COMMA + CN_MESSAGE + Backend.SQL_EQUAL_PAR_COMMA + CN_PROCESSED + Backend.SQL_EQUAL_PAR_COMMA + CN_SERIAL + Backend.SQL_EQUAL + CN_SERIAL + Backend.SQL_PLUS_ONE + Backend.SQL_WHERE + CN_ID + Backend.SQL_EQUAL_PAR + Backend.SQL_AND + CN_SERIAL + Backend.SQL_EQUAL_PAR; } /** * Gets the attribute objectId. * * @return object id */ public long getObjectId() { return objectId; } /** * Sets the attribute objectId. * * @param objectId object id */ public void setObjectId(long objectId) { assertMutable(); this.objectId = objectId; } /** * Gets the attribute objectClassId. * * @return object class id */ public int getObjectClassId() { return objectClassId; } /** * Sets the attribute objectClassId. * * @param objectClassId object class id */ public void setObjectClassId(int objectClassId) { assertMutable(); this.objectClassId = objectClassId; } /** * Gets the attribute objectClassName. * * @return object classname (if classid == 0) */ public String getObjectClassName() { return objectClassName; } /** * Sets the attribute objectClassName. * * @param objectClassName object classname (if classid == 0) */ public void setObjectClassName(String objectClassName) { assertMutable(); this.objectClassName = objectClassName; } /** * Gets the attribute txId. * * @return transaction id (optional) */ public long getTxId() { return txId; } /** * Sets the attribute txId. * * @param txId transaction id (optional) */ public void setTxId(long txId) { assertMutable(); this.txId = txId; } /** * Gets the attribute txName. * * @return transaction name (optional) */ public String getTxName() { return txName; } /** * Sets the attribute txName. * * @param txName transaction name (optional) */ public void setTxName(String txName) { assertMutable(); this.txName = txName; } /** * Gets the attribute modType. * * @return modification type */ public char getModType() { return modType; } /** * Sets the attribute modType. * * @param modType modification type */ public void setModType(char modType) { assertMutable(); this.modType = modType; } /** * Gets the attribute when. * * @return time of event */ public Timestamp getWhen() { return when; } /** * Sets the attribute when. * * @param when time of event */ public void setWhen(Timestamp when) { assertMutable(); this.when = when; Timestamp.setUTC(when, false); Freezable.freeze(when); } /** * Gets the attribute user. * * @return name of user */ public String getUser() { return user; } /** * Sets the attribute user. * * @param user name of user */ public void setUser(String user) { assertMutable(); this.user = user; } /** * no accessor methods for message. * optional informational or error message */ /** * Gets the attribute processed. * * @return processing time */ public Timestamp getProcessed() { return processed; } /** * Sets the attribute processed. * * @param processed processing time */ public void setProcessed(Timestamp processed) { assertMutable(); this.processed = processed; Timestamp.setUTC(processed, false); Freezable.freeze(processed); } // //GEN-END:methods /** * Canonicalize the strings in this modlog.
* Used to reduce communication bandwidth when sending larger collections of modlogs via RMI. * * @param stringCanonicalizer the canonicalizer for the strings, null if none * @param objectCanonicalizer the canonicalizer for the lazy object, null if none */ public void canonicalize(Canonicalizer stringCanonicalizer, Canonicalizer objectCanonicalizer) { if (stringCanonicalizer != null) { txName = stringCanonicalizer.canonicalize(txName); user = stringCanonicalizer.canonicalize(user); message = stringCanonicalizer.canonicalize(message); messageParameters = null; // force rebuild } if (objectCanonicalizer != null) { lazyObject = objectCanonicalizer.canonicalize(lazyObject); } } /** * {@inheritDoc}.
* Overridden to set the db in lazyObject too (if unmarshalled from remote db) */ @Override public void setSession(Session session) { super.setSession(session); if (lazyObject != null) { lazyObject.setSession(session); } } /** * Clears the lazyObject. * Necessary for replaying modlogs that should not copy the lazyObject * to a remote db. */ public void clearLazyObject() { lazyObject = null; } @Override public ModificationLog readFromResultSetWrapper(ResultSetWrapper rs) { ModificationLog log = super.readFromResultSetWrapper(rs); if (log != null) { // clear hidden attributes in case modlog is used more than once for resultSet...() log.clearLazyObject(); log.messageParameters = null; } return log; } /** * Check whether this modlog is modifying data.
* * @return true if DELETE, DELETEALL, INSERT or UPDATE */ public boolean isModifyingData() { return modType == DELETE || modType == INSERT || modType == UPDATE; } /** * Returns whether this modlog marks a transaction boundary. * * @return true if BEGIN or COMMIT */ public boolean isTransactionBoundary() { return modType == BEGIN || modType == COMMIT; } /** * Returns whether this modlog belongs to a transaction. * * @return true if part of current transaction */ public boolean isLogOfTransaction() { return getSession().isTxRunning(); } /** * Returns whether the modlog refers to the PDO at the destination side during replay. * * @return true if refer to destination db */ public boolean isDestinationReferringLog() { return modType == DELETE; } /** * {@inheritDoc} *

* Overridden to check for deferred logging. */ @Override public void saveObject() { Db db = getSession(); if (db.isRemote()) { ModificationLoggable oldLazyObject = lazyObject; lazyObject = null; // don't transfer the lazyObject to the remote server super.saveObject(); lazyObject = oldLazyObject; // restore lazyObject } else { if (isLogOfTransaction()) { if (db.isLogModificationDeferred()) { newId(); // obtain an Id to trigger BEGIN only once setSerial(getSerial() + 1); // increment serial as if it has been saved } else { super.saveObject(); } db.pushModificationLogOfTransaction(this); } else { super.saveObject(); } } } /** * Gets the message parameters. * * @return the parameters, never null * @throws ParseException if paramaters are malformed */ public ParameterString getMessageParameters() throws ParseException { if (messageParameters == null) { messageParameters = new ParameterString(getMessage()); } return messageParameters; } /** * Sets the message parameters. *

* Updates the message field as well. * * @param messageParameters the message paramaters */ public void setMessageParameters(ParameterString messageParameters) { this.messageParameters = messageParameters; this.message = messageParameters == null ? null : messageParameters.toString(); } /** * Gets a message parameter. * * @param name the parameter's name * @return the parameter's value * @throws ParseException if parsing the message failed */ public String getMessageParameter(String name) throws ParseException { return getMessageParameters().getParameter(name); } /** * Sets a message parameter. *

* Updates the message field as well. * * @param name the parameter's name * @param value the parameter's value * @throws ParseException if parsing the message failed */ public void setMessageParameter(String name, String value) throws ParseException { getMessageParameters().setParameter(name, value); this.message = messageParameters.toString(); } @Override public String toString() { StringBuilder buf = new StringBuilder(); buf.append('<').append(getId()).append('/').append(modType).append(':'); if (user == null) { buf.append("no-user"); } else { buf.append(user); } if (txId != 0) { buf.append(',').append(txId); if (txName != null) { buf.append('/').append(txName); } } buf.append(','); if (when == null) { buf.append("no-time"); } else { buf.append(when); } buf.append(">"); if (objectClassId != 0 && objectId != 0) { buf.append(' ').append(SessionUtilities.getInstance().getClassName(objectClassId)) .append('[').append(objectId).append('/').append(getSerial()).append(']'); } if (message != null) { buf.append(" \"").append(message).append("\""); } return buf.toString(); } /** * Gets the db object referenced by this ModificationLog.
* The object is lazily cached if the given session equals * the session of this modlog. * * @param the expected returned type * @param session is the session from which to load the object. * @return the object or null if not found */ @SuppressWarnings("unchecked") public T getObject(Session session) { if (lazyObject != null && lazyObject.getSession().equals(session)) { return (T) lazyObject; // already lazily cached for requested session } String className = objectClassName != null ? objectClassName : SessionUtilities.getInstance().getClassName(objectClassId); if (className == null) { throw new PersistenceException(this, "unknown class id " + objectClassId); } try { if (session.isRemote()) { LOGGER.warning("inefficient remote object load while processing " + this); } Class clazz = Class.forName(className); lazyObject = loadObject(session, clazz, objectId); return (T) lazyObject; } catch (Exception ex) { throw new PersistenceException(this, "can't load object " + className + "[" + objectId + "]", ex); } } /** * Gets the object referenced by this ModificationLog.
* The object is lazily cached. * * @param the expected returned type * @return the object or null if not found. */ public T getObject() { return getObject(getSession()); } /** * Selects the next record to process.
* This is the first unprocessed modlog with the lowest ID. * * @return the modlog, null if no unprocessed log found * @wurblet selectFirstUnprocessed DbSelectUnique --model=$mapfile processed:=:null +id */ // //GEN-BEGIN:selectFirstUnprocessed public ModificationLog selectFirstUnprocessed() { Db db = getSession(); if (db.isRemote()) { // invoke remote method try { ModificationLog obj = ((ModificationLogRemoteDelegate) getRemoteDelegate()). selectFirstUnprocessed(); db.applyTo(obj); return obj; } catch (RemoteException e) { throw PersistenceException.createFromRemoteException(this, e); } } // else: local mode PreparedStatementWrapper st = getPreparedStatement(SELECT_FIRST_UNPROCESSED_STMT, () -> { StringBuilder sql = createSelectAllInnerSql(); sql.append(Backend.SQL_AND); sql.append(CN_PROCESSED); sql.append(Backend.SQL_EQUAL_PAR); sql.append(Backend.SQL_ORDERBY) .append(CN_ID).append(Backend.SQL_SORTASC); getBackend().buildSelectSql(sql, false, 1, 0); return sql.toString(); } ); int ndx = getBackend().setLeadingSelectParameters(st, 1, 0); st.setTimestamp(ndx++, null, true); getBackend().setTrailingSelectParameters(st, ndx, 1, 0); try (ResultSetWrapper rs = st.executeQuery()) { if (rs.next()) { return readFromResultSetWrapper(rs); } return null; // not found } } private static final StatementId SELECT_FIRST_UNPROCESSED_STMT = new StatementId(); // //GEN-END:selectFirstUnprocessed /** * Selects the unprocessed modlogs as a result set.
* * @return the resultset * @wurblet resultSetUnprocessed DbSelectList --model=$mapfile --resultset processed:=:null +id */ // //GEN-BEGIN:resultSetUnprocessed public ResultSetWrapper resultSetUnprocessed() { Db db = getSession(); PreparedStatementWrapper st = getPreparedStatement(RESULT_SET_UNPROCESSED_STMT, () -> { StringBuilder sql = createSelectAllInnerSql(); sql.append(Backend.SQL_AND); sql.append(CN_PROCESSED); sql.append(Backend.SQL_EQUAL_PAR); sql.append(Backend.SQL_ORDERBY) .append(CN_ID).append(Backend.SQL_SORTASC); getBackend().buildSelectSql(sql, false, 0, 0); return sql.toString(); } ); int ndx = 1; st.setTimestamp(ndx++, null, true); return st.executeQuery(); } private static final StatementId RESULT_SET_UNPROCESSED_STMT = new StatementId(); // //GEN-END:resultSetUnprocessed /** * Selects the next record to process greater than a given id. * * @param id the modlog id * @return the modlog, null if no unprocessed log found * @wurblet selectFirstUnprocessedGreater DbSelectUnique --model=$mapfile id:> processed:=:null +id */ // //GEN-BEGIN:selectFirstUnprocessedGreater public ModificationLog selectFirstUnprocessedGreater(long id) { Db db = getSession(); if (db.isRemote()) { // invoke remote method try { ModificationLog obj = ((ModificationLogRemoteDelegate) getRemoteDelegate()). selectFirstUnprocessedGreater(id); db.applyTo(obj); return obj; } catch (RemoteException e) { throw PersistenceException.createFromRemoteException(this, e); } } // else: local mode PreparedStatementWrapper st = getPreparedStatement(SELECT_FIRST_UNPROCESSED_GREATER_STMT, () -> { StringBuilder sql = createSelectAllInnerSql(); sql.append(Backend.SQL_AND); sql.append(CN_ID); sql.append(Backend.SQL_GREATER_PAR); sql.append(Backend.SQL_AND); sql.append(CN_PROCESSED); sql.append(Backend.SQL_EQUAL_PAR); sql.append(Backend.SQL_ORDERBY) .append(CN_ID).append(Backend.SQL_SORTASC); getBackend().buildSelectSql(sql, false, 1, 0); return sql.toString(); } ); int ndx = getBackend().setLeadingSelectParameters(st, 1, 0); st.setLong(ndx++, id); st.setTimestamp(ndx++, null, true); getBackend().setTrailingSelectParameters(st, ndx, 1, 0); try (ResultSetWrapper rs = st.executeQuery()) { if (rs.next()) { return readFromResultSetWrapper(rs); } return null; // not found } } private static final StatementId SELECT_FIRST_UNPROCESSED_GREATER_STMT = new StatementId(); // //GEN-END:selectFirstUnprocessedGreater /** * Selects the first modlog since a given modification time.
* * @param when the starting modification time * @return the modlog if any exists * @wurblet resultSetSince DbSelectList --model=$mapfile --resultset when:>= +id */ // //GEN-BEGIN:resultSetSince public ResultSetWrapper resultSetSince(Timestamp when) { Db db = getSession(); PreparedStatementWrapper st = getPreparedStatement(RESULT_SET_SINCE_STMT, () -> { StringBuilder sql = createSelectAllInnerSql(); sql.append(Backend.SQL_AND); sql.append(CN_WHEN); sql.append(Backend.SQL_GREATEROREQUAL_PAR); sql.append(Backend.SQL_ORDERBY) .append(CN_ID).append(Backend.SQL_SORTASC); getBackend().buildSelectSql(sql, false, 0, 0); return sql.toString(); } ); int ndx = 1; st.setTimestamp(ndx++, when); return st.executeQuery(); } private static final StatementId RESULT_SET_SINCE_STMT = new StatementId(); // //GEN-END:resultSetSince /** * Selects the first modlog with an ID greater than given ID. * * @param id the given ID * @return the modlog if any exists * * @wurblet selectGreaterId DbSelectUnique --model=$mapfile id:> +id */ // //GEN-BEGIN:selectGreaterId public ModificationLog selectGreaterId(long id) { Db db = getSession(); if (db.isRemote()) { // invoke remote method try { ModificationLog obj = ((ModificationLogRemoteDelegate) getRemoteDelegate()). selectGreaterId(id); db.applyTo(obj); return obj; } catch (RemoteException e) { throw PersistenceException.createFromRemoteException(this, e); } } // else: local mode PreparedStatementWrapper st = getPreparedStatement(SELECT_GREATER_ID_STMT, () -> { StringBuilder sql = createSelectAllInnerSql(); sql.append(Backend.SQL_AND); sql.append(CN_ID); sql.append(Backend.SQL_GREATER_PAR); sql.append(Backend.SQL_ORDERBY) .append(CN_ID).append(Backend.SQL_SORTASC); getBackend().buildSelectSql(sql, false, 1, 0); return sql.toString(); } ); int ndx = getBackend().setLeadingSelectParameters(st, 1, 0); st.setLong(ndx++, id); getBackend().setTrailingSelectParameters(st, ndx, 1, 0); try (ResultSetWrapper rs = st.executeQuery()) { if (rs.next()) { return readFromResultSetWrapper(rs); } return null; // not found } } private static final StatementId SELECT_GREATER_ID_STMT = new StatementId(); // //GEN-END:selectGreaterId /** * Selects the first modlog with an ID greater than given ID. * * @param id the given ID * @return the modlog if any exists * * @wurblet resultSetGreaterId DbSelectList --model=$mapfile --resultset id:> +id */ // //GEN-BEGIN:resultSetGreaterId public ResultSetWrapper resultSetGreaterId(long id) { Db db = getSession(); PreparedStatementWrapper st = getPreparedStatement(RESULT_SET_GREATER_ID_STMT, () -> { StringBuilder sql = createSelectAllInnerSql(); sql.append(Backend.SQL_AND); sql.append(CN_ID); sql.append(Backend.SQL_GREATER_PAR); sql.append(Backend.SQL_ORDERBY) .append(CN_ID).append(Backend.SQL_SORTASC); getBackend().buildSelectSql(sql, false, 0, 0); return sql.toString(); } ); int ndx = 1; st.setLong(ndx++, id); return st.executeQuery(); } private static final StatementId RESULT_SET_GREATER_ID_STMT = new StatementId(); // //GEN-END:resultSetGreaterId /** * Selects the last processed modlog.
* This is the last processed modlog with the highest ID. * * @return the modlog, null if no processed log found * @wurblet selectLastProcessed DbSelectUnique --model=$mapfile processed:!=:null -id */ // //GEN-BEGIN:selectLastProcessed public ModificationLog selectLastProcessed() { Db db = getSession(); if (db.isRemote()) { // invoke remote method try { ModificationLog obj = ((ModificationLogRemoteDelegate) getRemoteDelegate()). selectLastProcessed(); db.applyTo(obj); return obj; } catch (RemoteException e) { throw PersistenceException.createFromRemoteException(this, e); } } // else: local mode PreparedStatementWrapper st = getPreparedStatement(SELECT_LAST_PROCESSED_STMT, () -> { StringBuilder sql = createSelectAllInnerSql(); sql.append(Backend.SQL_AND); sql.append(CN_PROCESSED); sql.append(Backend.SQL_NOTEQUAL_PAR); sql.append(Backend.SQL_ORDERBY) .append(CN_ID).append(Backend.SQL_SORTDESC); getBackend().buildSelectSql(sql, false, 1, 0); return sql.toString(); } ); int ndx = getBackend().setLeadingSelectParameters(st, 1, 0); st.setTimestamp(ndx++, null, true); getBackend().setTrailingSelectParameters(st, ndx, 1, 0); try (ResultSetWrapper rs = st.executeQuery()) { if (rs.next()) { return readFromResultSetWrapper(rs); } return null; // not found } } private static final StatementId SELECT_LAST_PROCESSED_STMT = new StatementId(); // //GEN-END:selectLastProcessed /** * Gets the modlogs for a given object. * * @param objectClassId the object's class ID * @param objectId the object's ID * @return the list of modlogs * @wurblet selectByObject DbSelectList --model=$mapfile processed:=:null objectClassId objectId +id */ // //GEN-BEGIN:selectByObject public List selectByObject(int objectClassId, long objectId) { Db db = getSession(); if (db.isRemote()) { // invoke remote method try { List list = ((ModificationLogRemoteDelegate) getRemoteDelegate()). selectByObject(objectClassId, objectId); db.applyTo(list); return list; } catch (RemoteException e) { throw PersistenceException.createFromRemoteException(db, e); } } // else: local mode PreparedStatementWrapper st = getPreparedStatement(SELECT_BY_OBJECT_STMT, () -> { StringBuilder sql = createSelectAllInnerSql(); sql.append(Backend.SQL_AND); sql.append(CN_PROCESSED); sql.append(Backend.SQL_EQUAL_PAR); sql.append(Backend.SQL_AND); sql.append(CN_OBJECTCLASSID); sql.append(Backend.SQL_EQUAL_PAR); sql.append(Backend.SQL_AND); sql.append(CN_OBJECTID); sql.append(Backend.SQL_EQUAL_PAR); sql.append(Backend.SQL_ORDERBY) .append(CN_ID).append(Backend.SQL_SORTASC); getBackend().buildSelectSql(sql, false, 0, 0); return sql.toString(); } ); int ndx = 1; st.setTimestamp(ndx++, null, true); st.setInt(ndx++, objectClassId); st.setLong(ndx++, objectId); try (ResultSetWrapper rs = st.executeQuery()) { List list = new ArrayList<>(); boolean derived = getClass() != ModificationLog.class; while (rs.next()) { ModificationLog obj = derived ? newInstance() : new ModificationLog(db); obj = obj.readFromResultSetWrapper(rs); if (obj != null) { list.add(obj); } } return list; } } private static final StatementId SELECT_BY_OBJECT_STMT = new StatementId(); // //GEN-END:selectByObject /** * Selects all logs for a given user and type. * * @param user the username * @param modType the modlog type * @return the modlogs * @wurblet selectByUserAndType DbSelectList --model=$mapfile processed:=:null user modType */ // //GEN-BEGIN:selectByUserAndType public List selectByUserAndType(String user, char modType) { Db db = getSession(); if (db.isRemote()) { // invoke remote method try { List list = ((ModificationLogRemoteDelegate) getRemoteDelegate()). selectByUserAndType(user, modType); db.applyTo(list); return list; } catch (RemoteException e) { throw PersistenceException.createFromRemoteException(db, e); } } // else: local mode PreparedStatementWrapper st = getPreparedStatement(SELECT_BY_USER_AND_TYPE_STMT, () -> { StringBuilder sql = createSelectAllInnerSql(); sql.append(Backend.SQL_AND); sql.append(CN_PROCESSED); sql.append(Backend.SQL_EQUAL_PAR); sql.append(Backend.SQL_AND); sql.append(CN_USER); sql.append(Backend.SQL_EQUAL_PAR); sql.append(Backend.SQL_AND); sql.append(CN_MODTYPE); sql.append(Backend.SQL_EQUAL_PAR); getBackend().buildSelectSql(sql, false, 0, 0); return sql.toString(); } ); int ndx = 1; st.setTimestamp(ndx++, null, true); st.setString(ndx++, user); st.setChar(ndx++, modType); try (ResultSetWrapper rs = st.executeQuery()) { List list = new ArrayList<>(); boolean derived = getClass() != ModificationLog.class; while (rs.next()) { ModificationLog obj = derived ? newInstance() : new ModificationLog(db); obj = obj.readFromResultSetWrapper(rs); if (obj != null) { list.add(obj); } } return list; } } private static final StatementId SELECT_BY_USER_AND_TYPE_STMT = new StatementId(); // //GEN-END:selectByUserAndType /** * Checks if there are logs for a given user. * * @param user the user name * @return true if there are logs * @wurblet isReferencingUser DbIsReferencing --model=$mapfile processed:=:null user */ // //GEN-BEGIN:isReferencingUser public boolean isReferencingUser(String user) { if (getSession().isRemote()) { // invoke remote method try { return ((ModificationLogRemoteDelegate) getRemoteDelegate()). isReferencingUser(user); } catch (RemoteException e) { throw PersistenceException.createFromRemoteException(getSession(), e); } } // else: local mode PreparedStatementWrapper st = getPreparedStatement(IS_REFERENCING_USER_STMT, () -> { StringBuilder sql = createSelectIdInnerSql(); sql.append(Backend.SQL_AND); sql.append(CN_PROCESSED); sql.append(Backend.SQL_EQUAL_PAR); sql.append(Backend.SQL_AND); sql.append(CN_USER); sql.append(Backend.SQL_EQUAL_PAR); getBackend().buildSelectSql(sql, false, 1, 0); return sql.toString(); } ); int ndx = getBackend().setLeadingSelectParameters(st, 1, 0); st.setTimestamp(ndx++, null, true); st.setString(ndx++, user); getBackend().setTrailingSelectParameters(st, ndx, 1, 0); try (ResultSetWrapper rs = st.executeQuery()) { return rs.next(); } } private static final StatementId IS_REFERENCING_USER_STMT = new StatementId(); // //GEN-END:isReferencingUser /** * Checks if there are logs for a given object. * * @param objectClassId the object's class ID * @param objectId the object's ID * @return true if there are logs * @wurblet isReferencingObject DbIsReferencing --model=$mapfile objectClassId objectId */ // //GEN-BEGIN:isReferencingObject public boolean isReferencingObject(int objectClassId, long objectId) { if (getSession().isRemote()) { // invoke remote method try { return ((ModificationLogRemoteDelegate) getRemoteDelegate()). isReferencingObject(objectClassId, objectId); } catch (RemoteException e) { throw PersistenceException.createFromRemoteException(getSession(), e); } } // else: local mode PreparedStatementWrapper st = getPreparedStatement(IS_REFERENCING_OBJECT_STMT, () -> { StringBuilder sql = createSelectIdInnerSql(); sql.append(Backend.SQL_AND); sql.append(CN_OBJECTCLASSID); sql.append(Backend.SQL_EQUAL_PAR); sql.append(Backend.SQL_AND); sql.append(CN_OBJECTID); sql.append(Backend.SQL_EQUAL_PAR); getBackend().buildSelectSql(sql, false, 1, 0); return sql.toString(); } ); int ndx = getBackend().setLeadingSelectParameters(st, 1, 0); st.setInt(ndx++, objectClassId); st.setLong(ndx++, objectId); getBackend().setTrailingSelectParameters(st, ndx, 1, 0); try (ResultSetWrapper rs = st.executeQuery()) { return rs.next(); } } private static final StatementId IS_REFERENCING_OBJECT_STMT = new StatementId(); // //GEN-END:isReferencingObject /** * Updates the processing timestamp by objectclass + objectid + serial less or equal than some value.
* Used to mark modlogs already processed for a given object. (poolkeeper) * * @param processed the new processing timestamp * @param objectClassId the object class ID * @param objectId the object id * @param modType the modification type * @param serial the object serial * @return the number of modlogs updated * * @wurblet updateByObjectTypeSerial DbUpdateBy --model=$mapfile \ * processed:=:null objectClassId objectId modType serial:<= | processed */ // //GEN-BEGIN:updateByObjectTypeSerial public int updateByObjectTypeSerial(Timestamp processed, int objectClassId, long objectId, char modType, long serial) { if (getSession().isRemote()) { try { return ((ModificationLogRemoteDelegate) getRemoteDelegate()). updateByObjectTypeSerial(processed, objectClassId, objectId, modType, serial); } catch (RemoteException e) { throw PersistenceException.createFromRemoteException(getSession(), e); } } // else: local mode PreparedStatementWrapper st = getPreparedStatement(UPDATE_BY_OBJECT_TYPE_SERIAL_STMT, () -> { StringBuilder sql = createSqlUpdate(); sql.append(CN_PROCESSED); sql.append(Backend.SQL_EQUAL_PAR); sql.append(Backend.SQL_WHEREALL); sql.append(Backend.SQL_AND); sql.append(CN_PROCESSED); sql.append(Backend.SQL_EQUAL_PAR); sql.append(Backend.SQL_AND); sql.append(CN_OBJECTCLASSID); sql.append(Backend.SQL_EQUAL_PAR); sql.append(Backend.SQL_AND); sql.append(CN_OBJECTID); sql.append(Backend.SQL_EQUAL_PAR); sql.append(Backend.SQL_AND); sql.append(CN_MODTYPE); sql.append(Backend.SQL_EQUAL_PAR); sql.append(Backend.SQL_AND); sql.append(CN_SERIAL); sql.append(Backend.SQL_LESSOREQUAL_PAR); return sql.toString(); } ); int ndx = 1; st.setTimestamp(ndx++, processed, true); st.setTimestamp(ndx++, null, true); st.setInt(ndx++, objectClassId); st.setLong(ndx++, objectId); st.setChar(ndx++, modType); st.setLong(ndx++, serial); return st.executeUpdate(); } private static final StatementId UPDATE_BY_OBJECT_TYPE_SERIAL_STMT = new StatementId(); // //GEN-END:updateByObjectTypeSerial /** * Deletes by objectclass + objectid + serial less or equal than some value.
* Used to remove modlogs already processed for a given object. (poolkeeper) * * @param objectClassId the object class ID * @param objectId the object id * @param modType the modification type * @param serial the object serial * @return the number of objects removed * * @wurblet deleteByObjectTypeSerial DbDeleteBy --model=$mapfile \ * processed:=:null objectClassId objectId modType serial:<= */ // //GEN-BEGIN:deleteByObjectTypeSerial public int deleteByObjectTypeSerial(int objectClassId, long objectId, char modType, long serial) { if (getSession().isRemote()) { try { return ((ModificationLogRemoteDelegate) getRemoteDelegate()). deleteByObjectTypeSerial(objectClassId, objectId, modType, serial); } catch (RemoteException e) { throw PersistenceException.createFromRemoteException(getSession(), e); } } // else: local mode PreparedStatementWrapper st = getPreparedStatement(DELETE_BY_OBJECT_TYPE_SERIAL_STMT, () -> { StringBuilder sql = createDeleteAllSql(); sql.append(Backend.SQL_AND); sql.append(CN_PROCESSED); sql.append(Backend.SQL_EQUAL_PAR); sql.append(Backend.SQL_AND); sql.append(CN_OBJECTCLASSID); sql.append(Backend.SQL_EQUAL_PAR); sql.append(Backend.SQL_AND); sql.append(CN_OBJECTID); sql.append(Backend.SQL_EQUAL_PAR); sql.append(Backend.SQL_AND); sql.append(CN_MODTYPE); sql.append(Backend.SQL_EQUAL_PAR); sql.append(Backend.SQL_AND); sql.append(CN_SERIAL); sql.append(Backend.SQL_LESSOREQUAL_PAR); return sql.toString(); } ); int ndx = 1; st.setTimestamp(ndx++, null, true); st.setInt(ndx++, objectClassId); st.setLong(ndx++, objectId); st.setChar(ndx++, modType); st.setLong(ndx++, serial); return st.executeUpdate(); } private static final StatementId DELETE_BY_OBJECT_TYPE_SERIAL_STMT = new StatementId(); // //GEN-END:deleteByObjectTypeSerial /** * Selects the transaction.
* * @param txId the transaction id * @return the modlogs of the transaction sorted by id * * @wurblet selectByTxId DbSelectList --model=$mapfile txId --bounded +id */ // //GEN-BEGIN:selectByTxId public List selectByTxId(long txId) { Db db = getSession(); if (db.isRemote()) { // invoke remote method try { List list = ((ModificationLogRemoteDelegate) getRemoteDelegate()). selectByTxId(txId); db.applyTo(list); return list; } catch (RemoteException e) { throw PersistenceException.createFromRemoteException(db, e); } } // else: local mode PreparedStatementWrapper st = getPreparedStatement(SELECT_BY_TX_ID_STMT, () -> { StringBuilder sql = createSelectAllInnerSql(); sql.append(Backend.SQL_AND); sql.append(CN_TXID); sql.append(Backend.SQL_EQUAL_PAR); sql.append(Backend.SQL_ORDERBY) .append(CN_ID).append(Backend.SQL_SORTASC); getBackend().buildSelectSql(sql, false, 0, 0); return sql.toString(); } ); int ndx = 1; st.setLong(ndx++, txId); try (ResultSetWrapper rs = st.executeQuery()) { List list = new ArrayList<>(); boolean derived = getClass() != ModificationLog.class; while (rs.next()) { ModificationLog obj = derived ? newInstance() : new ModificationLog(db); obj = obj.readFromResultSetWrapper(rs); if (obj != null) { list.add(obj); } } return list; } } private static final StatementId SELECT_BY_TX_ID_STMT = new StatementId(); // //GEN-END:selectByTxId /** * Updates the processing timestamp of a transaction for all unprocessed modlogs. * * @param processed the new processing timestamp, null to set unprocessed * @param txId the transaction id * @return the number of updated modlogs * * @wurblet updateUnprocessedByTxId DbUpdateBy --model=$mapfile txId processed:=:null | processed */ // //GEN-BEGIN:updateUnprocessedByTxId public int updateUnprocessedByTxId(Timestamp processed, long txId) { if (getSession().isRemote()) { try { return ((ModificationLogRemoteDelegate) getRemoteDelegate()). updateUnprocessedByTxId(processed, txId); } catch (RemoteException e) { throw PersistenceException.createFromRemoteException(getSession(), e); } } // else: local mode PreparedStatementWrapper st = getPreparedStatement(UPDATE_UNPROCESSED_BY_TX_ID_STMT, () -> { StringBuilder sql = createSqlUpdate(); sql.append(CN_PROCESSED); sql.append(Backend.SQL_EQUAL_PAR); sql.append(Backend.SQL_WHEREALL); sql.append(Backend.SQL_AND); sql.append(CN_TXID); sql.append(Backend.SQL_EQUAL_PAR); sql.append(Backend.SQL_AND); sql.append(CN_PROCESSED); sql.append(Backend.SQL_EQUAL_PAR); return sql.toString(); } ); int ndx = 1; st.setTimestamp(ndx++, processed, true); st.setLong(ndx++, txId); st.setTimestamp(ndx++, null, true); return st.executeUpdate(); } private static final StatementId UPDATE_UNPROCESSED_BY_TX_ID_STMT = new StatementId(); // //GEN-END:updateUnprocessedByTxId /** * Deletes a transaction from the modlogs. * * @param txId the transaction id * @return the number of deleted modlogs * * @wurblet deleteByTxId DbDeleteBy --model=$mapfile txId */ // //GEN-BEGIN:deleteByTxId public int deleteByTxId(long txId) { if (getSession().isRemote()) { try { return ((ModificationLogRemoteDelegate) getRemoteDelegate()). deleteByTxId(txId); } catch (RemoteException e) { throw PersistenceException.createFromRemoteException(getSession(), e); } } // else: local mode PreparedStatementWrapper st = getPreparedStatement(DELETE_BY_TX_ID_STMT, () -> { StringBuilder sql = createDeleteAllSql(); sql.append(Backend.SQL_AND); sql.append(CN_TXID); sql.append(Backend.SQL_EQUAL_PAR); return sql.toString(); } ); int ndx = 1; st.setLong(ndx++, txId); return st.executeUpdate(); } private static final StatementId DELETE_BY_TX_ID_STMT = new StatementId(); // //GEN-END:deleteByTxId /** * Deletes processed modlogs.
* Used to limit the backlog for recovery. *

* Note: does not honour any transaction boundaries, so the first already processed * transaction may be incomplete. * * @param processed the maximum processing date * @return the number of deleted modlogs * @wurblet deleteProcessed DbDeleteBy --model=$mapfile processed:!=:null processed:<= */ // //GEN-BEGIN:deleteProcessed public int deleteProcessed(Timestamp processed) { if (getSession().isRemote()) { try { return ((ModificationLogRemoteDelegate) getRemoteDelegate()). deleteProcessed(processed); } catch (RemoteException e) { throw PersistenceException.createFromRemoteException(getSession(), e); } } // else: local mode PreparedStatementWrapper st = getPreparedStatement(DELETE_PROCESSED_STMT, () -> { StringBuilder sql = createDeleteAllSql(); sql.append(Backend.SQL_AND); sql.append(CN_PROCESSED); sql.append(Backend.SQL_NOTEQUAL_PAR); sql.append(Backend.SQL_AND); sql.append(CN_PROCESSED); sql.append(Backend.SQL_LESSOREQUAL_PAR); return sql.toString(); } ); int ndx = 1; st.setTimestamp(ndx++, null, true); st.setTimestamp(ndx++, processed, true); return st.executeUpdate(); } private static final StatementId DELETE_PROCESSED_STMT = new StatementId(); // //GEN-END:deleteProcessed /** * Selects all modlogs that are unprocessed or modified up to a given timestamp.
* Used to replay modlogs after a crash. * * @param processed the minimum processing date, null = all * @return the modlogs * @wurblet selectUpTo DbSelectList --model=$mapfile processed:=:null or processed:>= +id */ // //GEN-BEGIN:selectUpTo public List selectUpTo(Timestamp processed) { Db db = getSession(); if (db.isRemote()) { // invoke remote method try { List list = ((ModificationLogRemoteDelegate) getRemoteDelegate()). selectUpTo(processed); db.applyTo(list); return list; } catch (RemoteException e) { throw PersistenceException.createFromRemoteException(db, e); } } // else: local mode PreparedStatementWrapper st = getPreparedStatement(SELECT_UP_TO_STMT, () -> { StringBuilder sql = createSelectAllInnerSql(); sql.append(Backend.SQL_AND).append(Backend.SQL_LEFT_PARENTHESIS); sql.append(CN_PROCESSED); sql.append(Backend.SQL_EQUAL_PAR); sql.append(Backend.SQL_OR); sql.append(CN_PROCESSED); sql.append(Backend.SQL_GREATEROREQUAL_PAR); sql.append(Backend.SQL_RIGHT_PARENTHESIS); sql.append(Backend.SQL_ORDERBY) .append(CN_ID).append(Backend.SQL_SORTASC); getBackend().buildSelectSql(sql, false, 0, 0); return sql.toString(); } ); int ndx = 1; st.setTimestamp(ndx++, null, true); st.setTimestamp(ndx++, processed, true); try (ResultSetWrapper rs = st.executeQuery()) { List list = new ArrayList<>(); boolean derived = getClass() != ModificationLog.class; while (rs.next()) { ModificationLog obj = derived ? newInstance() : new ModificationLog(db); obj = obj.readFromResultSetWrapper(rs); if (obj != null) { list.add(obj); } } return list; } } private static final StatementId SELECT_UP_TO_STMT = new StatementId(); // //GEN-END:selectUpTo /** * Updates the processing timestamp. * * @param processed the processing timestamp, null to mark unprocessed */ public void updateProcessed(Timestamp processed) { if (getSession().isRemote()) { // invoke remote method try { ((ModificationLogRemoteDelegate) getRemoteDelegate()).updateProcessed(processed, this); this.processed = processed; return; } catch (RemoteException e) { throw PersistenceException.createFromRemoteException(getSession(), e); } } // else: local mode PreparedStatementWrapper st = getPreparedStatement(UPDATE_PROCESSES_STMTID, () -> Backend.SQL_UPDATE + getTableName() + Backend.SQL_SET + CN_PROCESSED + Backend.SQL_EQUAL_PAR + Backend.SQL_WHERE + CN_ID + Backend.SQL_EQUAL_PAR ); st.setTimestamp(1, processed, true); st.setLong(2, getId()); assertThisRowAffected(st.executeUpdate()); this.processed = processed; } private static final StatementId UPDATE_PROCESSES_STMTID = new StatementId(); /** * Updates the processing timestamp and adds a comment to the message field. * * @param processed the processing timestamp, null to mark unprocessed * @param comment the comment to add to the message, null if none * @throws ParseException if message does not contain a valid parameter string */ public void updateDiagnostics(Timestamp processed, String comment) throws ParseException { String parStr; if (comment != null) { ParameterString ps = new ParameterString(message); ps.setParameter("comment", comment); parStr = ps.toString(); } else { parStr = message; // unchanged } if (getSession().isRemote()) { // invoke remote method try { ((ModificationLogRemoteDelegate) getRemoteDelegate()).updateDiagnostics(processed, comment, this); this.processed = processed; this.message = parStr; return; } catch (RemoteException e) { throw PersistenceException.createFromRemoteException(getSession(), e); } } // else: local mode PreparedStatementWrapper st = getPreparedStatement(UPDATE_DIAGNOSTICS_STMTID, () -> Backend.SQL_UPDATE + getTableName() + Backend.SQL_SET + CN_PROCESSED + Backend.SQL_EQUAL_PAR_COMMA + CN_MESSAGE + Backend.SQL_EQUAL_PAR + Backend.SQL_WHERE + CN_ID + Backend.SQL_EQUAL_PAR ); int ndx = 1; st.setTimestamp(ndx++, processed, true); st.setString(ndx++, parStr); st.setLong(ndx++, getId()); assertThisRowAffected(st.executeUpdate()); this.processed = processed; this.message = parStr; } private static final StatementId UPDATE_DIAGNOSTICS_STMTID = new StatementId(); @Override public DbObjectClassVariables getClassVariables() { return CLASSVARIABLES; } /** * Gets the message. * * @return optional informational or error message */ @Persistent("optional informational or error message") public String getMessage() { return message; } /** * Sets the message. *

* The lazy {@link #messageParameters} are cleared. * * @param message optional informational or error message */ @Persistent("optional informational or error message") public void setMessage(String message) { assertMutable(); this.message = message; messageParameters = null; // force rebuild } /** * Applies a modification to another db.
* The method is not static to allow overriding (e.g. to extend with more transaction types). * Method is invoked within a tx, so no begin/commit/rollback necessary. *

* The modlog to replay is passed as an argument because the replaying modlog is used * to replay a list of modlogs and may perform some housekeeping and maintain state. * * @param modlog the modification log to replay * @param toDb the db the logs will be applied to */ public void replay(ModificationLog modlog, Session toDb) { LOGGER.fine("replaying modlog {0} to {1}", modlog, toDb); // load the object (lazily during rmi, because the object is already switched to local db) boolean retrieveFromDestination = modlog.isDestinationReferringLog(); Session db = retrieveFromDestination ? toDb : modlog.getSession(); AbstractDbObject object = modlog.getObject(db); if (object == null) { handleMissingObject(modlog, toDb); return; // if no exception } if (!retrieveFromDestination) { // switch to other db object.setSession(toDb); } try { // perform any preprocessing replayInitModification(modlog, object); switch (modlog.getModType()) { case INSERT: replayInsert(modlog, object); break; case UPDATE: replayUpdate(modlog, object); break; case DELETE: replayDelete(modlog, object); break; default: throw new PersistenceException(modlog, "illegal modType " + modlog.getModType() + " for replay"); } // perform any post processing replayFinishModification(modlog, object); } finally { if (!retrieveFromDestination) { object.setSession(modlog.getSession()); } } } /** * Perform preprocessing for replay. * * @param modlog the modification log * @param object the object to insert */ public void replayInitModification(ModificationLog modlog, AbstractDbObject object) { try { object.initModification(modlog.getModType()); } catch (RuntimeException re) { LOGGER.severe("replaying init-modification failed for " + modlog, re); throw re; } } /** * Perform postprocessing for replay. * * @param modlog the modification log * @param object the object to insert */ public void replayFinishModification(ModificationLog modlog, AbstractDbObject object) { try { object.finishModification(modlog.getModType()); } catch (RuntimeException re) { LOGGER.severe("replaying finish modification failed for " + modlog, re); throw re; } } /** * Replay an insert. * * @param modlog the modification log * @param object the object to insert */ public void replayInsert(ModificationLog modlog, AbstractDbObject object) { try { // set the modlog's serial (usually 1), in case multiple updates follow object.setModificationLog(modlog); long oldSerial = object.getSerial(); // keep old serial because of Canonilizer object.setSerial(modlog.getSerial()); object.insertPlain(); object.setSerial(oldSerial); } catch (RuntimeException re) { LOGGER.severe("replaying update failed for " + modlog, re); throw re; } } /** * Replay an update. * * @param modlog the modification log * @param object the object to update */ public void replayUpdate(ModificationLog modlog, AbstractDbObject object) { try { object.setModificationLog(modlog); if (object.getSerial() > modlog.getSerial()) { // update only the serial long oldSerial = object.getSerial(); // keep old serial because of Canonilizer object.setSerial(modlog.getSerial() - 1); object.updateSerial(); object.setSerial(oldSerial); } else if (object.getSerial() < modlog.getSerial()) { throw new PersistenceException(modlog, "unexpected modlog serial " + modlog.getSerial() + " > object serial " + object.getSerial()); } else { // this is the final update long oldSerial = object.getSerial(); object.setSerial(object.getSerial() - 1); object.updatePlain(); object.setSerial(oldSerial); } } catch (RuntimeException re) { LOGGER.severe("replaying update failed for " + modlog, re); throw re; } } /** * Replay a delete. * * @param modlog the modification log * @param object the object to delete */ public void replayDelete(ModificationLog modlog, AbstractDbObject object) { try { object.setModificationLog(modlog); // set the modlog's serial, in case insert and further updates follow long oldSerial = object.getSerial(); // keep old serial because of Canonilizer object.setSerial(modlog.getSerial()); object.deletePlain(); object.setSerial(oldSerial); } catch (RuntimeException re) { LOGGER.severe("replaying delete failed for " + modlog, re); throw re; } } /** * Replay state shared between consecutive invocations of {@link #replay}. */ public static class ReplayState implements Serializable { private static final long serialVersionUID = 1L; /** saved logModificationAllowed. */ public boolean oldLogModificationAllowed; /** transaction voucher. */ public long txVoucher; /** the transaction id if BEGIN modlog found. */ public long pendingTxId; /** the IDs of the replayed modlogs of this chunk. */ public long[] modlogIDs; /** true if this is the first block. */ public boolean first; /** true if this is the last block. */ public boolean last; } /** * Replays a list of modlogs within a single transaction.
* It will also create new txId if the modlogs are copied. * The method is not static to allow overriding (e.g. to extend with more transaction types). *

* There may be several consecutive invocations within the same transaction. * This allows some feedback during long running large modlog-lists. * * @param state the state of the preceeding invocation, never null * @param modList the list of log objects from the source db * @param copyLog true to copy the logs as well * @param toDb the db the logs will be applied to * * @return the replay state, never null * * @throws PersistenceException if replay failed and transacation rolled back */ public ReplayState replay(ReplayState state, List modList, boolean copyLog, Db toDb) { if (state.first) { state.oldLogModificationAllowed = toDb.isLogModificationAllowed(); toDb.setLogModificationAllowed(false); // don't log twice! state.txVoucher = toDb.begin("replay"); } try { for (ModificationLog log: modList) { // replay object's modification if (log.getModType() != BEGIN && log.getModType() != COMMIT) { // ignore BEGIN/COMMIT replay(log, toDb); // replay next modlog } if (copyLog) { LOGGER.fine("copying modlog {0} to {1}", log, toDb); log.clearLazyObject(); // no more needed, and don't change db in lazyObject and don't transfer to remote db if (log.getModType() == BEGIN) { state.pendingTxId = log.getId(); } Session oldSession = log.getSession(); log.setSession(toDb); log.setId(0); log.newId(); // force to get a new Id for toDb! if (state.pendingTxId != 0) { log.setTxId(state.pendingTxId); // set the txId if a BEGIN was found } log.insertPlain(); if (log.getModType() == COMMIT) { state.pendingTxId = 0; // clear pending id } log.setSession(oldSession); } } if (state.last) { toDb.commit(state.txVoucher); toDb.setLogModificationAllowed(state.oldLogModificationAllowed); } state.modlogIDs = new long[modList.size()]; int ndx = 0; for (ModificationLog log: modList) { state.modlogIDs[ndx++] = log.getId(); } return state; } catch (RuntimeException e) { toDb.rollback(state.txVoucher); toDb.setLogModificationAllowed(state.oldLogModificationAllowed); if (e instanceof PersistenceException) { throw e; } else { throw new PersistenceException("replay modList failed", e); } } } /** * Loads the object from storage. * * @param session the session * @param clazz the object class * @param id the object id * @return the object, null if not found */ protected ModificationLoggable loadObject(Session session, Class clazz, long id) { return (ModificationLoggable) DbUtilities.getInstance().selectObject(session, clazz, id, true); } /** * Handles the case when an object to replay is not found. * * @param modlog the original modlog * @param toSession the destination db */ protected void handleMissingObject(ModificationLog modlog, Session toSession) { throw new PersistenceException(modlog.getModType() == DELETE ? modlog.getSession() : toSession, "object " + SessionUtilities.getInstance().getClassName(modlog.getObjectClassId()) + "[" + modlog.getObjectId() + "] does not exist"); } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy