Package com.dooapp.gaedo.blueprints.operations

Source Code of com.dooapp.gaedo.blueprints.operations.Updater$UpdateProperties

package com.dooapp.gaedo.blueprints.operations;

import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.TreeSet;
import java.util.logging.Level;
import java.util.logging.Logger;

import javax.persistence.CascadeType;

import com.dooapp.gaedo.blueprints.AbstractBluePrintsBackedFinderService;
import com.dooapp.gaedo.blueprints.CantCreateAVertexForALiteralException;
import com.dooapp.gaedo.blueprints.GraphDatabaseDriver;
import com.dooapp.gaedo.blueprints.GraphUtils;
import com.dooapp.gaedo.blueprints.ObjectCache;
import com.dooapp.gaedo.blueprints.Properties;
import com.dooapp.gaedo.blueprints.transformers.LiteralHelper;
import com.dooapp.gaedo.blueprints.transformers.Literals;
import com.dooapp.gaedo.properties.AbstractPropertyAdapter;
import com.dooapp.gaedo.properties.Property;
import com.dooapp.gaedo.utils.CollectionUtils;
import com.tinkerpop.blueprints.Direction;
import com.tinkerpop.blueprints.Edge;
import com.tinkerpop.blueprints.Graph;
import com.tinkerpop.blueprints.Vertex;

public class Updater {
  public final class UpdateProperties<DataType> extends AbstractCardinalityDistinguishingOperation {
    private final GraphDatabaseDriver driver;
    private final Graph database;
    private final AbstractBluePrintsBackedFinderService<? extends Graph, DataType, ?> service;
    private final ObjectCache objectsBeingAccessed;
    private final Object toUpdate;
    private final Vertex objectVertex;

    private UpdateProperties(GraphDatabaseDriver driver, Graph database, AbstractBluePrintsBackedFinderService<? extends Graph, DataType, ?> service,
            ObjectCache objectsBeingAccessed, Object toUpdate, Vertex objectVertex) {
      this.driver = driver;
      this.database = database;
      this.service = service;
      this.objectsBeingAccessed = objectsBeingAccessed;
      this.toUpdate = toUpdate;
      this.objectVertex = objectVertex;
    }

    @Override
    protected void operateOnSingle(Property p, CascadeType cascade) {
      updateSingle(service, driver, database, p, toUpdate, objectVertex, cascade, objectsBeingAccessed);
    }

    @Override
    protected void operateOnMap(Property p, CascadeType cascade) {
      updateMap(service, driver, database, p, toUpdate, objectVertex, cascade, objectsBeingAccessed);
    }

    @Override
    protected void operateOnCollection(Property p, CascadeType cascade) {
      updateCollection(service, driver, database, p, toUpdate, objectVertex, cascade, objectsBeingAccessed);
    }
  }

  public static final Boolean ELEMENT_IN_COLLECTION_MARKER = Boolean.TRUE;
  public static final String ELEMENT_IN_COLLECTION_MARKER_GRAPH_VALUE = Literals.get(ELEMENT_IN_COLLECTION_MARKER.getClass()).toString(ELEMENT_IN_COLLECTION_MARKER);

  private static final Logger logger = Logger.getLogger(Updater.class.getName());

