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