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.
17 package org.wamblee.persistence.hibernate;
19 import java.lang.reflect.InvocationTargetException;
20 import java.lang.reflect.Method;
21 import java.util.ArrayList;
22 import java.util.List;
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 {
40 private static final Log LOG = LogFactory.getLog(HibernateSupport.class);
43 * This class provided an equality operation based on the object reference
44 * of the wrapped object. This is required because we cannto assume that the
45 * equals operation has any meaning for different types of persistent
46 * objects. This allows us to use the standard collection classes for
47 * detecting cyclic dependences and avoiding recursion.
50 private static final class ObjectElem {
51 private Object _object;
53 public ObjectElem(Object aObject) {
57 public boolean equals(Object aObj) {
58 return ((ObjectElem) aObj)._object == _object;
61 public int hashCode() {
62 return _object.hashCode();
67 * Constructs the object.
70 public HibernateSupport() {
75 * Performes a hibernate <code>Session.merge()</code> and updates the
76 * object with the correct primary key and version. This is an extension to
77 * the hibernate merge operation because hibernate itself leaves the object
78 * passed to merge untouched.
80 * Use this method with extreme caution since it will recursively load all
81 * objects that the current object has relations with and for which
82 * cascade="merge" was specified in the Hibernate mapping file.
87 public void merge(Persistent aPersistent) {
88 merge(getHibernateTemplate(), aPersistent);
92 * As {@link #merge(Persistent)} but with a given template. This method can
93 * be accessed in a static way.
100 public static void merge(HibernateTemplate aTemplate, Persistent aPersistent) {
101 Persistent merged = (Persistent) aTemplate.merge(aPersistent);
102 processPersistent(aPersistent, merged, new ArrayList<ObjectElem>());
106 * Copies primary keys and version from the result of the merged to the
107 * object that was passed to the merge operation. It does this by traversing
108 * the properties of the object. It copies the primary key and version for
109 * objects that implement {@link Persistent} and applies the same rules to
110 * objects in maps and sets as well (i.e. recursively).
113 * Object whose primary key and version are to be set.
115 * Object that was the result of the merge.
117 * List of already processed Persistent objects of the persistent
120 public static void processPersistent(Persistent aPersistent,
121 Persistent aMerged, List<ObjectElem> aProcessed) {
122 if (aPersistent == null && aMerged == null) {
125 if (aPersistent == null || aMerged == null) {
126 throw new RuntimeException("persistent or merged object is null '"
127 + aPersistent + "'" + " '" + aMerged + "'");
129 ObjectElem elem = new ObjectElem(aPersistent);
130 if (aProcessed.contains(elem)) {
131 return; // already processed.
133 aProcessed.add(elem);
135 LOG.debug("Setting pk/version on " + aPersistent + " from " + aMerged);
137 if (aPersistent.getPrimaryKey() != null
138 && !aMerged.getPrimaryKey().equals(aPersistent.getPrimaryKey())) {
139 LOG.error("Mismatch between primary key values: " + aPersistent
142 aPersistent.setPersistedVersion(aMerged.getPersistedVersion());
143 aPersistent.setPrimaryKey(aMerged.getPrimaryKey());
146 Method[] methods = aPersistent.getClass().getMethods();
147 for (Method getter : methods) {
148 if (getter.getName().startsWith("get")) {
149 Class returnType = getter.getReturnType();
152 if (Set.class.isAssignableFrom(returnType)) {
153 Set merged = (Set) getter.invoke(aMerged);
154 Set persistent = (Set) getter.invoke(aPersistent);
155 processSet(persistent, merged, aProcessed);
156 } else if (List.class.isAssignableFrom(returnType)) {
157 List merged = (List) getter.invoke(aMerged);
158 List persistent = (List) getter.invoke(aPersistent);
159 processList(persistent, merged, aProcessed);
160 } else if (Map.class.isAssignableFrom(returnType)) {
161 Map merged = (Map) getter.invoke(aMerged);
162 Map persistent = (Map) getter.invoke(aPersistent);
163 processMap(persistent, merged, aProcessed);
164 } else if (Persistent.class.isAssignableFrom(returnType)) {
165 Persistent merged = (Persistent) getter.invoke(aMerged);
166 Persistent persistent = (Persistent) getter
167 .invoke(aPersistent);
168 processPersistent(persistent, merged, aProcessed);
169 } else if (returnType.isArray()
170 && Persistent.class.isAssignableFrom(returnType
171 .getComponentType())) {
172 Persistent[] merged = (Persistent[]) getter
174 Persistent[] persistent = (Persistent[]) getter
175 .invoke(aPersistent);
176 for (int i = 0; i < persistent.length; i++) {
177 processPersistent(persistent[i], merged[i],
181 } catch (InvocationTargetException e) {
182 throw new RuntimeException(e.getMessage(), e);
183 } catch (IllegalAccessException e) {
184 throw new RuntimeException(e.getMessage(), e);
192 * Process the persistent objects in the collections.
195 * Collection in the original object.
197 * Collection as a result of the merge.
199 * List of processed persistent objects.
201 public static void processList(List aPersistent, List aMerged,
202 List<ObjectElem> aProcessed) {
203 Object[] merged = aMerged.toArray();
204 Object[] persistent = aPersistent.toArray();
205 if (merged.length != persistent.length) {
206 throw new RuntimeException("Array sizes differ " + merged.length
207 + " " + persistent.length);
209 for (int i = 0; i < merged.length; i++) {
210 assert merged[i].equals(persistent[i]);
211 if (merged[i] instanceof Persistent) {
212 processPersistent((Persistent) persistent[i],
213 (Persistent) merged[i], aProcessed);
219 * Process the persistent objects in sets.
222 * Collection in the original object.
224 * Collection as a result of the merge.
226 * List of processed persistent objects.
228 public static void processSet(Set aPersistent, Set aMerged,
229 List<ObjectElem> aProcessed) {
230 if (aMerged.size() != aPersistent.size()) {
231 throw new RuntimeException("Array sizes differ " + aMerged.size()
232 + " " + aPersistent.size());
234 for (Object merged : aMerged) {
235 // Find the object that equals the merged[i]
236 for (Object persistent : aPersistent) {
237 if (persistent.equals(merged)) {
238 processPersistent((Persistent) persistent,
239 (Persistent) merged, aProcessed);
247 * Process the Map objects in the collections.
250 * Collection in the original object.
252 * Collection as a result of the merge.
254 * List of processed persistent objects.
256 public static void processMap(Map aPersistent, Map aMerged,
257 List<ObjectElem> aProcessed) {
258 if (aMerged.size() != aPersistent.size()) {
259 throw new RuntimeException("Sizes differ " + aMerged.size() + " "
260 + aPersistent.size());
262 Set keys = aMerged.keySet();
263 for (Object key : keys) {
264 if (!aPersistent.containsKey(key)) {
265 throw new RuntimeException("Key '" + key + "' not found");
267 Object mergedValue = aMerged.get(key);
268 Object persistentValue = aPersistent.get(key);
269 if (mergedValue instanceof Persistent) {
270 if (persistentValue instanceof Persistent) {
271 processPersistent((Persistent) persistentValue,
272 (Persistent) mergedValue, aProcessed);
274 throw new RuntimeException(
275 "Value in original object is null, whereas merged object contains a value");