org.wamblee.persistence.JpaMergeSupport Maven / Gradle / Ivy
The newest version!
/*
* Copyright 2005-2010 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.wamblee.persistence;
import java.io.Serializable;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.Map.Entry;
import java.util.logging.Logger;
import javax.persistence.EntityManager;
import org.wamblee.general.ObjectElem;
import org.wamblee.reflection.ReflectionUtils;
/**
* Support for merging of JPA entities. This utility allows the result of a
* merge (modifications of primary key and/or version) to be merged back into
* the argument that was merged. As a result, the merged entity can be reused
* and the application is not forced to use the new version that was returned
* from the merge.
*
* The utility traverses the object graph based on the public getter methods.
* Therefore, care should be taken with this utility as usage could lead to
* recursively loading all objects reachable from the given object. Then again,
* this utility is for working with detached objects and it would, in general,
* be bad practice to work with detached objects that still contain unresolved
* lazy loaded relations and with detached objects that implicitly refer to
* almost the entire datamodel.
*
* This utility best supports a service oriented design where interaction is
* through service interfaces where each service has its own storage isolated
* from other services. That would be opposed to a shared data design with many
* services acting on the same data.
*
* @author Erik Brakkee
*/
public class JpaMergeSupport {
private static final Logger LOG = Logger.getLogger(JpaMergeSupport.class
.getName());
/**
* Constructs the object.
*
*/
public JpaMergeSupport() {
// Empty
}
/**
* As {@link #merge(Persistent)} but with a given template. This method can
* be accessed in a static way.
*
* @param aMerge
* The result of the call to {@link EntityManager#merge(Object)}.
* @param aPersistent
* Object that was passed to {@link EntityManager#merge(Object)}.
*/
public static void merge(Object aMerged, Object aPersistent) {
processPersistent(aMerged, aPersistent, new ArrayList());
}
/**
* Copies primary keys and version from the result of the merged to the
* object that was passed to the merge operation. It does this by traversing
* the public properties of the object. It copies the primary key and
* version for objects that implement {@link Persistent} and applies the
* same rules to objects in maps and sets as well (i.e. recursively).
*
* @param aPersistent
* Object whose primary key and version are to be set.
* @param aMerged
* Object that was the result of the merge.
* @param aProcessed
* List of already processed Persistent objects of the persistent
* part.
*
*/
public static void processPersistent(Object aMerged, Object aPersistent,
List aProcessed) {
if ((aPersistent == null) && (aMerged == null)) {
return;
}
if ((aPersistent == null) || (aMerged == null)) {
throw new RuntimeException("persistent or merged object is null '" +
aPersistent + "'" + " '" + aMerged + "'");
}
ObjectElem elem = new ObjectElem(aPersistent);
if (aProcessed.contains(elem)) {
return; // already processed.
}
aProcessed.add(elem);
LOG.fine("Setting pk/version on " + aPersistent + " from " + aMerged);
Persistent persistentWrapper = PersistentFactory.create(aPersistent);
Persistent mergedWrapper = PersistentFactory.create(aMerged);
if (persistentWrapper == null) {
// Not an entity so it is ignored.
return;
}
Serializable pk = persistentWrapper.getPrimaryKey();
boolean pkIsNull = false;
if (pk instanceof Number) {
if (((Number) pk).longValue() != 0l) {
pkIsNull = false;
} else {
pkIsNull = true;
}
} else {
pkIsNull = (pk == null);
}
if (!pkIsNull &&
!mergedWrapper.getPrimaryKey().equals(
persistentWrapper.getPrimaryKey())) {
throw new IllegalArgumentException(
"Mismatch between primary key values: " + aPersistent + " " +
aMerged);
}
persistentWrapper.setPersistedVersion(mergedWrapper
.getPersistedVersion());
persistentWrapper.setPrimaryKey(mergedWrapper.getPrimaryKey());
List methods = ReflectionUtils.getAllMethods(aPersistent
.getClass(), Object.class);
for (Method getter : methods) {
if ((getter.getName().startsWith("get") || getter.getName()
.startsWith("is")) &&
!Modifier.isStatic(getter.getModifiers()) &&
Modifier.isPublic(getter.getModifiers()) &&
getter.getParameterTypes().length == 0 &&
getter.getReturnType() != Void.class) {
Class returnType = getter.getReturnType();
try {
if (Set.class.isAssignableFrom(returnType)) {
Set merged = (Set) getter.invoke(aMerged);
Set persistent = (Set) getter.invoke(aPersistent);
processSet(merged, persistent, aProcessed);
} else if (List.class.isAssignableFrom(returnType)) {
List merged = (List) getter.invoke(aMerged);
List persistent = (List) getter.invoke(aPersistent);
processList(merged, persistent, aProcessed);
} else if (Map.class.isAssignableFrom(returnType)) {
Map merged = (Map) getter.invoke(aMerged);
Map persistent = (Map) getter.invoke(aPersistent);
processMap(merged, persistent, aProcessed);
} else if (returnType.isArray()) {
Object[] merged = (Object[]) getter.invoke(aMerged);
Object[] persistent = (Object[]) getter
.invoke(aPersistent);
if (merged.length != persistent.length) {
throw new IllegalArgumentException(
"Array sizes differ " + merged.length + " " +
persistent.length);
}
for (int i = 0; i < persistent.length; i++) {
processPersistent(merged[i], persistent[i],
aProcessed);
}
} else {
Object merged = getter.invoke(aMerged);
Object persistent = getter.invoke(aPersistent);
processPersistent(merged, persistent, aProcessed);
}
} catch (InvocationTargetException e) {
throw new RuntimeException(e.getMessage(), e);
} catch (IllegalAccessException e) {
throw new RuntimeException(e.getMessage(), e);
}
}
}
}
/**
* Process the persistent objects in the collections.
*
* @param aPersistent
* Collection in the original object.
* @param aMerged
* Collection as a result of the merge.
* @param aProcessed
* List of processed persistent objects.
*
*/
public static void processList(List aMerged, List aPersistent,
List aProcessed) {
Object[] merged = aMerged.toArray();
Object[] persistent = aPersistent.toArray();
if (merged.length != persistent.length) {
throw new IllegalArgumentException("Array sizes differ " +
merged.length + " " + persistent.length);
}
for (int i = 0; i < merged.length; i++) {
assert merged[i].equals(persistent[i]);
processPersistent(merged[i], persistent[i], aProcessed);
}
}
/**
* Process the persistent objects in sets.
*
* @param aPersistent
* Collection in the original object.
* @param aMerged
* Collection as a result of the merge.
* @param aProcessed
* List of processed persistent objects.
*
*/
public static void processSet(Set aMerged, Set aPersistent,
List aProcessed) {
if (aMerged.size() != aPersistent.size()) {
throw new IllegalArgumentException("Array sizes differ " +
aMerged.size() + " " + aPersistent.size());
}
for (Object merged : aMerged) {
// Find the object that equals the merged[i]
for (Object persistent : aPersistent) {
if (persistent.equals(merged)) {
processPersistent(merged, persistent, aProcessed);
break;
}
}
}
}
/**
* Process the Map objects in the collections.
*
* @param aPersistent
* Collection in the original object.
* @param aMerged
* Collection as a result of the merge.
* @param aProcessed
* List of processed persistent objects.
*
*/
public static void processMap(Map aMerged,
Map aPersistent, List aProcessed) {
if (aMerged.size() != aPersistent.size()) {
throw new IllegalArgumentException("Sizes differ " +
aMerged.size() + " " + aPersistent.size());
}
Set> entries = aMerged.entrySet();
for (Entry entry : entries) {
Key key = entry.getKey();
if (!aPersistent.containsKey(key)) {
throw new IllegalArgumentException("Key '" + key +
"' not found");
}
Value mergedValue = entry.getValue();
Object persistentValue = aPersistent.get(key);
processPersistent(mergedValue, persistentValue, aProcessed);
}
}
}