org.tentackle.dbms.ModificationLog Maven / Gradle / Ivy
Show all versions of tentackle-database Show documentation
/**
* 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 extends ModificationLog> selectByTxId(long txId) {
Db db = getSession();
if (db.isRemote()) {
// invoke remote method
try {
List extends ModificationLog> 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 extends ModificationLog> 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");
}
}