org.jaffa.soa.dataaccess.TransformerUtils.java Source code

Java tutorial

Introduction

Here is the source code for org.jaffa.soa.dataaccess.TransformerUtils.java

Source

/*
 * ====================================================================
 * JAFFA - Java Application Framework For All
 *
 * Copyright (C) 2002 JAFFA Development Group
 *
 *     This library is free software; you can redistribute it and/or
 *     modify it under the terms of the GNU Lesser General Public
 *     License as published by the Free Software Foundation; either
 *     version 2.1 of the License, or (at your option) any later version.
 *
 *     This library 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
 *     Lesser General Public License for more details.
 *
 *     You should have received a copy of the GNU Lesser General Public
 *     License along with this library; if not, write to the Free Software
 *     Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 *
 * Redistribution and use of this software and associated documentation ("Software"),
 * with or without modification, are permitted provided that the following conditions are met:
 * 1.   Redistributions of source code must retain copyright statements and notices.
 *         Redistributions must also contain a copy of this document.
 * 2.   Redistributions in binary form must reproduce the above copyright notice,
 *  this list of conditions and the following disclaimer in the documentation
 *  and/or other materials provided with the distribution.
 * 3.   The name "JAFFA" must not be used to endorse or promote products derived from
 *  this Software without prior written permission. For written permission,
 *  please contact mail to: jaffagroup@yahoo.com.
 * 4.   Products derived from this Software may not be called "JAFFA" nor may "JAFFA"
 *  appear in their names without prior written permission.
 * 5.   Due credit should be given to the JAFFA Project (http://jaffa.sourceforge.net).
 *
 * THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESSED OR IMPLIED
 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED.  IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
 * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
 * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
 * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 * ====================================================================
 */

/*
 * TransformerUtils.java
 */
package org.jaffa.soa.dataaccess;

import java.beans.BeanInfo;
import java.beans.IntrospectionException;
import java.beans.Introspector;
import java.beans.PropertyDescriptor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.apache.commons.beanutils.DynaProperty;
import org.apache.log4j.Logger;
import org.jaffa.datatypes.DataTypeMapper;
import org.jaffa.exceptions.ApplicationException;
import org.jaffa.exceptions.ApplicationExceptionWithContext;
import org.jaffa.exceptions.ApplicationExceptions;
import org.jaffa.exceptions.DomainObjectChangedException;
import org.jaffa.exceptions.DomainObjectNotFoundException;
import org.jaffa.exceptions.FrameworkException;
import org.jaffa.exceptions.MultipleDomainObjectsFoundException;
import org.jaffa.flexfields.FlexBean;
import org.jaffa.flexfields.IFlexFields;
import org.jaffa.persistence.Criteria;
import org.jaffa.persistence.IPersistent;
import org.jaffa.persistence.Persistent;
import org.jaffa.persistence.UOW;
import org.jaffa.persistence.util.PersistentHelper;
import org.jaffa.soa.graph.GraphDataObject;
import org.jaffa.util.BeanHelper;
import org.jaffa.util.ExceptionHelper;
import org.jaffa.util.StringHelper;

/**
 * Utility Methods used by the Data Transformer
 *
 * @author PaulE
 * @version 1.0
 */
public class TransformerUtils {

    private static Logger log = Logger.getLogger(TransformerUtils.class);

    /**
     * Display the properties of this JavaBean. If this bean has properties that implement
     * either GraphDataObject or GraphDataObject[], then also print this objects too.
     *
     * @param source Javabean who's contents should be printed
     * @return multi-line string of this beans properties and their values
     */
    public static String printGraph(Object source) {
        return printGraph(source, null);
    }

    /**
     * Same as printGraph(Object source), except the objectStack lists all the parent
     * objects its printed, and if this is one of them, it stops. This allows detection
     * of possible infinite recusion.
     *
     * @param source      Javabean who's contents should be printed
     * @param objectStack List of objects already traversed
     * @return multi-line string of this beans properties and their values
     */
    public static String printGraph(Object source, List objectStack) {
        if (source == null)
            return null;

        // Prevent infinite object recursion
        if (objectStack != null)
            if (objectStack.contains(source))
                return "Object Already Used. " + source.getClass().getName() + '@' + source.hashCode();
            else
                objectStack.add(source);
        else {
            objectStack = new ArrayList();
            objectStack.add(source);
        }

        StringBuffer out = new StringBuffer();
        out.append(source.getClass().getName());
        out.append("\n");

        try {
            BeanInfo sInfo = Introspector.getBeanInfo(source.getClass());
            PropertyDescriptor[] sDescriptors = sInfo.getPropertyDescriptors();
            if (sDescriptors != null && sDescriptors.length != 0)
                for (int i = 0; i < sDescriptors.length; i++) {
                    PropertyDescriptor sDesc = sDescriptors[i];
                    Method sm = sDesc.getReadMethod();
                    if (sm != null && sDesc.getWriteMethod() != null) {
                        if (!sm.isAccessible())
                            sm.setAccessible(true);
                        Object sValue = sm.invoke(source, (Object[]) null);

                        out.append("  ");
                        out.append(sDesc.getName());
                        if (source instanceof GraphDataObject) {
                            if (((GraphDataObject) source).hasChanged(sDesc.getName()))
                                out.append('*');
                        }
                        out.append('=');
                        if (sValue == null)
                            out.append("<--NULL-->\n");
                        else if (sm.getReturnType().isArray()
                                && !sm.getReturnType().getComponentType().isPrimitive()) {
                            StringBuffer out2 = new StringBuffer();
                            out2.append("Array of ");
                            out2.append(sm.getReturnType().getComponentType().getName());
                            out2.append("\n");
                            // Loop through array
                            Object[] a = (Object[]) sValue;
                            for (int j = 0; j < a.length; j++) {
                                out2.append('[');
                                out2.append(j);
                                out2.append("] ");
                                if (a[j] == null)
                                    out2.append("<--NULL-->");
                                else if (GraphDataObject.class.isAssignableFrom(a[j].getClass()))
                                    out2.append(((GraphDataObject) a[j]).toString(objectStack));
                                else
                                    //out2.append(StringHelper.linePad(a[j].toString(), 4, " ",true));
                                    out2.append(a[j].toString());
                            }
                            out.append(StringHelper.linePad(out2.toString(), 4, " ", true));
                        } else {
                            if (GraphDataObject.class.isAssignableFrom(sValue.getClass()))
                                out.append(StringHelper.linePad(((GraphDataObject) sValue).toString(objectStack), 4,
                                        " ", true));
                            else {
                                out.append(StringHelper.linePad(sValue.toString(), 4, " ", true));
                                out.append("\n");
                            }

                        }
                    }
                }
        } catch (IllegalAccessException e) {
            TransformException me = new TransformException(TransformException.ACCESS_ERROR, "???", e.getMessage());
            log.error(me.getLocalizedMessage(), e);
            //throw me;
        } catch (InvocationTargetException e) {
            TransformException me = new TransformException(TransformException.INVOCATION_ERROR, "???", e);
            log.error(me.getLocalizedMessage(), me.getCause());
            //throw me;
        } catch (IntrospectionException e) {
            TransformException me = new TransformException(TransformException.INTROSPECT_ERROR, "???",
                    e.getMessage());
            log.error(me.getLocalizedMessage(), e);
            //throw me;
        }
        return out.toString();
    }

