Package com.dooapp.gaedo.blueprints

Source Code of com.dooapp.gaedo.blueprints.BluePrintsPersister

package com.dooapp.gaedo.blueprints;

import java.io.Serializable;
import java.lang.reflect.Modifier;
import java.lang.reflect.Proxy;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;

import javax.persistence.CascadeType;
import javax.persistence.FetchType;
import javax.persistence.ManyToMany;
import javax.persistence.OneToMany;

import com.dooapp.gaedo.blueprints.strategies.GraphMappingStrategy;
import com.dooapp.gaedo.finders.id.AnnotationsFinder.Annotations;
import com.dooapp.gaedo.finders.repository.ServiceRepository;
import com.dooapp.gaedo.patterns.WriteReplaceable;
import com.dooapp.gaedo.properties.Property;
import com.dooapp.gaedo.utils.Utils;
import com.tinkerpop.blueprints.Direction;
import com.tinkerpop.blueprints.Edge;
import com.tinkerpop.blueprints.Graph;
import com.tinkerpop.blueprints.IndexableGraph;
import com.tinkerpop.blueprints.Vertex;

public class BluePrintsPersister {
    private static final Logger logger = Logger.getLogger(BluePrintsPersister.class.getName());
    /**
     * Node kind used by this updater
     */
    private Kind nodeKind;

    public BluePrintsPersister(Kind node) {
        nodeKind = node;
    }


    /**
     * Create or update given object
     *
     * @param service             source of modification
     * @param objectVertexId      object expected vertex id
     * @param objectVertex        vertex corresponding to object to update
     * @param valueClass          class of the value to be updated here
     * @param containedProperties list of contained properties
     * @param toUpdate            object to update
     * @param cascade             kind of cascade used for dependent properties
     * @param objectsBeingUpdated map containing subgraph of obejcts currently being updated, this is used to avoid loops, and NOT as a cache
     * @return updated object
     */
    public <DataType> Object performUpdate(AbstractBluePrintsBackedFinderService<? extends Graph, DataType, ?> service, String objectVertexId, Vertex objectVertex, Class<?> valueClass, Map<Property, Collection<CascadeType>> containedProperties, Object toUpdate, CascadeType cascade, Map<String, Object> objectsBeingUpdated) {
        Graph database = service.getDatabase();
        // it's in fact an object creation
        if (objectVertex == null) {
            if (logger.isLoggable(Level.FINER)) {
                logger.log(Level.FINER, "object " + objectVertexId.toString() + " has never before been seen in graph, so create central node for it");
            }
            objectVertex = service.getDriver().createEmptyVertex(valueClass, objectVertexId, toUpdate);
            // Create a value for that node (useful for RDF export)
            service.getDriver().setValue(objectVertex, objectVertexId);
        }
        // Here come the caching !
        DataType updated = (DataType) objectsBeingUpdated.get(objectVertexId);
        if (updated == null) {
            try {
                objectsBeingUpdated.put(objectVertexId, toUpdate);
                updateProperties(service, database, toUpdate, objectVertex, containedProperties, cascade, objectsBeingUpdated);
                return toUpdate;
            } finally {
                objectsBeingUpdated.remove(objectVertexId);
            }
        } else {
            return updated;
        }
    }

