Package org.springmodules.prevayler.system

Source Code of org.springmodules.prevayler.system.DefaultPrevalentSystem

package org.springmodules.prevayler.system;

import edu.emory.mathcs.backport.java.util.concurrent.ConcurrentHashMap;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.Collection;
import java.util.IdentityHashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import org.apache.log4j.Logger;
import org.springmodules.prevayler.support.PrevaylerCascadePersistenceException;
import org.springmodules.prevayler.support.PrevaylerConfigurationException;
import org.springmodules.prevayler.support.PrevaylerDataRetrievalException;
import org.springmodules.prevayler.support.PrevaylerUnsavedObjectException;
import org.springmodules.prevayler.system.callback.SystemCallback;
import org.springmodules.prevayler.id.DefaultIdMerger;
import org.springmodules.prevayler.id.IdMerger;

/**
* <p>{@link PrevalentSystem} implementation based on concurrent hash maps.</p>
* <p>The only mandatory property to set here is the {@link PrevalenceInfo}.</p>
* <p>This class is <b>thread safe</b>.</p>
*
* @author Sergio Bossa
*/
public class DefaultPrevalentSystem implements PrevalentSystem {
   
    private static final long serialVersionUID = 476105268506333743L;
   
    private transient static final Logger logger = Logger.getLogger(DefaultPrevalentSystem.class);
   
    private transient static final ThreadLocal localIdentityMap = new ThreadLocal();
   
    // FIXME: Currently not configurable:
    private IdMerger merger = new DefaultIdMerger();
   
    private PrevalenceInfo prevalenceInfo = new PrevalenceInfo();
   
    private Map entitiesGlobalMap = new ConcurrentHashMap();
   
    public DefaultPrevalentSystem() {
        this.merger.setPrevalenceInfo(this.prevalenceInfo);
    }
   
    public Object save(Object newEntity) {
        this.localIdentityMap.set(new IdentityHashMap());
        this.internalSave(newEntity);
        return newEntity;
    }
   
    public Object update(Object entity) {
        Object id = null;
        try {
            id = this.prevalenceInfo.getIdResolutionStrategy().resolveId(entity).get(entity);
            if (id != null) {
                this.localIdentityMap.set(new IdentityHashMap());
                this.internalUpdate(entity, id);
                return entity;
            } else {
                throw new PrevaylerUnsavedObjectException("Cannot update unsaved object!");
            }
        } catch (IllegalAccessException actual) {
            throw new IllegalStateException("Cannot access id value: " + id, actual);
        }
    }
   
    public void delete(Object entity) {
        Object id = null;
        try {
            Map entityMap = this.lookupMap(entity.getClass());
            id = this.prevalenceInfo.getIdResolutionStrategy().resolveId(entity).get(entity);
            if (id != null) {
                Object old = entityMap.get(id);
                if (old != null) {
                    entityMap.remove(id);
                } else {
                    throw new PrevaylerDataRetrievalException("Cannot find object with id: " + id);
                }
            } else {
                throw new PrevaylerUnsavedObjectException("Cannot delete unsaved object!");
            }
        } catch (IllegalAccessException actual) {
            throw new IllegalStateException("Cannot access id value: " + id, actual);
        }
    }
   
    public void delete(Class entityClass) {
        Map entityMap = this.lookupMap(entityClass);
        entityMap.clear();
    }
   
    public Object get(Class entityClass, Object id) {
        Map entityMap = this.lookupMap(entityClass);
        return entityMap.get(id);
    }
   
    public List get(Class entityClass) {
        Map entityMap = this.lookupMap(entityClass);
        List result = new LinkedList();
        Iterator it = entityMap.values().iterator();
        while (it.hasNext()) {
            Object currentEntity = it.next();
            if (entityClass.isAssignableFrom(currentEntity.getClass())) {
                result.add(currentEntity);
            }
        }
        return result;
    }
   
    public void merge(Object sourceEntity, Object destinationEntity) {
        this.merger.merge(sourceEntity, destinationEntity);
    }
   
    public Object execute(SystemCallback callback) {
        return callback.execute(this);
    }
   
    public void setPrevalenceInfo(PrevalenceInfo info) {
        this.prevalenceInfo = info;
        this.merger.setPrevalenceInfo(this.prevalenceInfo);
    }
   
    /*** Persistence internals ***/
   
    private Object internalSave(Object newEntity) {
        DefaultPrevalentSystem.logger.debug("Saving object: " + newEntity);
        Object id = null;
        try {
            //  Get the identity map by entity class:
            Map entityMap = this.lookupMap(newEntity.getClass());
            // Assign id:
            id = this.prevalenceInfo.getIdGenerationStrategy().generateId();
            this.prevalenceInfo.getIdResolutionStrategy().resolveId(newEntity).set(newEntity, id);
            // Add the entity to the thread local identity map, for tracking its traversal (an entity once traversed must not be tarversed again):
            IdentityHashMap localIdentityMap = (IdentityHashMap) DefaultPrevalentSystem.localIdentityMap.get();
            localIdentityMap.put(newEntity, new Integer(1));
            // Add the new entity to the system:
            entityMap.put(id, newEntity);
            // Self cascade persistence (needed for updating pointers to already persisted entities):
            this.doCascadePersistence(newEntity, newEntity);
            // Return the new, updated and saved, entity:
            return newEntity;
        } catch(IllegalAccessException actual) {
            throw new IllegalStateException("Cannot access id value: " + id, actual);
        }
    }
   
