OrmUtils.java
/*
** Module : OrmUtils.java
** Abstract : ORM-related utility methods.
**
** Copyright (c) 2020-2024, Golden Code Development Corporation.
**
** -#- -I- --Date-- ---------------------------------------Description---------------------------------------
** 001 OM 20200731 First revision.
** 002 CA 20200921 Added getField(record, index).
** OM 20201001 Improved DMO manipulation performance by caching slow Property annotation access.
** AIL 20201210 Added setAllFields for bulk copy of fields.
** OM 20210223 Invalidate index-based cache in the event of low-level SQL operations.
** ECF 20210423 BaseRecord API name change.
** TJD 20220504 Upgrade do Java 11 minor changes.
** 003 OM 20240215 Dropping the records in dropUniqueIndexConflicts() is done only after evicting them, if
** exist, from Session.cache.
** 004 OM 20241128 Multi-tenant runtime support: selected the proper persistence context, eventually
** based on [sharedDb] parameter.
*/
/*
** This program is free software: you can redistribute it and/or modify
** it under the terms of the GNU Affero General Public License as
** published by the Free Software Foundation, either version 3 of the
** License, or (at your option) any later version.
**
** This program is distributed in the hope that it will be useful,
** but WITHOUT ANY WARRANTY; without even the implied warranty of
** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
** GNU Affero General Public License for more details.
**
** You may find a copy of the GNU Affero GPL version 3 at the following
** location: https://www.gnu.org/licenses/agpl-3.0.en.html
**
** Additional terms under GNU Affero GPL version 3 section 7:
**
** Under Section 7 of the GNU Affero GPL version 3, the following additional
** terms apply to the works covered under the License. These additional terms
** are non-permissive additional terms allowed under Section 7 of the GNU
** Affero GPL version 3 and may not be removed by you.
**
** 0. Attribution Requirement.
**
** You must preserve all legal notices or author attributions in the covered
** work or Appropriate Legal Notices displayed by works containing the covered
** work. You may not remove from the covered work any author or developer
** credit already included within the covered work.
**
** 1. No License To Use Trademarks.
**
** This license does not grant any license or rights to use the trademarks
** Golden Code, FWD, any Golden Code or FWD logo, or any other trademarks
** of Golden Code Development Corporation. You are not authorized to use the
** name Golden Code, FWD, or the names of any author or contributor, for
** publicity purposes without written authorization.
**
** 2. No Misrepresentation of Affiliation.
**
** You may not represent yourself as Golden Code Development Corporation or FWD.
**
** You may not represent yourself for publicity purposes as associated with
** Golden Code Development Corporation, FWD, or any author or contributor to
** the covered work, without written authorization.
**
** 3. No Misrepresentation of Source or Origin.
**
** You may not represent the covered work as solely your work. All modified
** versions of the covered work must be marked in a reasonable way to make it
** clear that the modified work is not originating from Golden Code Development
** Corporation or FWD. All modified versions must contain the notices of
** attribution required in this license.
*/
package com.goldencode.p2j.persist.orm;
import com.goldencode.p2j.persist.*;
import com.goldencode.p2j.util.*;
import java.math.*;
import java.util.*;
/**
* This abstract class groups together a set of static methods that need access to ORM data member only
* visible from within this package. The methods added here are always static and the parameters must provide
* all context necessary for its execution
*/
public abstract class OrmUtils
{
/**
* Given a TEMP-TABLE record, this method drops all records from the table that are in conflict with the
* record stored in the {@code buffer} so that at the next flush (insert) it is guaranteed there will be
* no unique validation issues.
* <p>
* The method works by executing a series of {@code n} DELETE statements, each one for a specific unique
* index of the record's table.
* <p>
* Note: the method deletes all records for a specified {@code multiplex}, INCLUDING the record in the
* buffer, if it was already saved;
* if multiple existing records conflict with the one stored in buffer, all of them are dropped.
*
* @param buffer
* The buffer that stores the record.
* @param p
* The persistence to be used when executing DELETE SQL statements.
* @param multiplexID
* The temp-table multiplexer.
*
* @throws PersistenceException
* when a problem is encountered during the process.
*/
public static void dropUniqueIndexConflicts(RecordBuffer buffer, Persistence p, int multiplexID)
throws PersistenceException
{
DmoMeta dmoInfo = buffer.getDmoInfo();
RecordMeta metadata = dmoInfo.getRecordMeta();
PropertyMeta[] props = metadata.getPropertyMeta(false);
String[] collisionQueries = metadata.getIndexKeyCollisionsSQLs(p.getDialect());
BitSet[] uIndices = metadata.getUniqueIndices();
boolean sharedDb = !dmoInfo.multiTenant;
boolean tx = false;
try
{
tx = p.beginTransaction(sharedDb);
int len = collisionQueries.length;
for (int i = 0; i < len; i++)
{
String sql = collisionQueries[i];
BitSet uIndex = uIndices[i];
Object[] args = new Object[uIndex.cardinality() + 1];
args[0] = multiplexID;
int argc = 1;
for (int k = uIndex.nextSetBit(0); k >= 0; k = uIndex.nextSetBit(k + 1))
{
Object datum = buffer.getCurrentRecord().data[k];
if (props[k].getType() == character.class)
{
datum = TextOps.trim((String) datum, " ", false, true);
if (!props[k].isCaseSensitive())
{
datum = ((String) datum).toUpperCase();
}
}
else if (props[k].getType() == decimal.class)
{
// must round [datum] to field's decimals
datum = ((BigDecimal) datum).setScale(props[k].getDecimals(), RoundingMode.HALF_UP);
}
args[argc++] = datum;
}
ScrollableResults<Long> results = p.executeSQLQuery(sql, sharedDb, args);
if (results.first())
{
int recCount = 1;
do
{
if (recCount > 1)
{
System.out.println("No more than one result expected in index-key collisions.");
}
Long pk = results.get(0, Long.class);
p.getSession(!dmoInfo.multiTenant)
.evictIfExist(dmoInfo.getImplementationClass().getName(), pk);
p.executeSQL("delete from " + dmoInfo.sqlTable + " where " + Session.PK + " = ?",
sharedDb,
new Object[]{pk});
recCount++;
}
while (results.next());
}
results.close();
}
}
finally
{
if (tx)
{
p.commit(sharedDb);
}
// make sure the ffc will read these nest time ity is used
buffer.invalidateFFCache(0);
}
}
/**
* Sets a field on a record. The low-level value is directly assigned to property at {@code index} location
* in the {@code data} array of the record.
*
* @param record
* The {@code BaseRecord} to be changed.
* @param index
* The absolute index of the property (including extent offset, if the case).
* @param datum
* The value to be set (plain value, not wrapped in BDT).
*/
public static void setField(BaseRecord record, int index, Object datum)
{
record.setDatum(index, datum);
}
/**
* Sets all fields on a record based on a provided record. The low-level values are directly assigned
* in the {@code data} array of the record.
*
* @param src
* The source record from which the data will be copied.
* @param dst
* The destination record to which the data will be copied.
*/
public static boolean setAllFields(BaseRecord src, BaseRecord dst)
{
return dst.setAllData(src);
}
/**
* Get the field on the given index, from the specified record.
*
* @param record
* The record instance.
* @param index
* The absolute index of the property (including extent offset, if the case).
*
* @return The field's value (plain value, not wrapped in BDT).
*/
public static Object getField(BaseRecord record, int index)
{
return record.getDatum(index);
}
}