Package org.springframework.data.neo4j.fieldaccess

Source Code of org.springframework.data.neo4j.fieldaccess.DetachedEntityState

/**
* Copyright 2011 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.springframework.data.neo4j.fieldaccess;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.neo4j.graphdb.Transaction;
import org.springframework.data.neo4j.core.EntityState;
import org.springframework.data.neo4j.core.GraphBacked;
import org.springframework.data.neo4j.core.NodeBacked;
import org.springframework.data.neo4j.mapping.Neo4JPersistentEntity;
import org.springframework.data.neo4j.mapping.Neo4JPersistentProperty;
import org.springframework.data.neo4j.support.GraphDatabaseContext;
import org.springframework.util.ObjectUtils;

import java.lang.reflect.Field;
import java.util.Collection;
import java.util.ConcurrentModificationException;
import java.util.HashMap;
import java.util.Map;

import static org.springframework.data.neo4j.support.DoReturn.unwrap;

/**
* @author Michael Hunger
* @since 15.09.2010
*/
public class DetachedEntityState<ENTITY extends GraphBacked<STATE>, STATE> implements EntityState<ENTITY,STATE> {
    private final Map<Field, ExistingValue> dirty = new HashMap<Field, ExistingValue>();
    protected final EntityState<ENTITY,STATE> delegate;
    private final static Log log = LogFactory.getLog(DetachedEntityState.class);
    private GraphDatabaseContext graphDatabaseContext;
    private Neo4JPersistentEntity<ENTITY> persistentEntity;

    public DetachedEntityState(final EntityState<ENTITY, STATE> delegate, GraphDatabaseContext graphDatabaseContext) {
        this.delegate = delegate;
        this.persistentEntity = delegate.getPersistentEntity();
        this.graphDatabaseContext = graphDatabaseContext;
    }

    @Override
    public boolean isWritable(final Field field) {
        return delegate.isWritable(field);
    }

    @Override
    public ENTITY getEntity() {
        return delegate.getEntity();
    }

    @Override
    public boolean hasPersistentState() {
        return delegate.hasPersistentState();
    }

    @Override
    public STATE getPersistentState() {
        return delegate.getPersistentState();
    }

    @Override
    public Neo4JPersistentEntity<ENTITY> getPersistentEntity() {
        return persistentEntity;
    }

    @Override
    public Object getValue(final Field field) {
        if (isDetached()) {
            if (getEntity().getPersistentState()==null || isDirty(field)) {
                if (log.isDebugEnabled()) log.debug("Outside of transaction, GET value from field " + field);
                Object entityValue = getValueFromEntity(field);
                if (entityValue != null) {
                  return entityValue;
                }
               
                Object defaultValue = getDefaultImplementation(field);
                if (defaultValue != null) {
                    final ENTITY entity = getEntity();
                    try {
                        field.setAccessible(true);
                        field.set(entity, defaultValue);
                        addDirty(field, defaultValue, false);
                    } catch(IllegalAccessException e) {
                      throw new RuntimeException("Error setting default value for field " + field + " in " + entity.getClass(), e);
                    }
                }
                return defaultValue;
            }
        } else {
//            flushDirty();
        }
        return delegate.getValue(field);
    }

    protected boolean isDetached() {
        return !transactionIsRunning() || !hasPersistentState() || isDirty();
    }

    protected boolean transactionIsRunning() {
        return getGraphDatabaseContext().transactionIsRunning();
    }

    static class ExistingValue {
        public final Object value;
        private final boolean fromGraph;

        ExistingValue(Object value, boolean fromGraph) {
            this.value = value;
            this.fromGraph = fromGraph;
        }

        @Override
        public String toString() {
            return String.format("ExistingValue{value=%s, fromGraph=%s}", value, fromGraph);
        }

        private boolean mustCheckConcurrentModification() {
            return fromGraph;
        }
    }
    @Override
    public Object setValue(final Field field, final Object newVal) {
        return setValue(property(field),newVal);
    }

    private Neo4JPersistentProperty property(Field field) {
        return persistentEntity.getPersistentProperty(field.getName());
    }

