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.neo4j.graphdb.Transaction;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.data.neo4j.core.EntityState;
import org.springframework.data.neo4j.mapping.MappingPolicy;
import org.springframework.data.neo4j.mapping.Neo4jPersistentEntity;
import org.springframework.data.neo4j.mapping.Neo4jPersistentProperty;
import org.springframework.data.neo4j.support.Neo4jTemplate;
import org.springframework.util.ObjectUtils;

import java.lang.reflect.Field;
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<STATE> implements EntityState<STATE> {
    private final static Logger log = LoggerFactory.getLogger(DetachedEntityState.class);

    private final Map<Neo4jPersistentProperty, ExistingValue> dirty = new HashMap<Neo4jPersistentProperty, ExistingValue>();
    protected final EntityState<STATE> delegate;
    private Neo4jTemplate template;
    private Neo4jPersistentEntity<?> persistentEntity;

    public DetachedEntityState(final EntityState<STATE> delegate, Neo4jTemplate template) {
        this.delegate = delegate;
        this.persistentEntity = delegate.getPersistentEntity();
        this.template = template;
    }

    @Override
    public boolean isWritable(Neo4jPersistentProperty property) {
        return delegate.isWritable(property);
    }

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

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

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

    @Override
    public Neo4jPersistentEntity<?> getPersistentEntity() {
        return persistentEntity;
    }

    @Override
    public Object getValue(Neo4jPersistentProperty property, MappingPolicy mappingPolicy) {
        if (mappingPolicy == null) {
            if (property!=null) mappingPolicy = property.getMappingPolicy();
            else mappingPolicy = MappingPolicy.MAP_FIELD_DIRECT_POLICY;
        }
        if (isDetached()) {
            if (!template.transactionIsRunning() || template.getPersistentState(getEntity())==null || isDirty(property)) {
                if (log.isDebugEnabled()) log.debug("Outside of transaction, GET value from field " + property);
                Object entityValue = getValueFromEntity(property, MappingPolicy.MAP_FIELD_DIRECT_POLICY);
                if (entityValue != null) {
                  return entityValue;
                }

                Object defaultValue = getDefaultValue(property);
                if (defaultValue != null) {
                    final Object entity = getEntity();
                    property.setValue(entity, defaultValue);
                    addDirty(property, defaultValue, false);
                    if (defaultValue instanceof DirtyValue) {
                        ((DirtyValue)defaultValue).setDirty(true);
                    }
                }
                return defaultValue;
            }
        } else {
//            flushDirty();
        }
        return delegate.getValue(property, mappingPolicy);
    }

    @Override
    public Object getValue(final Field field, MappingPolicy mappingPolicy) {
        return getValue(property(field), mappingPolicy);
    }

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

    protected boolean transactionIsRunning() {
        return getTemplate().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, MappingPolicy mappingPolicy) {
        Neo4jPersistentProperty property = property(field);
        if (property != null) {
            return setValue(property, newVal, mappingPolicy);
        }
        return delegate.setValue(property, newVal, mappingPolicy);
    }

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

    @Override
    public Object setValue(final Neo4jPersistentProperty property, final Object newVal, MappingPolicy mappingPolicy) {
        if (isDetached()) {
            if (!isDirty(property) && isWritable(property)) {
                if (hasPersistentState()) {
                    Object valueFromDb = null;
                    if (template.transactionIsRunning()) {
                        valueFromDb = unwrap(delegate.getValue(property, MappingPolicy.MAP_FIELD_DIRECT_POLICY));
                    } else {
                        // For Implicit Transactions, we need to create
                        // a tx to ensure we get the correct previous value
                        // otherwise the possibility of getting a concurrent
                        // modification exception when next persisting may occur
                        try (Transaction tx = template.getGraphDatabaseService().beginTx()) {
                            valueFromDb = unwrap(delegate.getValue(property, MappingPolicy.MAP_FIELD_DIRECT_POLICY));
                            tx.success();
                        }
                    }
                    addDirty(property, valueFromDb, true);
                }
                else {
                    addDirty(property, newVal, false);
                }
            }
            return newVal;
        }
        // flushDirty();
        return delegate.setValue(property, newVal, mappingPolicy);
    }
  @Override
  public Object getDefaultValue(Neo4jPersistentProperty property) {
        return delegate.getDefaultValue(property);
  }

    @SuppressWarnings("deprecation")
    @Override
    public void createAndAssignState() {
        if (template.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 Object 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<Neo4jPersistentProperty, ExistingValue> dirtyCopy = new HashMap<Neo4jPersistentProperty, ExistingValue>(dirty);
            try {
                for (final Map.Entry<Neo4jPersistentProperty, ExistingValue> entry : dirtyCopy.entrySet()) {
                    final Neo4jPersistentProperty property = entry.getKey();
                    Object valueFromEntity = getValueFromEntity(property, MappingPolicy.MAP_FIELD_DIRECT_POLICY);
                    cascadePersist(valueFromEntity);
                    if (log.isDebugEnabled()) log.debug("Flushing dirty Entity new node " + entity + " field " + property+ " with value "+ valueFromEntity);
                    final MappingPolicy mappingPolicy = property.getMappingPolicy();
                    checkConcurrentModification(entity, entry, property, mappingPolicy);
                    delegate.setValue(property, valueFromEntity, mappingPolicy);
                    dirty.remove(property);
                    if (valueFromEntity instanceof DirtyValue) {
                        ((DirtyValue)valueFromEntity).setDirty(false);
                    }
                }
            } finally {
                if (!dirty.isEmpty()) { // restore all dirty data
                    dirty.putAll(dirtyCopy);
                    for (Map.Entry<Neo4jPersistentProperty, ExistingValue> entry : dirtyCopy.entrySet()) {
                        final Neo4jPersistentProperty property = entry.getKey();
                        Object valueFromEntity = getValueFromEntity(property, MappingPolicy.MAP_FIELD_DIRECT_POLICY);
                        if (valueFromEntity instanceof DirtyValue) {
                            ((DirtyValue)valueFromEntity).setDirty(true);
                        }
                    }
                }
            }
        }
    }


    private void cascadePersist(Object valueFromEntity) {
    /* TODO   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 Neo4jPersistentProperty property, MappingPolicy mappingPolicy) {
        final Object entity = getEntity();
        return property.getValue(entity, mappingPolicy);
    }

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

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

    private boolean isDirty(final Neo4jPersistentProperty property) {
        return this.dirty.containsKey(property);
    }

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


    public Neo4jTemplate getTemplate() {
        return template;
    }

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

            flushDirty();
            tx.success();
            return result;
        } catch(Throwable t) {
      tx.failure();
      if (t instanceof Error) throw (Error)t;
      if (t instanceof RuntimeException) throw (RuntimeException)t;
      throw new org.springframework.data.neo4j.core.UncategorizedGraphStoreException("Error persisting entity "+getEntity(),t);
        } finally {
            tx.close();
        }
    }
}
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.