    /**
     * Display the properties of this JavaBean in XML format.
     *
     * @param source Javabean who's contents should be printed
     * @return XML formatted string of this beans properties and their values
     */
    public static String printXMLGraph(Object source) {

        StringBuffer out = new StringBuffer();
        out.append("<" + source.getClass().getSimpleName() + ">");

        try {
            BeanInfo sInfo = Introspector.getBeanInfo(source.getClass());
            PropertyDescriptor[] sDescriptors = sInfo.getPropertyDescriptors();
            if (sDescriptors != null && sDescriptors.length != 0) {
                for (int i = 0; i < sDescriptors.length; i++) {
                    PropertyDescriptor sDesc = sDescriptors[i];
                    Method sm = sDesc.getReadMethod();
                    if (sm != null && sDesc.getWriteMethod() != null) {
                        if (!sm.isAccessible())
                            sm.setAccessible(true);
                        Object sValue = sm.invoke(source, (Object[]) null);

                        out.append("<" + sDesc.getName() + ">");

                        if (sValue != null && !sm.getReturnType().isArray()
                                && !GraphDataObject.class.isAssignableFrom(sValue.getClass())) {
                            out.append(sValue.toString().trim());
                        }
                        out.append("</" + sDesc.getName() + ">");
                    }
                }
            }
        } catch (IllegalAccessException e) {
            TransformException me = new TransformException(TransformException.ACCESS_ERROR, "???", e.getMessage());
            log.error(me.getLocalizedMessage(), e);
            //throw me;
        } catch (InvocationTargetException e) {
            TransformException me = new TransformException(TransformException.INVOCATION_ERROR, "???", e);
            log.error(me.getLocalizedMessage(), me.getCause());
            //throw me;
        } catch (IntrospectionException e) {
            TransformException me = new TransformException(TransformException.INTROSPECT_ERROR, "???",
                    e.getMessage());
            log.error(me.getLocalizedMessage(), e);
            //throw me;
        }
        out.append("</" + source.getClass().getSimpleName() + ">");
        return out.toString();
    }

    /**
     * Pass in an empty map and it fills it with Key = Value for the source
     * object. It returns false if one or more key values are null, or if this
     * object has no keys defined
     */
    static boolean fillInKeys(String path, GraphDataObject source, GraphMapping mapping, Map map)
            throws InvocationTargetException, TransformException {
        try {
            Set keys = mapping.getKeyFields();
            boolean nullKey = false;
            if (keys == null || keys.size() == 0) {
                if (log.isDebugEnabled())
                    log.debug("Object Has No KEYS! - " + source.getClass().getName());
                return false;
            }
            // Loop through all the keys get het the values
            for (Iterator k = keys.iterator(); k.hasNext();) {
                String keyField = (String) k.next();
                PropertyDescriptor pd = mapping.getDataFieldDescriptor(keyField);
                if (pd != null && pd.getReadMethod() != null) {
                    Method m = pd.getReadMethod();
                    if (!m.isAccessible())
                        m.setAccessible(true);
                    Object value = m.invoke(source, new Object[] {});
                    map.put(keyField, value);
                    if (log.isDebugEnabled())
                        log.debug("Key " + keyField + "='" + value + "' on object '" + source.getClass().getName()
                                + '\'');
                    if (value == null) {
                        nullKey = true;
                    }
                } else {
                    TransformException me = new TransformException(TransformException.NO_KEY_ON_OBJECT, path,
                            keyField, source.getClass().getName());
                    log.error(me.getLocalizedMessage());
                    throw me;
                }
            }
            return !nullKey;
        } catch (IllegalAccessException e) {
            TransformException me = new TransformException(TransformException.ACCESS_ERROR, path, e.getMessage());
            log.error(me.getLocalizedMessage(), e);
            throw me;
            //        } catch (InvocationTargetException e) {
            //            TransformException me = new TransformException(TransformException.INVOCATION_ERROR, path, e );
            //            log.error(me.getLocalizedMessage(),me.getCause());
            //            throw me;
        }
    }