  /**
   * Create or update given object
   *
   * @param service
   *            source of modification
   * @param driver
   *            TODO
   * @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, GraphDatabaseDriver driver,
          String objectVertexId, Vertex objectVertex, Class<?> valueClass, Map<Property, Collection<CascadeType>> containedProperties,
          Object toUpdate, CascadeType cascade, ObjectCache 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 = driver.createEmptyVertex(valueClass, objectVertexId, toUpdate);
      // Create a value for that node (useful for RDF export)
      driver.setValue(objectVertex, objectVertexId);
    }
    // Here come the caching !
    DataType updated = (DataType) objectsBeingUpdated.get(objectVertexId);
    if (updated == null) {
      try {
        objectsBeingUpdated.put(objectVertexId, toUpdate);
        updateProperties(service, driver, database, toUpdate, objectVertex, containedProperties, cascade, objectsBeingUpdated);
        return toUpdate;
      } finally {
        objectsBeingUpdated.remove(objectVertexId);
      }
    } else {
      return updated;
    }
  }

  /**
   * Update all properties of given object
   *
   * @param driver
   *            TODO
   * @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(
          final AbstractBluePrintsBackedFinderService<? extends Graph, DataType, ?> service,
          final GraphDatabaseDriver driver,
          final Graph database,
          final Object toUpdate,
          final Vertex objectVertex,
          Map<Property, Collection<CascadeType>> containedProperties,
          CascadeType cascade,
          final ObjectCache objectsBeingAccessed) {
    new OperateOnProperties().execute(containedProperties, cascade, new UpdateProperties(driver, database, service, objectsBeingAccessed, toUpdate, objectVertex));
    // 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 driver
   *            TODO
   * @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, GraphDatabaseDriver driver, Graph database,
          Property p, Object toUpdate, Vertex rootVertex, CascadeType cascade, ObjectCache 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 associated edges
      }
      // And finally add new vertices
      for (Vertex newVertex : newVertices) {
        driver.createEdgeFor(rootVertex, newVertex, p);
      }
    }
  }

  /**
   * Update given collection by creating a set of edges/vertices for each
   * element
   *
   * @param driver
   *            TODO
   * @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, GraphDatabaseDriver driver,
          Graph database, Property p, Object toUpdate, Vertex rootVertex, CascadeType cascade, ObjectCache 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> previousEdges = service.getStrategy().getOutEdgesFor(rootVertex, p);
      // Get the new, updated Collection of vertices (which is already
      // sorted).
      Map<Object, Vertex> allVertices = createCollectionVerticesFor(service, value, cascade, objectsBeingAccessed);
      // Keep track of the edges that correspond to each vertex (for later
      // ordering)...
      Map<Vertex, List<Edge>> savedEdges = new HashMap<Vertex, List<Edge>>();
      // ...and put the old, invalid vertices aside for later deletion.
      Set<Edge> edgesToRemove = new HashSet<Edge>();

      // Add previous edges
      for (Edge e : previousEdges) {
        Vertex inVertex = e.getVertex(Direction.IN);
        if (allVertices.values().contains(inVertex)) {
          if (!savedEdges.containsKey(inVertex))
            savedEdges.put(inVertex, new LinkedList<Edge>());
          savedEdges.get(inVertex).add(e);
        } else {
          edgesToRemove.add(e);
        }
      }
      // And previous properties
      String propertyNamePrefix = GraphUtils.getEdgeNameFor(p);
      Collection<String> suspectProperties = new TreeSet<String>();
      for(String propertyName : rootVertex.getPropertyKeys()) {
        if(propertyName.startsWith(propertyNamePrefix)) {
          suspectProperties.add(propertyName);
        }
      }

      // Delete the edges that we don't need anymore.
      for (Edge edge : edgesToRemove) {
        GraphUtils.removeSafely(database, edge);
      }

      // Then, go through the updated Vertices. Create edges if necessary,
      // then always set the order property.
      // This is possible since #createCollectionVerticesFor maintains the
      // ordering.
      int order = 0;
      for(Object element : value) {
        if(allVertices.containsKey(element)) {
          Vertex vertex = allVertices.get(element);
          Edge edgeForVertex;
          if (savedEdges.containsKey(vertex)) {
            List<Edge> edges = savedEdges.get(vertex);
            edgeForVertex = edges.remove(0);
            if (edges.size() == 0)
              savedEdges.remove(vertex);
          } else
            edgeForVertex = driver.createEdgeFor(rootVertex, vertex, p);

          // Add a fancy-schmancy property to maintain order in this town (this property is NOT indexed)
          edgeForVertex.setProperty(Properties.collection_index.name(), order++);
        } else {
          // Element is a literal value, so it won't require a vertex, but a literal saving
          AbstractPropertyAdapter elementByIndexProperty = new LiteralInCollectionUpdaterProperty(p, order, element);
          updateLiteralPropertyIn(service.getDatabase(), toUpdate, rootVertex, elementByIndexProperty, element);
          suspectProperties.remove(GraphUtils.getEdgeNameFor(elementByIndexProperty));
          // We also add an inverted property allowing fast query of containing collection
          // Value here is not signifiant : we only want to mark collection as containing value. And for that, one simple string is enough
          AbstractPropertyAdapter elementByValueProperty = new LiteralInCollectionUpdaterProperty(p, element, ELEMENT_IN_COLLECTION_MARKER);
          updateLiteralPropertyIn(service.getDatabase(), toUpdate, rootVertex, elementByValueProperty, ELEMENT_IN_COLLECTION_MARKER);
          suspectProperties.remove(GraphUtils.getEdgeNameFor(elementByValueProperty));
          order++;
        }
      }

      // Finally, delete any remaining edges.
      for (Vertex vertex : savedEdges.keySet())
        for (Edge edge : savedEdges.get(vertex))
          GraphUtils.removeSafely(database, edge);
      // and remaining suspect properties
      for(String suspect : suspectProperties) {
        rootVertex.removeProperty(suspect);
      }
      // and don't forget to write collection size property, for easier querying
      CollectionSizeProperty sizeProperty = new CollectionSizeProperty(p);
      updateLiteralPropertyIn(service.getDatabase(), toUpdate, rootVertex, sizeProperty, value.size());
    }
  }

  /**
   * 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 map linking objects to vertices created by {@link #getVertexFor(Object)}.
   *         order of vertices is guaranteed to be the same as input value
   *         one.
   */
  private Map<Object, Vertex> createCollectionVerticesFor(AbstractBluePrintsBackedFinderService<? extends Graph, ?, ?> service, Collection<?> value,
          CascadeType cascade, ObjectCache objectsBeingAccessed) {
    Map<Object, Vertex> returned = new LinkedHashMap<Object, Vertex>();
    for (Object o : value) {
      // already heard about null-containing collections ? I do know them,
      // and they're pure EVIL
      if (o != null) {
        try {
          returned.put(o, service.getVertexFor(o, cascade, objectsBeingAccessed));
        } catch(CantCreateAVertexForALiteralException e) {
          // Literal objects have no vertices created for them. They
          // will be stored as "special" property values, which may
          // put a mess in many code blocks
        }
      }
    }
    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,
          ObjectCache objectsBeingAccessed) {
    Collection<Vertex> returned = new HashSet<Vertex>();
    // Strangely, the entrySet is not seen as a Set<Entry>
    for (Entry o : (Set<Entry>) value.entrySet()) {
      Vertex newVertex = service.getVertexFor(o, cascade, objectsBeingAccessed);
      returned.add(newVertex);
    }
    return returned;
  }