    /**
     * Delete given object
     *
     * @param service              source of modification
     * @param objectVertexId       object expected vertex id
     * @param objectVertex         vertex corresponding to object to delete
     * @param valueClass           class contained by service
     * @param containedProperties  list of contained properties
     * @param toDelete             object to delete
     * @param cascade              kind of cascade used for dependent properties
     * @param objectsBeingAccessed map containing subgraph of objects currently being delete, this is used to avoid loops, and NOT as a cache
     */
    public <DataType> void performDelete(AbstractBluePrintsBackedFinderService<? extends Graph, DataType, ?> service, String objectVertexId, Vertex objectVertex, Class<?> valueClass, Map<Property, Collection<CascadeType>> containedProperties, DataType toDelete, CascadeType cascade, Map<String, Object> objectsBeingAccessed) {
        Graph database = service.getDatabase();
        for (Map.Entry<Property, Collection<CascadeType>> entry : containedProperties.entrySet()) {
          Property p = entry.getKey();
            // Static properties are by design not written
            if (!p.hasModifier(Modifier.STATIC) && !Annotations.TRANSIENT.is(p)) {
                // Per default, no operation is cascaded
                CascadeType used = null;
                // However, if property supports that cascade type, we cascade operation
                if (entry.getValue().contains(cascade)) {
                    used = cascade;
                }
                if(used!=null) {
                  Class<?> rawPropertyType = p.getType();
                  Collection<CascadeType> toCascade = containedProperties.get(p);
                  if (Collection.class.isAssignableFrom(rawPropertyType)) {
                      if (logger.isLoggable(Level.FINEST)) {
                          logger.log(Level.FINEST, "property " + p.getName() + " is considered a collection one");
                      }
                      deleteCollection(service, database, p, toDelete, objectVertex, toCascade, objectsBeingAccessed);
                      // each value should be written as an independant value
                  } else if (Map.class.isAssignableFrom(rawPropertyType)) {
                      if (logger.isLoggable(Level.FINEST)) {
                          logger.log(Level.FINEST, "property " + p.getName() + " is considered a map one");
                      }
                      deleteMap(service, database, p, toDelete, objectVertex, toCascade, objectsBeingAccessed);
                  } else {
                      deleteSingle(service, database, p, toDelete, objectVertex, toCascade, objectsBeingAccessed);
                  }
                }
            }
        }
        /* We try to locate vertex in graph before to delete it. Indeed, mainly due cascade delete, this vertex may have already been removed */
        Vertex notYetDeleted = service.getDriver().loadVertexFor(objectVertexId, valueClass.getName());
        if(notYetDeleted!=null)
          GraphUtils.removeSafely(database, notYetDeleted);
    }


    /**
     * Delete value mapped by a single property.
     * Value is deleted only if unchanged during the delete call (that to say value in object is the same that value in graph).
     * @param service
     * @param database
     * @param p
     * @param toDelete
     * @param objectVertex
     * @param toCascade
     * @param objectsBeingAccessed
     */
    private void deleteSingle(AbstractBluePrintsBackedFinderService<? extends Graph, ?, ?> service, Graph database, Property p, Object toDelete, Vertex objectVertex, Collection<CascadeType> toCascade, Map<String, Object> objectsBeingAccessed) {
        // there should be only one vertex to delete
        Iterable<Edge> edges = service.getStrategy().getOutEdgesFor(objectVertex, p);
        for (Edge e : edges) {
            Vertex valueVertex = e.getVertex(Direction.IN);
            GraphUtils.removeSafely(database, e);
            // Now what to do with vertex ? Delete it ?
            if (toCascade.contains(CascadeType.REMOVE)) {
                // yes, delete it forever (but before, see if there aren't more datas to delete
                Object value = p.get(toDelete);
                if(value!=null) {
                  Vertex knownValueVertex = service.getVertexFor(value, CascadeType.REFRESH, objectsBeingAccessed);
                  if(knownValueVertex!=null && knownValueVertex.equals(valueVertex))
                    service.deleteOutEdgeVertex(objectVertex, valueVertex, value, objectsBeingAccessed);
                }

            }
        }
    }

    /**
     * Delete values from a map.
     * Notice we only delete values which are mapped to vertices in graph that the edges corresponding to that property can lead us to.
     * @param service service used to map values to vertices
     * @param database database to remvoe vertices from
     * @param p property from which map is loaded
     * @param toDelete object we want to delete the map from
     * @param objectVertex vertex corresponding to toDelete
     * @param toCascade cascade mode
     * @param objectsBeingAccessed
     */
    private void deleteMap(AbstractBluePrintsBackedFinderService<? extends Graph, ?, ?> service, Graph database, Property p, Object toDelete, Vertex objectVertex, Collection<CascadeType> toCascade, Map<String, Object> objectsBeingAccessed) {
        Iterable<Edge> edges = service.getStrategy().getOutEdgesFor(objectVertex, p);
        Map<?, ?> values = (Map<?, ?>) p.get(toDelete);
        Map<Vertex, Edge> oldVertices = new HashMap<Vertex, Edge>();
        for (Edge e : edges) {
            Vertex inVertex = e.getVertex(Direction.IN);
            oldVertices.put(inVertex, e);
        }
        for (Object v : values.entrySet()) {
            Vertex valueVertex = service.getVertexFor(v, CascadeType.REFRESH, objectsBeingAccessed);
            if (valueVertex!=null && oldVertices.containsKey(valueVertex)) {
                Edge oldEdge = oldVertices.remove(valueVertex);
                GraphUtils.removeSafely(database, oldEdge);
                if (toCascade.contains(CascadeType.REMOVE)) {
                    service.deleteOutEdgeVertex(objectVertex, valueVertex, v, objectsBeingAccessed);
                }
            }
        }
        if (oldVertices.size() > 0) {
            // force deletion of remaining edges
            // BUT assocaited vertices may not be deleted
            for (Edge e : oldVertices.values()) {
                GraphUtils.removeSafely(database, e);
            }
        }
    }