    static void updateBeanData(String path, GraphDataObject source, UOW uow, ITransformationHandler handler,
            GraphMapping mapping, IPersistent domainObject, DataTransformer.Mode mode, GraphDataObject newGraph)
            throws InstantiationException, IllegalAccessException, InvocationTargetException, ApplicationExceptions,
            FrameworkException {

        try {
            // Call custom validation code in the GraphObject
            // The validate() method may have mandatory-rules bound to it via AOP. Hence do not invoke it during prevalidation
            // No need to invoke it during CLONING/MASS_UPDATE as well, since the source object should be unmodified
            if (mode != DataTransformer.Mode.VALIDATE_ONLY && mode != DataTransformer.Mode.CLONE
                    && mode != DataTransformer.Mode.MASS_UPDATE)
                source.validate();

            List<ITransformationHandler> handlers = null;
            if (handler != null) {
                handlers = handler.getTransformationHandlers();
            }

            // Ensure the domain object has not been modified
            domainObjectChangedTest(path, source, mapping, domainObject);

            // Stamp the UOW on the domain object to avoid creation of separate UOWs during foreign-key validations
            if (domainObject.getUOW() == null)
                domainObject.setUOW(uow);

            // Reflect ProcessEventGraphs from newGraph to source - This will handle Pending/Warning Events during a clone/mass update
            if (newGraph != null && newGraph.getProcessEventGraphs() != null)
                source.setProcessEventGraphs(newGraph.getProcessEventGraphs());
            //----------------------------------------------------------------
            // Fire 'startBean' handler
            if (mode != DataTransformer.Mode.VALIDATE_ONLY && handlers != null) {
                for (ITransformationHandler transformationHandler : handlers) {
                    transformationHandler.startBean(path, source, domainObject);
                }
            }

            //----------------------------------------------------------------
            // Reflect all normal fields
            for (Iterator it = mapping.getFields().iterator(); it.hasNext();) {
                String field = (String) it.next();
                // ignore read-only fields
                if (mapping.isReadOnly(field))
                    continue;

                // values from the newGraph take precedence in CLONE/MASS_UPDATE mode
                if (mode == DataTransformer.Mode.CLONE) {
                    // ignore dirty-read fields, and no-cloning fields unless a value is passed in the newGraph
                    if (field.equals(mapping.getDirtyReadDataFieldName())
                            || (mapping.isNoCloning(field) && (newGraph == null || !newGraph.hasChanged(field))))
                        continue;
                    Object value = getProperty(mapping.getDataFieldDescriptor(field),
                            newGraph != null && newGraph.hasChanged(field) ? newGraph : source);
                    updateProperty(mapping.getDomainFieldDescriptor(field), value, domainObject);
                } else if (mode == DataTransformer.Mode.MASS_UPDATE) {
                    if (newGraph != null && newGraph.hasChanged(field)) {
                        Object value = getProperty(mapping.getDataFieldDescriptor(field), newGraph);
                        updateProperty(mapping.getDomainFieldDescriptor(field), value, domainObject);
                    }
                } else {
                    Object value = getProperty(mapping.getDataFieldDescriptor(field), source);
                    if ((!domainObject.isDatabaseOccurence() && value != null) || source.hasChanged(field))
                        updateProperty(mapping.getDomainFieldDescriptor(field), value, domainObject);
                }
            }

            //----------------------------------------------------------------
            // Update flex fields
            if (source instanceof IFlexFields && domainObject instanceof IFlexFields) {
                if (log.isDebugEnabled())
                    log.debug("Updating flex fields for " + path);
                FlexBean sFlexBean = ((IFlexFields) source).getFlexBean();
                FlexBean tFlexBean = ((IFlexFields) domainObject).getFlexBean();
                if (sFlexBean != null && tFlexBean != null) {
                    for (DynaProperty flexProperty : tFlexBean.getDynaClass().getDynaProperties()) {
                        String name = flexProperty.getName();
                        // values from the newGraph take precedence in CLONE/MASS_UPDATE mode
                        if (mode == DataTransformer.Mode.CLONE) {
                            FlexBean nFlexBean = newGraph != null && newGraph instanceof IFlexFields
                                    ? ((IFlexFields) newGraph).getFlexBean()
                                    : null;
                            Object value = nFlexBean != null && nFlexBean.hasChanged(name) ? nFlexBean.get(name)
                                    : sFlexBean.get(name);
                            if (value != null)
                                tFlexBean.set(name, value);
                        } else if (mode == DataTransformer.Mode.MASS_UPDATE) {
                            FlexBean nFlexBean = newGraph != null && newGraph instanceof IFlexFields
                                    ? ((IFlexFields) newGraph).getFlexBean()
                                    : null;
                            if (nFlexBean != null && nFlexBean.hasChanged(name)) {
                                Object value = nFlexBean.get(name);
                                tFlexBean.set(name, value);
                            }
                        } else {
                            if (sFlexBean.hasChanged(name)) {
                                Object value = sFlexBean.get(name);
                                if (log.isDebugEnabled())
                                    log.debug("Update flex field '" + name + '=' + value + "' on object '"
                                            + domainObject.getClass().getName() + '\'');
                                tFlexBean.set(name, value);
                            } else {
                                if (log.isDebugEnabled())
                                    log.debug("Flex field '" + name + " has not changed on object "
                                            + source.getClass().getName());
                            }
                        }
                    }
                }
            }

            //----------------------------------------------------------------
            // Reflect any foreign keys
            for (Iterator it = mapping.getForeignFields().iterator(); it.hasNext();) {
                String field = (String) it.next();
                // ignore read-only fields
                if (mapping.isReadOnly(field))
                    continue;

                // It is possible that the foreign object may get resused, and only its fields may have been changed.
                // Hence also invoke the hasChanged() method on the foreign object itself
                Object value = null;
                boolean hasChanged = false;
                if (mode == DataTransformer.Mode.CLONE) {
                    // ignore dirty-read fields, and no-cloning fields unless a value is passed in the newGraph
                    if (field.equals(mapping.getDirtyReadDataFieldName())
                            || (mapping.isNoCloning(field) && (newGraph == null || !newGraph.hasChanged(field))))
                        continue;
                    value = getProperty(mapping.getDataFieldDescriptor(field),
                            newGraph != null && newGraph.hasChanged(field) ? newGraph : source);
                    hasChanged = value != null;
                } else if (mode == DataTransformer.Mode.MASS_UPDATE) {
                    if (newGraph != null && newGraph.hasChanged(field)) {
                        value = getProperty(mapping.getDataFieldDescriptor(field), newGraph);
                        hasChanged = true;
                    }
                } else {
                    value = getProperty(mapping.getDataFieldDescriptor(field), source);
                    hasChanged = (!domainObject.isDatabaseOccurence() && value != null) || source.hasChanged(field);
                }
                if (!hasChanged && value != null && value instanceof GraphDataObject)
                    hasChanged = ((GraphDataObject) value).hasChanged();
                if (hasChanged) {
                    // need to map foreign keys back
                    List targetKeys = mapping.getForeignKeys(field);
                    GraphMapping fMapping = MappingFactory
                            .getInstance(mapping.getDataFieldDescriptor(field).getPropertyType());
                    Set sourceKeys = fMapping.getKeyFields();
                    int i = 0;
                    for (Iterator i2 = sourceKeys.iterator(); i2.hasNext(); i++) {
                        String sourceFld = (String) i2.next();
                        String targetFld = (String) targetKeys.get(i);
                        if (log.isDebugEnabled())
                            log.debug("Copy Foreign Key Field from " + sourceFld + " to " + targetFld);
                        if (value == null) {
                            // ForeignGraph is null. Null out the foreign-key
                            updateProperty(mapping.getRealDomainFieldDescriptor(targetFld), null, domainObject);
                        } else {
                            // Obtain the key-field from the ForeignGraph
                            Object value2 = getProperty(fMapping.getDataFieldDescriptor(sourceFld), value);

                            // Set the foreign-key, only if the key-field has been flagged as changed in the ForeignGraph.
                            // This will allow the UI to pass in just the modified portion of a composite foreign-key, and thus
                            // ensuring that the un-modified portion doesn't get nulled out
                            // The check is not required while cloning
                            if (mode == DataTransformer.Mode.CLONE || !(value instanceof GraphDataObject)
                                    || ((GraphDataObject) value).hasChanged(sourceFld))
                                updateProperty(mapping.getRealDomainFieldDescriptor(targetFld), value2,
                                        domainObject);
                        }
                    }

                    // Invoke the getter on the domain. An exception will be raised if the foreign-key is invalid
                    if (log.isDebugEnabled())
                        log.debug("Performing validation on the domain object for the foreign object "
                                + mapping.getDomainFieldName(field));
                    PropertyDescriptor pd = mapping.getDomainFieldDescriptor(field);
                    if (pd != null && pd.getReadMethod() != null) {
                        Method m = pd.getReadMethod();
                        if (!m.isAccessible())
                            m.setAccessible(true);
                        m.invoke(domainObject, (Object[]) null);
                    }
                }
            }

            //----------------------------------------------------------------
            // Store Record
            if (mode == DataTransformer.Mode.VALIDATE_ONLY) {
                if (log.isDebugEnabled())
                    log.debug(
                            "Domain object will not be persisted during prevalidation. Invoking the prevalidateBean handler");
                if (handlers != null) {
                    for (ITransformationHandler transformationHandler : handlers) {
                        transformationHandler.prevalidateBean(path, source, domainObject);
                    }
                }
            } else if (domainObject.isDatabaseOccurence()) {
                if (log.isDebugEnabled())
                    log.debug("UOW.Update Domain Object");
                //----------------------------------------------------------------
                // Fire 'startBeanUpdate' handler
                if (handlers != null) {
                    for (ITransformationHandler transformationHandler : handlers) {
                        transformationHandler.startBeanUpdate(path, source, domainObject);
                    }
                }
                if (domainObject.isModified()) {
                    uow.update(domainObject);
                    if (handlers != null) {
                        for (ITransformationHandler transformationHandler : handlers) {
                            transformationHandler.setChangeDone(true);
                        }
                    }
                }
                //----------------------------------------------------------------
                // Fire 'endBeanUpdate' handler
                if (handlers != null) {
                    for (ITransformationHandler transformationHandler : handlers) {
                        transformationHandler.endBeanUpdate(path, source, domainObject);
                    }
                }
            } else {
                if (handlers != null && mode == DataTransformer.Mode.CLONE) {
                    if (log.isDebugEnabled()) {
                        log.debug("Invoke startBeanClone");
                    }
                    for (ITransformationHandler transformationHandler : handlers) {
                        transformationHandler.startBeanClone(path, source, domainObject, newGraph);
                    }
                } else if (handlers != null && mode == DataTransformer.Mode.MASS_UPDATE) {
                    if (log.isDebugEnabled()) {
                        log.debug("Invoke startBeanMassUpdate");
                    }
                    for (ITransformationHandler transformationHandler : handlers) {
                        transformationHandler.startBeanMassUpdate(path, source, domainObject, newGraph);
                    }
                }
                if (log.isDebugEnabled())
                    log.debug("UOW.Add Domain Object");
                //----------------------------------------------------------------
                // Fire 'startBeanAdd' handler
                if (handlers != null) {
                    for (ITransformationHandler transformationHandler : handlers) {
                        transformationHandler.startBeanAdd(path, source, domainObject);
                    }
                }
                uow.add(domainObject);
                if (handlers != null) {
                    for (ITransformationHandler transformationHandler : handlers) {
                        transformationHandler.setChangeDone(true);
                    }
                }
                //----------------------------------------------------------------
                // Fire 'endBeanAdd' handler
                if (handlers != null) {
                    for (ITransformationHandler transformationHandler : handlers) {
                        transformationHandler.endBeanAdd(path, source, domainObject);
                    }
                }
                if (handlers != null && mode == DataTransformer.Mode.CLONE) {
                    if (log.isDebugEnabled()) {
                        log.debug("Invoke endBeanClone");
                    }
                    for (ITransformationHandler transformationHandler : handlers) {
                        transformationHandler.endBeanClone(path, source, domainObject, newGraph);
                    }
                } else if (handlers != null && mode == DataTransformer.Mode.MASS_UPDATE) {
                    if (log.isDebugEnabled()) {
                        log.debug("Invoke endBeanMassUpdate");
                    }
                    for (ITransformationHandler transformationHandler : handlers) {
                        transformationHandler.endBeanMassUpdate(path, source, domainObject, newGraph);
                    }
                }
            }

            //----------------------------------------------------------------
            // Reflect any related objects
            for (Iterator it = mapping.getRelatedFields().iterator(); it.hasNext();) {
                String field = (String) it.next();
                if (mapping.isReadOnly(field))
                    continue;
                Object value = null;
                if (mode == DataTransformer.Mode.CLONE) {
                    // ignore no-cloning fields unless a value is passed in the newGraph
                    if (mapping.isNoCloning(field) && (newGraph == null || !newGraph.hasChanged(field)))
                        continue;
                    value = getProperty(mapping.getDataFieldDescriptor(field),
                            newGraph != null && newGraph.hasChanged(field) ? newGraph : source);
                } else if (mode == DataTransformer.Mode.MASS_UPDATE) {
                    if (newGraph != null && newGraph.hasChanged(field))
                        value = getProperty(mapping.getDataFieldDescriptor(field), newGraph);
                } else
                    value = getProperty(mapping.getDataFieldDescriptor(field), source);
                if (value != null) {
                    if (value.getClass().isArray()) {
                        // The related field is an array of objects (one-to-many)
                        Object[] values = (Object[]) value;
                        for (int i = 0; i < values.length; i++) {
                            GraphDataObject dao = (GraphDataObject) values[i]; // Assumes its a DAO....what else could it be?
                            if (dao != null) {
                                if (dao.getDeleteObject() != null && dao.getDeleteObject()) {
                                    if (mode == DataTransformer.Mode.VALIDATE_ONLY) {
                                        if (log.isDebugEnabled())
                                            log.debug(
                                                    "The 'deleteObject' property is true. No prevalidations will be performed for the childBean.");
                                    } else {
                                        if (log.isDebugEnabled())
                                            log.debug(
                                                    "The 'deleteObject' property is true. Invoking deleteChildBean()");
                                        deleteChildBean(path + '.' + field + '[' + i + ']', dao, uow, handler,
                                                domainObject, mapping, field);
                                    }
                                } else {
                                    Object newValue = newGraph != null
                                            ? getProperty(mapping.getDataFieldDescriptor(field), newGraph)
                                            : null;
                                    GraphDataObject newDao = newValue != null
                                            && ((GraphDataObject[]) newValue).length > i
                                                    ? ((GraphDataObject[]) newValue)[i]
                                                    : null;
                                    updateChildBean(path + '.' + field + '[' + i + ']', dao, uow, handler,
                                            domainObject, mapping, field, mode, newDao);
                                }
                            }
                        }
                    } else {
                        // Or a single Object (one-to-one)
                        GraphDataObject dao = (GraphDataObject) value; // Assumes its a DAO....what else could it be?
                        if (dao.getDeleteObject() != null && dao.getDeleteObject()) {
                            if (mode == DataTransformer.Mode.VALIDATE_ONLY) {
                                if (log.isDebugEnabled())
                                    log.debug(
                                            "The 'deleteObject' property is true. No prevalidations will be performed for the childBean.");
                            } else {
                                if (log.isDebugEnabled())
                                    log.debug("The 'deleteObject' property is true. Invoking deleteChildBean()");
                                deleteChildBean(path + '.' + field, dao, uow, handler, domainObject, mapping,
                                        field);
                            }
                        } else {
                            GraphDataObject newDao = newGraph != null
                                    ? (GraphDataObject) getProperty(mapping.getDataFieldDescriptor(field), newGraph)
                                    : null;
                            updateChildBean(path + '.' + field, dao, uow, handler, domainObject, mapping, field,
                                    mode, newDao);
                        }
                    }
                }
            }

            //----------------------------------------------------------------
            // Fire 'endBean' handler
            if (mode != DataTransformer.Mode.VALIDATE_ONLY && handlers != null) {
                for (ITransformationHandler transformationHandler : handlers) {
                    transformationHandler.endBean(path, source, domainObject);
                }
            }

        } catch (SkipTransformException e) {
            if (log.isDebugEnabled()) {
                log.debug("Processing of " + path + " will be skipped", e);
            }
        } catch (ApplicationException e) {
            throw new ApplicationExceptions(e);
        }
    }

