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;
25 import java.util.logging.Logger;
27 import org.springframework.orm.hibernate3.HibernateTemplate;
28 import org.springframework.orm.hibernate3.support.HibernateDaoSupport;
29 import org.wamblee.persistence.Persistent;
33 * {@link org.springframework.orm.hibernate.support.HibernateDaoSupport}.
35 * @author Erik Brakkee
37 public class HibernateSupport extends HibernateDaoSupport {
38 private static final Logger LOG = Logger.getLogger(HibernateSupport.class.getName());
41 * Constructs the object.
44 public HibernateSupport() {
49 * Performes a hibernate <code>Session.merge()</code> and updates the object
50 * with the correct primary key and version. This is an extension to the
51 * hibernate merge operation because hibernate itself leaves the object
52 * passed to merge untouched. Use this method with extreme caution since it
53 * will recursively load all objects that the current object has relations
54 * with and for which cascade="merge" was specified in the Hibernate mapping
60 public void merge(Persistent aPersistent) {
61 merge(getHibernateTemplate(), aPersistent);
65 * As {@link #merge(Persistent)} but with a given template. This method can
66 * be accessed in a static way.
73 public static void merge(HibernateTemplate aTemplate, Persistent aPersistent) {
74 Persistent merged = (Persistent) aTemplate.merge(aPersistent);
75 processPersistent(aPersistent, merged, new ArrayList<ObjectElem>());
79 * Copies primary keys and version from the result of the merged to the
80 * object that was passed to the merge operation. It does this by traversing
81 * the properties of the object. It copies the primary key and version for
82 * objects that implement {@link Persistent} and applies the same rules to
83 * objects in maps and sets as well (i.e. recursively).
86 * Object whose primary key and version are to be set.
88 * Object that was the result of the merge.
90 * List of already processed Persistent objects of the persistent
94 public static void processPersistent(Persistent aPersistent,
95 Persistent aMerged, List<ObjectElem> aProcessed) {
96 if ((aPersistent == null) && (aMerged == null)) {
100 if ((aPersistent == null) || (aMerged == null)) {
101 throw new RuntimeException("persistent or merged object is null '" +
102 aPersistent + "'" + " '" + aMerged + "'");
105 ObjectElem elem = new ObjectElem(aPersistent);
107 if (aProcessed.contains(elem)) {
108 return; // already processed.
111 aProcessed.add(elem);
113 LOG.fine("Setting pk/version on " + aPersistent + " from " + aMerged);
115 if ((aPersistent.getPrimaryKey() != null) &&
116 !aMerged.getPrimaryKey().equals(aPersistent.getPrimaryKey())) {
117 LOG.warning("Mismatch between primary key values: " + aPersistent +
120 aPersistent.setPersistedVersion(aMerged.getPersistedVersion());
121 aPersistent.setPrimaryKey(aMerged.getPrimaryKey());
124 Method[] methods = aPersistent.getClass().getMethods();
126 for (Method getter : methods) {
127 if (getter.getName().startsWith("get")) {
128 Class returnType = getter.getReturnType();
131 if (Set.class.isAssignableFrom(returnType)) {
132 Set merged = (Set) getter.invoke(aMerged);
133 Set persistent = (Set) getter.invoke(aPersistent);
134 processSet(persistent, merged, aProcessed);
135 } else if (List.class.isAssignableFrom(returnType)) {
136 List merged = (List) getter.invoke(aMerged);
137 List persistent = (List) getter.invoke(aPersistent);
138 processList(persistent, merged, aProcessed);
139 } else if (Map.class.isAssignableFrom(returnType)) {
140 Map merged = (Map) getter.invoke(aMerged);
141 Map persistent = (Map) getter.invoke(aPersistent);
142 processMap(persistent, merged, aProcessed);
143 } else if (Persistent.class.isAssignableFrom(returnType)) {
144 Persistent merged = (Persistent) getter.invoke(aMerged);
145 Persistent persistent = (Persistent) getter
146 .invoke(aPersistent);
147 processPersistent(persistent, merged, aProcessed);
148 } else if (returnType.isArray() &&
149 Persistent.class.isAssignableFrom(returnType
150 .getComponentType())) {
151 Persistent[] merged = (Persistent[]) getter
153 Persistent[] persistent = (Persistent[]) getter
154 .invoke(aPersistent);
156 for (int i = 0; i < persistent.length; i++) {
157 processPersistent(persistent[i], merged[i],
161 } catch (InvocationTargetException e) {
162 throw new RuntimeException(e.getMessage(), e);
163 } catch (IllegalAccessException e) {
164 throw new RuntimeException(e.getMessage(), e);
171 * Process the persistent objects in the collections.
174 * Collection in the original object.
176 * Collection as a result of the merge.
178 * List of processed persistent objects.
181 public static void processList(List aPersistent, List aMerged,
182 List<ObjectElem> aProcessed) {
183 Object[] merged = aMerged.toArray();
184 Object[] persistent = aPersistent.toArray();
186 if (merged.length != persistent.length) {
187 throw new RuntimeException("Array sizes differ " + merged.length +
188 " " + persistent.length);
191 for (int i = 0; i < merged.length; i++) {
192 assert merged[i].equals(persistent[i]);
194 if (merged[i] instanceof Persistent) {
195 processPersistent((Persistent) persistent[i],
196 (Persistent) merged[i], aProcessed);
202 * Process the persistent objects in sets.
205 * Collection in the original object.
207 * Collection as a result of the merge.
209 * List of processed persistent objects.
212 public static void processSet(Set aPersistent, Set aMerged,
213 List<ObjectElem> aProcessed) {
214 if (aMerged.size() != aPersistent.size()) {
215 throw new RuntimeException("Array sizes differ " + aMerged.size() +
216 " " + aPersistent.size());
219 for (Object merged : aMerged) {
220 // Find the object that equals the merged[i]
221 for (Object persistent : aPersistent) {
222 if (persistent.equals(merged)) {
223 processPersistent((Persistent) persistent,
224 (Persistent) merged, aProcessed);
233 * Process the Map objects in the collections.
236 * Collection in the original object.
238 * Collection as a result of the merge.
240 * List of processed persistent objects.
243 public static <Key,Value> void processMap(Map<Key,Value> aPersistent, Map<Key,Value> aMerged,
244 List<ObjectElem> aProcessed) {
245 if (aMerged.size() != aPersistent.size()) {
246 throw new RuntimeException("Sizes differ " + aMerged.size() + " " +
250 Set<Entry<Key,Value>> entries = aMerged.entrySet();
252 for (Entry<Key,Value> entry : entries) {
253 Key key = entry.getKey();
254 if (!aPersistent.containsKey(key)) {
255 throw new RuntimeException("Key '" + key + "' not found");
258 Value mergedValue = entry.getValue();
259 Object persistentValue = aPersistent.get(key);
261 if (mergedValue instanceof Persistent) {
262 if (persistentValue instanceof Persistent) {
263 processPersistent((Persistent) persistentValue,
264 (Persistent) mergedValue, aProcessed);
266 throw new RuntimeException(
267 "Value in original object is null, whereas merged object contains a value");
274 * This class provided an equality operation based on the object reference
275 * of the wrapped object. This is required because we cannto assume that the
276 * equals operation has any meaning for different types of persistent
277 * objects. This allows us to use the standard collection classes for
278 * detecting cyclic dependences and avoiding recursion.
280 private static final class ObjectElem {
281 private Object object;
283 public ObjectElem(Object aObject) {
287 public boolean equals(Object aObj) {
291 if (!(aObj instanceof ObjectElem)) {
294 return ((ObjectElem) aObj).object == object;
297 public int hashCode() {
298 return object.hashCode();