# DataObjectFactory.java

    /*
    ** Module   : DataObjectFactory.java
    ** Abstract : A factory which is responsible for creating data objects (DMOs and DTOs).
    **
    ** Copyright (c) 2022-2024, Golden Code Development Corporation.
    **
    ** -#- -I- --Date-- ---------------------------------------Description---------------------------------------
    ** 001 OM  20220331 First revision. Supports instantiating DMOs based on their interface and DTOs which
    **                  extends DMO.
    **     TJD 20220504 Upgrade do Java 11 minor changes
    ** 002 SBI 20230223 Fixed createDTO.
    **         20230302 Implemented ProperiesDescriptor for created DTO objects.
    ** 003 GBB 20230512 Logging methods replaced by CentralLogger/ConversionStatus.
    ** 004 SBI 20230627 Generalized createDTO to extend its field of usages, added marker interfaces for generated DTO.
    ** 005 TJD 20240123 Java 17 compatibility update
    */

    /*
    ** 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 com.goldencode.p2j.persist.*;
    import com.goldencode.p2j.persist.Record;
    import com.goldencode.p2j.util.logging.*;
    import com.goldencode.proxy.*;

    import java.lang.reflect.*;
    import java.util.*;
    import java.util.logging.Level;

    /**
     * A factory utility which creates two kind of proxies:
     * <ul>
     *    <li>DMOs: based on the interface received, and instance of it is created and returned. There are
     *              multiple overloaded methods which provide different level of customizations of the object to
     *              be returned;
     *    <li>DTOs which extends DMOs and use one instance to delegate the unimplemented methods to them. In this
     *             case, a proxy is created and returned. The methods declared by the DTO abstract class will be
     *             invoked directly while the methods inherited from the DMO interface are delegated to the DMO
     *             instance provided.
     * </ul>
     */
    public class DataObjectFactory
    {
       /** Logger */
       private static final CentralLogger LOG = CentralLogger.get(DataObjectFactory.class);

       private Session session;

       public static DataObjectFactory getInstance(Session session)
       {
          DataObjectFactory ret = new DataObjectFactory();
          ret.session = session;
          return ret;
       }

       /**
        * Creates a new instance of a DMO. The returned object is NOT initialized and will not have a private key
        * set. That means the object can be used only as a POJO container for standard Java operations. Also, the
        * caller is responsible for filling ALL fields before using the object.
        *
        * @param   <T>
        *          The generic type of the object to be created. Inferred from {@code clazz} parameter.
        * @param   clazz
        *          The interface of the DMO object to be created.
        *
        * @return  A new object of the class implementing the specified DMO interface.
        */
       public <T extends DataModelObject> T createDMO(Class<T> clazz)
       {
          return createDMO(clazz, false, false, null);
       }

       /**
        * Creates a new instance of a DMO. The returned object is NOT initialized. That means the object can be
        * used only as a POJO container for standard Java operations. Also, the caller is responsible for filling
        * ALL fields before using the object.
        *
        * @param   <T>
        *          The generic type of the object to be created. Inferred from {@code clazz} parameter.
        * @param   clazz
        *          The interface of the DMO object to be created.
        * @param   pk
        *          If not {@code null}, the value is used as private key for the newly created record.
        *
        * @return  A new object of the class implementing the specified DMO interface.
        */
       public <T extends DataModelObject> T createDMO(Class<T> clazz, Long pk)
       {
          return createDMO(clazz, false, false, pk);
       }

       /**
        * Creates a new instance of a DMO.
        *
        * @param   <T>
        *          The generic type of the object to be created. Inferred from {@code clazz} parameter.
        * @param   clazz
        *          The interface of the DMO object to be created.
        * @param   init
        *          If {@code true}, the returned object is initialized (registered with the {@code session},
        *          a change set and internal state are prepared) for complex usage of the object. <br>
        *          Otherwise, the object is to be used as a POJO container instead.
        * @param   defs
        *          Valid only if {@code init} is true. Sets the default values for fields as the record was
        *          defined with INITIAL value in table definition. <br>
        *          Otherwise the caller is responsible for filling ALL fields before using the object.
        * @param   pk
        *          If not {@code null}, the value is used as private key for the newly created record.
        *
        * @return  A new object of the class implementing the specified DMO interface.
        */
       @SuppressWarnings(value="unchecked")
       public <T extends DataModelObject> T createDMO(Class<T> clazz, boolean init, boolean defs, Long pk)
       {
          DmoMeta dmoInfo = DmoMetadataManager.getDmoInfo(clazz);

          Record record = null;
          try
          {
             record = dmoInfo.getImplementationClass().getDeclaredConstructor().newInstance();
          }
          catch (ReflectiveOperationException e)
          {
             LOG.severe("", e);
             return null;
          }

          if (init)
          {
             record.initialize(session, defs);
          }

          if (pk != null)
          {
             record.primaryKey(pk);
          }

          return (T) record;
       }

       /**
        * Creates a new instance of a DTO which implements a DMO interface in order to reuse the code of its
        * implementation. The returned objects wraps the DMO object and delegates the specific calls to it while
        * directly accessing the method the DTO declares and implements directly.
        *
        * @param   clazz
        *          The DTO type of the object to be returned.
        * @param   dmo
        *          An instance of the DMO interface to which the calls to 'super' class will be delegated.
        * @param   markerInterfaces
        *          The marker interfaces, can be null.
        * @param   <T>
        *          The DTO class of the object to be returned.
        *
        * @return  A proxy which 'responds' to all calls from both the abstract class and the delegated object.
        */
       public static <T> T createDTO(Class<T> clazz, Object dmo, Class<?>[] markerInterfaces)
       {
          DtoInvocationHandler handler = new DtoInvocationHandler(dmo, clazz);
          Class<?> dmoClass = dmo.getClass();
          Class<?>[] interfaces = dmoClass.getInterfaces();
          int countInterfaces = interfaces.length;
          int additionalInterfaces = 0;
          if (markerInterfaces != null)
          {
             additionalInterfaces = markerInterfaces.length;
          }
          Class<?>[] extendedInterfaces = new Class<?>[countInterfaces + additionalInterfaces + 1];
          System.arraycopy(interfaces, 0, extendedInterfaces, 0, countInterfaces);
          extendedInterfaces[countInterfaces] = PropertiesDescriptor.class;
          if (additionalInterfaces > 0)
          {
             System.arraycopy(markerInterfaces, 0, extendedInterfaces, countInterfaces + 1, additionalInterfaces);
          }
          T proxiedObject = ProxyFactory.getProxy(clazz, false, extendedInterfaces, true, null, null, handler);
          handler.setProxiedObject(proxiedObject);

          return proxiedObject;
       }

       /**
        * Creates a new instance of a DTO which implements a DMO interface in order to reuse the code of its
        * implementation. The returned objects wraps the DMO object and delegates the specific calls to it while
        * directly accessing the method the DTO declares and implements directly.
        *
        * @param   clazz
        *          The DTO type of the object to be returned.
        * @param   dmo
        *          An instance of the DMO interface to which the calls to 'super' class will be delegated.
        * @param   <T>
        *          The DTO class of the object to be returned.
        *
        * @return  A proxy which 'responds' to all calls from both the abstract class and the delegated object.
        */
       public static <T> T createDTO(Class<T> clazz, Object dmo)
       {
          return createDTO(clazz, dmo, null);
       }

       /**
        * The handler which delegates the calls unimplemented method in a DTO abstract class to a DMO instance
        * it receives as parameter.
        */
       private static class DtoInvocationHandler
       implements InvocationHandler,
                  PropertiesDescriptor
       {
          /** The DMO which will handle unimplemented calls of the proxy object. */
          private final Object delegate;

          /** The class representing abstract DTO class */
          private final Class<?> dtoClass;

          /** The object created from DTO abstract class and DMO instance*/
          private Object dtoObject;

          /**
           * The constructor. Saves the delegate as a constant member.
           *
           * @param   delegate
           *          The delegate DMO.
           * @param   dtoClass
           *          The object created from DTO abstract class and DMO instance
           */
          public DtoInvocationHandler(Object delegate, Class<?> dtoClass)
          {
             this.delegate = delegate;
             this.dtoClass = dtoClass;
          }

          /**
           * Processes a method invocation on a proxy instance and returns the result. This method will be
           * invoked on an invocation handler when a method is invoked on a proxy instance that it is associated
           * with.
           *
           * @param   proxy
           *          the proxy instance that the method was invoked on.
           * @param   method
           *          the Method instance corresponding to the interface method invoked on the proxy instance. The
           *          proxy is constructed so that this method should be relayed to the {@code delegate} instead.
           * @param   args
           *          an array of objects containing the values of the arguments passed in the method invocation
           *          on the proxy instance, or null if interface method takes no arguments.
           *
           * @return  the value obtained from invocation of the method on DMO, if any.
           *
           * @throws  Throwable
           *          the exception to throw from the method invocation on the DMO instance.
           */
          @Override
          public Object invoke(Object proxy, Method method, Object[] args)
          throws Throwable
          {
             if ("getPropertyValues".equals(method.getName()))
             {
                return getPropertyValues();
             }
             else
             {
                return method.invoke(delegate, args);
             }
          }

          /**
           * Get all the properties of this compound object by collecting all properties of the nested object,
           * an instance of dmo interface and the created proxy object, an instance of the dto abstract class.
           *
           * @return   The map of properties to their values.
           */
          @Override
          public Map<String, Object> getPropertyValues()
          {
             Map<String, Object> valuesMap = new LinkedHashMap<String, Object>();
             if (delegate instanceof PropertiesDescriptor)
             {
                valuesMap.putAll(((PropertiesDescriptor) delegate).getPropertyValues());
             }
             else if (delegate != null)
             {
                setProperties(valuesMap, delegate, delegate.getClass());
             }

             if (dtoObject != null)
             {
                setProperties(valuesMap, dtoObject, dtoClass);
             }
             return valuesMap;
          }

          /**
           * Set the reference to the object created from DTO abstract class and DMO instance
           *
           * @param    dtoObject
           *           The object created from DTO abstract class and DMO instance
           */
          public void setProxiedObject(Object dtoObject)
          {
             this.dtoObject = dtoObject;
          }

          /**
           * Fills the given properties map by expecting the target object properties implemented by the given type.
           *
           * @param    valuesMap
           *           The given properties map
           * @param    obj
           *           The target object
           * @param    objClass
           *           The given type
           */
          @SuppressWarnings("deprecation")
          private static void setProperties(Map<String, Object> valuesMap, Object obj, Class<?> objClass)
          {
             Field[] fields = objClass.getDeclaredFields();

             for (Field field : fields)
             {
                try
                {
    // Field.canAccess is not available in JDK 1.8, so until it has to be supported it needs to stay here
    // in JDK 9+ it should be replaced with:
    //             if (!field.canAccess(obj))
                   if (!field.isAccessible())
                   {
                      field.setAccessible(true);
                   }
                   valuesMap.put(field.getName(), field.get(obj));
                }
                catch (IllegalArgumentException | IllegalAccessException ex)
                {
                   LOG.log(Level.SEVERE, "Failed to get value of the field '" + field.getName() + "'", ex);
                }
             }

          }
       }
    }