/*
* Copyright 2005 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 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}.
*/
public class HibernateSupport extends HibernateDaoSupport {
private static final Log LOG = LogFactory.getLog(HibernateSupport.class);
/**
* 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 obj) {
return ((ObjectElem)obj)._object == _object;
}
public int hashCode() {
return _object.hashCode();
}
}
/**
* 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.info("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 keys = aMerged.keySet();
for (Object key: keys) {
if ( !aPersistent.containsKey(key) ) {
throw new RuntimeException("Key '" + key + "' not found");
}
Object mergedValue = aMerged.get(key);
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");
}
}
}
}
}