    /**
     * Take a source object and try and mold it back it its domain object.
     * This is the same as updateParent, except from the way it retrieved the
     * record, and the way it creates a new record.
     */
    static void updateChildBean(String path, GraphDataObject source, UOW uow, ITransformationHandler handler,
            IPersistent parentDomain, GraphMapping parentMapping, String parentField, DataTransformer.Mode mode,
            GraphDataObject newGraph) throws ApplicationExceptions, FrameworkException {
        if (log.isDebugEnabled())
            log.debug("Update Child Bean " + path);
        String relationshipName = parentMapping.getDomainFieldName(parentField);
        if (relationshipName.endsWith("Array"))
            relationshipName = relationshipName.substring(0, relationshipName.length() - 5);
        if (relationshipName.endsWith("Object"))
            relationshipName = relationshipName.substring(0, relationshipName.length() - 6);

        try {

            IPersistent domainObject = null;
            GraphMapping mapping = MappingFactory.getInstance(source);
            Map keys = new LinkedHashMap();
            Class doClass = mapping.getDomainClass();
            boolean gotKeys = false;

            // The path for a one-to-many relationship ends with a "[i]". The absence of that suffix indicates a one-to-one relationship
            //// No keys, must be one-to-one
            //if (mapping.getKeyFields() == null || mapping.getKeyFields().size() == 0) {
            if (path == null || path.charAt(path.length() - 1) != ']') {
                if (log.isDebugEnabled())
                    log.debug("Find 'one-to-one' object - " + path);

                // Just use the getXxxObject method to get the related domain object,
                // if there is one...
                domainObject = (IPersistent) getProperty(parentMapping.getDomainFieldDescriptor(parentField),
                        parentDomain);
                if (domainObject == null) {
                    if (log.isDebugEnabled())
                        log.debug("Not Found - " + path);
                }
            } else if (mode == DataTransformer.Mode.CLONE) {
                // In CLONE mode, get the keys from the new graph, and force the creation of the domain object
                if (newGraph != null)
                    fillInKeys(path, newGraph, mapping, keys);
            } else {
                // Get the key fields used in the domain object. Use the findXxxxxCriteria() method,
                // then add the extra fields to the criteria object, to get the unique record.
                gotKeys = fillInKeys(path, source, mapping, keys);

                // read DO based on key
                if (gotKeys) {

                    // get the method to get the PK criteria (i.e. public Criteria findVendorSiteCriteria(); )
                    Method findCriteria = null;
                    String methodName = "find" + StringHelper.getUpper1(relationshipName) + "Criteria";
                    try {
                        findCriteria = parentDomain.getClass().getMethod(methodName, new Class[] {});
                    } catch (NoSuchMethodException e) {
                        log.error("Find method '" + methodName + "' not found!");
                    }
                    if (findCriteria == null)
                        throw new TransformException(TransformException.METHOD_NOT_FOUND, path, methodName);

                    // Find Criteria For Related Object
                    Criteria criteria = (Criteria) findCriteria.invoke(parentDomain, new Object[] {});
                    // Add extra key info...
                    for (Iterator it = keys.keySet().iterator(); it.hasNext();) {
                        String keyField = (String) it.next();
                        Object value = keys.get(keyField);
                        keyField = StringHelper.getUpper1(mapping.getDomainFieldName(keyField));
                        criteria.addCriteria(keyField, value);
                        if (log.isDebugEnabled())
                            log.debug(path + "- Add to criteria:" + keyField + '=' + value);
                    }
                    // See if we get an object :-)
                    Iterator itr = uow.query(criteria).iterator();
                    if (itr.hasNext())
                        domainObject = (IPersistent) itr.next();
                    if (itr.hasNext()) {
                        // Error, multiple objects found
                        throw new ApplicationExceptions(new ApplicationExceptionWithContext(path,
                                new MultipleDomainObjectsFoundException(findDomainLabel(criteria.getTable()))));
                    }

                } else {
                    if (log.isDebugEnabled())
                        log.debug("Object " + path
                                + " has either missing or null key values - Assume Create is needed");
                }
            }

            // Create object if not found
            if (domainObject == null) {
                // In MASS_UPDATE mode, error if DO not found
                if (mode == DataTransformer.Mode.MASS_UPDATE)
                    throw new ApplicationExceptions(new ApplicationExceptionWithContext(path,
                            new DomainObjectNotFoundException(TransformerUtils.findDomainLabel(doClass))));

                // NEW OBJECT, create and reflect keys
                if (log.isDebugEnabled())
                    log.debug("DO '" + mapping.getDomainClassShortName()
                            + "' not found with key, create a new one...");
                // find method on parent used to create object
                Method newObject = null;
                String methodName = "new" + StringHelper.getUpper1(relationshipName) + "Object";
                try {
                    newObject = parentDomain.getClass().getMethod(methodName, new Class[] {});
                } catch (NoSuchMethodException e) {
                    log.error("Method '" + methodName + "()' not found!");
                }
                if (newObject == null)
                    throw new TransformException(TransformException.METHOD_NOT_FOUND, path, methodName);

                // Call method to create object
                domainObject = (IPersistent) newObject.invoke(parentDomain, new Object[] {});

                // Set the key fields
                for (Iterator it = keys.keySet().iterator(); it.hasNext();) {
                    String keyField = (String) it.next();
                    if (mapping.isReadOnly(keyField))
                        continue;
                    Object value = keys.get(keyField);
                    updateProperty(mapping.getDomainFieldDescriptor(keyField), value, domainObject);
                }
            } else {
                if (log.isDebugEnabled())
                    log.debug("Found DO '" + mapping.getDomainClassShortName() + "' with key,");
            }

            // Now update all domain fields
            updateBeanData(path, source, uow, handler, mapping, domainObject, mode, newGraph);

        } catch (IllegalAccessException e) {
            TransformException me = new TransformException(TransformException.ACCESS_ERROR, path, e.getMessage());
            log.error(me.getLocalizedMessage(), e);
            throw me;
        } catch (InvocationTargetException e) {
            ApplicationExceptions appExps = ExceptionHelper.extractApplicationExceptions(e);
            if (appExps != null)
                throw appExps;
            FrameworkException fe = ExceptionHelper.extractFrameworkException(e);
            if (fe != null)
                throw fe;
            TransformException me = new TransformException(TransformException.INVOCATION_ERROR, path, e);
            log.error(me.getLocalizedMessage(), me.getCause());
            throw me;
        } catch (InstantiationException e) {
            TransformException me = new TransformException(TransformException.INSTANTICATION_ERROR, path,
                    e.getMessage());
            log.error(me.getLocalizedMessage(), e);
            throw me;
        }
    }

