All Downloads are FREE. Search and download functionalities are using the official Maven repository.

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);
        }
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy