Java tutorial
/* * ==================================================================== * 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. * ==================================================================== */ /* * DataTransformer.java * */ package org.jaffa.soa.dataaccess; import java.beans.PropertyDescriptor; import java.lang.reflect.AccessibleObject; import java.lang.reflect.Array; import java.lang.reflect.Constructor; import java.lang.reflect.Field; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.ArrayList; import java.util.Collection; import java.util.Iterator; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Set; import java.util.regex.Matcher; import java.util.regex.Pattern; import org.apache.commons.beanutils.DynaProperty; import org.apache.log4j.Logger; import org.jaffa.datatypes.DataTypeMapper; import org.jaffa.datatypes.exceptions.InvalidForeignKeyException; import org.jaffa.exceptions.*; import org.jaffa.flexfields.FlexBean; import org.jaffa.flexfields.FlexProperty; import org.jaffa.flexfields.IFlexFields; import org.jaffa.persistence.Criteria; import org.jaffa.persistence.IPersistent; import org.jaffa.persistence.UOW; import org.jaffa.soa.graph.GraphCriteria; import org.jaffa.soa.graph.GraphDataObject; import org.jaffa.util.ExceptionHelper; /** * Bean Moudler is used to mapp data between two Java Beans via a mapping file. * It has been specifcially coded to map between benas that extend/implement * GraphDataObject and IPersistent for marshalling data to and from the database. * * @author PaulE * @version 1.0 * @TODO - Use nested query clauses for limiting the results of sub queries * @TODO - Stop using the getXxxArray() methods for subQueries, do the query in here * @TODO - Implement the objectStart on the subQueries */ public class DataTransformer { private static Logger log = Logger.getLogger(DataTransformer.class); private static final Pattern RELATED_OBJECT_GETTER = Pattern.compile("get(.+)Array"); enum Mode { VALIDATE_ONLY, CLONE, MASS_UPDATE } /** * Mould data from domain object and its related objects into a new JavaBean based * domain object graph, based on the defined mapping rules. * <p/> * Same as {@link #buildGraphFromDomain(Object source, Object target, GraphMapping graph, MappingFilter filter, String objectPath, boolean includeKeys)} * except the graph is derived from the target object, a default MappingFilter (that * just returns fields from the root object) and includeKeys is false. * The objectPath is also null, assuming this is the first object in the domain * object graph. * <p/> * * @param source Source object to mould data from, typically extends Persistent * @param target Target object to mould data to, typically extends GraphDataObject * @throws ApplicationExceptions Thrown if one or more application logic errors are generated during moulding * @throws FrameworkException Thrown if any runtime moulding error has occured. */ public static void buildGraphFromDomain(Object source, Object target) throws ApplicationExceptions, FrameworkException { buildGraphFromDomain(source, target, null, null); } /** * Mould data from domain object and its related objects into a new JavaBean based * domain object graph, based on the defined mapping rules. * <p/> * Same as {@link #buildGraphFromDomain(Object source, Object target, GraphMapping graph, MappingFilter filter, String objectPath, boolean includeKeys)} * except the graph is derived from the target object, and includeKeys is false. * <p/> * * @param source Source object to mould data from, typically extends Persistent * @param target Target object to mould data to, typically extends GraphDataObject * @param filter Filter object that it is used to control what fields are populated or the target objects * @param objectPath The path of this object being processed. This identifies possible parent * and/or indexed entries where this object is contained. * @throws ApplicationExceptions Thrown if one or more application logic errors are generated during moulding * @throws FrameworkException Thrown if any runtime moulding error has occured. */ public static void buildGraphFromDomain(Object source, Object target, MappingFilter filter, String objectPath) throws ApplicationExceptions, FrameworkException { buildGraphFromDomain(source, target, null, filter, objectPath, false); } /** * Mould data from domain object and its related objects into a new JavaBean based * domain object graph, based on the defined mapping rules. * * @param source Source object to mould data from, typically extends Persistent * @param target Target object to mould data to, typically extends GraphDataObject * @param graph The mapping class with the rules of how to map this source object * @param filter Filter object that it is used to control what fields are populated or the target objects * @param objectPath The path of this object being processed. This identifies possible parent * and/or indexed entries where this object is contained. * @param includeKeys true if key fields should be included in results regardless of the filters * @throws ApplicationExceptions Thrown if one or more application logic errors are generated during moulding * @throws FrameworkException Thrown if any runtime moulding error has occured. */ public static void buildGraphFromDomain(Object source, Object target, GraphMapping graph, MappingFilter filter, String objectPath, boolean includeKeys) throws ApplicationExceptions, FrameworkException { buildGraphFromDomain(source, target, graph, filter, objectPath, includeKeys, null, null); } /** * Mould data from domain object and its related objects into a new JavaBean based * domain object graph, based on the defined mapping rules. * * @param source Source object to mould data from, typically extends Persistent * @param target Target object to mould data to, typically extends GraphDataObject * @param graph The mapping class with the rules of how to map this source object * @param filter Filter object that it is used to control what fields are populated or the target objects * @param objectPath The path of this object being processed. This identifies possible parent * and/or indexed entries where this object is contained. * @param includeKeys true if key fields should be included in results regardless of the filters * @param originalCriteria the original graph criteria. * @param handler Possible bean handler to be used when processing this source object graph * @throws ApplicationExceptions Thrown if one or more application logic errors are generated during moulding * @throws FrameworkException Thrown if any runtime moulding error has occured. */ public static void buildGraphFromDomain(Object source, Object target, GraphMapping graph, MappingFilter filter, String objectPath, boolean includeKeys, GraphCriteria originalCriteria, ITransformationHandler handler) throws ApplicationExceptions, FrameworkException { if (graph == null) graph = MappingFactory.getInstance(target); boolean useQueryDomain = graph.getQueryDomainClass() != null && source.getClass().isAssignableFrom(graph.getQueryDomainClass()); //throw new InstantiationException("A GraphMapping must be supplied"); if (filter == null) filter = MappingFilter.getInstance(graph); try { // get list of target fileds to populate String[] tFields = graph.getDataFieldNames(); if (tFields != null && tFields.length != 0) for (int i = 0; i < tFields.length; i++) { // Try to map a source to a target String tName = tFields[i]; String fullName = tName; if (objectPath != null) fullName = objectPath + "." + fullName; if (filter == null || filter.isFieldIncluded(fullName) || (includeKeys && graph.isKeyField(tName))) { String sName = graph.getDomainFieldName(tName); AccessibleObject tAccessibleObject = graph.getDataMutator(tName); PropertyDescriptor tDesc = graph.getDataFieldDescriptor(tName); PropertyDescriptor sDesc = useQueryDomain ? graph.getQueryDomainFieldDescriptor(tName) : graph.getDomainFieldDescriptor(tName); if (useQueryDomain && sDesc == null) continue; // Based on validation in GraphMapping, that there is a // GraphObject descriptor with a setter, and a DO descriptor with a getter if (sDesc == null) log.error("No Getter for " + tName + " in path " + fullName); // incase getter is not public, make it available Method sm = sDesc.getReadMethod(); if (!sm.isAccessible()) sm.setAccessible(true); // Set the value if the source and target are the same datatype Class tClass = tDesc.getPropertyType(); Class sClass = sDesc.getPropertyType(); if (tClass.isAssignableFrom(sClass)) { // Get the value being copied Object sValue = sm.invoke(source, (Object[]) null); setValue(tAccessibleObject, target, sValue); if (log.isDebugEnabled()) log.debug("Set " + tName + " = " + sValue); // See if there is a datatype mapper for these classes } else if (DataTypeMapper.instance().isMappable(sClass, tClass)) { // Get the value being copied Object sValue = sm.invoke(source, (Object[]) null); if (sValue != null) { sValue = DataTypeMapper.instance().map(sValue, tClass); if (log.isDebugEnabled()) log.debug("Set " + tName + " = " + sValue); } setValue(tAccessibleObject, target, sValue); // See if target is a GraphObject, this could be a foreign object or one-to-one relationship... } else if (GraphDataObject.class.isAssignableFrom(tClass) && IPersistent.class.isAssignableFrom(sClass)) { // Get the mapper for the related GraphObject, if it has keys, it must be a foriegn object if (graph.isForeignField(tName)) { // look at foreign key fields, and make sure they are not null List foreignKeys = graph.getForeignKeys(tName); List foreignKeyValues = new ArrayList(); boolean nullKey = false; for (Iterator k = foreignKeys.iterator(); k.hasNext();) { String doProp = (String) k.next(); Object value = null; PropertyDescriptor doPd = graph.getRealDomainFieldDescriptor(doProp); if (doPd != null && doPd.getReadMethod() != null) { Method m = doPd.getReadMethod(); if (!m.isAccessible()) m.setAccessible(true); value = m.invoke(source, new Object[] {}); if (value == null) nullKey = true; foreignKeyValues.add(value); } else { throw new TransformException(TransformException.INVALID_FK_MAPPING, objectPath, doProp, graph.getDomainClassShortName()); } } if (nullKey) { if (log.isDebugEnabled()) log.debug("Did not create skeleton object '" + tClass.getName() + "': one or more foreign key values missing."); } else { // Create the foreign object if (log.isDebugEnabled()) log.debug("Creating foreign object - " + tClass.getName()); Object newGDO = newGraphDataObject(tClass); boolean createSkeleton = true; // Only retrieve related domain object and introspect if need if (filter.areSubFieldsIncluded(fullName)) { // read object and introspect all if (log.isDebugEnabled()) log.debug("Read foreign object '" + fullName + "' and mold"); try { Object sValue = sm.invoke(source, (Object[]) null); if (sValue != null) { DataTransformer.buildGraphFromDomain(sValue, newGDO, null, filter, fullName, true, originalCriteria, handler); createSkeleton = false; } } catch (InvocationTargetException e) { // If the foreign object is not found, create the skeleton if (e.getCause() != null && e.getCause() instanceof InvalidForeignKeyException) { if (log.isDebugEnabled()) log.debug( "All foreign keys present, but foreign object does not exist", e); } else throw e; } } if (createSkeleton) { // just set foreign keys from current object if (log.isDebugEnabled()) log.debug("Set keys on skeleton foreign object only"); GraphMapping graph2 = MappingFactory.getInstance(newGDO); Set keys = graph2.getKeyFields(); if (keys == null || keys.size() != foreignKeyValues.size()) { throw new TransformException(TransformException.MISMATCH_FK_MAPPING, objectPath, target.getClass().getName(), newGDO.getClass().getName()); } int k2 = 0; // Look through all the foreign keys on the skeleton object for (Iterator k = keys.iterator(); k.hasNext(); k2++) { String keyField = (String) k.next(); Object keyValue = foreignKeyValues.get(k2); AccessibleObject accessibleObject = graph2.getDataMutator(keyField); if (accessibleObject != null) { setValue(accessibleObject, newGDO, keyValue); } else { throw new TransformException(TransformException.CANT_SET_KEY_FIELD, objectPath, keyField, newGDO.getClass().getName()); } } } setValue(tAccessibleObject, target, newGDO); if (log.isDebugEnabled()) log.debug("Set " + tName + " = " + newGDO); } } else { // This is not a foreign object, must be a related object if (filter.areSubFieldsIncluded(fullName)) { // Create the related object if (log.isDebugEnabled()) log.debug("Creating One-To-One object - " + tClass.getName()); Object newGDO = newGraphDataObject(tClass); // read object and introspect all if (log.isDebugEnabled()) log.debug("Read related object '" + fullName + "' and mold"); Object sValue = sm.invoke(source, (Object[]) null); if (sValue != null) { DataTransformer.buildGraphFromDomain(sValue, newGDO, null, filter, fullName, false, originalCriteria, handler); setValue(tAccessibleObject, target, newGDO); if (log.isDebugEnabled()) log.debug("Set " + tName + " = " + newGDO); } else { if (log.isDebugEnabled()) log.debug("Related object '" + fullName + "' not found. Ignore it!"); } } else { if (log.isDebugEnabled()) log.debug("No subfields for object " + fullName + " included. Object not retrieved"); } } //END-related object // See if Target may be an array of GraphObject's } else if (tClass.isArray() && GraphDataObject.class.isAssignableFrom(tClass.getComponentType()) && filter.areSubFieldsIncluded(fullName)) { if (log.isDebugEnabled()) log.debug("Target is an array of GraphObject's"); if (sClass.isArray() && IPersistent.class.isAssignableFrom(sClass.getComponentType())) { if (log.isDebugEnabled()) { log.debug("Source is an array of Persistent Objects"); log.debug("Read related objects '" + fullName + "' and mold"); } Object[] sArray = findRelatedObjects(source, sClass, sm, handler, originalCriteria, fullName); if (sArray != null && sArray.length > 0) { Object[] tArray = (Object[]) Array.newInstance(tClass.getComponentType(), sArray.length); if (log.isDebugEnabled()) log.debug("Translate Array of Size " + sArray.length); for (int j = 0; j < sArray.length; j++) { Object newGDO = newGraphDataObject(tClass.getComponentType()); DataTransformer.buildGraphFromDomain(sArray[j], newGDO, null, filter, fullName, false, originalCriteria, handler); tArray[j] = newGDO; if (log.isDebugEnabled()) log.debug("Add to array [" + j + "] : " + newGDO); } setValue(tAccessibleObject, target, tArray); if (log.isDebugEnabled()) log.debug("Set Array " + tName); } else { if (log.isDebugEnabled()) log.debug("Source Array is empty! Do Nothing"); } } // source is DO array // Error... No way to map property } else { String err = "Can't Mold Property " + fullName + " from " + sClass.getName() + " to " + tClass.getName(); log.error(err); throw new RuntimeException(err); } } // is included in filtered fields } // Load flex fields // By default all the domain-mapped flex fields will be loaded; unless excluded by a rule if (source instanceof IFlexFields && target instanceof IFlexFields) { String fullName = (objectPath != null ? objectPath + '.' : "") + "flexBean"; if (filter == null || filter.isFieldIncluded(fullName)) { if (log.isDebugEnabled()) log.debug("Loading FlexBean " + fullName); FlexBean sFlexBean = ((IFlexFields) source).getFlexBean(); FlexBean tFlexBean = ((IFlexFields) target).getFlexBean(); if (sFlexBean != null && tFlexBean != null) { for (DynaProperty flexProperty : sFlexBean.getDynaClass().getDynaProperties()) { String name = flexProperty.getName(); Boolean include = filter.includeField(fullName + '.' + name); if (include != null ? include : ((FlexProperty) flexProperty).getFlexInfo() .getProperty("domain-mapping") != null) { Object value = sFlexBean.get(name); if (value != null) { if (log.isDebugEnabled()) log.debug("Loaded flex field '" + name + '=' + value + '\''); tFlexBean.set(name, value); } } } } } } // Clear changed fields on updated GraphObject if (target != null && target instanceof GraphDataObject) ((GraphDataObject) target).clearChanges(); // Invoke the handler if (handler != null) { if (log.isDebugEnabled()) { log.debug("Invoking the endBeanLoad on the handler"); } for (ITransformationHandler transformationHandler : handler.getTransformationHandlers()) { transformationHandler.endBeanLoad(objectPath, source, target, filter, originalCriteria); } } } catch (ApplicationException e) { throw new ApplicationExceptions(e); } catch (IllegalAccessException e) { TransformException me = new TransformException(TransformException.ACCESS_ERROR, objectPath, 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, objectPath, e); log.error(me.getLocalizedMessage(), me.getCause()); throw me; } catch (InstantiationException e) { TransformException me = new TransformException(TransformException.INSTANTICATION_ERROR, objectPath, e.getMessage()); log.error(me.getLocalizedMessage(), e); throw me; } } // Create a new Graph Data Object based on a specified class that implement this abstract class private static Object newGraphDataObject(Class clazz) throws InstantiationException { try { Constructor c = clazz.getConstructor(new Class[] {}); if (c == null) throw new InstantiationException("No zero argument construtor found"); Object dao = c.newInstance((Object[]) null); if (log.isDebugEnabled()) log.debug("Created Object : " + dao); return dao; } catch (InstantiationException e) { throw e; } catch (Exception e) { log.error("Can't create Graph Data Object - " + e.getMessage(), e); throw new InstantiationException(e.getMessage()); } } /** * Take a source object and try and mold it back it its domain object * This method is typically used to validate a few user-entered fields * and to default related data back on to the source object. * * @param path The path of this object being processed. This identifies possible parent and/or indexed entries where this object is contained. * @param source Source object to mould from, typically a GraphDataObject * @param uow Transaction handle all creates/update will be performed within. Throws an exception if null. * @param handler Possible bean handler to be used when processing this source object graph * @return the source object will be returned with default data. * @throws ApplicationExceptions Thrown if one or more application logic errors are generated during moulding * @throws FrameworkException Thrown if any runtime moulding error has occured. */ public static GraphDataObject prevalidateGraph(String path, GraphDataObject source, UOW uow, ITransformationHandler handler) throws ApplicationExceptions, FrameworkException { return updateGraph(path, source, uow, handler, Mode.VALIDATE_ONLY, null); } /** * Clones the source object and adds it to the persistent store. * The newGraph is used to supply values for the key fields and others. * * @param path The path of this object being processed. This identifies possible parent and/or indexed entries where this object is contained. * @param source Source object to mould from, typically a GraphDataObject * @param uow Transaction handle all creates/update will be performed within. Throws an exception if null. * @param handler Possible bean handler to be used when processing this source object graph * @param newGraph supplies values for the key fields and others. * @return a GraphDataObject with just the key-fields of the root object will be returned. * @throws ApplicationExceptions Thrown if one or more application logic errors are generated during moulding * @throws FrameworkException Thrown if any runtime moulding error has occured. */ public static GraphDataObject cloneGraph(String path, GraphDataObject source, UOW uow, ITransformationHandler handler, GraphDataObject newGraph) throws ApplicationExceptions, FrameworkException { return updateGraph(path, source, uow, handler, Mode.CLONE, newGraph); } /** * MassUpdates the source object in the persistent store. * The newGraph is used to supply non-key values. * * @param path The path of this object being processed. This identifies possible parent and/or indexed entries where this object is contained. * @param source Source object to mould from, typically a GraphDataObject * @param uow Transaction handle all creates/update will be performed within. Throws an exception if null. * @param handler Possible bean handler to be used when processing this source object graph * @param newGraph supplies non-key values. * @throws ApplicationExceptions Thrown if one or more application logic errors are generated during moulding * @throws FrameworkException Thrown if any runtime moulding error has occured. */ public static void massUpdateGraph(String path, GraphDataObject source, UOW uow, ITransformationHandler handler, GraphDataObject newGraph) throws ApplicationExceptions, FrameworkException { updateGraph(path, source, uow, handler, Mode.MASS_UPDATE, newGraph); } /** * Take a source object and try and mold it back it its domain object * * @param path The path of this object being processed. This identifies possible parent and/or indexed entries where this object is contained. * @param source Source object to mould from, typically a GraphDataObject * @param uow Transaction handle all creates/update will be performed within. Throws an exception if null. * @param handler Possible bean handler to be used when processing this source object graph * @return a GraphDataObject with just the key-fields of the root object will be returned if that object was newly created. Else a null will be returned. * @throws ApplicationExceptions Thrown if one or more application logic errors are generated during moulding * @throws FrameworkException Thrown if any runtime moulding error has occured. */ public static GraphDataObject updateGraph(String path, GraphDataObject source, UOW uow, ITransformationHandler handler) throws ApplicationExceptions, FrameworkException { return updateGraph(path, source, uow, handler, null, null); } /** * Take a source object and try and mold it back it its domain object * * @param path The path of this object being processed. This identifies possible parent and/or indexed entries where this object is contained. * @param source Source object to mould from, typically a GraphDataObject * @param uow Transaction handle all creates/update will be performed within. Throws an exception if null. * @param handler Possible bean handler to be used when processing this source object graph * @return In VALIDATE_ONLY mode the source object will be returned with default data. Else a GraphDataObject with just the key-fields of the root object will be returned if that object was newly created. Else a null will be returned. * @throws ApplicationExceptions Thrown if one or more application logic errors are generated during moulding * @throws FrameworkException Thrown if any runtime moulding error has occured. */ private static GraphDataObject updateGraph(String path, GraphDataObject source, UOW uow, ITransformationHandler handler, Mode mode, GraphDataObject newGraph) throws ApplicationExceptions, FrameworkException { if (log.isDebugEnabled()) log.debug("Update Bean " + path); if (source.getDeleteObject() != null && source.getDeleteObject()) { if (mode == Mode.VALIDATE_ONLY) { if (log.isDebugEnabled()) log.debug( "The 'deleteObject' property is true. No prevalidations will be performed. The input object will be returned as is."); return source; } else { if (log.isDebugEnabled()) log.debug("The 'deleteObject' property is true. Invoking deleteGraph()"); deleteGraph(path, source, uow, handler); return null; } } else { try { IPersistent domainObject = null; GraphMapping mapping = MappingFactory.getInstance(source); Map keys = new LinkedHashMap(); Class doClass = mapping.getDomainClass(); // Get the key fields used in the domain object // In CLONE mode, get the keys from the new graph, and force the creation of the domain object boolean gotKeys = false; if (mode == Mode.CLONE) { if (newGraph != null) gotKeys = TransformerUtils.fillInKeys(path, newGraph, mapping, keys); } else gotKeys = TransformerUtils.fillInKeys(path, source, mapping, keys); // read DO based on key if (gotKeys) { // get the method on the DO to read via PK Method[] ma = doClass.getMethods(); Method findByPK = null; for (int i = 0; i < ma.length; i++) { if (ma[i].getName().equals("findByPK")) { if (ma[i].getParameterTypes().length == (keys.size() + 1) && (ma[i].getParameterTypes())[0] == UOW.class) { // Found with name and correct no. of input params findByPK = ma[i]; break; } } } if (findByPK == null) throw new ApplicationExceptions( new DomainObjectNotFoundException(TransformerUtils.findDomainLabel(doClass))); // Build input array Object[] inputs = new Object[keys.size() + 1]; { inputs[0] = uow; int i = 1; for (Iterator it = keys.values().iterator(); it.hasNext(); i++) { inputs[i] = it.next(); } } // Find Object based on key domainObject = (IPersistent) findByPK.invoke(null, inputs); if (domainObject != null && mode == Mode.CLONE) throw new ApplicationExceptions( new DuplicateKeyException(TransformerUtils.findDomainLabel(doClass))); } else { if (log.isDebugEnabled()) log.debug("Object " + path + " has either missing or null key values - Assume Create is needed"); } // Create object if not found boolean createMode = false; if (domainObject == null) { // In MASS_UPDATE mode, error if DO not found if (mode == Mode.MASS_UPDATE) throw new ApplicationExceptions( 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..."); domainObject = (IPersistent) doClass.newInstance(); // 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); TransformerUtils.updateProperty(mapping.getDomainFieldDescriptor(keyField), value, domainObject); } createMode = true; } else { if (log.isDebugEnabled()) log.debug("Found DO '" + mapping.getDomainClassShortName() + "' with key,"); } // Now update all domain fields TransformerUtils.updateBeanData(path, source, uow, handler, mapping, domainObject, mode, newGraph); // Invoke the changeDone trigger if (handler != null && handler.isChangeDone()) { for (ITransformationHandler transformationHandler : handler.getTransformationHandlers()) { transformationHandler.changeDone(path, source, domainObject, uow); } } // Return an appropriate output if (mode == Mode.VALIDATE_ONLY) { // In VALIDATE_ONLY mode, return the input graph (with defaulted data) return source; } else if (createMode) { // In create-mode, Create a new graph and stamp just the keys GraphDataObject outputGraph = source.getClass().newInstance(); for (Iterator i = keys.keySet().iterator(); i.hasNext();) { String keyField = (String) i.next(); PropertyDescriptor pd = mapping.getDomainFieldDescriptor(keyField); if (pd != null && pd.getReadMethod() != null) { Method m = pd.getReadMethod(); if (!m.isAccessible()) m.setAccessible(true); Object value = m.invoke(domainObject, (Object[]) null); AccessibleObject accessibleObject = mapping.getDataMutator(keyField); setValue(accessibleObject, outputGraph, value); } else { TransformException me = new TransformException(TransformException.NO_KEY_ON_OBJECT, path, keyField, source.getClass().getName()); log.error(me.getLocalizedMessage()); throw me; } } return outputGraph; } else { // In update-mode, return a null return null; } } 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 delete it or delete is children if it has any * * @param path The path of this object being processed. This identifies possible parent * and/or indexed entries where this object is contained. * @param source Source object to mould from, typically a GraphDataObject * @param uow Transaction handle all creates/update will be performed within. * Throws an exception if null. * @param handler Possible bean handler to be used when processing this source object graph * @throws ApplicationExceptions Thrown if one or more application logic errors are generated during moulding * @throws FrameworkException Thrown if any runtime moulding error has occured. */ public static void deleteGraph(String path, GraphDataObject source, UOW uow, ITransformationHandler handler) throws ApplicationExceptions, FrameworkException { if (log.isDebugEnabled()) log.debug("Delete Bean " + path); try { IPersistent domainObject = null; GraphMapping mapping = MappingFactory.getInstance(source); Map keys = new LinkedHashMap(); Class doClass = mapping.getDomainClass(); // Get the key fields used in the domain object boolean gotKeys = TransformerUtils.fillInKeys(path, source, mapping, keys); //---------------------------------------------------------------- // read DO based on key if (gotKeys) { // get the method on the DO to read via PK Method[] ma = doClass.getMethods(); Method findByPK = null; for (int i = 0; i < ma.length; i++) { if (ma[i].getName().equals("findByPK")) { if (ma[i].getParameterTypes().length == (keys.size() + 1) && (ma[i].getParameterTypes())[0] == UOW.class) { // Found with name and correct no. of input params findByPK = ma[i]; break; } } } if (findByPK == null) throw new ApplicationExceptions(new DomainObjectNotFoundException(doClass.getName())); // Build input array Object[] inputs = new Object[keys.size() + 1]; { inputs[0] = uow; int i = 1; for (Iterator it = keys.values().iterator(); it.hasNext(); i++) { inputs[i] = it.next(); } } // Find Object based on key domainObject = (IPersistent) findByPK.invoke(null, inputs); } else { if (log.isDebugEnabled()) log.debug( "Object " + path + " has either missing or null key values - Assume Create is needed"); } // Error if DO not found if (domainObject == null) throw new ApplicationExceptions( new DomainObjectNotFoundException(TransformerUtils.findDomainLabel(doClass))); // Process the delete, either on this DO, or a related DO if there is one TransformerUtils.deleteBeanData(path, source, uow, handler, mapping, domainObject); // Invoke the changeDone trigger if (handler != null && handler.isChangeDone()) { for (ITransformationHandler transformationHandler : handler.getTransformationHandlers()) { transformationHandler.changeDone(path, source, domainObject, uow); } } } 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; } // } catch (Exception e) { // throw handleException(e,aes); // } } /** * Retrieves the related object, as specified by the relatedObjectClass, from the input domain object. * It'll utilize the findXyzCriteria() method, if available, to build the query, as well as to fire the handler. * Else it'll utilize the getXyzArray() method, which may cache a huge number of objects. * * @param domain The domain object against which the query is to be invoked. * @param relatedObjectClass The Class of the related object. * @param relatedObjectGetter The getter for obtaining the array of related objects. * @param handler The handler, if passed, will be invoked prior to retrieving the related object. * @param originalCriteria The original Criteria used for the query. This is passed to the handler. * @param path This is the source path of this graph, used when processing a more complex tree, where this is the path to get to this root object being processed. * @return an array of related objects. * @throws IllegalAccessException if the relatedObjectGetter is not accessible. * @throws InvocationTargetException if any error occurs during invocation of the relatedObjectGetter. */ private static Object[] findRelatedObjects(Object domain, Class relatedObjectClass, Method relatedObjectGetter, ITransformationHandler handler, GraphCriteria originalCriteria, String path) throws IllegalAccessException, InvocationTargetException { // The Getter typically caches the related objects. Hence try to locate the corresponding findXyzCriteria() method try { Matcher m = RELATED_OBJECT_GETTER.matcher(relatedObjectGetter.getName()); if (m.matches()) { Method criteriaMethod = domain.getClass().getMethod("find" + m.group(1) + "Criteria"); if (Criteria.class.isAssignableFrom(criteriaMethod.getReturnType())) { Criteria criteria = (Criteria) criteriaMethod.invoke(domain); // Invoke the handler if (handler != null) { if (log.isDebugEnabled()) { log.debug("Invoking the preQuery on the handler"); } for (ITransformationHandler transformationHandler : handler.getTransformationHandlers()) { Criteria handlerCriteria = transformationHandler.preQuery(path, criteria, originalCriteria, relatedObjectClass); if (handlerCriteria != null) { criteria = handlerCriteria; } } } if (criteria == null) { // The handler will return a null if this query is not to be performed return null; } else { UOW uow = domain instanceof IPersistent ? ((IPersistent) domain).getUOW() : null; boolean localUow = uow == null; try { if (localUow) uow = new UOW(); Collection col = uow.query(criteria); // Iterate through the Collection to retrieve all the objects for (Object o : col) { } return col.toArray(); } finally { if (localUow && uow != null) uow.rollback(); } } } else { if (log.isDebugEnabled()) log.debug("Method '" + criteriaMethod.getName() + "' does not does not return a Criteria. Will invoke the method '" + relatedObjectGetter.getName() + "' directly to obtain the related object"); } } else { if (log.isDebugEnabled()) log.debug("Method '" + relatedObjectGetter.getName() + "' does not match the pattern '" + RELATED_OBJECT_GETTER + "'. Will invoke the method directly to obtain the related object"); } } catch (Exception e) { // do nothing if (log.isDebugEnabled()) log.debug( "Exception thrown during creation of the criteria for the related object or while retrieving it. Will invoke the method '" + relatedObjectGetter.getName() + "' directly to obtain the related object", e); } // There must have been an error in invoking the findXyzCriteria() method. Simply invoke the Getter return (Object[]) relatedObjectGetter.invoke(domain); } private static void setValue(AccessibleObject accessibleObject, Object target, Object value) throws IllegalAccessException, InvocationTargetException { if (accessibleObject instanceof Field) ((Field) accessibleObject).set(target, value); else ((Method) accessibleObject).invoke(target, new Object[] { value }); } }