    /**
     * Take a source object and try and mold it back it its domain object.
     * This is the same as updateParent, except from the way it retrieved the
     * record, and the way it creates a new record.
     */
    static void deleteChildBean(String path, GraphDataObject source, UOW uow, ITransformationHandler handler,
            IPersistent parentDomain, GraphMapping parentMapping, String parentField)
            throws ApplicationExceptions, FrameworkException {
        if (log.isDebugEnabled())
            log.debug("Delete Child Bean " + path);
        String relationshipName = parentMapping.getDomainFieldName(parentField);
        if (relationshipName.endsWith("Array"))
            relationshipName = relationshipName.substring(0, relationshipName.length() - 5);
        if (relationshipName.endsWith("Object"))
            relationshipName = relationshipName.substring(0, relationshipName.length() - 6);

        try {

            IPersistent domainObject = null;
            GraphMapping mapping = MappingFactory.getInstance(source);
            Map keys = new LinkedHashMap();
            Class doClass = mapping.getDomainClass();
            boolean gotKeys = false;

            // The path for a one-to-many relationship ends with a "[i]". The absence of that suffix indicates a one-to-one relationship
            //// No keys, must be one-to-one
            //if (mapping.getKeyFields() == null || mapping.getKeyFields().size() == 0) {
            if (path == null || path.charAt(path.length() - 1) != ']') {
                if (log.isDebugEnabled())
                    log.debug("Find 'one-to-one' object - " + path);

                // Just use the getXxxObject method to get the related domain object,
                // if there is one...
                domainObject = (IPersistent) getProperty(parentMapping.getDomainFieldDescriptor(parentField),
                        parentDomain);
                if (domainObject == null) {
                    if (log.isDebugEnabled())
                        log.debug("Not Found - " + path);
                }
            } else {
                // Get the key fields used in the domain object. Use the findXxxxxCriteria() method,
                // then add the extra fields to the criteria object, to get the unique record.
                gotKeys = fillInKeys(path, source, mapping, keys);

                // read DO based on key
                if (gotKeys) {
                    // get the method to get the PK criteria (i.e. public Criteria findVendorSiteCriteria(); )
                    Method findCriteria = null;
                    String methodName = "find" + StringHelper.getUpper1(relationshipName) + "Criteria";
                    try {
                        findCriteria = parentDomain.getClass().getMethod(methodName, new Class[] {});
                    } catch (NoSuchMethodException e) {
                        log.error("Find method '" + methodName + "' not found!");
                    }
                    if (findCriteria == null)
                        throw new TransformException(TransformException.METHOD_NOT_FOUND, path, methodName);

                    // Find Criteria For Related Object
                    Criteria criteria = (Criteria) findCriteria.invoke(parentDomain, new Object[] {});
                    // Add extra key info...
                    for (Iterator it = keys.keySet().iterator(); it.hasNext();) {
                        String keyField = (String) it.next();
                        Object value = keys.get(keyField);
                        keyField = StringHelper.getUpper1(mapping.getDomainFieldName(keyField));
                        criteria.addCriteria(keyField, value);
                        if (log.isDebugEnabled())
                            log.debug(path + "- Add to criteria:" + keyField + '=' + value);
                    }
                    // See if we get an object :-)
                    Iterator itr = uow.query(criteria).iterator();
                    if (itr.hasNext())
                        domainObject = (IPersistent) itr.next();
                    if (itr.hasNext()) {
                        // Error, multiple objects found
                        throw new ApplicationExceptions(new ApplicationExceptionWithContext(path,
                                new MultipleDomainObjectsFoundException(findDomainLabel(criteria.getTable()))));
                    }
                }

            }
            // Error if DO not found
            if (domainObject == null)
                throw new ApplicationExceptions(new ApplicationExceptionWithContext(path,
                        new DomainObjectNotFoundException(findDomainLabel(doClass))));

            // Process the delete, either on this DO, or a related DO if there is one
            deleteBeanData(path, source, uow, handler, mapping, domainObject);

        } catch (IllegalAccessException e) {
            TransformException me = new TransformException(TransformException.ACCESS_ERROR, path, e.getMessage());
            log.error(me.getLocalizedMessage(), e);
            throw me;
        } catch (InvocationTargetException e) {
            ApplicationExceptions appExps = ExceptionHelper.extractApplicationExceptions(e);
            if (appExps != null)
                throw appExps;
            FrameworkException fe = ExceptionHelper.extractFrameworkException(e);
            if (fe != null)
                throw fe;
            TransformException me = new TransformException(TransformException.INVOCATION_ERROR, path, e);
            log.error(me.getLocalizedMessage(), me.getCause());
            throw me;
        } catch (InstantiationException e) {
            TransformException me = new TransformException(TransformException.INSTANTICATION_ERROR, path,
                    e.getMessage());
            log.error(me.getLocalizedMessage(), e);
            throw me;
        }
    }

