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