2 * Copyright 2005 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 org.apache.commons.logging.Log;
19 import org.apache.commons.logging.LogFactory;
21 import org.springframework.orm.hibernate3.HibernateTemplate;
22 import org.springframework.orm.hibernate3.support.HibernateDaoSupport;
24 import org.wamblee.persistence.Persistent;
26 import java.lang.reflect.InvocationTargetException;
27 import java.lang.reflect.Method;
29 import java.util.ArrayList;
30 import java.util.List;
37 * org.springframework.orm.hibernate.support.HibernateDaoSupport}.
39 * @author Erik Brakkee
41 public class HibernateSupport extends HibernateDaoSupport {
45 private static final Log LOG = LogFactory.getLog(HibernateSupport.class);
48 * Constructs the object.
51 public HibernateSupport() {
56 * Performes a hibernate <code>Session.merge()</code> and updates
57 * the object with the correct primary key and version. This is an
58 * extension to the hibernate merge operation because hibernate itself
59 * leaves the object passed to merge untouched. Use this method with
60 * extreme caution since it will recursively load all objects that the
61 * current object has relations with and for which cascade="merge" was
62 * specified in the Hibernate mapping file.
64 * @param aPersistent Object to merge.
66 public void merge(Persistent aPersistent) {
67 merge(getHibernateTemplate(), aPersistent);
71 * As {@link #merge(Persistent)} but with a given template. This
72 * method can be accessed in a static way.
74 * @param aTemplate Hibernate template
75 * @param aPersistent Object to merge.
77 public static void merge(HibernateTemplate aTemplate, Persistent aPersistent) {
78 Persistent merged = (Persistent) aTemplate.merge(aPersistent);
79 processPersistent(aPersistent, merged, new ArrayList<ObjectElem>());
83 * Copies primary keys and version from the result of the merged to
84 * the object that was passed to the merge operation. It does this by
85 * traversing the properties of the object. It copies the primary key and
86 * version for objects that implement {@link Persistent} and applies the
87 * same rules to objects in maps and sets as well (i.e. recursively).
89 * @param aPersistent Object whose primary key and version are to be set.
90 * @param aMerged Object that was the result of the merge.
91 * @param aProcessed List of already processed Persistent objects of the
94 * @throws RuntimeException DOCUMENT ME!
96 public static void processPersistent(Persistent aPersistent,
97 Persistent aMerged, List<ObjectElem> aProcessed) {
98 if ((aPersistent == null) && (aMerged == null)) {
102 if ((aPersistent == null) || (aMerged == null)) {
103 throw new RuntimeException("persistent or merged object is null '"
104 + aPersistent + "'" + " '" + aMerged + "'");
107 ObjectElem elem = new ObjectElem(aPersistent);
109 if (aProcessed.contains(elem)) {
110 return; // already processed.
113 aProcessed.add(elem);
115 LOG.debug("Setting pk/version on " + aPersistent + " from " + aMerged);
117 if ((aPersistent.getPrimaryKey() != null)
118 && !aMerged.getPrimaryKey().equals(aPersistent.getPrimaryKey())) {
119 LOG.error("Mismatch between primary key values: " + aPersistent
122 aPersistent.setPersistedVersion(aMerged.getPersistedVersion());
123 aPersistent.setPrimaryKey(aMerged.getPrimaryKey());
126 Method[] methods = aPersistent.getClass().getMethods();
128 for (Method getter : methods) {
129 if (getter.getName().startsWith("get")) {
130 Class returnType = getter.getReturnType();
133 if (Set.class.isAssignableFrom(returnType)) {
134 Set merged = (Set) getter.invoke(aMerged);
135 Set persistent = (Set) getter.invoke(aPersistent);
136 processSet(persistent, merged, aProcessed);
137 } else if (List.class.isAssignableFrom(returnType)) {
138 List merged = (List) getter.invoke(aMerged);
139 List persistent = (List) getter.invoke(aPersistent);
140 processList(persistent, merged, aProcessed);
141 } else if (Map.class.isAssignableFrom(returnType)) {
142 Map merged = (Map) getter.invoke(aMerged);
143 Map persistent = (Map) getter.invoke(aPersistent);
144 processMap(persistent, merged, aProcessed);
145 } else if (Persistent.class.isAssignableFrom(returnType)) {
146 Persistent merged = (Persistent) getter.invoke(aMerged);
147 Persistent persistent = (Persistent) getter.invoke(aPersistent);
148 processPersistent(persistent, merged, aProcessed);
149 } else if (returnType.isArray()
150 && Persistent.class.isAssignableFrom(
151 returnType.getComponentType())) {
152 Persistent[] merged = (Persistent[]) getter.invoke(aMerged);
153 Persistent[] persistent = (Persistent[]) getter.invoke(aPersistent);
155 for (int i = 0; i < persistent.length; i++) {
156 processPersistent(persistent[i], merged[i],
160 } catch (InvocationTargetException e) {
161 throw new RuntimeException(e.getMessage(), e);
162 } catch (IllegalAccessException e) {
163 throw new RuntimeException(e.getMessage(), e);
170 * Process the persistent objects in the collections.
172 * @param aPersistent Collection in the original object.
173 * @param aMerged Collection as a result of the merge.
174 * @param aProcessed List of processed persistent objects.
176 * @throws RuntimeException DOCUMENT ME!
178 public static void processList(List aPersistent, List aMerged,
179 List<ObjectElem> aProcessed) {
180 Object[] merged = aMerged.toArray();
181 Object[] persistent = aPersistent.toArray();
183 if (merged.length != persistent.length) {
184 throw new RuntimeException("Array sizes differ " + merged.length
185 + " " + persistent.length);
188 for (int i = 0; i < merged.length; i++) {
189 assert merged[i].equals(persistent[i]);
191 if (merged[i] instanceof Persistent) {
192 processPersistent((Persistent) persistent[i],
193 (Persistent) merged[i], aProcessed);
199 * Process the persistent objects in sets.
201 * @param aPersistent Collection in the original object.
202 * @param aMerged Collection as a result of the merge.
203 * @param aProcessed List of processed persistent objects.
205 * @throws RuntimeException DOCUMENT ME!
207 public static void processSet(Set aPersistent, Set aMerged,
208 List<ObjectElem> aProcessed) {
209 if (aMerged.size() != aPersistent.size()) {
210 throw new RuntimeException("Array sizes differ " + aMerged.size()
211 + " " + aPersistent.size());
214 for (Object merged : aMerged) {
215 // Find the object that equals the merged[i]
216 for (Object persistent : aPersistent) {
217 if (persistent.equals(merged)) {
218 processPersistent((Persistent) persistent,
219 (Persistent) merged, aProcessed);
228 * Process the Map objects in the collections.
230 * @param aPersistent Collection in the original object.
231 * @param aMerged Collection as a result of the merge.
232 * @param aProcessed List of processed persistent objects.
234 * @throws RuntimeException DOCUMENT ME!
236 public static void processMap(Map aPersistent, Map aMerged,
237 List<ObjectElem> aProcessed) {
238 if (aMerged.size() != aPersistent.size()) {
239 throw new RuntimeException("Sizes differ " + aMerged.size() + " "
240 + aPersistent.size());
243 Set keys = aMerged.keySet();
245 for (Object key : keys) {
246 if (!aPersistent.containsKey(key)) {
247 throw new RuntimeException("Key '" + key + "' not found");
250 Object mergedValue = aMerged.get(key);
251 Object persistentValue = aPersistent.get(key);
253 if (mergedValue instanceof Persistent) {
254 if (persistentValue instanceof Persistent) {
255 processPersistent((Persistent) persistentValue,
256 (Persistent) mergedValue, aProcessed);
258 throw new RuntimeException(
259 "Value in original object is null, whereas merged object contains a value");
266 * This class provided an equality operation based on the object
267 * reference of the wrapped object. This is required because we cannto
268 * assume that the equals operation has any meaning for different types of
269 * persistent objects. This allows us to use the standard collection
270 * classes for detecting cyclic dependences and avoiding recursion.
272 private static final class ObjectElem {
273 private Object object;
275 public ObjectElem(Object aObject) {
279 public boolean equals(Object aObj) {
280 return ((ObjectElem) aObj).object == object;
283 public int hashCode() {
284 return object.hashCode();