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