    static void deleteBeanData(String path, GraphDataObject source, UOW uow, ITransformationHandler handler,
            GraphMapping mapping, IPersistent domainObject) throws InstantiationException, IllegalAccessException,
            InvocationTargetException, ApplicationExceptions, FrameworkException {
        try {
            // Ensure the domain object has not been modified
            domainObjectChangedTest(path, source, mapping, domainObject);

            List<ITransformationHandler> handlers = null;
            if (handler != null) {
                handlers = handler.getTransformationHandlers();
            }

            //----------------------------------------------------------------
            // Fire 'startBean' handler
            if (handlers != null) {
                for (ITransformationHandler transformationHandler : handlers) {
                    transformationHandler.startBean(path, source, domainObject);
                }
            }

            //----------------------------------------------------------------
            // Now loop through children, if there is one, delete it, and leave parent alone
            boolean deleteChild = false;

            // Reflect any related objects
            for (Iterator it = mapping.getRelatedFields().iterator(); it.hasNext();) {
                String field = (String) it.next();
                Object value = getProperty(mapping.getDataFieldDescriptor(field), source);
                if (value != null) {
                    if (value.getClass().isArray()) {
                        // The related field is an array of objects (one-to-many)
                        Object[] values = (Object[]) value;
                        for (int i = 0; i < values.length; i++) {
                            GraphDataObject dao = (GraphDataObject) values[i]; // Assumes its a DAO....what else could it be?
                            if (dao != null) {
                                deleteChild = true;
                                deleteChildBean(path + '.' + field + '[' + i + ']', dao, uow, handler, domainObject,
                                        mapping, field);
                            }
                        }
                    } else {
                        // The related field is a single object (one-to-many)
                        GraphDataObject dao = (GraphDataObject) value; // Assumes its a DAO....what else could it be?
                        deleteChild = true;
                        deleteChildBean(path + '.' + field, dao, uow, handler, domainObject, mapping, field);
                    }
                }
            }

            //----------------------------------------------------------------
            // Delete this record, as it had no children
            if (!deleteChild) {
                if (log.isDebugEnabled())
                    log.debug("UOW.Delete Domain Object");
                // Fire 'startBeanDelete' handler
                if (handlers != null) {
                    for (ITransformationHandler transformationHandler : handlers) {
                        transformationHandler.startBeanDelete(path, source, domainObject);
                    }
                }

                uow.delete(domainObject);
                if (handlers != null) {
                    for (ITransformationHandler transformationHandler : handlers) {
                        transformationHandler.setChangeDone(true);
                    }
                }

                // Fire 'endBeanDelete' handler
                if (handlers != null) {
                    for (ITransformationHandler transformationHandler : handlers) {
                        transformationHandler.endBeanDelete(path, source, domainObject);
                    }
                }
            }

            //----------------------------------------------------------------
            // Fire 'endBean' handler
            if (handlers != null) {
                for (ITransformationHandler transformationHandler : handlers) {
                    transformationHandler.endBean(path, source, domainObject);
                }
            }

        } catch (SkipTransformException e) {
            if (log.isDebugEnabled())
                log.debug("Processing of " + path + " will be skipped", e);
        } catch (ApplicationException e) {
            throw new ApplicationExceptions(e);
        }
    }

