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