# XmlRemapper.java

    /*
    ** Module   :XmlRemapper.java
    ** Abstract :XML back-end implementation.
    **
    ** Copyright (c) 2005-2025, Golden Code Development Corporation.
    **
    ** -#- -I- --Date-- --JPRM-- ----------------Description---------------------
    ** 001 SIY 20050221   @20052 Created initial version
    ** 002 SIY 20050301   @20373 Added access serialization, added some
    **                           checks to prevent NullPointerExceptions,
    **                           fixed memory-only mode.
    **                           Organised imports and reordered methods.
    **                           Fixed deleteNode().
    ** 003 SIY 20050322   @20463 Fixed comments.
    ** 004 SIY 20050325   @20591 Added methods required for LdapRemapper,
    **                           added new object classes. Fixed comments
    **                           and cleaned up code.
    ** 005 SIY 20050406   @21037 Moved most of the common code into the
    **                           RamRemapper and XmlRemapperIO.
    ** 006 SIY 20050505   @21206 Unified constructor with LdapRemapper,
    **                           updated documentation.
    ** 007 NVS 20090928   @44047 load() throws exception if the file that must
    **                           exist can't be loaded.
    ** 008 SIY 20091007   @44128 save() now is atomic, file is updated or remain
    **                           unchanged in case of any I/O problem. This
    **                           prevents creation of partially saved directory.
    ** 009 SIY 20091118   @44424 Added save(String) method.
    ** 010 MAG 20140131          Added moveTo() method in order to fix Windows
    **                           issue File.renameTo().
    ** 011 EVL 20160222          Javadoc fixes to make compatible with Oracle Java 8 for Solaris 10.
    ** 012 SP  20250416          Code formatting 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.io.*;
    import java.nio.file.*;

    import com.goldencode.p2j.cfg.*;

    /**
     * The implementation of the <code>Remapper</code> interface which uses XML
     * file as a backing storage for the directory data.
     * <p>
     * <code>XmlRemapper</code> is derived from the <code>RamRemapper</code>
     * which implements all back-end functionality related to directory services
     * and combines it with loading/saving data from/to XML file implemented
     * by the <code>XmlRemapperIO</code> class.
     *
     * @author  SIY
     * @version 1.0
     */
    class XmlRemapper
    extends RamRemapper
    {
       /** XML file which will be used to load and store data */
       private File file = null;

       /** XML file must exist flag */
       private boolean mustExist = false;

       /**
        * Construct an instance of the XmlRemapper for a given file.
        *
        * @param   config
        *          Configuration data.
        * @throws  ConfigurationException
        *          in case of configuration error.
        */
       XmlRemapper(BootstrapConfig config) throws ConfigurationException
       {
          super();

          String name = config.getConfigItem("directory", "xml", "FILENAME");
          mustExist = config.getConfigItem("directory", "xml", "must_exist") != null;

          if (name == null)
          {
             throw new ConfigurationException("File name is not defined");
          }

          file = new File(name);
       }

       /**
        * Load tree from the XML document.
        *
        * @throws  Exception
        *          Various exceptions are forwarded from <code>XmlHelper</code>
        *          or thrown in case of errors.
        */
       protected void load()
       throws Exception
       {
          // Missing file is not necessarily an error, only when mustExist is set
          // Such a situation may happen for a new directory.
          if (file == null || !file.exists() || !file.isFile())
          {
             if (mustExist)
             {
                throw new RuntimeException("the directory file should exist");
             }
             return;
          }

          XmlRemapperIO io = new XmlRemapperIO(this, null);

          io.load(new FileInputStream(file));
       }

       /**
        * Save current tree into XML file.
        *
        * @throws  Exception
        *          If I/O error occurred during operation or
        *          <code>XmlHelper</code> throws an
        *          <code>ParserConfigurationException</code>.
        */
       protected void save()
       throws Exception
       {
          int[] res = new int[] {DirectoryService.OK};
          saveWorker(file, res);
       }

       /**
        * Save current tree into XML specified file.
        *
        * @param    newFile
        *           Name of file to save the directory.
        *
        * @return   return code (refer to {@link DirectoryService} for possible
        *           error codes).
        */
       public int save(String newFile)
       {
          int[] res = new int[] {DirectoryService.OK};

          try
          {
             saveWorker(new File(newFile), res);
          }
          catch (Throwable t)
          {
             // ignore error here
          }

          return res[0];
       }

       /**
        * Save current tree into XML specified file.
        *
        * @throws  Exception
        *          If I/O error occurred during operation or
        *          <code>XmlHelper</code> throws an
        *          <code>ParserConfigurationException</code>.
        */
       private void saveWorker(File file, int[] res)
       throws Exception
       {
          res[0] = DirectoryService.OK;

          if (file == null)
          {
             return;
          }

          XmlRemapperIO io = new XmlRemapperIO(this, null);

          File tmpFile = new File(file.getAbsolutePath().concat(".tmp"));

          try
          {
             res[0] = DirectoryService.ERR_FATAL;

             FileOutputStream fos = new FileOutputStream(tmpFile);

             res[0] = DirectoryService.ERR_CANT_WRITE;

             io.save(fos);

             res[0] = DirectoryService.ERR_FATAL;

             fos.close();

             res[0] = DirectoryService.ERR_CANT_WRITE;

             boolean rc = renameTo(tmpFile, file);

             if (!rc)
             {
                throw new IOException("Unable to replace existing directory with updated one.");
             }

             res[0] = DirectoryService.OK;
          }
          finally
          {
             try
             {
                tmpFile.delete();
             }
             catch (Exception e)
             {
             }
          }
       }

       /**
        * File.renameTo(File) implementation is platform dependent.
        * In Windows if target file already exists the execution of this method fails.
        * In Linux on the other hands succeed.
        * A cross platform solution is implemented using JDK 7 Files.move feature.
        *
        * @param   srcFile
        *          source file.
        * @param   dstFile
        *          destination file.
        *
        * @return  <code>true</code> on success <code>false</code> on failure.
        */
       private boolean renameTo(File srcFile, File dstFile)
       {
          boolean rc = false;

          try
          {
             FileSystem fileSystem = FileSystems.getDefault();
             // get files path
             Path source = fileSystem.getPath(srcFile.getAbsolutePath());
             Path target = fileSystem.getPath(dstFile.getAbsolutePath());
             // do rename
             Files.move(source, target, StandardCopyOption.ATOMIC_MOVE);
             rc = true;
          }
          catch (IOException e)
          {
          }

          return rc;
       }
    }