    static void setProperty(PropertyDescriptor pd, Object value, Object source)
            throws IllegalAccessException, InvocationTargetException, TransformException {
        if (pd != null && pd.getWriteMethod() != null) {
            Method m = pd.getWriteMethod();
            if (!m.isAccessible())
                m.setAccessible(true);
            Class tClass = m.getParameterTypes()[0];
            if (value == null || tClass.isAssignableFrom(value.getClass())) {
                m.invoke(source, new Object[] { value });
                if (log.isDebugEnabled())
                    log.debug("Set property '" + pd.getName() + '=' + value + "' on object '"
                            + source.getClass().getName() + '\'');
            } else if (DataTypeMapper.instance().isMappable(value.getClass(), tClass)) {
                // See if there is a datatype mapper for these classes
                value = DataTypeMapper.instance().map(value, tClass);
                m.invoke(source, new Object[] { value });
                if (log.isDebugEnabled())
                    log.debug("Translate+Set property '" + pd.getName() + '=' + value + "' on object '"
                            + source.getClass().getName() + '\'');
            } else {
                // Data type mismatch
                throw new TransformException(TransformException.DATATYPE_MISMATCH,
                        source.getClass().getName() + '.' + m.getName(), tClass.getName(),
                        value.getClass().getName());
            }
        } else {
            TransformException me = new TransformException(TransformException.NO_SETTER, null,
                    pd == null ? "???" : pd.getName(), source.getClass().getName());
            log.error(me.getLocalizedMessage());
            throw me;
        }
    }

    static Object getProperty(PropertyDescriptor pd, Object source)
            throws IllegalAccessException, InvocationTargetException, TransformException {
        if (pd != null && pd.getReadMethod() != null) {
            Method m = pd.getReadMethod();
            if (!m.isAccessible())
                m.setAccessible(true);
            Object value = m.invoke(source, new Object[] {});
            if (log.isDebugEnabled())
                log.debug("Get property '" + pd.getName() + '=' + value + "' on object '"
                        + source.getClass().getName() + '\'');
            return value;
        } else {
            TransformException me = new TransformException(TransformException.NO_GETTER, null,
                    pd == null ? "???" : pd.getName(), source.getClass().getName());
            log.error(me.getLocalizedMessage());
            throw me;
        }
    }

    /**
     * Set a value on a Bean, if its a persistent bean, try to use an update method first
     * (for v1.0 domain objects), otherwise use the setter (for v1.1 and above).
     */
    static void updateProperty(PropertyDescriptor pd, Object value, Object source)
            throws IllegalAccessException, InvocationTargetException, TransformException {
        if (source instanceof Persistent) {
            if (pd != null && pd.getWriteMethod() != null) {
                try {
                    Method m = source.getClass().getMethod("update" + StringHelper.getUpper1(pd.getName()),
                            pd.getWriteMethod().getParameterTypes());
                    if (!m.isAccessible())
                        m.setAccessible(true);
                    Class tClass = m.getParameterTypes()[0];
                    if (value == null || tClass.isAssignableFrom(value.getClass())) {
                        m.invoke(source, new Object[] { value });
                        if (log.isDebugEnabled())
                            log.debug("Update property '" + pd.getName() + '=' + value + "' on object '"
                                    + source.getClass().getName() + '\'');
                        // See if there is a datatype mapper for these classes
                    } else if (DataTypeMapper.instance().isMappable(value.getClass(), tClass)) {
                        value = DataTypeMapper.instance().map(value, tClass);
                        m.invoke(source, new Object[] { value });
                        if (log.isDebugEnabled())
                            log.debug("Translate+Update property '" + pd.getName() + '=' + value + "' on object '"
                                    + source.getClass().getName() + '\'');
                    } else {
                        // Data type mismatch
                        throw new TransformException(TransformException.DATATYPE_MISMATCH,
                                source.getClass().getName() + '.' + m.getName(), tClass.getName(),
                                value.getClass().getName());
                    }
                } catch (NoSuchMethodException e) {
                    if (log.isDebugEnabled())
                        log.debug("No Updator, try Setter for DO property '" + pd.getName() + "' on object '"
                                + source.getClass().getName() + '\'');
                    // try to use the setter if there is no update method
                    setProperty(pd, value, source);
                }
            } else {
                TransformException me = new TransformException(TransformException.NO_SETTER, null,
                        pd == null ? "???" : pd.getName(), source.getClass().getName());
                log.error(me.getLocalizedMessage());
                throw me;
            }
        } else
            setProperty(pd, value, source);
    }

