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