SchemaMapping.java

/*
** Module   :SchemaMapping.java
** Abstract :The storage for the schema mapping information.
**
** Copyright (c) 2005-2025, Golden Code Development Corporation.
**
** -#- -I- --Date-- -T- --JPRM-- ----------------Description-----------------
** 001 SIY 20050411 ADD  @21032  Created initial version
** 002 SIY 20050516 CHG  @21203  Updated documentation.
** 003 GBB 20230825              SchemaLoad.REQUIRED reused, replaces local duplicate.
** 004 SP  20250417              Code formatting and javadoc fixes.
*/

/*
** 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.directory;

import java.util.*;

import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

import com.goldencode.util.*;
import com.goldencode.p2j.schema.SchemaException;

/**
 * This class holds all schema-level mapping information for the LDAP back-end
 * and provides methods to read/write data from/to XML. Reading/writing XML is
 * performed by implementing <code>XmlProcessor</code> interface. This allows
 * to save mapping data in the same file where stored node-level mapping data.
 *
 * @author  SIY
 * @version 1.0
 */
class SchemaMapping
implements XmlProcessor
{
   /** XML tag for the attribute-to-attribute mapping element */
   private static final String ELEM_TAG_ATTRIBUTE_MAPPING = "attribute-mapping";

   /** XML tag for the class-to-class mapping element */
   private static final String ELEM_TAG_CLASS_MAPPING = "class-mapping";

   /** XML tag for the root element */
   private static final String ELEM_TAG_ROOT = "schema-storage";

   /** List of attribute mappings associated with each LDAP Object Class */
   private Map<String, Vector<NamePair>> ldapAttrList;

   /** Mapping between LDAP class names and P2J class names */
   private Map<String, String> ldapToP2j;

   /** List of attribute mappings associated with each P2J Object Class */
   private Map<String, Vector<NamePair>> p2jAttrList;

   /** Mapping between P2J class names and LDAP class names */
   private Map<String, String> p2jToLdap;

   /**
    * Constructs an instance of SchemaMapping.
    */
   SchemaMapping()
   {
      init();
   }

   /**
    * Retrieve schema mapping from specified XML document after restoring
    * directory tree.
    *
    * @param   dom
    *          Reference to <code>Document</code> instance.
    */
   public void postprocess(Document dom)
   {
      init();

      Element elem = dom.getDocumentElement();
      Node rootNode = elem.getFirstChild();

      while (rootNode != null && !rootNode.getNodeName().equals(ELEM_TAG_ROOT))
      {
         rootNode = rootNode.getNextSibling();
      }
      if (rootNode == null)
      {
         throw new NoSuchElementException("No " + ELEM_TAG_ROOT + " in XML document");
      }

      Node node = rootNode.getFirstChild();
      while (node != null)
      {
         //System.out.println("XML node " + node.getNodeName());
         if (node.getNodeName().equals(ELEM_TAG_CLASS_MAPPING))
         {
            elem = (Element)node;
            String ldapClass = elem.getAttribute("ldap");
            String p2jClass = elem.getAttribute("p2j");
            if (ldapClass == null || p2jClass == null)
            {
               continue;
            }
            mapLdapClassToP2jClass(ldapClass, p2jClass);
            NodeList l = node.getChildNodes();
            for (int i = 0; i < l.getLength(); i++)
            {
               if (l.item(i).getNodeName().equals(ELEM_TAG_ATTRIBUTE_MAPPING))
               {
                  elem = (Element)l.item(i);
                  String ldapName = elem.getAttribute("ldap");
                  String p2jName = elem.getAttribute("p2j");
                  if (ldapName == null || p2jName == null)
                  {
                     continue;
                  }
                  mapAttribute(p2jClass, p2jName, ldapClass, ldapName);
               }
            }
         }
         node = node.getNextSibling();
      }
   }

   /**
    * Same schema mapping to specified XML document before document will be
    * saved into external storage.
    *
    * @param   dom
    *          Reference to <code>Document</code> instance.
    * @param   xmlRoot
    *          Reference to root node.
    */
   public void preprocess(Document dom, Node xmlRoot)
   {
      Element root = dom.createElement(ELEM_TAG_ROOT);

      xmlRoot.appendChild(root);

      Set<String> nodes = new TreeSet<>(ldapToP2j.keySet());

      for (Iterator<String> iter = nodes.iterator(); iter.hasNext();)
      {
         String ldapClass = iter.next();
         if (getP2jClass(ldapClass) == null)
         {
            throw new NoClassDefFoundError("No P2J class for the " + ldapClass);
         }

         Element elem = dom.createElement(ELEM_TAG_CLASS_MAPPING);
         elem.setAttribute("ldap", ldapClass);
         elem.setAttribute("p2j", getP2jClass(ldapClass));
         root.appendChild(elem);

         Vector<NamePair> mapping = ldapAttrList.get(ldapClass);
         if (mapping == null)
         {
            continue;
         }
         for (Iterator<NamePair> iterator = mapping.iterator(); iterator.hasNext();)
         {
            NamePair pair = iterator.next();

            Element nodeAttr = dom.createElement(ELEM_TAG_ATTRIBUTE_MAPPING);
            nodeAttr.setAttribute("ldap", pair.getLdapAttribute());
            nodeAttr.setAttribute("p2j", pair.getP2jAttribute());

            elem.appendChild(nodeAttr);
         }
      }
   }

   /**
    * Check mapping for the presence of required classes.
    *
    * @throws  SchemaException
    */
   void checkRequiredClasses()
   throws SchemaException
   {
      for (int i = 0; i < SchemaLoad.REQUIRED.length; i++)
      {
         if (p2jToLdap.get(SchemaLoad.REQUIRED[i]) == null)
         {
            throw new SchemaException("Schema definition lacks required "
                                         + SchemaLoad.REQUIRED[i] + " object class");
         }
      }
   }

   /**
    * Get LDAP attribute name for specified LDAP class and P2J attribute.
    *
    * @param   p2jClass
    *          Name of the LDAP Object Class.
    * @param   p2jAttribute
    *          Name of the P2J attribute.
    *
    * @return  Name of the LDAP attribute or <code>null</code> if no such
    *          mapping exists.
    */
   String getLdapAttribute(String p2jClass, String p2jAttribute)
   {
      Vector<NamePair> mapping = p2jAttrList.get(p2jClass);

      if (mapping == null)
         return null;

      for (int i = 0; i < mapping.size(); i++)
      {
         NamePair element = mapping.get(i);

         if (element.getP2jAttribute().equals(p2jAttribute))
         {
            return element.getLdapAttribute();
         }
      }

      return null;
   }

   /**
    * Get list of LDAP attributes for specified class.
    *
    * @param   ldapClass
    *          Name of LDAP Object Class.
    *
    * @return  An array of attribute names.
    */
   String[] getLdapAttributes(String ldapClass)
   {
      Vector<NamePair> mapping = ldapAttrList.get(ldapClass);
      if (mapping == null)
      {
         return null;
      }
      String[] res = new String[mapping.size()];
      for (int i = 0; i < res.length; i++)
      {
         res[i] = (mapping.get(i)).getLdapAttribute();
      }
      return res;
   }

   /**
    * Return LDAP Object Class name for specified P2J Object Class.
    *
    * @param   p2jClass
    *          P2J Object Class name.
    *
    * @return  LDAP Object Class name.
    */
   String getLdapClass(String p2jClass)
   {
      return p2jToLdap.get(p2jClass);
   }

   /**
    * Return list of all mappings prepared for storing in LDAP.
    *
    * @return  An array of strings, where each string represents mapping for
    *          one attribute.
    */
   String[] getMapList()
   {
      ArrayList<String> mapList = new ArrayList<>();

      Set<String> keys = new TreeSet<>(ldapToP2j.keySet());

      for (Iterator<String> iter = keys.iterator(); iter.hasNext();)
      {
         String ldapClass = iter.next();
         String p2jClass = getP2jClass(ldapClass);

         Vector<NamePair> mapping = ldapAttrList.get(ldapClass);
         if (mapping == null)
         {
            continue;
         }
         for (int i = 0; i < mapping.size(); i++)
         {
            String ldapAttr = (mapping.get(i)).getLdapAttribute();
            String p2jAttr = (mapping.get(i)).getP2jAttribute();

            mapList.add(p2jClass + "/" + p2jAttr + "/" + ldapClass + "/" + ldapAttr);
         }
      }
      return mapList.toArray(new String[0]);
   }

   /**
    * Get P2J attribute name for specified P2J class and LDAP attribute.
    *
    * @param   p2jClass
    *          Name of the P2J Object Class.
    * @param   ldapAttribute
    *          Name of the LDAP attribute.
    *
    * @return  Name of the P2J attribute or <code>null</code> if no such
    *          mapping exists.
    */
   String getP2jAttribute(String p2jClass, String ldapAttribute)
   {
      Vector<NamePair> mapping = p2jAttrList.get(p2jClass);
      if (mapping == null)
      {
         return null;
      }
      for (int i = 0; i < mapping.size(); i++)
      {
         NamePair element = mapping.get(i);
         if (element.getLdapAttribute().equals(ldapAttribute))
         {
            return element.getP2jAttribute();
         }
      }
      return null;
   }

   /**
    * Get list of P2J attributes for specified class.
    *
    * @param   p2jClass
    *          Name of LDAP Object Class.
    *
    * @return  An array of attribute names.
    */
   String[] getP2jAttributes(String p2jClass)
   {
      Vector<NamePair> mapping = p2jAttrList.get(p2jClass);
      if (mapping == null)
      {
         return null;
      }
      String[] res = new String[mapping.size()];
      for (int i = 0; i < res.length; i++)
      {
         res[i] = (mapping.get(i)).getP2jAttribute();
      }
      return res;
   }

   /**
    * Return P2J Object Class name for specified LDAP Object Class.
    *
    * @param   ldapClass
    *          Name of the LDAP Object Class.
    *
    * @return  P2J Object Class name.
    */
   String getP2jClass(String ldapClass)
   {
      return ldapToP2j.get(ldapClass);
   }

   /**
    * Create mapping between specified attributes.
    *
    * @param   p2jClass
    *          Name of the P2J Object Class.
    * @param   p2jAttribute
    *          Name of the attribute of the P2J Object Class.
    * @param   ldapClass
    *          Name of the LDAP Object Class.
    * @param   ldapAtribute
    *          Name of the attribute of the LDAP Object Class.
    */
   void mapAttribute(String p2jClass, String p2jAttribute, String ldapClass,
                     String ldapAtribute)
   {
      Vector<NamePair> mapping = ldapAttrList.get(ldapClass);
      if (mapping == null)
      {
         mapping = new Vector<>();
      }
      NamePair pair = new NamePair(ldapAtribute, p2jAttribute);

      //Check if such a mapping already exists
      boolean present = false;

      for (int i = 0; i < mapping.size(); i++)
      {
         NamePair element = mapping.get(i);

         if (element.getP2jAttribute().equals(p2jAttribute))
         {
            present = true;

            mapping.set(i, pair);
         }
      }
      if (!present)
      {
         mapping.add(pair);
      }

      ldapAttrList.put(ldapClass, mapping);
      p2jAttrList.put(p2jClass, mapping);
   }

   /**
    * Create mapping between LDAP and P2J Object Classes.
    *
    * @param   ldapClass
    *          Name of the LDAP Object Class.
    * @param   p2jClass
    *          Name of the P2J Object Class.
    */
   void mapLdapClassToP2jClass(String ldapClass, String p2jClass)
   {
      p2jToLdap.put(p2jClass, ldapClass);
      ldapToP2j.put(ldapClass, p2jClass);
   }

   /**
    * Create mapping between LDAP and P2J Object Classes.
    *
    * @param   p2jClass
    *          Name of the P2J Object Class.
    * @param   ldapClass
    *          Name of the LDAP Object Class.
    */
   void mapP2jClassToLdap(String p2jClass, String ldapClass)
   {
      p2jToLdap.put(p2jClass, ldapClass);
      ldapToP2j.put(ldapClass, p2jClass);
   }

   /**
    * Create storage
    */
   private void init()
   {
      ldapToP2j = new CaseInsensitiveHashMap<>();
      p2jToLdap = new CaseInsensitiveHashMap<>();
      ldapAttrList = new CaseInsensitiveHashMap<>();
      p2jAttrList = new CaseInsensitiveHashMap<>();
   }

   /**
    * This class is used to store attribute mapping. 
    */
   static class NamePair
   {
      /** LDAP attribute name */
      private String ldapAttribute;

      /** P2J attribute name */
      private String p2jAttribute;

      /**
       * Construct an instance of NamePaitr for specified pair of attribute
       * names.
       *
       * @param   ldap
       *          LDAP attribute name.
       * @param   p2j
       *          P2J attribute name.
       */
      NamePair(String ldap, String p2j)
      {
         ldapAttribute = ldap;
         p2jAttribute = p2j;
      }

      /**
       * Get name of the LDAP attribute.
       *
       * @return	LDAP attribute name.
       */
      String getLdapAttribute()
      {
         return ldapAttribute;
      }

      /**
       * Get name of the P2J attribute.
       *
       * @return	P2J attribute name.
       */
      String getP2jAttribute()
      {
         return p2jAttribute;
      }
   }
}