org.springframework.data.neo4j.fieldaccess.DetachedEntityState.java Source code

Java tutorial

Introduction

Here is the source code for org.springframework.data.neo4j.fieldaccess.DetachedEntityState.java

Source

/**
 * 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.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 Map<Neo4jPersistentProperty, ExistingValue> dirty = new HashMap<Neo4jPersistentProperty, ExistingValue>();
    protected final EntityState<STATE> delegate;
    private final static Log log = LogFactory.getLog(DetachedEntityState.class);
    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) {
        mappingPolicy = mappingPolicy == null ? property.getMappingPolicy() : mappingPolicy;
        if (isDetached()) {
            if (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);
                }
                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) {
        return setValue(property(field), 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()) {
                    addDirty(property, unwrap(delegate.getValue(property, MappingPolicy.MAP_FIELD_DIRECT_POLICY)),
                            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);
    }

    private Object getDefaultValue(final Class<?> type) {
        if (type.isPrimitive()) {
            if (type.equals(boolean.class))
                return false;
            return 0;
        }
        return null;
    }

    @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);
                }
            } finally {
                if (!dirty.isEmpty()) { // restore all dirty data
                    dirty.putAll(dirtyCopy);
                }
            }
        }
    }

    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.mustCheckConcurrentModification()) {
            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 clearDirty() {
        this.dirty.clear();
    }

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

    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.beginTx();
        try {
            Object result = delegate.persist();

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