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;
      }
   }
}