    private void deleteCollection(AbstractBluePrintsBackedFinderService<? extends Graph, ?, ?> service, Graph database, Property p, Object toDelete, Vertex objectVertex, Collection<CascadeType> toCascade, Map<String, Object> objectsBeingAccessed) {
        Iterable<Edge> edges = service.getStrategy().getOutEdgesFor(objectVertex, p);
        Collection<?> values = (Collection<?>) p.get(toDelete);
        Map<Vertex, Edge> oldVertices = new HashMap<Vertex, Edge>();
        for (Edge e : edges) {
            Vertex inVertex = e.getVertex(Direction.IN);
            oldVertices.put(inVertex, e);
        }
        for (Object v : values) {
          // already heard about null-containing collections ? I do know them, and they're pure EVIL
          if(v!=null) {
              Vertex valueVertex = service.getVertexFor(v, CascadeType.REFRESH, objectsBeingAccessed);
              if (valueVertex !=null && oldVertices.containsKey(valueVertex)) {
                  Edge oldEdge = oldVertices.remove(valueVertex);
                  GraphUtils.removeSafely(database, oldEdge);
                  if (toCascade.contains(CascadeType.REMOVE)) {
                      service.deleteOutEdgeVertex(objectVertex, valueVertex, v, objectsBeingAccessed);
                  }
              }
          }
        }
        if (oldVertices.size() > 0) {
            // force deletion of remaining edges
            // BUT assocaited vertices may not be deleted
            for (Edge e : oldVertices.values()) {
                GraphUtils.removeSafely(database, e);
            }
        }
    }

    /**
     * Update all properties of given object
     *
     * @param toUpdate             object to update
     * @param objectVertex         object root vertex
     * @param containedProperties  map linking each object property to the cascade types associated to it (allows us to easily see if there is any cascade to perform on object)
     * @param cascade              cascade type used to perform this operation, depend if this method is called from a {@link #create(Object)} or an {@link #update(Object)}
     * @param objectsBeingAccessed cache of objects being accessed during that write
     */
    private <DataType> void updateProperties(AbstractBluePrintsBackedFinderService<? extends Graph, DataType, ?> service, Graph database, Object toUpdate, Vertex objectVertex, Map<Property, Collection<CascadeType>> containedProperties, CascadeType cascade, Map<String, Object> objectsBeingAccessed) {
        for (Map.Entry<Property, Collection<CascadeType>> entry : containedProperties.entrySet()) {
            Property p = entry.getKey();
            // Static properties are by design not written
            if (!p.hasModifier(Modifier.STATIC) && !Annotations.TRANSIENT.is(p)) {
                // Per default, no operation is cascaded
                CascadeType used = null;
                // However, if property supports that cascade type, we cascade operation
                if (entry.getValue().contains(cascade)) {
                    used = cascade;
                }
                // We only perform operations on cascaded fields
                if(used!=null) {
                  Class<?> rawPropertyType = p.getType();
                  if (Collection.class.isAssignableFrom(rawPropertyType)) {
                      if (logger.isLoggable(Level.FINEST)) {
                          logger.log(Level.FINEST, "property " + p.getName() + " is considered a collection one");
                      }
                      updateCollection(service, database, p, toUpdate, objectVertex, used, objectsBeingAccessed);
                      // each value should be written as an independant value
                  } else if (Map.class.isAssignableFrom(rawPropertyType)) {
                      if (logger.isLoggable(Level.FINEST)) {
                          logger.log(Level.FINEST, "property " + p.getName() + " is considered a map one");
                      }
                      updateMap(service, database, p, toUpdate, objectVertex, used, objectsBeingAccessed);
                  } else {
                      updateSingle(service, database, p, toUpdate, objectVertex, used, objectsBeingAccessed);
                  }
                }
            }
        }
        // Migrator property has been added to object if needed
        // it's also the case of classes list
    }

