52dbc352b742a8ea2c0552b4b454dccc2b164bfd
[utils] / support / spring / src / main / java / org / wamblee / persistence / hibernate / HibernateSupport.java
1 /*
2  * Copyright 2005-2010 the original author or authors.
3  * 
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
7  * 
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  * 
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.
15  */
16 package org.wamblee.persistence.hibernate;
17
18 import java.lang.reflect.InvocationTargetException;
19 import java.lang.reflect.Method;
20 import java.util.ArrayList;
21 import java.util.List;
22 import java.util.Map;
23 import java.util.Set;
24 import java.util.Map.Entry;
25
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;
31
32 /**
33  * Extension of
34  * {@link org.springframework.orm.hibernate.support.HibernateDaoSupport}.
35  * 
36  * @author Erik Brakkee
37  */
38 public class HibernateSupport extends HibernateDaoSupport {
39     private static final Log LOG = LogFactory.getLog(HibernateSupport.class);
40
41     /**
42      * Constructs the object.
43      * 
44      */
45     public HibernateSupport() {
46         // Empty
47     }
48
49     /**
50      * Performes a hibernate <code>Session.merge()</code> and updates the object
51      * with the correct primary key and version. This is an extension to the
52      * hibernate merge operation because hibernate itself leaves the object
53      * passed to merge untouched. Use this method with extreme caution since it
54      * will recursively load all objects that the current object has relations
55      * with and for which cascade="merge" was specified in the Hibernate mapping
56      * file.
57      * 
58      * @param aPersistent
59      *            Object to merge.
60      */
61     public void merge(Persistent aPersistent) {
62         merge(getHibernateTemplate(), aPersistent);
63     }
64
65     /**
66      * As {@link #merge(Persistent)} but with a given template. This method can
67      * be accessed in a static way.
68      * 
69      * @param aTemplate
70      *            Hibernate template
71      * @param aPersistent
72      *            Object to merge.
73      */
74     public static void merge(HibernateTemplate aTemplate, Persistent aPersistent) {
75         Persistent merged = (Persistent) aTemplate.merge(aPersistent);
76         processPersistent(aPersistent, merged, new ArrayList<ObjectElem>());
77     }
78
79     /**
80      * Copies primary keys and version from the result of the merged to the
81      * object that was passed to the merge operation. It does this by traversing
82      * the properties of the object. It copies the primary key and version for
83      * objects that implement {@link Persistent} and applies the same rules to
84      * objects in maps and sets as well (i.e. recursively).
85      * 
86      * @param aPersistent
87      *            Object whose primary key and version are to be set.
88      * @param aMerged
89      *            Object that was the result of the merge.
90      * @param aProcessed
91      *            List of already processed Persistent objects of the persistent
92      *            part.
93      * 
94      */
95     public static void processPersistent(Persistent aPersistent,
96         Persistent aMerged, List<ObjectElem> aProcessed) {
97         if ((aPersistent == null) && (aMerged == null)) {
98             return;
99         }
100
101         if ((aPersistent == null) || (aMerged == null)) {
102             throw new RuntimeException("persistent or merged object is null '" +
103                 aPersistent + "'" + "  '" + aMerged + "'");
104         }
105
106         ObjectElem elem = new ObjectElem(aPersistent);
107
108         if (aProcessed.contains(elem)) {
109             return; // already processed.
110         }
111
112         aProcessed.add(elem);
113
114         LOG.debug("Setting pk/version on " + aPersistent + " from " + aMerged);
115
116         if ((aPersistent.getPrimaryKey() != null) &&
117             !aMerged.getPrimaryKey().equals(aPersistent.getPrimaryKey())) {
118             LOG.error("Mismatch between primary key values: " + aPersistent +
119                 " " + aMerged);
120         } else {
121             aPersistent.setPersistedVersion(aMerged.getPersistedVersion());
122             aPersistent.setPrimaryKey(aMerged.getPrimaryKey());
123         }
124
125         Method[] methods = aPersistent.getClass().getMethods();
126
127         for (Method getter : methods) {
128             if (getter.getName().startsWith("get")) {
129                 Class returnType = getter.getReturnType();
130
131                 try {
132                     if (Set.class.isAssignableFrom(returnType)) {
133                         Set merged = (Set) getter.invoke(aMerged);
134                         Set persistent = (Set) getter.invoke(aPersistent);
135                         processSet(persistent, merged, aProcessed);
136                     } else if (List.class.isAssignableFrom(returnType)) {
137                         List merged = (List) getter.invoke(aMerged);
138                         List persistent = (List) getter.invoke(aPersistent);
139                         processList(persistent, merged, aProcessed);
140                     } else if (Map.class.isAssignableFrom(returnType)) {
141                         Map merged = (Map) getter.invoke(aMerged);
142                         Map persistent = (Map) getter.invoke(aPersistent);
143                         processMap(persistent, merged, aProcessed);
144                     } else if (Persistent.class.isAssignableFrom(returnType)) {
145                         Persistent merged = (Persistent) getter.invoke(aMerged);
146                         Persistent persistent = (Persistent) getter
147                             .invoke(aPersistent);
148                         processPersistent(persistent, merged, aProcessed);
149                     } else if (returnType.isArray() &&
150                         Persistent.class.isAssignableFrom(returnType
151                             .getComponentType())) {
152                         Persistent[] merged = (Persistent[]) getter
153                             .invoke(aMerged);
154                         Persistent[] persistent = (Persistent[]) getter
155                             .invoke(aPersistent);
156
157                         for (int i = 0; i < persistent.length; i++) {
158                             processPersistent(persistent[i], merged[i],
159                                 aProcessed);
160                         }
161                     }
162                 } catch (InvocationTargetException e) {
163                     throw new RuntimeException(e.getMessage(), e);
164                 } catch (IllegalAccessException e) {
165                     throw new RuntimeException(e.getMessage(), e);
166                 }
167             }
168         }
169     }
170
171     /**
172      * Process the persistent objects in the collections.
173      * 
174      * @param aPersistent
175      *            Collection in the original object.
176      * @param aMerged
177      *            Collection as a result of the merge.
178      * @param aProcessed
179      *            List of processed persistent objects.
180      * 
181      */
182     public static void processList(List aPersistent, List aMerged,
183         List<ObjectElem> aProcessed) {
184         Object[] merged = aMerged.toArray();
185         Object[] persistent = aPersistent.toArray();
186
187         if (merged.length != persistent.length) {
188             throw new RuntimeException("Array sizes differ " + merged.length +
189                 " " + persistent.length);
190         }
191
192         for (int i = 0; i < merged.length; i++) {
193             assert merged[i].equals(persistent[i]);
194
195             if (merged[i] instanceof Persistent) {
196                 processPersistent((Persistent) persistent[i],
197                     (Persistent) merged[i], aProcessed);
198             }
199         }
200     }
201
202     /**
203      * Process the persistent objects in sets.
204      * 
205      * @param aPersistent
206      *            Collection in the original object.
207      * @param aMerged
208      *            Collection as a result of the merge.
209      * @param aProcessed
210      *            List of processed persistent objects.
211      * 
212      */
213     public static void processSet(Set aPersistent, Set aMerged,
214         List<ObjectElem> aProcessed) {
215         if (aMerged.size() != aPersistent.size()) {
216             throw new RuntimeException("Array sizes differ " + aMerged.size() +
217                 " " + aPersistent.size());
218         }
219
220         for (Object merged : aMerged) {
221             // Find the object that equals the merged[i]
222             for (Object persistent : aPersistent) {
223                 if (persistent.equals(merged)) {
224                     processPersistent((Persistent) persistent,
225                         (Persistent) merged, aProcessed);
226
227                     break;
228                 }
229             }
230         }
231     }
232
233     /**
234      * Process the Map objects in the collections.
235      * 
236      * @param aPersistent
237      *            Collection in the original object.
238      * @param aMerged
239      *            Collection as a result of the merge.
240      * @param aProcessed
241      *            List of processed persistent objects.
242      * 
243      */
244     public static <Key,Value> void processMap(Map<Key,Value> aPersistent, Map<Key,Value> aMerged,
245         List<ObjectElem> aProcessed) {
246         if (aMerged.size() != aPersistent.size()) {
247             throw new RuntimeException("Sizes differ " + aMerged.size() + " " +
248                 aPersistent.size());
249         }
250
251         Set<Entry<Key,Value>> entries = aMerged.entrySet();
252
253         for (Entry<Key,Value> entry : entries) {
254             Key key = entry.getKey();
255             if (!aPersistent.containsKey(key)) {
256                 throw new RuntimeException("Key '" + key + "' not found");
257             }
258
259             Value mergedValue = entry.getValue();
260             Object persistentValue = aPersistent.get(key);
261
262             if (mergedValue instanceof Persistent) {
263                 if (persistentValue instanceof Persistent) {
264                     processPersistent((Persistent) persistentValue,
265                         (Persistent) mergedValue, aProcessed);
266                 } else {
267                     throw new RuntimeException(
268                         "Value in original object is null, whereas merged object contains a value");
269                 }
270             }
271         }
272     }
273
274     /**
275      * This class provided an equality operation based on the object reference
276      * of the wrapped object. This is required because we cannto assume that the
277      * equals operation has any meaning for different types of persistent
278      * objects. This allows us to use the standard collection classes for
279      * detecting cyclic dependences and avoiding recursion.
280      */
281     private static final class ObjectElem {
282         private Object object;
283
284         public ObjectElem(Object aObject) {
285             object = aObject;
286         }
287
288         public boolean equals(Object aObj) {
289             if (aObj == null) {
290                 return false;
291             }
292             if (!(aObj instanceof ObjectElem)) {
293                 return false;
294             }
295             return ((ObjectElem) aObj).object == object;
296         }
297
298         public int hashCode() {
299             return object.hashCode();
300         }
301     }
302 }