RelationInfo.java
/*
** Module : RelationInfo.java
** Abstract : Object which encapsulates information about foreign relations
**
** Copyright (c) 2006-2024, Golden Code Development Corporation.
**
** -#- -I- --Date-- --JPRM-- -----------------------------Description-----------------------------
** 001 ECF 20060302 @25009 Created initial version. A descriptor object for foreign key
** relations.
** 002 ECF 20061105 @31096 Added data and methods used to track inverse foreign key
** associations. Needed when nulling out foreign keys during record
** deletion.
** 003 ECF 20061114 @31142 Added getForeignProperties() method. This was needed to support
** fuller foreign association synchronization.
** 004 ECF 20080310 @37484 Integrated generics.
** 005 ECF 20150328 Added support for cross-schema natural joins.
** 006 OM 20200401 Moved to orm package.
** 007 TJD 20240123 Java 17 compatibility updates
*/
/*
** 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.util.*;
import com.goldencode.p2j.persist.*;
import com.goldencode.p2j.persist.Record;
import com.goldencode.p2j.util.*;
/**
* A descriptor object for a primary/foreign key relation between two tables.
* Stores the following data about this relation:
* <ul>
* <li>interface of the local (referring) DMO
* <li>implementation class of the local DMO
* <li>interface of the foreign (referent) DMO
* <li>implementation class of the foreign DMO
* <li>name of the schema of the foreign DMO
* <li>the properties in common between the DMOs, which comprise the legacy
* foreign key in the pre-conversion application
* </ul>
* <p>
* Property data is stored in a map whose keys are the local DMO's property
* names, and whose values are the pairings of local and foreign property
* names, and whether case is ignored when comparing the property values
* (for character property types only).
* <p>
* Instances of this class are created at database initialization (read from
* the dmo index file). The information stored within these instances is
* used to construct HQL query statements for the following purposes:
* <ul>
* <li>in normal queries to search for records by foreign key
* <li>by the record buffer to synchronize foreign keys with application
* changes to properties which comprise legacy keys between tables
* </ul>
* <p>
* It is also used to null out foreign records referenced by a DMO, when the
* foreign record is deleted.
*
* @see AssociationSyncher
* @see AbstractJoin
* @see ForeignNuller
*/
public final class RelationInfo
{
/** Local (referring) DMO interface */
private final Class<? extends DataModelObject> localIface;
/** Local (referring) DMO implementation class */
private final Class<? extends Record> localClass;
/** Foreign (referent) DMO interface */
private final Class<? extends DataModelObject> foreignIface;
/** Foreign (referent) DMO implementation class */
private final Class<? extends Record> foreignClass;
/** Foreign (referent) schema name */
private final String foreignSchema;
/** Common properties which define legacy foreign key */
private final Map<String, PropertyData> properties = new LinkedHashMap<>();
/** Is relation uniquely constrained at the local table? */
private boolean unique = false;
/**
* Constructor.
*
* @param localIface
* Local (referring) DMO interface.
* @param localClass
* Local (referring) DMO implementation class.
* @param foreignIface
* Foreign (referent) DMO interface.
* @param foreignClass
* Foreign (referent) DMO implementation class.
*/
public RelationInfo(Class<? extends DataModelObject> localIface,
Class<? extends Record> localClass,
Class<? extends DataModelObject> foreignIface,
Class<? extends Record> foreignClass,
String foreignSchema)
{
this.localIface = localIface;
this.localClass = localClass;
this.foreignIface = foreignIface;
this.foreignClass = foreignClass;
this.foreignSchema = foreignSchema;
}
/**
* Add a pair of properties which, alone or in combination with other pairs, define the legacy
* key between related database tables.
*
* @param localName
* Name of the local DMO's property.
* @param foreignName
* Name of the foreign DMO's related property.
* @param caseSensitive
* {@code true} if comparisons of these properties must take case into account, else
* {@code false} (meaningful for character properties only).
*/
public void addPropertyData(String localName, String foreignName, boolean caseSensitive)
{
properties.put(localName, new PropertyData(localName, foreignName, !caseSensitive));
}
/**
* Indicate whether the pair of properties indexed by the specified, local property name are
* recorded as case insensitive properties.
*
* @param property
* Name of property in the local DMO class.
*
* @return {@code true} if {@code property} represents a case insensitive, character property.
*/
public boolean isIgnoreCase(String property)
{
return properties.get(property).ignoreCase;
}
/**
* Get the interface of the local (referring) DMO.
*
* @return Local interface.
*/
public Class<? extends DataModelObject> getLocalInterface()
{
return localIface;
}
/**
* Get the implementation class of the local (referring) DMO.
*
* @return Local class.
*/
public Class<? extends Record> getLocalClass()
{
return localClass;
}
/**
* Get the alias of the local (referring) DMO, which is its unqualified
* interface name with the first letter lowercased.
*
* @return Local alias.
*/
public String getLocalAlias()
{
String name = getLocalInterfaceName();
return Character.toLowerCase(name.charAt(0)) + name.substring(1);
}
/**
* Get the unqualified name of the local (referring) DMO's interface.
*
* @return Local interface name.
*/
public String getLocalInterfaceName()
{
return Utils.getUnqualifiedName(localIface);
}
/**
* Get the unqualified name of the local (referring) DMO's implementation
* class.
*
* @return Local class name.
*/
public String getLocalClassName()
{
return Utils.getUnqualifiedName(localClass);
}
/**
* Get the interface of the foreign (referent) DMO.
*
* @return Foreign interface.
*/
public Class<? extends DataModelObject> getForeignInterface()
{
return foreignIface;
}
/**
* Get the implementation class of the foreign (referent) DMO.
*
* @return Foreign class.
*/
public Class<? extends Record> getForeignClass()
{
return foreignClass;
}
/**
* Get the alias of the foreign (referent) DMO, which is its unqualified
* interface name with the first letter lowercased.
*
* @return Foreign alias.
*/
public String getForeignAlias()
{
String name = getForeignInterfaceName();
return Character.toLowerCase(name.charAt(0)) + name.substring(1);
}
/**
* Get the unqualified name of the foreign (referent) DMO's interface.
*
* @return Foreign interface name.
*/
public String getForeignInterfaceName()
{
return Utils.getUnqualifiedName(foreignIface);
}
/**
* Get the unqualified name of the foreign (referent) DMO's implementation
* class.
*
* @return Foreign class name.
*/
public String getForeignClassName()
{
return Utils.getUnqualifiedName(foreignClass);
}
/**
* Get the name of the foreign class' schema.
*
* @return Name of the foreign schema.
*/
public String getForeignSchema()
{
return foreignSchema;
}
/**
* Get an iterator on all of the legacy key property names for the foreign
* (referent) DMO. Iteration will be in the same order in which the
* properties were added to this instance.
*
* @return Foreign property name iterator.
*/
public Iterator<String> foreignProperties()
{
return new Iterator<String>()
{
private final Iterator<PropertyData> iter = properties.values().iterator();
public boolean hasNext() { return iter.hasNext(); }
public String next() { return iter.next().foreign; }
public void remove() { throw new UnsupportedOperationException(); }
};
}
/**
* Get the set of all of the legacy key property names for the foreign
* (referent) DMO. Iteration over the set's elements will be in the same
* order in which the properties were added to this instance.
*
* @return Read-only set of local property names.
*/
public Set<String> getForeignProperties()
{
Set<String> set = new AbstractSet<String>()
{
public Iterator<String> iterator() { return foreignProperties(); }
public int size() { return properties.size(); }
};
return Collections.unmodifiableSet(set);
}
/**
* Get an iterator on all of the legacy key property names for the local
* (referring) DMO. Iteration will be in the same order in which the
* properties were added to this instance.
*
* @return Local property name iterator.
*/
public Iterator<String> localProperties()
{
return properties.keySet().iterator();
}
/**
* Get the set all of the legacy key property names for the local
* (referring) DMO. Iteration over the set's elements will be in the same
* order in which the properties were added to this instance.
*
* @return Read-only set of local property names.
*/
public Set<String> getLocalProperties()
{
return Collections.unmodifiableSet(properties.keySet());
}
/**
* Indicate whether the relation described by this object is uniquely
* constrained on the local side. That is, indicate whether the local DMO
* has a one-to-one relation with the foreign DMO.
*
* @return <code>true</code> if the relation with the foreign DMO is
* one-to-one; <code>false</code> if it is many-to-one.
*/
public boolean isUnique()
{
return unique;
}
/**
* Record the fact that the relation described by this object is uniquely
* constrained on the local side. That is, record that the local DMO has
* a one-to-one relation with the foreign DMO (vs. a many-to-one relation).
*/
void setUnique()
{
unique = true;
}
/**
* Container object for data which defines a local/foreign property pair.
* <ul>
* <li>property name in the local DMO
* <li>corresponding property name in the foreign DMO
* <li>ignore case flag (relevant for character properties only)
* </ul>
*/
private static class PropertyData
{
/** Local property name */
private final String local;
/** Foreign property name */
private final String foreign;
/** Ignore case flag */
private final boolean ignoreCase;
/**
* Constructor
*
* @param local
* Local property name.
* @param foreign
* Foreign property name
* @param ignoreCase
* Ignore case flag.
*/
PropertyData(String local, String foreign, boolean ignoreCase)
{
this.local = local;
this.foreign = foreign;
this.ignoreCase = ignoreCase;
}
}
}