    /**
     * Persisting a map consist into considering each map entry as an object of the map entries collection, then associating each entry object to its contained key and value.
     * To make this association as easy (and readable as posisble) map entries keys are their keys objects ids (if managed) or values) elsewhere, and values are
     * their values ids (if managed) or values (elsewhere). Notice a link is always made between a map entry and both its key and value.
     *
     * @param p          property containing that map
     * @param toUpdate   map to update
     * @param rootVertex object root vertex
     * @param cascade    used cascade type, can be either {@link CascadeType#PERSIST} or {@link CascadeType#MERGE}
     */
    private <DataType> void updateMap(AbstractBluePrintsBackedFinderService<? extends Graph, DataType, ?> service, Graph database, Property p, Object toUpdate, Vertex rootVertex, CascadeType cascade, Map<String, Object> objectsBeingAccessed) {
        // Cast should work like a charm
        Map<?, ?> value = (Map<?, ?>) p.get(toUpdate);
        // As a convention, null values are never stored
        if (value != null /* && value.size()>0 that case precisely created https://github.com/Riduidel/gaedo/issues/13 */) {
            // Get previously existing vertices
            Iterable<Edge> existingIterator = service.getStrategy().getOutEdgesFor(rootVertex, p);
            // Do not change previously existing vertices if they correspond to new ones
            // Which is done in that call : as vertex is always looked up before creation, there is little duplication risk
            // or at last that risk should be covered by selected Blueprints implementation
            Collection<Vertex> newVertices = createMapVerticesFor(service, value, cascade, objectsBeingAccessed);
            Map<Vertex, Edge> oldVertices = new HashMap<Vertex, Edge>();
            for (Edge e : existingIterator) {
                Vertex inVertex = e.getVertex(Direction.IN);
                if (newVertices.contains(inVertex)) {
                    newVertices.remove(inVertex);
                } else {
                    oldVertices.put(inVertex, e);
                }
            }
            // Now the have been collected, remove all old vertices
            for (Map.Entry<Vertex, Edge> entry : oldVertices.entrySet()) {
                GraphUtils.removeSafely(database, entry.getValue());
                // TODO also remove map entry vertex assocaited edges
            }
            // And finally add new vertices
            for (Vertex newVertex : newVertices) {
                service.getDriver().createEdgeFor(rootVertex, newVertex, p);
            }
        }
    }

    /**
     * Update given collection by creating a set of edges/vertices for each element
     *
     * @param p          properties to update associated vertices for
     * @param toUpdate   source object to update
     * @param rootVertex vertex associated to toUpdate
     * @param cascade    used cascade type, can be either {@link CascadeType#PERSIST} or {@link CascadeType#MERGE}
     * @category update
     */
    private <DataType> void updateCollection(AbstractBluePrintsBackedFinderService<? extends Graph, DataType, ?> service, Graph database, Property p, Object toUpdate, Vertex rootVertex, CascadeType cascade, Map<String, Object> objectsBeingAccessed) {
        // Cast should work like a charm
        Collection<?> value = (Collection<?>) p.get(toUpdate);
        // As a convention, null values are never stored
        if (value != null /* && value.size()>0 that case precisely created https://github.com/Riduidel/gaedo/issues/13 */) {
            // Get previously existing vertices
            Iterable<Edge> existingIterator = service.getStrategy().getOutEdgesFor(rootVertex, p);
            // Do not change previously existing vertices if they correspond to new ones
            // Which is done in that call : as vertex is always looked up before creation, there is little duplication risk
            // or at elast that risk should be covered by selected Blueprints implementation
            Collection<Vertex> newVertices = createCollectionVerticesFor(service, value, cascade, objectsBeingAccessed);
            Map<Vertex, Edge> oldVertices = new HashMap<Vertex, Edge>();
            for (Edge e : existingIterator) {
                Vertex inVertex = e.getVertex(Direction.IN);
                if (newVertices.contains(inVertex)) {
                    newVertices.remove(inVertex);
                } else {
                    oldVertices.put(inVertex, e);
                }
            }
            // Now the have been collected, remove all old vertices
            for (Map.Entry<Vertex, Edge> entry : oldVertices.entrySet()) {
                GraphUtils.removeSafely(database, entry.getValue());
            }
            // And finally add new vertices
            int order = 0;
            for (Vertex newVertex : newVertices) {
                Edge createdEdge = service.getDriver().createEdgeFor(rootVertex, newVertex, p);
                // Add a fancy-schmancy property to maintain order in this town
                // This property is not indexed, as it would have absolutely no meaning to query that index
                createdEdge.setProperty(Properties.collection_index.name(), order++);
            }
        }
    }