    @Override
    public Object setValue(final Neo4JPersistentProperty property, final Object newVal) {
        if (isDetached()) {
            final Field field = property.getField();
            if (!isDirty(field) && isWritable(field)) {
                Object existingValue;
                if (hasPersistentState()) {
                    addDirty(field, unwrap(delegate.getValue(field)), true);
                }
                else {
                    // existingValue = getValueFromEntity(field);
                    // if (existingValue == null) existingValue = getDefaultValue(field.getType());
                    addDirty(field, newVal, false);
                }
            }
            return newVal;
        }
        // flushDirty();
        return delegate.setValue(property, newVal);
    }
  @Override
  public Object getDefaultImplementation(Field field) {
        return delegate.getDefaultImplementation(field);
  }
    private Object getDefaultValue(final Class<?> type) {
        if (type.isPrimitive()) {
            if (type.equals(boolean.class)) return false;
            return 0;
        }
        return null;
    }

    @Override
    public void createAndAssignState() {
        if (graphDatabaseContext.transactionIsRunning()) {
            delegate.createAndAssignState();
        } else {
            log.warn("New Nodebacked created outside of transaction " + delegate.getEntity().getClass());
        }
    }

    /**
     * always runs inside of a transaction
     */
    private void flushDirty() {
        final ENTITY entity = getEntity();
        if (!hasPersistentState()) {
            // createAndAssignState();
            throw new IllegalStateException("Flushing detached entity without a persistent state, this had to be created first.");
        }

        if (isDirty()) {
            final Map<Field, ExistingValue> dirtyCopy = new HashMap<Field, ExistingValue>(dirty);
            clearDirty();
            for (final Map.Entry<Field, ExistingValue> entry : dirtyCopy.entrySet()) {
                final Field field = entry.getKey();
                Object valueFromEntity = getValueFromEntity(field);
                cascadePersist(valueFromEntity);
                if (log.isDebugEnabled()) log.debug("Flushing dirty Entity new node " + entity.getPersistentState() + " field " + field+ " with value "+ valueFromEntity);
                checkConcurrentModification(entity, entry, field);
                delegate.setValue(field, valueFromEntity);
            }
        }
    }

    private void cascadePersist(Object valueFromEntity) {
        if (valueFromEntity instanceof NodeBacked) {
            ((NodeBacked) valueFromEntity).persist();
        }
        if (valueFromEntity instanceof Collection) {
            for (Object o : (Collection<Object>)valueFromEntity) {
                if (o instanceof NodeBacked) {
                    ((NodeBacked) o).persist();
                }
            }
        }
    }

    @Override
    public void setPersistentState(final STATE state) {
        delegate.setPersistentState(state);
    }


    private Object getValueFromEntity(final Field field) {
        final ENTITY entity = getEntity();
        try {
            field.setAccessible(true);
            return field.get(entity);
        } catch (IllegalAccessException e) {
            throw new RuntimeException("Error accessing field " + field + " in " + entity.getClass(), e);
        }
    }

    private void checkConcurrentModification(final ENTITY entity, final Map.Entry<Field, ExistingValue> entry, final Field field) {
        final ExistingValue previousValue = entry.getValue();
        if (previousValue.mustCheckConcurrentModification()) {
            final Object nodeValue = unwrap(delegate.getValue(field));
            if (!ObjectUtils.nullSafeEquals(nodeValue, previousValue.value)) {
                throw new ConcurrentModificationException("Node " + entity.getPersistentState() + " field " + field + " changed in between previous " + previousValue + " current " + nodeValue); // todo or just overwrite
            }
        }
    }

    private boolean isDirty() {
        return !this.dirty.isEmpty();
    }

    private boolean isDirty(final Field f) {
        return this.dirty.containsKey(f);
    }

    private void clearDirty() {
        this.dirty.clear();
    }
   
    private void clearDirty(final Field f) {
        this.dirty.remove(f);
    }

    private void addDirty(final Field f, final Object previousValue, boolean fromGraph) {
        this.dirty.put(f, new ExistingValue(previousValue,fromGraph));
    }


    public GraphDatabaseContext getGraphDatabaseContext() {
        return graphDatabaseContext;
    }

    // todo always create an transaction for persist, atomic operation when no outside tx exists
    @Override
    public ENTITY persist() {
        if (!isDetached()) return getEntity();
        Transaction tx = graphDatabaseContext.beginTx();
        try {
            ENTITY result = delegate.persist();

            flushDirty();
            tx.success();
            return result;
        } finally {
            tx.finish();
        }
    }
}
TOP

Related Classes of org.springframework.data.neo4j.fieldaccess.DetachedEntityState

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.