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