    /**
     * Create a collection of vertices for the given collection of values
     *
     * @param value   collection of values to create vertices for
     * @param cascade used cascade type, can be either {@link CascadeType#PERSIST} or {@link CascadeType#MERGE}
     * @return collection of vertices created by {@link #getVertexFor(Object)}
     */
    private Collection<Vertex> createCollectionVerticesFor(AbstractBluePrintsBackedFinderService<? extends Graph, ?, ?> service, Collection<?> value, CascadeType cascade, Map<String, Object> objectsBeingAccessed) {
        Collection<Vertex> returned = new java.util.LinkedList<Vertex>();
        for (Object o : value) {
          // already heard about null-containing collections ? I do know them, and they're pure EVIL
          if(o!=null)
            returned.add(service.getVertexFor(o, cascade, objectsBeingAccessed));
        }
        return returned;
    }

    /**
     * Create a collection of map vertices (each representing one map entry) for each entry of the input map.
     *
     * @param value   map of values to create vertices for
     * @param cascade used cascade type, can be either {@link CascadeType#PERSIST} or {@link CascadeType#MERGE}
     * @return collection of vertices created by {@link #getVertexFor(Object)}
     * @see #getVertexFor(AbstractBluePrintsBackedFinderService<? extends Graph, DataType, ?>, CascadeType, Map) for details about the way to generate a vertex for a Map.Entry node
     */
    private Collection<Vertex> createMapVerticesFor(AbstractBluePrintsBackedFinderService<? extends Graph, ?, ?> service, Map value, CascadeType cascade, Map<String, Object> objectsBeingAccessed) {
        Collection<Vertex> returned = new HashSet<Vertex>();
        // Strangely, the entrySet is not seen as a Set<Entry>
        for (Entry o : (Set<Entry>) value.entrySet()) {
            returned.add(service.getVertexFor(o, cascade, objectsBeingAccessed));
        }
        return returned;
    }

    /**
     * Update single-valued property by changing target of edge used to represent the property
     *
     * @param p          updated property
     * @param toUpdate   updated object
     * @param rootVertex vertex representing the object
     * @param cascade    used cascade type, can be either {@link CascadeType#PERSIST} or {@link CascadeType#MERGE}
     * @category update
     */
    private <DataType> void updateSingle(AbstractBluePrintsBackedFinderService<? extends Graph, DataType, ?> service, Graph database, Property p, Object toUpdate, Vertex rootVertex, CascadeType cascade, Map<String, Object> objectsBeingAccessed) {
        Object value = p.get(toUpdate);
        // As a convention, null values are never stored but they may replace existing ones, in which case previous values must be removed
        // as a consequenc,v alueVertex is loaded only for non null values
        Vertex valueVertex = null;
        if (value != null) {
            valueVertex = service.getVertexFor(value, cascade, objectsBeingAccessed);
        }
        /* Totally crazy confident non-nullity lack of test :
         * this method is only called when cascade type is either PERSIST or MERGE. In both cases the call to getVertexFor will create the vertex if missing.
         * As a consequence there is no need for nullity check.
         */
        Edge link = null;
        // Get previously existing vertex
        Iterator<Edge> existingIterator = service.getStrategy().getOutEdgesFor(rootVertex, p).iterator();
        // property is single-valued, so iteration can be done at most one
        if (existingIterator.hasNext()) {
            // There is an existing edge, change its target and maybe delete previous one
            Edge existing = existingIterator.next();
            if (valueVertex != null && existing.getVertex(Direction.IN).equals(valueVertex)) {
                // Nothing to do
                link = existing;
            } else {
                // delete old edge (if it exists)
              GraphUtils.removeSafely(database, existing);
                if (value != null)
                    link = service.getDriver().createEdgeFor(rootVertex, valueVertex, p);
            }
        }
        if (existingIterator.hasNext()) {
            if (logger.isLoggable(Level.SEVERE)) {
                // There is some incoherent data in graph .. log it !
                StringBuilder sOut = new StringBuilder("An object with the following monovalued property\n").append(p.toGenericString()).append(" is linked to more than one vertex :");
                while (existingIterator.hasNext()) {
                    sOut.append("\n\t").append(existingIterator.next().getVertex(Direction.IN).toString());
                }
                logger.log(Level.SEVERE, "Graph contains some incoherence :" + sOut.toString());
            }
            while (existingIterator.hasNext()) {
              GraphUtils.removeSafely(database, existingIterator.next());
            }
        } else {
            if (link == null && value != null)
                link = service.getDriver().createEdgeFor(rootVertex, valueVertex, p);
        }
    }

