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