/*
* 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}.
*
* @author Erik Brakkee
*/
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 aObj) {
return ((ObjectElem) aObj)._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.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 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");
}
}
}
}
}