    public <DataType> DataType loadObject(AbstractBluePrintsBackedFinderService<? extends Graph, DataType, ?> service, Vertex objectVertex, Map<String, Object> objectsBeingAccessed) {
        String objectVertexId = service.getDriver().getIdOf(objectVertex);
        return loadObject(service, objectVertexId, objectVertex, objectsBeingAccessed);
    }

    /**
     * Load object with given vertex id and vertex node
     *
     * @param objectVertexId
     * @param objectVertex
     * @param objectsBeingAccessed map of objects currently being accessed, it avoid some loops during loading, but is absolutely NOT a persistent cache
     * @return loaded object
     */
    public <DataType> DataType loadObject(AbstractBluePrintsBackedFinderService<? extends Graph, DataType, ?> service, String objectVertexId, Vertex objectVertex, Map<String, Object> objectsBeingAccessed) {
        if (objectsBeingAccessed.containsKey(objectVertexId))
            return (DataType) objectsBeingAccessed.get(objectVertexId);
        // Shortcut
        if (objectVertex == null) {
            objectsBeingAccessed.put(objectVertexId, null);
            return null;
        } else {
            ClassLoader classLoader = service.getContainedClass().getClassLoader();
            ServiceRepository repository = service.getRepository();
            DataType returned = (DataType) GraphUtils.createInstance(service.getDriver(), service.getStrategy(), classLoader, objectVertex, Object.class /* we use object here, as this default type should not be used */, repository, objectsBeingAccessed);
            try {
                if (service.getStrategy().shouldLoadPropertiesOf(objectVertexId, objectVertex, objectsBeingAccessed)) {
                    Map<Property, Collection<CascadeType>> containedProperties = service.getStrategy().getContainedProperties(returned, objectVertex, CascadeType.MERGE);
                    objectsBeingAccessed.put(objectVertexId, returned);
                    loadObjectProperties(service.getDriver(), service.getStrategy(), classLoader, repository, objectVertex, returned, containedProperties, objectsBeingAccessed);
                }
                return returned;
            } finally {
                // make sure loading call is always called, even if something failed during loading
                service.getStrategy().loaded(objectVertexId, objectVertex, returned, objectsBeingAccessed);
//        objectsBeingAccessed.remove(objectVertexId);
            }
        }
    }


    public <DataType> void loadObjectProperties(GraphDatabaseDriver driver, GraphMappingStrategy strategy, ClassLoader classLoader, ServiceRepository repository, Vertex objectVertex,
                                                DataType returned, Map<Property, Collection<CascadeType>> containedProperties, Map<String, Object> objectsBeingAccessed) {
        for (Property p : containedProperties.keySet()) {
            if (!p.hasModifier(Modifier.STATIC) && !Annotations.TRANSIENT.is(p)) {
                Class<?> rawPropertyType = p.getType();
                if (Collection.class.isAssignableFrom(rawPropertyType)) {
                    loadCollection(driver, strategy, classLoader, repository, p, returned, objectVertex, objectsBeingAccessed);
                    // each value should be written as an independant value
                } else if (Map.class.isAssignableFrom(rawPropertyType)) {
                    loadMap(driver, strategy, classLoader, repository, p, returned, objectVertex, objectsBeingAccessed);
                } else {
                    loadSingle(driver, strategy, classLoader, repository, p, returned, objectVertex, objectsBeingAccessed);
                }
            }
        }
    }

