AbstractRowStructure.java
/*
** Module : AbstractRowStructure.java
** Abstract : Defines an abstract row structure in a query.
**
** Copyright (c) 2020-2024, Golden Code Development Corporation.
**
** -#- -I- --Date-- ----------------------------------------Description---------------------------------------
** 001 AL2 20231128 Initial version.
** AL2 20231211 Recieve the DMO meta directly in the constructor.
** AL2 20231212 Use dmoMeta directly, without getter. Reworked toString.
** 002 TJD 20240131 Check SQLException for Interrupted exception cause
** 003 LS 20241105 Added hasOnlyPK().
*/
/*
** 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 java.sql.*;
import java.util.Iterator;
import com.goldencode.p2j.persist.DataModelObject;
import com.goldencode.p2j.persist.PersistenceException;
import com.goldencode.p2j.util.StopConditionException;
/**
* Common behavior for all row structures. This aggregates all routines that are common
* like non-expanded extent hydration and non-expanded property detection.
*/
public abstract class AbstractRowStructure
implements RowStructure
{
/** The DMO class. */
protected final Class<? extends DataModelObject> dmoClass;
/** The DMO meta of the DMO class */
protected final DmoMeta dmoMeta;
/** Check if this row structure should also hydrate the non-expanded extent fields. */
protected boolean hasExtents = false;
/** A cached value of the property meta array that can be found in the record meta */
private PropertyMeta[] pm;
/**
* Create a new instance.
*
* @param dmoMeta
* The DMO metadata of the underlying DMO for this structure.
*/
public AbstractRowStructure(DmoMeta dmoMeta)
{
this.dmoMeta = dmoMeta;
this.dmoClass = dmoMeta.dmoImplInterface;
}
/**
* The DMO class whose properties are included in this structure.
*
* @return The DMO class underlying this structure.
*/
@Override
public Class<? extends DataModelObject> getDmoClass()
{
return dmoClass;
}
/**
* Append a property to the row structure. This doesn't check if the property actually belongs to the
* DMO class used for this structure.
*
* This is implemented in the abstract class to uniformly handle non-expanded extent fields.
*
* @param prop
* The property to be appended to the structure.
*
* @return {@code true} if adding the prop to the row structure succeeded
* If there is some invariant on the structure, adding props may fail.
*/
@Override
public boolean addProperty(Property prop)
{
if (prop != null && prop.extent != 0 && prop.index == 0)
{
hasExtents = true;
return true;
}
return false;
}
/**
* Check if this structure requires a hydration of non-expanded properties. This will only return true
* if a non-expanded property was ever added through {@link #addProperty} or {@link #forceExtents()} was
* called.
*
* @return {@code true} if this structure will also hydrate non-expanded properties.
*/
public boolean hasExtents()
{
return hasExtents;
}
/**
* Force the non-expanded extents to be hydrated.
*/
public void forceExtents()
{
hasExtents = true;
}
/**
* Hydrate record's non-expanded extents.
*
* @param session
* The session to be used to query the non-expanded extent tables.
* @param r
* The record to be hydrated.
* @param recOffset
* The offset to which the extent data should be added into the record.
*
* @throws PersistenceException
* thrown when hydrating extents fails (as it requires extra SQL queries to be run).
*/
protected void hydrateExtents(Session session, BaseRecord r, int recOffset)
throws PersistenceException
{
// TODO: collect extent properties and do a new query from SQL, see Loader.readExtentData()
try
{
Connection conn = session.getConnection();
RecordMeta recordMeta = r._recordMeta();
PropertyMeta[] propsMeta = recordMeta.getPropertyMeta(false);
String[] loadSqls = recordMeta.loadSql;
int[] loadSqlsIndexes = recordMeta.loadSqlIndexes;
boolean incomplete = isIncomplete();
// only use the extent queries, skip the loadSqls[0] which contains the simple properties
for (int i = 1; i < loadSqls.length; i++) // skip "plain" columns
{
if (incomplete)
{
// resolve the offset in the 'props' array where this sql starts.
// all non-expanded extent fields are loaded even for incomplete records.
recOffset = loadSqlsIndexes[i];
}
ResultSet rs = null;
try (PreparedStatement ps = conn.prepareStatement(loadSqls[i]))
{
// set the [parent_id] parameter
ps.setLong(1, r.primaryKey());
rs = ps.executeQuery();
recOffset = Loader.readExtentData(r, rs, propsMeta, recOffset);
}
finally
{
if (rs != null)
{
rs.close();
}
}
}
}
catch (SQLException e)
{
// extract Interrupted Exception out of SQLException
if (e instanceof SQLException && e.getCause() instanceof InterruptedException)
{
throw new StopConditionException("Failed to populate extents for class " + dmoClass.getName(), e);
}
else
{
throw new PersistenceException("Failed to populate extents for class " + dmoClass.getName(), e);
}
}
}
/**
* Check if this structure contains only the primary key.
* @return {@code true} if PK is the only property.
*/
protected boolean hasOnlyPK()
{
boolean hasOnlyPK = false;
if (getCount() == 1)
{
Iterator<Property> props = getProps();
if (props.hasNext())
{
Property prop = props.next();
hasOnlyPK = prop.id == ReservedProperty.ID_PRIMARY_KEY;
}
}
return hasOnlyPK;
}
/**
* Obtain a short description of this object used for debugging.
*
* @return a short description of this object used for debugging.
*/
@Override
public String toString()
{
StringBuilder sb = new StringBuilder(dmoClass.getName() + "{\n");
sb.append(" count : " + getCount()).append("\n");
sb.append(" not expanded extents : " + hasExtents).append("\n");
sb.append(" props : [\n");
Iterator<Property> it = getProps();
while (it.hasNext())
{
sb.append(" ").append(it.next().name).append("\n");
}
sb.append("]}");
return sb.toString();
}
/**
* Retrieve the DMO meta for the provided DMO class. This should be initialized lazily, as not all
* implementations require the DMO meta.
*
* @return The meta of the underlying DMO class.
*/
@Override
public DmoMeta getDmoMeta()
{
return this.dmoMeta;
}
/**
* Check if this row structure is for a temporary table.
*
* @return {@code true} if this DMO structure is for a temporary table.
*/
protected boolean isTempTable()
{
return dmoMeta.tempTable;
}
/**
* The property meta array - lazily computed based on the dmo meta.
*
* @return the array of property meta data.
*/
protected PropertyMeta[] getPropertyMeta()
{
if (pm == null)
{
this.pm = dmoMeta.recordMeta.getPropertyMeta(false);
}
return this.pm;
}
}