    /**
     * Tries to use meta data to get domain objects label.
     */
    static String findDomainLabel(Class doClass) {
        return findDomainLabel(doClass.getName());
    }

    /**
     * Tries to use meta data to get domain objects label.
     */
    static String findDomainLabel(String doClassName) {
        String label = doClassName;
        try {
            label = PersistentHelper.getLabelToken(doClassName);
        } catch (Exception e) {
            // ignore any problem trying to get the label!
        }
        return label;
    }

    /**
     * Performs the dirty-read check to ensure that the underlying record in the database hasn't
     * been changed by another user, while the current user has been trying to modify it.
     */
    private static void domainObjectChangedTest(String path, GraphDataObject source, GraphMapping mapping,
            IPersistent domainObject) throws InstantiationException, IllegalAccessException,
            InvocationTargetException, FrameworkException, ApplicationExceptions {
        if (source.getPerformDirtyReadCheck() != null && source.getPerformDirtyReadCheck()
                && mapping.getDirtyReadDataFieldName() != null) {
            if (log.isDebugEnabled())
                log.debug("Performing dirty-read check for " + domainObject);
            Object lastKnownValue = getProperty(mapping.getDataFieldDescriptor(mapping.getDirtyReadDataFieldName()),
                    source);
            Object currentValue = getProperty(mapping.getDomainFieldDescriptor(mapping.getDirtyReadDataFieldName()),
                    domainObject);
            if (lastKnownValue == null ? currentValue != null : !lastKnownValue.equals(currentValue)) {

                if (log.isDebugEnabled())
                    log.debug("Dirty-read check failed: lastKnownValue='" + lastKnownValue + "', currentValue='"
                            + currentValue + '\'');
                if (!domainObject.isDatabaseOccurence()) {
                    if (log.isDebugEnabled())
                        log.debug("Dirty-read check failed: " + domainObject + " may have been removed.");
                    throw new ApplicationExceptions(new ApplicationExceptionWithContext(path,
                            new ApplicationException(DomainObjectChangedException.OBJECT_REMOVED, null, null)));
                } else {
                    String[] errorParamNames = mapping.getDirtyReadErrorParams();
                    Object[] errorParamValues = new Object[errorParamNames.length];
                    try {
                        for (int i = 0; i < errorParamNames.length; i++)
                            errorParamValues[i] = BeanHelper.getField(domainObject, errorParamNames[i]);
                    } catch (Exception e) {
                        log.warn(
                                "Error in creation of the argument array to pass to the DomainObjectChangedException from the list: "
                                        + Arrays.toString(mapping.getDirtyReadErrorParams()),
                                e);
                    }
                    throw new ApplicationExceptions(
                            new ApplicationExceptionWithContext(path, new DomainObjectChangedException(
                                    mapping.getDirtyReadErrorLabelToken(), errorParamValues, null)));
                }
            } else {
                if (log.isDebugEnabled())
                    log.debug("Dirty-read check succeeded. Both lastKnownValue and currentValue equal '"
                            + currentValue + '\'');
            }
        }
    }

    /**
     * This will generate a unique string for the input persistent object, based on the persistent class name and its key values.
     * The format of the generated key will be: package.classname;key1value;key2value;key3value
     * For eg:
     * For a Person persistent object having a primary key PersonId, the serialized key could be "org.example.Person;P0021"
     * For an EventEntry persistent object having a composite primary key of EventId and PersonId primary, the serialized key could be "org.example.EventEntry;E01;P0021"
     * The back-slash '\' will be the escape character.
     * Hence, if the key-value contains a '\', then it'll be replaced by '\\'
     * If the key value contains a semi-colon, then it'll be replaced by '\;'
     * <p/>
     * Note: This method will determine the key fields by looking up the getKeyFields method in the corresponding meta class for the input persistent object.
     *
     * @param source The persistent object.
     * @return a unique String for identifying the persistent object.
     * @throws ClassNotFoundException    if the Meta class for the input persistent class is not found.
     * @throws NoSuchMethodException     if the Meta class does not have the 'public static FieldMetaData[] getKeyFields()' method.
     * @throws IllegalAccessException    if the 'public static FieldMetaData[] getKeyFields()' method of the Meta class enforces Java language access control and the underlying method is inaccessible.
     * @throws InvocationTargetException if the 'public static FieldMetaData[] getKeyFields()' method of the Meta class throws an exception.
     * @throws IllegalArgumentException  if the input persistent class does not have any key-fields or if any of the key-fields is null.
     */
    public static String generateSerializedKey(GraphDataObject source) throws ClassNotFoundException,
            NoSuchMethodException, IllegalAccessException, InvocationTargetException, IllegalArgumentException {

        GraphMapping mapping = MappingFactory.getInstance(source);
        Set<String> keyFields = mapping.getKeyFields();
        Class doClass = mapping.getDomainClass();

        StringBuffer buf = new StringBuffer(doClass.getName());

        if (keyFields != null && keyFields.size() > 0) {
            for (String keyFieldName : keyFields) {
                Object keyFieldValue = BeanHelper.getField(source, keyFieldName);
                if (keyFieldValue != null) {
                    buf.append(';').append(quoteSerializedKeyValue(keyFieldValue.toString()));
                } else {
                    String str = "SerializedKey cannot be generated. The input Graph object has a null value for its key-field "
                            + source + ':' + keyFieldName;
                    if (log.isDebugEnabled())
                        log.debug(str);
                    throw new IllegalArgumentException(str);
                }
            }
        } else {
            String str = "SerializedKey cannot be generated. The input persistent object does not have any key fields defined in its meta class: "
                    + doClass.getName();
            if (log.isDebugEnabled())
                log.debug(str);
            throw new IllegalArgumentException(str);
        }
        return buf.toString();
    }

    /**
     * This will quote the semi-colon characters in the input string.
     * The back-slash '\' will be the escape character.
     * Hence, if the key-value contains a '\', then it'll be replaced by '\\'
     * If the key value contains a semi-colon, then it'll be replaced by '\;'
     */
    private static String quoteSerializedKeyValue(String input) {
        // replace '\' with '\\' and then replace ';' with '\;'
        return StringHelper.replace(StringHelper.replace(input, "\\", "\\\\"), ";", "\\;");
    }

    /**
     * This will unquote the semi-colon characters in the input string.
     * The back-slash '\' will be the escape character.
     * Hence, if the key-value contains a '\;', then it'll be replaced by ';'
     * If the key value contains a '\\', then it'll be replaced by '\'
     */
    private static String unquoteSerializedKeyValue(String input) {
        // replace '\;' with ';' and then replace '\\' with '\'
        return StringHelper.replace(StringHelper.replace(input, "\\;", ";"), "\\\\", "\\");
    }
}