    /**
     * Implementation tied to the future implementation of {@link #updateMap(Property, Object, Vertex, CascadeType)}
     *
     * @param strategy     TODO
     * @param p
     * @param returned
     * @param objectVertex
     */
    private <DataType> void loadMap(GraphDatabaseDriver driver, GraphMappingStrategy strategy, ClassLoader classLoader, ServiceRepository repository, Property p, DataType returned, Vertex objectVertex, Map<String, Object> objectsBeingAccessed) {
        boolean eagerLoad = false;
        // property may be associated to a onetomany or manytomany mapping. in such a case, check if there is an eager loading info
        OneToMany oneToMany = p.getAnnotation(OneToMany.class);
        if (oneToMany != null) {
            eagerLoad = FetchType.EAGER.equals(oneToMany.fetch());
        }
        if (!eagerLoad) {
            ManyToMany manyToMany = p.getAnnotation(ManyToMany.class);
            if (manyToMany != null) {
                eagerLoad = FetchType.EAGER.equals(manyToMany.fetch());
            }
        }
        Map<Object, Object> generatedCollection = (Map<Object, Object>) Utils.generateMap((Class<?>) p.getType(), null);
        MapLazyLoader handler = new MapLazyLoader(driver, strategy, classLoader, repository, p, objectVertex, generatedCollection, objectsBeingAccessed);
        if (eagerLoad) {
            handler.loadMap(generatedCollection, objectsBeingAccessed);
            p.set(returned, generatedCollection);
        } else {
            // Java proxy code
            p.set(returned, Proxy.newProxyInstance(
                    classLoader,
                    new Class[]{p.getType(), Serializable.class, WriteReplaceable.class},
                    handler));
        }
    }

    /**
     * Load a single-valued property from graph
     *
     * @param strategy             TODO
     * @param p
     * @param returned
     * @param objectVertex
     * @param objectsBeingAccessed
     */
    private <DataType> void loadSingle(GraphDatabaseDriver driver, GraphMappingStrategy strategy, ClassLoader classloader, ServiceRepository repository, Property p, DataType returned, Vertex objectVertex, Map<String, Object> objectsBeingAccessed) {
        Iterator<Edge> iterator = strategy.getOutEdgesFor(objectVertex, p).iterator();
        if (iterator.hasNext()) {
            // yeah, there is a value !
            Edge edge = iterator.next();
            Vertex firstVertex = edge.getVertex(Direction.IN);
            Object value = GraphUtils.createInstance(driver, strategy, classloader, firstVertex, p.getType(), repository, objectsBeingAccessed);
            if (repository.containsKey(value.getClass())) {
                // value requires fields loading
                AbstractBluePrintsBackedFinderService<IndexableGraph, DataType, ?> blueprints = (AbstractBluePrintsBackedFinderService<IndexableGraph, DataType, ?>) repository.get(value.getClass());
                value = loadObject(blueprints, firstVertex, objectsBeingAccessed);
            }
            p.set(returned, value);
        }
        // TODO test unsupported multi-values
    }


    /**
     * Load collection corresponding to the given property for the given vertex.
     * BEWARE : here be lazy loading !
     *
     * @param strategy     TODO
     * @param p
     * @param returned
     * @param objectVertex
     */
    private <DataType> void loadCollection(
        GraphDatabaseDriver driver,
        GraphMappingStrategy strategy,
        ClassLoader classLoader,
        ServiceRepository repository,
        Property p,
        DataType returned,
        Vertex objectVertex,
        Map<String, Object> objectsBeingAccessed) {
     
      // Figure out whether we want to lazy- or eager-load
     
        boolean eagerLoad = false;
        // property may be associated with a one-to-many or many-to-many mapping. in such a case, check if
        // there is an eager loading info
        OneToMany oneToMany = p.getAnnotation(OneToMany.class);
        if (oneToMany != null) {
            eagerLoad = FetchType.EAGER.equals(oneToMany.fetch());
        }
        if (!eagerLoad) {
            ManyToMany manyToMany = p.getAnnotation(ManyToMany.class);
            if (manyToMany != null) {
                eagerLoad = FetchType.EAGER.equals(manyToMany.fetch());
            }
        }
       
        // Get down to brass tacks
       
        Collection<Object> generatedCollection = Utils.generateCollection((Class<?>) p.getType(), null);
        CollectionLazyLoader handler = new CollectionLazyLoader(driver, strategy, classLoader, repository, p, objectVertex, generatedCollection, objectsBeingAccessed);
        if (eagerLoad) {
            handler.loadCollection(generatedCollection, objectsBeingAccessed);
            p.set(returned, generatedCollection);
        } else {
            // Java proxy code
            p.set(returned, Proxy.newProxyInstance(
                    classLoader,
                    new Class[]{p.getType(), Serializable.class, WriteReplaceable.class},
                    handler));
        }
    }
}
TOP

Related Classes of com.dooapp.gaedo.blueprints.BluePrintsPersister

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.