2 * Copyright 2005 the original author or authors.
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
8 * http://www.apache.org/licenses/LICENSE-2.0
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
17 package org.wamblee.persistence.hibernate;
19 import java.lang.reflect.InvocationTargetException;
20 import java.lang.reflect.Method;
21 import java.util.ArrayList;
22 import java.util.List;
26 import org.apache.commons.logging.Log;
27 import org.apache.commons.logging.LogFactory;
28 import org.springframework.orm.hibernate3.HibernateTemplate;
29 import org.springframework.orm.hibernate3.support.HibernateDaoSupport;
30 import org.wamblee.persistence.Persistent;
34 * {@link org.springframework.orm.hibernate.support.HibernateDaoSupport}.
36 public class HibernateSupport extends HibernateDaoSupport {
38 private static final Log LOG = LogFactory.getLog(HibernateSupport.class);
41 * This class provided an equality operation based on the object reference of the
42 * wrapped object. This is required because we cannto assume that the equals operation
43 * has any meaning for different types of persistent objects. This allows us to use
44 * the standard collection classes for detecting cyclic dependences and avoiding
48 private static final class ObjectElem {
49 private Object _object;
50 public ObjectElem(Object aObject) {
53 public boolean equals(Object obj) {
54 return ((ObjectElem)obj)._object == _object;
56 public int hashCode() {
57 return _object.hashCode();
62 * Constructs the object.
65 public HibernateSupport() {
70 * Performes a hibernate <code>Session.merge()</code> and updates the
71 * object with the correct primary key and version. This is an extension to
72 * the hibernate merge operation because hibernate itself leaves the object
73 * passed to merge untouched.
75 * Use this method with extreme caution since it will recursively load all
76 * objects that the current object has relations with and for which cascade="merge"
77 * was specified in the Hibernate mapping file.
79 * @param aPersistent Object to merge.
81 public void merge(Persistent aPersistent) {
82 merge(getHibernateTemplate(), aPersistent);
86 * As {@link #merge(Persistent)} but with a given template.
87 * This method can be accessed in a static way.
88 * @param aTemplate Hibernate template
89 * @param aPersistent Object to merge.
91 public static void merge(HibernateTemplate aTemplate, Persistent aPersistent) {
92 Persistent merged = (Persistent) aTemplate.merge(
94 processPersistent(aPersistent, merged, new ArrayList<ObjectElem>());
100 * Copies primary keys and version from the result of the merged to the
101 * object that was passed to the merge operation. It does this by traversing
102 * the properties of the object. It copies the primary key and version for
103 * objects that implement {@link Persistent} and applies the same rules to
104 * objects in maps and sets as well (i.e. recursively).
105 * @param aPersistent Object whose primary key and version are to be set.
106 * @param aMerged Object that was the result of the merge.
107 * @param aProcessed List of already processed Persistent objects of the persistent part.
109 public static void processPersistent(Persistent aPersistent,
110 Persistent aMerged, List<ObjectElem> aProcessed) {
111 if ( aPersistent == null && aMerged == null ) {
114 if ( aPersistent == null || aMerged == null ) {
115 throw new RuntimeException("persistent or merged object is null '" + aPersistent + "'" +
116 " '" + aMerged + "'");
118 ObjectElem elem = new ObjectElem(aPersistent);
119 if ( aProcessed.contains(elem)) {
120 return; // already processed.
122 aProcessed.add(elem);
124 LOG.info("Setting pk/version on " + aPersistent + " from " + aMerged);
126 if ( aPersistent.getPrimaryKey() != null &&
127 !aMerged.getPrimaryKey().equals(aPersistent.getPrimaryKey()) ) {
128 LOG.error("Mismatch between primary key values: " + aPersistent + " " + aMerged);
130 aPersistent.setPersistedVersion(aMerged.getPersistedVersion());
131 aPersistent.setPrimaryKey(aMerged.getPrimaryKey());
134 Method[] methods = aPersistent.getClass().getMethods();
135 for (Method getter : methods) {
136 if (getter.getName().startsWith("get")) {
137 Class returnType = getter.getReturnType();
140 if (Set.class.isAssignableFrom(returnType)) {
141 Set merged = (Set) getter.invoke(aMerged);
142 Set persistent = (Set) getter.invoke(aPersistent);
143 processSet(persistent, merged, aProcessed);
144 } else if ( List.class.isAssignableFrom(returnType)) {
145 List merged = (List) getter.invoke(aMerged);
146 List persistent = (List) getter.invoke(aPersistent);
147 processList(persistent, merged, aProcessed);
148 } else if ( Map.class.isAssignableFrom(returnType)) {
149 Map merged = (Map) getter.invoke(aMerged);
150 Map persistent = (Map) getter.invoke(aPersistent);
151 processMap(persistent, merged, aProcessed);
152 } else if ( Persistent.class.isAssignableFrom(returnType)) {
153 Persistent merged = (Persistent) getter.invoke(aMerged);
154 Persistent persistent = (Persistent) getter.invoke(aPersistent);
155 processPersistent(persistent, merged, aProcessed);
156 } else if ( returnType.isArray() &&
157 Persistent.class.isAssignableFrom(returnType.getComponentType())) {
158 Persistent[] merged = (Persistent[]) getter.invoke(aMerged);
159 Persistent[] persistent = (Persistent[]) getter.invoke(aPersistent);
160 for (int i = 0; i < persistent.length; i++) {
161 processPersistent(persistent[i], merged[i], aProcessed);
164 } catch (InvocationTargetException e) {
165 throw new RuntimeException(e.getMessage(), e);
166 } catch (IllegalAccessException e) {
167 throw new RuntimeException(e.getMessage(), e);
175 * Process the persistent objects in the collections.
176 * @param aPersistent Collection in the original object.
177 * @param aMerged Collection as a result of the merge.
178 * @param aProcessed List of processed persistent objects.
180 public static void processList(List aPersistent, List aMerged, List<ObjectElem> aProcessed) {
181 Object[] merged = aMerged.toArray();
182 Object[] persistent = aPersistent.toArray();
183 if (merged.length != persistent.length) {
184 throw new RuntimeException("Array sizes differ " + merged.length
185 + " " + persistent.length);
187 for (int i = 0; i < merged.length; i++) {
188 assert merged[i].equals(persistent[i]);
189 if (merged[i] instanceof Persistent) {
190 processPersistent((Persistent)persistent[i], (Persistent)merged[i], aProcessed);
196 * Process the persistent objects in sets.
197 * @param aPersistent Collection in the original object.
198 * @param aMerged Collection as a result of the merge.
199 * @param aProcessed List of processed persistent objects.
201 public static void processSet(Set aPersistent, Set aMerged, List<ObjectElem> aProcessed) {
202 if (aMerged.size() != aPersistent.size()) {
203 throw new RuntimeException("Array sizes differ " + aMerged.size()
204 + " " + aPersistent.size());
206 for (Object merged: aMerged) {
207 // Find the object that equals the merged[i]
208 for (Object persistent: aPersistent) {
209 if ( persistent.equals(merged)) {
210 processPersistent((Persistent)persistent, (Persistent)merged, aProcessed);
218 * Process the Map objects in the collections.
219 * @param aPersistent Collection in the original object.
220 * @param aMerged Collection as a result of the merge.
221 * @param aProcessed List of processed persistent objects.
223 public static void processMap(Map aPersistent, Map aMerged, List<ObjectElem> aProcessed) {
224 if ( aMerged.size() != aPersistent.size() ) {
225 throw new RuntimeException("Sizes differ " + aMerged.size() + " " + aPersistent.size());
227 Set keys = aMerged.keySet();
228 for (Object key: keys) {
229 if ( !aPersistent.containsKey(key) ) {
230 throw new RuntimeException("Key '" + key + "' not found");
232 Object mergedValue = aMerged.get(key);
233 Object persistentValue = aPersistent.get(key);
234 if ( mergedValue instanceof Persistent ) {
235 if ( persistentValue instanceof Persistent ) {
236 processPersistent((Persistent)persistentValue, (Persistent)mergedValue, aProcessed);
238 throw new RuntimeException("Value in original object is null, whereas merged object contains a value");