/*
*
* Copyright (c) 2004 SourceTap - www.sourcetap.com
*
* The contents of this file are subject to the SourceTap Public License
* ("License"); You may not use this file except in compliance with the
* License. You may obtain a copy of the License at http://www.sourcetap.com/license.htm
* Software distributed under the License is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License for
* the specific language governing rights and limitations under the License.
*
* The above copyright notice and this permission notice shall be included
* in all copies or substantial portions of the Software.
*
*/
package com.sourcetap.sfa.replication;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Vector;
import org.ofbiz.base.util.Debug;
import org.ofbiz.base.util.UtilTimer;
import org.ofbiz.entity.GenericDelegator;
import org.ofbiz.entity.GenericEntityException;
import org.ofbiz.entity.GenericPK;
import org.ofbiz.entity.GenericValue;
import com.sourcetap.sfa.util.UserInfo;
/**
* This class is used for entity instance replication. <P>
*
* Typically this class, or its descendant, is used to replicate a single instance
* of an entity. This is accomplished by instantiating this class and calling its
* replicateInstance() method. <P>
*
* For each instance replicated, one or more related entity instances may be replicated
* when the replicateAllRelated() method is called automatically by the replicateInstance()
* method. If the relatedEntityMapVector attribute has been populated, the
* replicateAllRelated() method will call the replicateOneRelated() method for each item in
* the vector to replicate one entities instances. The related entities are located in the
* findOneRelated() method, which is called by replicateOneRelated(). For each related
* entity instance found, another instance of this class is used to replicate it. <P>
*
* Entity replication is used to do full replication. Full replication is required to
* populate a newly registered replication node, or to re-populate an inactive
* replication node. To perform full replication, the relatedEntityMapVector should be
* populated with a list of all entities to be removed and replicated, and the information
* required to find them. Then the removeAll() and replicateAll() methods
* should be called.
*
* @author <a href='mailto:jnutting@sourcetap.com'>John Nutting</a>
*/
public class EntityReplicator extends GenericReplicator {
private static final boolean TIMER = false;
public static final String module = EntityReplicator.class.getName();
/**
* The name of the entity to be replicated.
*/
protected String entityName;
/**
* Related Entity Map Vector <P>
*
* Each vector element contains a HashMap describing a related entity to be replicated
* for each instance of the main entity that is replicated. <P>
*
* Each HashMap contains: <P>
*
* String relationTitle <BR>
* String relatedEntityName <BR>
* HashMap filterMap <BR>
* String replicatorClassName <BR>
* Boolean replicateAll
* Boolean removeAll
*/
protected Vector relatedEntityMapVector = new Vector();
/**
* User Information
*/
protected UserInfo userInfo;
/**
* Constructor with no args.
*/
public EntityReplicator() {
populateRelatedEntityMapVector();
}
/**
* Constructor with args.
*
* @param mainInstance Main entity instance for which related entities will be replicated
* @param localDelegator Delegator to attach to local data base
* @param masterDelegator Delegator to attach to master data base
* @param entityName Name of the entity to be replicated
* @param userInfo UserInfo object containing user information
*/
public EntityReplicator(GenericDelegator localDelegator,
GenericDelegator masterDelegator, String entityName, UserInfo userInfo) {
super(localDelegator, masterDelegator);
setEntityName(entityName);
setUserInfo(userInfo);
populateRelatedEntityMapVector();
}
/**
* Gets the name of the entity to be replicated.
*
* @author <a href='mailto:jnutting@sourcetap.com'>John Nutting</a>
*
* @return Name of the entity to be replicated
*
*/
public String getEntityName() {
return entityName;
}
/**
* Sets the name of the entity to be replicated.
*
* @author <a href='mailto:jnutting@sourcetap.com'>John Nutting</a>
*
* @param entityName_ Name of the entity to be replicated
*
*/
public void setEntityName(String entityName_) {
entityName = entityName_;
return;
}
/**
* Adds a related entity map to the related entity map vector.
*
* @author <a href='mailto:jnutting@sourcetap.com'>John Nutting</a>
*
* @param relatedEntityMap HashMap containing information about a releated entity
*/
public void addRelatedEntityMap(HashMap relatedEntityMap) {
relatedEntityMapVector.add(relatedEntityMap);
return;
}
/**
* Adds a related entity map to the related entity map vector.
*
* @author <a href='mailto:jnutting@sourcetap.com'>John Nutting</a>
*
* @param relationTitle Relation title to be used by the entity engine to find related
* entity instances
* @param relatedEntityName Name of the related entity to be replicated
* @param filterMap HashMap containing additional filter values to be used by the entity
* engine when finding related entity instances
* @param replicateAll Flag indicating to replicate all instances of this entity
* instead of just the ones related to the main entity.
* @param removeAll Flag indicating to remove all instances of this entity instead of
* just the ones related to the main entity.
* @param replicatorClassName Name of a descendant class of this class which will be used
* to replicate the related entity instances
*/
public void addRelatedEntityMap(String relationTitle,
String relatedEntityName, HashMap filterMap, boolean replicateAll,
boolean removeAll, String replicatorClassName) {
HashMap relatedEntityMap = new HashMap();
relatedEntityMap.put("relationTitle", relationTitle);
relatedEntityMap.put("relatedEntityName", relatedEntityName);
relatedEntityMap.put("filterMap", filterMap);
relatedEntityMap.put("replicateAll", new Boolean(replicateAll));
relatedEntityMap.put("removeAll", new Boolean(removeAll));
relatedEntityMap.put("replicatorClassName", replicatorClassName);
addRelatedEntityMap(relatedEntityMap);
return;
}
/**
* Populate the related entity map vector. This method is called from the constructors
* to allow the related entities to be specified before replication begins.
*
* @author <a href='mailto:jnutting@sourcetap.com'>John Nutting</a>
*/
public void populateRelatedEntityMapVector() {
}
/**
* Gets the related entity map vector.
*
* @author <a href='mailto:jnutting@sourcetap.com'>John Nutting</a>
*
* @return Related entity map vector.
*
*/
public Vector getRelatedEntityMapVector() {
return relatedEntityMapVector;
}
/**
* Gets the user information
*
* @author <a href='mailto:jnutting@sourcetap.com'>John Nutting</a>
*
* @return UserInfo object
*
*/
public UserInfo getUserInfo() {
return userInfo;
}
/**
* Sets the user information
*
* @author <a href='mailto:jnutting@sourcetap.com'>John Nutting</a>
*
* @param userInfo_ UserInfo object
*
*/
public void setUserInfo(UserInfo userInfo_) {
userInfo = userInfo_;
return;
}
/**
* This method replicates one instance of the entity named in the entityName attribute. <P>
*
* Ths instance passed in the instance argument is copied from the master data base
* to the local data base. Then the replicateRelatedEntities() method is
* called so child entity instance can be replicated. <P>
*
* This method requires that all arguments passed to the constructor were not null and
* not empty. Alternatively, this method requires the setEntityName, setLocalDelegator,
* and setMasterDelegator methods to have been called since construction.
*
* @author <a href='mailto:jnutting@sourcetap.com'>John Nutting</a>
*
* @param mainInstance Entity instance being replicated
*
* @return The status of the replication. Possible values: STATUS_CONTINUE, STATUS_ERROR,
* STATUS_CANCEL
*/
public int replicateInstance(GenericValue mainInstance) {
// Check pre-conditions.
if (mainInstance == null) {
Debug.logError(
"[replicateInstance] Main entity instance is required. " +
"Replication canceled.", module);
return STATUS_ERROR;
}
;
// Insert the instance into the local data base.
int status = insertLocalInstance(mainInstance);
if (status != STATUS_CONTINUE) {
return status;
}
// Insert all related entity instances.
status = replicateAllRelated(mainInstance);
if (status != STATUS_CONTINUE) {
return status;
}
return STATUS_CONTINUE;
}
/**
* This method removes one instance of the entity named in the entityName attribute. <P>
*
* First the removeRelatedEntities() method is called so child entity instances
* can be removed from the local data base. Then the instance passed in the
* instance argument is removed from the local data base.
*
* @author <a href='mailto:jnutting@sourcetap.com'>John Nutting</a>
*
* @param mainInstance Entity instance being replicated
*
* @return The status of the replication. Possible values: STATUS_CONTINUE, STATUS_ERROR,
* STATUS_CANCEL
*/
public int removeInstance(GenericValue mainInstance) {
if (mainInstance == null) {
Debug.logError(
"[replicateInstance] Main entity instance is required.", module);
return STATUS_ERROR;
}
// Remove all related entity instances.
int status = removeAllRelated(mainInstance);
if (status != STATUS_CONTINUE) {
return status;
}
// Remove the instance from the local data base.
status = removeLocalInstance(mainInstance);
if (status != STATUS_CONTINUE) {
return status;
}
return STATUS_CONTINUE;
}
/**
* This method stores the instance in the local data base.
*
* @author <a href='mailto:jnutting@sourcetap.com'>John Nutting</a>
*
* @param masterInstance Entity instance from the master data base
*
* @return Status. Possible values: STATUS_CONTINUE, STATUS_ERROR, STATUS_CANCELED
*/
public int insertLocalInstance(GenericValue masterInstance) {
if (masterInstance == null) {
Debug.logError(
"[replicateInstance] Master entity instance is required.", module);
return STATUS_ERROR;
}
;
if (getLocalDelegator() == null) {
Debug.logError("[insertLocalInstance] Local delegator is required.", module);
return STATUS_ERROR;
}
;
// Copy the instance, and assign it to the local delegator instead of the master one.
GenericValue localInstance = new GenericValue(masterInstance);
localInstance.setDelegator(getLocalDelegator());
GenericPK localInstancePK = localInstance.getPrimaryKey();
try {
getLocalDelegator().create(localInstance);
Debug.logVerbose("Inserted key " + localInstancePK.toString(), module);
return STATUS_CONTINUE;
} catch (GenericEntityException gee) {
if (gee.getLocalizedMessage().indexOf("Duplicate entry") > 0) {
Debug.logError("[insertLocalInstance] Duplicate key " +
localInstancePK.toString(), module);
return STATUS_CONTINUE;
} else {
Debug.logError("[insertLocalInstance] Error inserting key " +
localInstancePK.toString() + ":", module);
Debug.logError("[insertLocalInstance] " +
gee.getLocalizedMessage(), module);
return STATUS_ERROR;
}
}
}
/**
* This method removes an instance of the main entity from the local data base.
*
* @author <a href='mailto:jnutting@sourcetap.com'>John Nutting</a>
*
* @param localInstance Entity instance being removed
*
* @return Status. Possible values: STATUS_CONTINUE, STATUS_ERROR, STATUS_CANCELED
*/
public int removeLocalInstance(GenericValue localInstance) {
if (localInstance == null) {
Debug.logError(
"[replicateInstance] Local entity instance is required.", module);
return STATUS_ERROR;
}
;
if (getLocalDelegator() == null) {
Debug.logError("[insertLocalInstance] Local delegator is required.", module);
return STATUS_ERROR;
}
;
GenericPK localInstancePK = localInstance.getPrimaryKey();
try {
getLocalDelegator().removeByPrimaryKey(localInstance.getPrimaryKey());
Debug.logVerbose("Removed key " + localInstancePK.toString(), module);
return STATUS_CONTINUE;
} catch (GenericEntityException gee) {
Debug.logError("[removeLocalInstance] Error removing key " +
localInstancePK.toString() + ":", module);
Debug.logError("[removeLocalInstance] " +
gee.getLocalizedMessage(), module);
return STATUS_ERROR;
}
}
/**
* This method replicates all instances for all related entities. <P>
*
* This method uses the relatedEntityMapVector to determine what related
* entities need to be replicated. For each related entity, this method calls
* replicateOneRelated(). Each call to replicateOneRelated() calls
* findOneRelated(), which finds all instances of the specified entity. Then
* replicateOneRelated() calls replicateInstance for each instance of the
* related entity to recursively replicate the tree of instances.
*
* @author <a href='mailto:jnutting@sourcetap.com'>John Nutting</a>
*
* @param mainInstance Main entity instance for which related entities will be replicated
*
* @return Status. Possible values: STATUS_CONTINUE, STATUS_ERROR, STATUS_CANCELED
*/
public int replicateAllRelated(GenericValue mainInstance) {
Debug.logVerbose("[replicateAllRelated] Start", module);
// if (mainInstance == null) {
// Debug.logError("[replicateInstance] Entity instance is required.");
// return STATUS_ERROR;
// };
Iterator relatedEntityMapVectorI = getRelatedEntityMapVector().iterator();
while (relatedEntityMapVectorI.hasNext()) {
HashMap relatedEntityMap = (HashMap) relatedEntityMapVectorI.next();
String relationTitle = (String) relatedEntityMap.get(
"relationTitle");
String relatedEntityName = (String) relatedEntityMap.get(
"relatedEntityName");
String replicatorClassName = (String) relatedEntityMap.get(
"replicatorClassName");
Boolean replicateAll = (Boolean) relatedEntityMap.get(
"replicateAll");
HashMap filterMap = (HashMap) relatedEntityMap.get("filterMap");
int status = replicateOneRelated(mainInstance, relationTitle,
relatedEntityName, filterMap, replicateAll.booleanValue(),
replicatorClassName);
if (status != STATUS_CONTINUE) {
return status;
}
}
return STATUS_CONTINUE;
}
/**
* This method removes all instances for all related entities. <P>
*
* This method uses the relatedEntityMapVector to determine what related
* entities need to be removed. For each related entity, this method calls
* removeOneRelated(). Each call to removeOneRelated() calls
* findOneRelated(), which finds all instances of the specified entity. Then
* removeOneRelated() calls removeLocalInstance for each instance of the
* related entity to recursively replicate the tree of instances.
*
* @author <a href='mailto:jnutting@sourcetap.com'>John Nutting</a>
*
* @param localInstance Local entity from which related entity instances are to be removed
*
* @return Status. Possible values: STATUS_CONTINUE, STATUS_ERROR, STATUS_CANCELED
*/
public int removeAllRelated(GenericValue localInstance) {
Debug.logVerbose("[removeAllRelated] Start", module);
// if (localInstance == null) {
// Debug.logError("[replicateInstance] Entity instance is required.");
// return STATUS_ERROR;
// };
// Remove the entities in reverse order in case RI is turned on.
for (int relatedEntityNbr = getRelatedEntityMapVector().size() - 1;
relatedEntityNbr >= 0; relatedEntityNbr--) {
// Iterator relatedEntityMapVectorI = getRelatedEntityMapVector().iterator();
// while (relatedEntityMapVectorI.hasNext()) {
HashMap relatedEntityMap = (HashMap) getRelatedEntityMapVector()
.get(relatedEntityNbr);
// HashMap relatedEntityMap = (HashMap)relatedEntityMapVectorI.next();
String relationTitle = (String) relatedEntityMap.get(
"relationTitle");
String relatedEntityName = (String) relatedEntityMap.get(
"relatedEntityName");
Boolean removeAll = (Boolean) relatedEntityMap.get("removeAll");
String replicatorClassName = (String) relatedEntityMap.get(
"replicatorClassName");
HashMap filterMap = (HashMap) relatedEntityMap.get("filterMap");
int status = removeOneRelated(localInstance, relationTitle,
relatedEntityName, filterMap, removeAll.booleanValue(),
replicatorClassName);
if (status != STATUS_CONTINUE) {
return status;
}
}
return STATUS_CONTINUE;
}
/**
* This method replicates all instances for one entity related to the main entity. <P>
*
* This method calls findOneRelated(), which finds all instances of the related entity.
* Then this method calls replicateInstance for each instance of the
* related entity to recursively replicate the tree of instances.
*
* @author <a href='mailto:jnutting@sourcetap.com'>John Nutting</a>
*
* @param mainInstance Main entity instance for which related entities will be replicated
* @param relationTitle Relation title to be used by the entity engine to find related
* entity instances
* @param relatedEntityName Name of the related entity to be replicated
* @param filterMap HashMap containing additional filter values to be used by the entity
* engine when finding related entity instances
* @param replicatorClassName Name of a descendant class of this class which will be used
* to replicate the related entity instances
*
* @return Status. Possible values: STATUS_CONTINUE, STATUS_ERROR, STATUS_CANCELED
*/
protected int replicateOneRelated(GenericValue mainInstance,
String relationTitle, String relatedEntityName, HashMap filterMap,
boolean replicateAll, String replicatorClassName) {
UtilTimer timer = new UtilTimer();
if (TIMER) {
timer.timerString(1, "[replicateOneRelated] Start");
}
Debug.logVerbose("[replicateOneRelated] Start", module);
Debug.logVerbose("[replicateOneRelated] relationTitle: " + relationTitle, module);
Debug.logVerbose("[replicateOneRelated] relatedEntityName: " + relatedEntityName, module);
Debug.logVerbose("[replicateOneRelated] replicatorClassName: " + replicatorClassName, module);
// Instantiate related replicator class.
EntityReplicator relatedReplicator = getEntityReplicator(replicatorClassName,
getLocalDelegator(), getMasterDelegator(), relatedEntityName,
getUserInfo());
if (relatedReplicator == null) {
return STATUS_ERROR;
}
String entityName = "*";
if (mainInstance != null) {
entityName = mainInstance.getEntityName();
}
if (TIMER) {
timer.timerString(1,
"[EntityReplicator.replicateOneRelated] Starting findOneRelated (" +
entityName + "/" + relationTitle + relatedEntityName + ")");
}
List relatedGVL = findOneRelated(getMasterDelegator(), mainInstance,
relationTitle, relatedEntityName, filterMap, replicateAll);
if (relatedGVL == null) {
return STATUS_ERROR;
}
if (TIMER) {
timer.timerString(1,
"[EntityReplicator.replicateOneRelated] Finished findOneRelated (" +
entityName + "/" + relationTitle + relatedEntityName + ")");
}
Iterator relatedGVI = relatedGVL.iterator();
while (relatedGVI.hasNext()) {
GenericValue relatedGV = (GenericValue) relatedGVI.next();
int status = relatedReplicator.replicateInstance(relatedGV);
if (status != STATUS_CONTINUE) {
return status;
}
}
if (TIMER) {
timer.timerString(1, "[replicateOneRelated] End");
}
return STATUS_CONTINUE;
}
/**
* This method removes all instances for one entity related to the main entity. <P>
*
* This method calls findOneRelated(), which finds all instances of the related entity.
* Then this method calls removeInstance for each instance of the
* related entity to recursively replicate the tree of instances.
*
* @author <a href='mailto:jnutting@sourcetap.com'>John Nutting</a>
*
* @param mainInstance Main entity instance for which related entities will be replicated
* @param relationTitle Relation title to be used by the entity engine to find related
* entity instances
* @param relatedEntityName Name of the related entity to be replicated
* @param filterMap HashMap containing additional filter values to be used by the entity
* engine when finding related entity instances
* @param removeAll Flag indicating to remove all instances of this entity instead of
* just removing the ones related to the main entity.
* @param replicatorClassName Name of a descendant class of this class which will be used
* to replicate the related entity instances
*
* @return Status. Possible values: STATUS_CONTINUE, STATUS_ERROR, STATUS_CANCELED
*/
protected int removeOneRelated(GenericValue mainInstance,
String relationTitle, String relatedEntityName, HashMap filterMap,
boolean removeAll, String replicatorClassName) {
// Instantiate related replicator class.
EntityReplicator relatedReplicator = getEntityReplicator(replicatorClassName,
getLocalDelegator(), getMasterDelegator(), relatedEntityName,
getUserInfo());
if (relatedReplicator == null) {
return STATUS_ERROR;
}
List relatedGVL = findOneRelated(getLocalDelegator(), mainInstance,
relationTitle, relatedEntityName, filterMap, removeAll);
if (relatedGVL == null) {
return STATUS_ERROR;
}
Iterator relatedGVI = relatedGVL.iterator();
while (relatedGVI.hasNext()) {
GenericValue relatedGV = (GenericValue) relatedGVI.next();
int status = relatedReplicator.removeInstance(relatedGV);
if (status != STATUS_CONTINUE) {
return status;
}
}
return STATUS_CONTINUE;
}
/**
* This method finds all instances of an entity related to the main entity.
* This is used for replicating and removing, so it accepts the delegator as an
* argument to point it to the correct data base.
*
* @author <a href='mailto:jnutting@sourcetap.com'>John Nutting</a>
*
* @param mainInstance Main entity instance for which related entities will be replicated
* @param relationTitle Relation title to be used by the entity engine to find related
* entity instances
* @param relatedEntityName Name of the related entity to be replicated
* @param filterMap HashMap containing additional filter values to be used by the entity
* engine when finding related entity instances
* @param findAll Flag indicating to find all instances of the entity instead of using
* a relation
*
* @return List of generic values related to the main entity instance
*/
protected List findOneRelated(GenericDelegator delegator,
GenericValue mainInstance, String relationTitle,
String relatedEntityName, HashMap filterMap, boolean findAll) {
UtilTimer timer = new UtilTimer();
if (TIMER) {
timer.timerString(2, "[EntityReplicator.findOneRelated] Start");
}
String entityName = "*";
if (mainInstance != null) {
entityName = mainInstance.getEntityName();
}
if (delegator == null) {
Debug.logError("[findOneRelated] Delegator is required.", module);
return null;
}
if (userInfo == null) {
Debug.logError("[findOneRelated] User info object is required.", module);
return null;
}
GenericPK entityPK = null;
String entityKeyString = null;
List relatedGVL = null;
if (findAll) {
// Need to find all entity instances, not just the ones related to
// the main entity instance.
try {
if (TIMER) {
timer.timerString(2,
"[EntityReplicator.findOneRelated] Start findByAnd");
}
relatedGVL = delegator.findByAnd(relatedEntityName, filterMap);
if (TIMER) {
timer.timerString(2,
"[EntityReplicator.findOneRelated] Finished findByAnd");
}
return relatedGVL;
} catch (GenericEntityException e) {
Debug.logError("[findOneRelated] Error getting " +
relatedEntityName + " records by and: " +
e.getLocalizedMessage(), module);
return null;
}
} else {
// Need to find just the entity instances related to the main entity instance.
if (mainInstance == null) {
Debug.logError(
"[findOneRelated] Main instance is required if findAll is false.", module);
return null;
}
entityPK = mainInstance.getPrimaryKey();
entityKeyString = entityPK.toString();
Debug.logVerbose("[findOneRelated] Retrieving all " +
relatedEntityName + " records related to " +
entityKeyString, module);
try {
if (TIMER) {
timer.timerString(2,
"[EntityReplicator.findOneRelated] Start getRelated");
}
relatedGVL = delegator.getRelated(relationTitle +
relatedEntityName, filterMap, null, mainInstance);
if (TIMER) {
timer.timerString(2,
"[EntityReplicator.findOneRelated] Finished getRelated");
}
return relatedGVL;
} catch (GenericEntityException e) {
Debug.logError("[findOneRelated] Error getting the related " +
relatedEntityName + " records: " + e.getLocalizedMessage(), module);
return null;
}
}
}
/**
* Instantiates EntityReplicator or one of its descendant classes.
*
* @param className The class to be instantiated
*
* @return Object of class specified by className parameter
*/
public static EntityReplicator getEntityReplicator(String className,
GenericDelegator localDelegator, GenericDelegator masterDelegator,
String entityName, UserInfo userInfo) {
Debug.logVerbose("[getEntityReplicator] className: " + className, module);
Debug.logVerbose("[getEntityReplicator] entityName: " + entityName, module);
Class entityReplicatorClass = null;
EntityReplicator entityReplicator = null;
if ((className.length() > 0) && !className.equals("null")) {
try {
entityReplicatorClass = Class.forName(className);
} catch (ClassNotFoundException e) {
Debug.logError("[getEntityReplicator] Class \"" + className +
"\" specified in relatedEntityMapVector could not be found.", module);
Debug.logError(e, module);
return null;
}
try {
entityReplicator = (EntityReplicator) entityReplicatorClass.newInstance();
} catch (IllegalAccessException e) {
Debug.logError(
"[getEntityReplicator] EntityReplicator class \"" +
className + "\" could not be instantiated because " +
"the class or initializer is not accessible.", module);
Debug.logError(e, module);
return null;
} catch (InstantiationException e) {
Debug.logError(
"[getEntityReplicator] EntityReplicator class \"" +
className + "\" cannot be instantiated because it is an " +
"abstract class, an interface, an array class, a primitive type, or void.", module);
Debug.logError(e, module);
return null;
}
} else {
// Class name was not specified in the display object. Use the default class.
entityReplicator = new EntityReplicator();
}
// Set the delegators on the new entity replicator object.
entityReplicator.setLocalDelegator(localDelegator);
entityReplicator.setMasterDelegator(masterDelegator);
entityReplicator.setEntityName(entityName);
entityReplicator.setUserInfo(userInfo);
// Return the new instance of EntityReplicator.
return entityReplicator;
}
}