  /**
   * Update single-valued property by changing target of edge used to
   * represent the property
   *
   * @param driver
   *            TODO
   * @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, GraphDatabaseDriver driver,
          Graph database, Property p, Object toUpdate, Vertex rootVertex, CascadeType cascade, ObjectCache 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 consequence, valueVertex is loaded only for non null values
    Vertex valueVertex = null;
    if (value != null) {
      if (Literals.containsKey(value.getClass())) {
        updateLiteralPropertyIn(service.getDatabase(), toUpdate, rootVertex, p, value);
        return;
      } else {
        valueVertex = service.getVertexFor(value, cascade, objectsBeingAccessed);
      }
    }
    /*
     * If vertex has a property named from the property name, remove it : it
     * is due to a previous call to update with a literal non null property
     * value, which may no more be the case if property type is ... Object
     * (which is a bad idea)
     */
    if (rootVertex.getPropertyKeys().contains(GraphUtils.getEdgeNameFor(p))) {
      rootVertex.removeProperty(GraphUtils.getEdgeNameFor(p));
    }
    /*
     * 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
    List<Edge> matching = CollectionUtils.asList(service.getStrategy().getOutEdgesFor(rootVertex, p));
    // property is single-valued, so iteration can be done at most one
    if (matching.size() == 1) {
      // There is an existing edge, change its target and maybe delete
      // previous one
      Edge existing = matching.get(0);
      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 = driver.createEdgeFor(rootVertex, valueVertex, p);
      }
    } else if (matching.size() > 1) {
      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 :");
        for (Edge e : matching) {
          sOut.append("\n\t").append(e.getVertex(Direction.IN).toString());
        }
        logger.log(Level.SEVERE, "Graph contains some incoherence :" + sOut.toString());
      }
      // absolutly all edges are removed, including the first one. As a
      // consequence, initial edge will have to be re-created
      for (Edge e : matching) {
        GraphUtils.removeSafely(database, e);
      }
    }
    if (link == null && value != null)
      link = driver.createEdgeFor(rootVertex, valueVertex, p);
  }

  public void updateLiteralPropertyIn(Graph database, Object toUpdate, Vertex vertexToUpdate, Property property, Object propertyValue) {
    Class propertyClass = property.getType();
    GraphUtils.setIndexedProperty(database, vertexToUpdate, GraphUtils.getEdgeNameFor(property),
            LiteralHelper.getLiteralTextFor(propertyClass, propertyValue));
  }
}
TOP

Related Classes of com.dooapp.gaedo.blueprints.operations.Updater$UpdateProperties

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.