2 * Copyright 2005-2010 the original author or authors.
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
8 * http://www.apache.org/licenses/LICENSE-2.0
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.
16 package org.wamblee.persistence.hibernate;
18 import java.lang.reflect.InvocationTargetException;
19 import java.lang.reflect.Method;
20 import java.util.ArrayList;
21 import java.util.List;
24 import java.util.Map.Entry;
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;
34 * {@link org.springframework.orm.hibernate.support.HibernateDaoSupport}.
36 * @author Erik Brakkee
38 public class HibernateSupport extends HibernateDaoSupport {
39 private static final Log LOG = LogFactory.getLog(HibernateSupport.class);
42 * Constructs the object.
45 public HibernateSupport() {
50 * Performes a hibernate <code>Session.merge()</code> and updates the object
51 * with the correct primary key and version. This is an extension to the
52 * hibernate merge operation because hibernate itself leaves the object
53 * passed to merge untouched. Use this method with extreme caution since it
54 * will recursively load all objects that the current object has relations
55 * with and for which cascade="merge" was specified in the Hibernate mapping
61 public void merge(Persistent aPersistent) {
62 merge(getHibernateTemplate(), aPersistent);
66 * As {@link #merge(Persistent)} but with a given template. This method can
67 * be accessed in a static way.
74 public static void merge(HibernateTemplate aTemplate, Persistent aPersistent) {
75 Persistent merged = (Persistent) aTemplate.merge(aPersistent);
76 processPersistent(aPersistent, merged, new ArrayList<ObjectElem>());
80 * Copies primary keys and version from the result of the merged to the
81 * object that was passed to the merge operation. It does this by traversing
82 * the properties of the object. It copies the primary key and version for
83 * objects that implement {@link Persistent} and applies the same rules to
84 * objects in maps and sets as well (i.e. recursively).
87 * Object whose primary key and version are to be set.
89 * Object that was the result of the merge.
91 * List of already processed Persistent objects of the persistent
95 public static void processPersistent(Persistent aPersistent,
96 Persistent aMerged, List<ObjectElem> aProcessed) {
97 if ((aPersistent == null) && (aMerged == null)) {
101 if ((aPersistent == null) || (aMerged == null)) {
102 throw new RuntimeException("persistent or merged object is null '" +
103 aPersistent + "'" + " '" + aMerged + "'");
106 ObjectElem elem = new ObjectElem(aPersistent);
108 if (aProcessed.contains(elem)) {
109 return; // already processed.
112 aProcessed.add(elem);
114 LOG.debug("Setting pk/version on " + aPersistent + " from " + aMerged);
116 if ((aPersistent.getPrimaryKey() != null) &&
117 !aMerged.getPrimaryKey().equals(aPersistent.getPrimaryKey())) {
118 LOG.error("Mismatch between primary key values: " + aPersistent +
121 aPersistent.setPersistedVersion(aMerged.getPersistedVersion());
122 aPersistent.setPrimaryKey(aMerged.getPrimaryKey());
125 Method[] methods = aPersistent.getClass().getMethods();
127 for (Method getter : methods) {
128 if (getter.getName().startsWith("get")) {
129 Class returnType = getter.getReturnType();
132 if (Set.class.isAssignableFrom(returnType)) {
133 Set merged = (Set) getter.invoke(aMerged);
134 Set persistent = (Set) getter.invoke(aPersistent);
135 processSet(persistent, merged, aProcessed);
136 } else if (List.class.isAssignableFrom(returnType)) {
137 List merged = (List) getter.invoke(aMerged);
138 List persistent = (List) getter.invoke(aPersistent);
139 processList(persistent, merged, aProcessed);
140 } else if (Map.class.isAssignableFrom(returnType)) {
141 Map merged = (Map) getter.invoke(aMerged);
142 Map persistent = (Map) getter.invoke(aPersistent);
143 processMap(persistent, merged, aProcessed);
144 } else if (Persistent.class.isAssignableFrom(returnType)) {
145 Persistent merged = (Persistent) getter.invoke(aMerged);
146 Persistent persistent = (Persistent) getter
147 .invoke(aPersistent);
148 processPersistent(persistent, merged, aProcessed);
149 } else if (returnType.isArray() &&
150 Persistent.class.isAssignableFrom(returnType
151 .getComponentType())) {
152 Persistent[] merged = (Persistent[]) getter
154 Persistent[] persistent = (Persistent[]) getter
155 .invoke(aPersistent);
157 for (int i = 0; i < persistent.length; i++) {
158 processPersistent(persistent[i], merged[i],
162 } catch (InvocationTargetException e) {
163 throw new RuntimeException(e.getMessage(), e);
164 } catch (IllegalAccessException e) {
165 throw new RuntimeException(e.getMessage(), e);
172 * Process the persistent objects in the collections.
175 * Collection in the original object.
177 * Collection as a result of the merge.
179 * List of processed persistent objects.
182 public static void processList(List aPersistent, List aMerged,
183 List<ObjectElem> aProcessed) {
184 Object[] merged = aMerged.toArray();
185 Object[] persistent = aPersistent.toArray();
187 if (merged.length != persistent.length) {
188 throw new RuntimeException("Array sizes differ " + merged.length +
189 " " + persistent.length);
192 for (int i = 0; i < merged.length; i++) {
193 assert merged[i].equals(persistent[i]);
195 if (merged[i] instanceof Persistent) {
196 processPersistent((Persistent) persistent[i],
197 (Persistent) merged[i], aProcessed);
203 * Process the persistent objects in sets.
206 * Collection in the original object.
208 * Collection as a result of the merge.
210 * List of processed persistent objects.
213 public static void processSet(Set aPersistent, Set aMerged,
214 List<ObjectElem> aProcessed) {
215 if (aMerged.size() != aPersistent.size()) {
216 throw new RuntimeException("Array sizes differ " + aMerged.size() +
217 " " + aPersistent.size());
220 for (Object merged : aMerged) {
221 // Find the object that equals the merged[i]
222 for (Object persistent : aPersistent) {
223 if (persistent.equals(merged)) {
224 processPersistent((Persistent) persistent,
225 (Persistent) merged, aProcessed);
234 * Process the Map objects in the collections.
237 * Collection in the original object.
239 * Collection as a result of the merge.
241 * List of processed persistent objects.
244 public static <Key,Value> void processMap(Map<Key,Value> aPersistent, Map<Key,Value> aMerged,
245 List<ObjectElem> aProcessed) {
246 if (aMerged.size() != aPersistent.size()) {
247 throw new RuntimeException("Sizes differ " + aMerged.size() + " " +
251 Set<Entry<Key,Value>> entries = aMerged.entrySet();
253 for (Entry<Key,Value> entry : entries) {
254 Key key = entry.getKey();
255 if (!aPersistent.containsKey(key)) {
256 throw new RuntimeException("Key '" + key + "' not found");
259 Value mergedValue = entry.getValue();
260 Object persistentValue = aPersistent.get(key);
262 if (mergedValue instanceof Persistent) {
263 if (persistentValue instanceof Persistent) {
264 processPersistent((Persistent) persistentValue,
265 (Persistent) mergedValue, aProcessed);
267 throw new RuntimeException(
268 "Value in original object is null, whereas merged object contains a value");
275 * This class provided an equality operation based on the object reference
276 * of the wrapped object. This is required because we cannto assume that the
277 * equals operation has any meaning for different types of persistent
278 * objects. This allows us to use the standard collection classes for
279 * detecting cyclic dependences and avoiding recursion.
281 private static final class ObjectElem {
282 private Object object;
284 public ObjectElem(Object aObject) {
288 public boolean equals(Object aObj) {
292 if (!(aObj instanceof ObjectElem)) {
295 return ((ObjectElem) aObj).object == object;
298 public int hashCode() {
299 return object.hashCode();