    private Object internalUpdate(Object updatedEntity, Object id) {
        DefaultPrevalentSystem.logger.debug("Updating object " + updatedEntity + " with id: " + id);
        //  Get the identity map by the entity class:
        Map entityMap = this.lookupMap(updatedEntity.getClass());
        // Look for the entity in the map and update it (if found):
        Object currentEntity = entityMap.get(id);
        if ((currentEntity != null) && (currentEntity.getClass().equals(updatedEntity.getClass()))) {
            IdentityHashMap localIdentityMap = (IdentityHashMap) DefaultPrevalentSystem.localIdentityMap.get();
            // If the object has still not been reached:
            if (localIdentityMap.get(currentEntity) == null) {
                // Add the actual entity to the thread local identity map, for tracking its traversal (an entity once traversed must not be tarversed again):
                localIdentityMap.put(currentEntity, new Integer(1));
                // Copy new into actual and do cascade persistence:
                this.doCascadePersistence(updatedEntity, currentEntity);
            }
            // Return the updated (original) entity:
            return currentEntity;
        } else {
            throw new PrevaylerDataRetrievalException("Cannot find object with id: " + id);
        }
    }
   
    private void doCascadePersistence(Object source, Object destination) {
        Class currentClass = source.getClass();
        while (currentClass != null) {
            try {
                Field[] fields = currentClass.getDeclaredFields();
                for (int counter = 0; counter < fields.length; counter++) {
                    // Get the field:
                    Field currentField = fields[counter];
                    currentField.setAccessible(true);
                    // Update the field, from source to destination:
                    this.updateValue(source, destination, currentField);
                }
                currentClass = currentClass.getSuperclass();
            } catch(Exception ex) {
                throw new PrevaylerCascadePersistenceException(ex.getMessage(), ex);
            }
        }
    }
   
    private void updateValue(Object source, Object destination, Field field) throws Exception {
        int modifiers = field.getModifiers();
        if (! Modifier.isFinal(modifiers) && ! Modifier.isStatic(modifiers)) {
            Object value = field.get(source);
            if (value != null) {
                value = this.getUpdatedValue(value);
            }
            field.set(destination, value);
        }
    }
   
    private Object getUpdatedValue(Object value) throws Exception {
        Object result = value;
        if (value.getClass().isArray()) {
            result = this.getUpdatedArray((Object[]) value);
        } else if (Collection.class.isAssignableFrom(value.getClass())) {
            result = this.getUpdatedCollection((Collection) value);
        } else if (Map.class.isAssignableFrom(value.getClass())) {
            result = this.getUpdatedMap((Map) value);
        } else if (this.prevalenceInfo.getPrevalentClass(value.getClass()) != null) {
            result = this.getUpdatedPrevalentObject(value);
        }
        return result;
    }
   
    private Object getUpdatedArray(Object[] array) throws Exception {
        Object[] result = (Object[]) array.clone();
        for (int i = 0; i < array.length; i++) {
            result[i] = this.getUpdatedValue(array[i]);
        }
        return result;
    }
   
    private Collection getUpdatedCollection(Collection collection) throws Exception {
        Collection result = (Collection) collection.getClass().getConstructor(new Class[0]).newInstance(new Object[0]);
        Iterator it = collection.iterator();
        while (it.hasNext()) {
            result.add(this.getUpdatedValue(it.next()));
        }
        return result;
    }
   
    private Map getUpdatedMap(Map map) throws Exception {
        Map result = (Map) map.getClass().getConstructor(new Class[0]).newInstance(new Object[0]);
        Iterator it = map.entrySet().iterator();
        while (it.hasNext()) {
            Map.Entry entry = (Entry) it.next();
            Object key = this.getUpdatedValue(entry.getKey());
            Object value = this.getUpdatedValue(entry.getValue());
            result.put(key, value);
        }
        return result;
    }
   
    private Object getUpdatedPrevalentObject(Object entity) {
        Object result = null;
        Object id = null;
        try {
            Field idField = this.prevalenceInfo.getIdResolutionStrategy().resolveId(entity);
            id = idField.get(entity);
            if (id == null) {
                result = this.internalSave(entity);
            } else {
                result = this.internalUpdate(entity, id);
            }
            return result;
        } catch (IllegalAccessException actual) {
            throw new IllegalStateException("Cannot access id value: " + id, actual);
        }
    }
   
    /** Other internals **/
   
    private Map lookupMap(Class entityClass) {
        Class prevalentClass = this.prevalenceInfo.getPrevalentClass(entityClass);
        if (prevalentClass != null) {
            Map entityMap = (Map) this.entitiesGlobalMap.get(prevalentClass);
            if (entityMap == null) {
                entityMap = new ConcurrentHashMap();
                this.entitiesGlobalMap.put(prevalentClass, entityMap);
            }
            return entityMap;
        } else {
            throw new PrevaylerConfigurationException("Object with no configured prevalent class: " + entityClass);
        }
    }
}
TOP

Related Classes of org.springmodules.prevayler.system.DefaultPrevalentSystem

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.