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