/* * Copyright 2005-2010 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.wamblee.persistence.hibernate; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.Set; import java.util.Map.Entry; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.springframework.orm.hibernate3.HibernateTemplate; import org.springframework.orm.hibernate3.support.HibernateDaoSupport; import org.wamblee.persistence.Persistent; /** * Extension of * {@link org.springframework.orm.hibernate.support.HibernateDaoSupport}. * * @author Erik Brakkee */ public class HibernateSupport extends HibernateDaoSupport { private static final Log LOG = LogFactory.getLog(HibernateSupport.class); /** * Constructs the object. * */ public HibernateSupport() { // Empty } /** * Performes a hibernate Session.merge() and updates the object * with the correct primary key and version. This is an extension to the * hibernate merge operation because hibernate itself leaves the object * passed to merge untouched. Use this method with extreme caution since it * will recursively load all objects that the current object has relations * with and for which cascade="merge" was specified in the Hibernate mapping * file. * * @param aPersistent * Object to merge. */ public void merge(Persistent aPersistent) { merge(getHibernateTemplate(), aPersistent); } /** * As {@link #merge(Persistent)} but with a given template. This method can * be accessed in a static way. * * @param aTemplate * Hibernate template * @param aPersistent * Object to merge. */ public static void merge(HibernateTemplate aTemplate, Persistent aPersistent) { Persistent merged = (Persistent) aTemplate.merge(aPersistent); processPersistent(aPersistent, merged, new ArrayList()); } /** * Copies primary keys and version from the result of the merged to the * object that was passed to the merge operation. It does this by traversing * the properties of the object. It copies the primary key and version for * objects that implement {@link Persistent} and applies the same rules to * objects in maps and sets as well (i.e. recursively). * * @param aPersistent * Object whose primary key and version are to be set. * @param aMerged * Object that was the result of the merge. * @param aProcessed * List of already processed Persistent objects of the persistent * part. * */ public static void processPersistent(Persistent aPersistent, Persistent aMerged, List aProcessed) { if ((aPersistent == null) && (aMerged == null)) { return; } if ((aPersistent == null) || (aMerged == null)) { throw new RuntimeException("persistent or merged object is null '" + aPersistent + "'" + " '" + aMerged + "'"); } ObjectElem elem = new ObjectElem(aPersistent); if (aProcessed.contains(elem)) { return; // already processed. } aProcessed.add(elem); LOG.debug("Setting pk/version on " + aPersistent + " from " + aMerged); if ((aPersistent.getPrimaryKey() != null) && !aMerged.getPrimaryKey().equals(aPersistent.getPrimaryKey())) { LOG.error("Mismatch between primary key values: " + aPersistent + " " + aMerged); } else { aPersistent.setPersistedVersion(aMerged.getPersistedVersion()); aPersistent.setPrimaryKey(aMerged.getPrimaryKey()); } Method[] methods = aPersistent.getClass().getMethods(); for (Method getter : methods) { if (getter.getName().startsWith("get")) { Class returnType = getter.getReturnType(); try { if (Set.class.isAssignableFrom(returnType)) { Set merged = (Set) getter.invoke(aMerged); Set persistent = (Set) getter.invoke(aPersistent); processSet(persistent, merged, aProcessed); } else if (List.class.isAssignableFrom(returnType)) { List merged = (List) getter.invoke(aMerged); List persistent = (List) getter.invoke(aPersistent); processList(persistent, merged, aProcessed); } else if (Map.class.isAssignableFrom(returnType)) { Map merged = (Map) getter.invoke(aMerged); Map persistent = (Map) getter.invoke(aPersistent); processMap(persistent, merged, aProcessed); } else if (Persistent.class.isAssignableFrom(returnType)) { Persistent merged = (Persistent) getter.invoke(aMerged); Persistent persistent = (Persistent) getter .invoke(aPersistent); processPersistent(persistent, merged, aProcessed); } else if (returnType.isArray() && Persistent.class.isAssignableFrom(returnType .getComponentType())) { Persistent[] merged = (Persistent[]) getter .invoke(aMerged); Persistent[] persistent = (Persistent[]) getter .invoke(aPersistent); for (int i = 0; i < persistent.length; i++) { processPersistent(persistent[i], merged[i], aProcessed); } } } catch (InvocationTargetException e) { throw new RuntimeException(e.getMessage(), e); } catch (IllegalAccessException e) { throw new RuntimeException(e.getMessage(), e); } } } } /** * Process the persistent objects in the collections. * * @param aPersistent * Collection in the original object. * @param aMerged * Collection as a result of the merge. * @param aProcessed * List of processed persistent objects. * */ public static void processList(List aPersistent, List aMerged, List aProcessed) { Object[] merged = aMerged.toArray(); Object[] persistent = aPersistent.toArray(); if (merged.length != persistent.length) { throw new RuntimeException("Array sizes differ " + merged.length + " " + persistent.length); } for (int i = 0; i < merged.length; i++) { assert merged[i].equals(persistent[i]); if (merged[i] instanceof Persistent) { processPersistent((Persistent) persistent[i], (Persistent) merged[i], aProcessed); } } } /** * Process the persistent objects in sets. * * @param aPersistent * Collection in the original object. * @param aMerged * Collection as a result of the merge. * @param aProcessed * List of processed persistent objects. * */ public static void processSet(Set aPersistent, Set aMerged, List aProcessed) { if (aMerged.size() != aPersistent.size()) { throw new RuntimeException("Array sizes differ " + aMerged.size() + " " + aPersistent.size()); } for (Object merged : aMerged) { // Find the object that equals the merged[i] for (Object persistent : aPersistent) { if (persistent.equals(merged)) { processPersistent((Persistent) persistent, (Persistent) merged, aProcessed); break; } } } } /** * Process the Map objects in the collections. * * @param aPersistent * Collection in the original object. * @param aMerged * Collection as a result of the merge. * @param aProcessed * List of processed persistent objects. * */ public static void processMap(Map aPersistent, Map aMerged, List aProcessed) { if (aMerged.size() != aPersistent.size()) { throw new RuntimeException("Sizes differ " + aMerged.size() + " " + aPersistent.size()); } Set> entries = aMerged.entrySet(); for (Entry entry : entries) { Key key = entry.getKey(); if (!aPersistent.containsKey(key)) { throw new RuntimeException("Key '" + key + "' not found"); } Value mergedValue = entry.getValue(); Object persistentValue = aPersistent.get(key); if (mergedValue instanceof Persistent) { if (persistentValue instanceof Persistent) { processPersistent((Persistent) persistentValue, (Persistent) mergedValue, aProcessed); } else { throw new RuntimeException( "Value in original object is null, whereas merged object contains a value"); } } } } /** * This class provided an equality operation based on the object reference * of the wrapped object. This is required because we cannto assume that the * equals operation has any meaning for different types of persistent * objects. This allows us to use the standard collection classes for * detecting cyclic dependences and avoiding recursion. */ private static final class ObjectElem { private Object object; public ObjectElem(Object aObject) { object = aObject; } public boolean equals(Object aObj) { if (aObj == null) { return false; } if (!(aObj instanceof ObjectElem)) { return false; } return ((ObjectElem) aObj).object == object; } public int hashCode() { return object.hashCode(); } } }