Package com.dooapp.gaedo.blueprints

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

package com.dooapp.gaedo.blueprints;

import java.io.Serializable;
import java.lang.ref.WeakReference;
import java.lang.reflect.Modifier;
import java.lang.reflect.Proxy;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
import java.util.UUID;
import java.util.WeakHashMap;
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.ManyToOne;
import javax.persistence.OneToMany;
import javax.persistence.OneToOne;

import com.dooapp.gaedo.blueprints.transformers.Literals;
import com.dooapp.gaedo.extensions.migrable.Migrator;
import com.dooapp.gaedo.extensions.migrable.VersionMigratorFactory;
import com.dooapp.gaedo.finders.FinderCrudService;
import com.dooapp.gaedo.finders.Informer;
import com.dooapp.gaedo.finders.QueryBuilder;
import com.dooapp.gaedo.finders.QueryExpression;
import com.dooapp.gaedo.finders.QueryStatement;
import com.dooapp.gaedo.finders.expressions.Expressions;
import com.dooapp.gaedo.finders.id.AnnotationUtils;
import com.dooapp.gaedo.finders.id.AnnotationsFinder.Annotations;
import com.dooapp.gaedo.finders.id.IdBasedService;
import com.dooapp.gaedo.finders.repository.ServiceRepository;
import com.dooapp.gaedo.finders.root.AbstractFinderService;
import com.dooapp.gaedo.finders.root.InformerFactory;
import com.dooapp.gaedo.patterns.WriteReplaceable;
import com.dooapp.gaedo.properties.ClassCollectionProperty;
import com.dooapp.gaedo.properties.Property;
import com.dooapp.gaedo.properties.PropertyProvider;
import com.dooapp.gaedo.properties.PropertyProviderUtils;
import com.dooapp.gaedo.utils.Utils;
import com.tinkerpop.blueprints.pgm.Edge;
import com.tinkerpop.blueprints.pgm.IndexableGraph;
import com.tinkerpop.blueprints.pgm.Vertex;

/**
* Standard blueprints backed implementation of FinderService
*
* Notice we maintain {@link AbstractCooperantFinderService} infos about objects being accessed as String containing, in fact, vertex ids
* @author ndx
*
*/
public class BluePrintsBackedFinderService <DataType, InformerType extends Informer<DataType>>
  extends AbstractFinderService<DataType, InformerType>
  implements FinderCrudService<DataType, InformerType>, IdBasedService<DataType>{
 
  private static final Logger logger = Logger.getLogger(BluePrintsBackedFinderService.class.getName());
 
  /**
   * Graph used as database
   */
  private IndexableGraph database;
  /**
   * Property used to store id
   */
  private Property idProperty;

  /**
   * Accelerator cache linking classes objects to the collection of properties and cascade informations associated to
   * persist those fields.
   */
  protected Map<Class<?>, Map<Property, Collection<CascadeType>>> classes = new HashMap<Class<?>, Map<Property, Collection<CascadeType>>>();

  /**
   * Property provider indicating what, and how, saving infos from object
   */
  protected PropertyProvider propertyProvider;

  /**
   * Migrator for given contained class
   */
  protected Migrator migrator;

  /**
   * Get access to the service repository to handle links between objects
   */
  protected final ServiceRepository repository;
 
  public BluePrintsBackedFinderService(Class<DataType> containedClass, Class<InformerType> informerClass, InformerFactory factory, ServiceRepository repository,
          PropertyProvider provider, IndexableGraph graph) {
    super(containedClass, informerClass, factory);
    this.repository = repository;
    this.propertyProvider = provider;
    this.database = graph;
    this.idProperty = AnnotationUtils.locateIdField(provider, containedClass, Long.TYPE, Long.class, String.class);
    this.migrator = VersionMigratorFactory.create(containedClass);
    // if there is a migrator, generate property from it
    if (logger.isLoggable(Level.FINE)) {
      logger.log(Level.FINE, "created graph service handling "+containedClass.getCanonicalName()+"\n" +
          "using as id "+idProperty+"\n" +
          "supporting migration ? "+(migrator!=null)+"\n");
    }
  }
 
  /**
   * Get map linking properties to their respective cascading informations
   * @param provider used provider
   * @param searchedClass searched class
   * @return a map linking each property to all its cascading informations
   */
  public Map<Property, Collection<CascadeType>> getPropertiesFor(PropertyProvider provider, Class<?> searchedClass) {
    Map<Property, Collection<CascadeType>> returned = new HashMap<Property, Collection<CascadeType>>();
    Property[] properties = PropertyProviderUtils.getAllProperties(provider, searchedClass);
    for(Property p : properties) {
      if(p.getAnnotation(OneToOne.class)!=null) {
        returned.put(p, extractCascadeOf(p.getAnnotation(OneToOne.class).cascade()));
      } else if(p.getAnnotation(OneToMany.class)!=null) {
        returned.put(p, extractCascadeOf(p.getAnnotation(OneToMany.class).cascade()));
      } else if(p.getAnnotation(ManyToMany.class)!=null) {
        returned.put(p, extractCascadeOf(p.getAnnotation(ManyToMany.class).cascade()));
      } else if(p.getAnnotation(ManyToOne.class)!=null) {
        returned.put(p, extractCascadeOf(p.getAnnotation(ManyToOne.class).cascade()));
      } else {
        returned.put(p, new LinkedList<CascadeType>());
      }
    }
    // And, if class is the contained one, add the (potential) Migrator property
    if(this.migrator!=null) {
      // Migrator has no cascade to be done on
      returned.put(migrator.getMigratorProperty(returned.keySet()), new LinkedList<CascadeType>());
    }
    // Finally, create a fake "classesCollection" property and add it to property
    try {
      returned.put(new ClassCollectionProperty(containedClass), new LinkedList<CascadeType>());
    } catch (Exception e) {
      logger.log(Level.SEVERE, "what ? a class without a \"class\" field ? WTF", e);
    }
    return returned;
  }

  private Collection<CascadeType> extractCascadeOf(CascadeType[] cascade) {
    Set<CascadeType> returned = new HashSet<CascadeType>();
    returned.addAll(Arrays.asList(cascade));
    if(returned.contains(CascadeType.ALL)) {
      returned.remove(CascadeType.ALL);
      returned.add(CascadeType.MERGE);
      returned.add(CascadeType.PERSIST);
      returned.add(CascadeType.REFRESH);
      returned.add(CascadeType.REMOVE);
    }
    return returned;
  }

  /**
   * To put object in graph, we have to find all its fields, then put them in graph elements
   * @param toCreate
   * @return
   * @see com.dooapp.gaedo.AbstractCrudService#create(java.lang.Object)
   */
  @Override
  public DataType create(DataType toCreate) {
    return doUpdate(toCreate, CascadeType.PERSIST, new TreeMap<String, Object>());
  }

  /**
   * Delete id and all edges
   * @param toDelete
   * @see com.dooapp.gaedo.AbstractCrudService#delete(java.lang.Object)
   */
  @Override
  public void delete(DataType toDelete) {
    if(toDelete!=null) {
      doDelete(toDelete, new TreeMap<String, Object>());
    }
  }

  /**
   * Local delete implementation
   * @param toDelete
   */
  private void doDelete(DataType toDelete, Map<String, Object> objectsBeingAccessed) {
    String vertexId = getIdVertexId(toDelete, idProperty);
    Vertex objectVertex = GraphUtils.locateVertex(database, Properties.vertexId.name(), vertexId);
    if(objectVertex!=null) {
      Map<Property, Collection<CascadeType>> containedProperties = getContainedProperties(toDelete);
      for(Property p : containedProperties.keySet()) {
        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(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(p, toDelete, objectVertex, toCascade, objectsBeingAccessed);
        } else {
          deleteSingle(p, toDelete, objectVertex, toCascade, objectsBeingAccessed);
        }
      }
      // What to do with incoming edges ?
      database.removeVertex(objectVertex);
    }
  }

  private void deleteSingle(Property p, DataType toDelete, Vertex objectVertex, Collection<CascadeType> toCascade, Map<String, Object> objectsBeingAccessed) {
    // there should be only one vertex to delete
    String edgeNameFor = GraphUtils.getEdgeNameFor(p);
    Iterable<Edge> edges = objectVertex.getOutEdges(edgeNameFor);
    if (logger.isLoggable(Level.FINEST)) {
      logger.log(Level.FINEST, "deleting edge "+edgeNameFor+" of "+objectVertex.getProperty(Properties.vertexId.name()));
    }
    for(Edge e : edges) {
      Vertex valueVertex = e.getInVertex();
      database.removeEdge(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
        deleteOutEdgeVertex(objectVertex, valueVertex, p.get(toDelete), objectsBeingAccessed);
       
      }
    }
  }

  private void deleteMap(Property p, DataType toDelete, Vertex objectVertex, Collection<CascadeType> toCascade, Map<String, Object> objectsBeingAccessed) {
    // TODO implement when create/update will have been implemented
  }

  private void deleteCollection(Property p, DataType toDelete, Vertex objectVertex, Collection<CascadeType> toCascade, Map<String, Object> objectsBeingAccessed) {
    String edgeNameFor = GraphUtils.getEdgeNameFor(p);
    Iterable<Edge> edges = objectVertex.getOutEdges(edgeNameFor);
    Collection values = (Collection) p.get(toDelete);
    Map<Vertex, Edge> oldVertices = new HashMap<Vertex, Edge>();
    for(Edge e : edges) {
      Vertex inVertex = e.getInVertex();
      oldVertices.put(inVertex, e);
    }
    for(Object v : values) {
      Vertex valueVertex = getVertexFor(v, CascadeType.REFRESH, objectsBeingAccessed);
      if(oldVertices.containsKey(valueVertex)) {
        Edge oldEdge = oldVertices.remove(valueVertex);
        database.removeEdge(oldEdge);
        if(toCascade.contains(CascadeType.REMOVE)) {
          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()) {
        database.removeEdge(e);
      }
    }
  }
 
  /**
   * Delete an out edge vertex. Those are vertex corresponding to properties.
   * @param objectVertex source object vertex, used for debugging purpose only
   * @param valueVertex value vertex to remove
   * @param value object value
   */
  private <Type> void deleteOutEdgeVertex(Vertex objectVertex, Vertex valueVertex, Type value, Map<String, Object> objectsBeingUpdated) {
    // Locate vertex
    Vertex knownValueVertex = getVertexFor(value, CascadeType.REFRESH, objectsBeingUpdated);
    // Ensure vertex is our out one
    if(valueVertex.equals(knownValueVertex)) {
      // Delete vertex and other associated ones, only if they have no other input links (elsewhere delete is silently ignored)
      if(valueVertex.getInEdges().iterator().hasNext()) {
        // There are incoming edges to that vertex. Do nothing but log it
        if (logger.isLoggable(Level.FINE)) {
          logger.log(Level.FINE, "while deleting "+objectVertex.getProperty(Properties.vertexId.name())+"" +
              " we tried to delete "+knownValueVertex.getProperty(Properties.vertexId.name())+"" +
                  " which has other incoming edges, so we didn't deleted it");
        }
      } else {
        // OK, time to delete value vertex. Is it a managed node ?
        if(repository.containsKey(value.getClass())) {
          FinderCrudService<Type, ?> finderCrudService = (FinderCrudService<Type, ?>) repository.get(value.getClass());
          finderCrudService.delete(value);
        } else {
          // Literal nodes can be deleted without any trouble
          database.removeVertex(valueVertex);
        }
      }
    } else {
      if (logger.isLoggable(Level.WARNING)) {
        logger.log(Level.WARNING, "that's strange : value "+value+" is associated to "+knownValueVertex.getProperty(Properties.vertexId.name())+"" +
            " which blueprints says is different from "+valueVertex.getProperty(Properties.vertexId.name())+"." +
                " Under those circumstances, we can delete neither of them");
      }
    }
  }

  private Map<Property, Collection<CascadeType>> getContainedProperties(DataType object) {
    Class<? extends Object> objectClass = object.getClass();
    return getContainedProperties(objectClass);
  }

  private Map<Property, Collection<CascadeType>> getContainedProperties(Class<? extends Object> objectClass) {
    if(!classes.containsKey(objectClass)) {
      classes.put(objectClass, getPropertiesFor(propertyProvider, objectClass));
    }
    return classes.get(objectClass);
  }

  /**
   * Gets the id vertex for the given object (if that object exists)
   * @param object
   * @return first matching node if found, and null if not
   */
  private Vertex getIdVertexFor(DataType object) {
    return GraphUtils.locateVertex(database, Properties.vertexId.name(), getIdVertexId(object, idProperty));
  }

  /**
   * Notice it only works if id is a literal type
   * @param object object for which we want the id vertex id property
   * @param idProperty property used to extract id from object
   * @return a composite id containing the service class, the data class and the the instance value
   * @see GraphUtils#getIdVertexId(IndexableGraph, Class, Object, Property)
   */
  private String getIdVertexId(Object object, Property idProperty) {
    return GraphUtils.getIdVertexId(database, containedClass, object, idProperty);
  }
 
  /**
   * Get id of given object, provided of course it's an instance of this class
   * @param data object to extract an id for
   * @return id of that object
   */
  public Object getIdOf(DataType data) {
    return getIdVertexId(data, idProperty);
  }

  @Override
  public DataType update(DataType toUpdate) {
    return doUpdate(toUpdate, CascadeType.MERGE, new TreeMap<String, Object>());
  }

  /**
   * Create or update given object
   * @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
   */
  private DataType doUpdate(DataType toUpdate, CascadeType cascade, Map<String, Object> objectsBeingUpdated) {
    String objectVertexId = getIdVertexId(toUpdate, idProperty);
    Vertex objectVertex = GraphUtils.locateVertex(database, Properties.vertexId, objectVertexId);
    // 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 = database.addVertex(objectVertexId);
      // As an aside, we add some indications regarding object id
      objectVertex.setProperty(Properties.vertexId.name(), objectVertexId);
      objectVertex.setProperty(Properties.kind.name(), Kind.managed.name());
      objectVertex.setProperty(Properties.type.name(), toUpdate.getClass().getName());
    }
    DataType updated = (DataType) objectsBeingUpdated.get(objectVertexId);
    if(updated==null) {
      try {
        objectsBeingUpdated.put(objectVertexId, toUpdate);
        updateProperties(toUpdate, objectVertex, getContainedProperties(toUpdate), cascade, objectsBeingUpdated);
        return toUpdate;
      } finally {
        objectsBeingUpdated.remove(objectVertexId);
      }
    } else {
      return updated;
    }
  }

  /**
   * Update all properties of given object
   * @param toUpdate object to update
   * @param objectVertex object root vertex
   * @param containedProperties collection of object properties
   */
  private void updateProperties(DataType toUpdate, Vertex objectVertex, Map<Property, Collection<CascadeType>> containedProperties, CascadeType cascade, Map<String, Object> objectsBeingAccessed) {
    for(Property p : containedProperties.keySet()) {
      // Static properties are by design not written
      if(!p.hasModifier(Modifier.STATIC) && !Annotations.TRANSIENT.is(p)) {
        Class<?> rawPropertyType = p.getType();
        // Per default, no operation is cascaded
        CascadeType used = null;
        // However, if property supports that cascade type, we cascade operation
        if(containedProperties.get(p).contains(cascade)) {
          used = cascade;
        }
        if(Collection.class.isAssignableFrom(rawPropertyType)) {
          if (logger.isLoggable(Level.FINEST)) {
            logger.log(Level.FINEST, "property "+p.getName()+" is considered a collection one");
          }
          updateCollection(p, toUpdate, objectVertex, cascade, 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(p, toUpdate, objectVertex, cascade, objectsBeingAccessed);
        } else {
          updateSingle(p, toUpdate, objectVertex, cascade, 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
   * @param p
   * @param toUpdate
   * @param cascade used cascade type, can be either {@link CascadeType#PERSIST} or {@link CascadeType#MERGE}
   * @param objectVertex
   */
  private void updateMap(Property p, DataType 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) {
      throw new UnsupportedOperationException("la persistence des Maps FFFFFFFFFFFFUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUU");
    }
  }

  /**
   * Create collection of entry vertices.
   * Those vertices are very specific : they are not managed, but not really literal either
   * @param value
   * @return
   */
  private Collection<Vertex> createVerticesFor(Map value) {
    // TODO Auto-generated method stub
    throw new UnsupportedOperationException("method "+BluePrintsBackedFinderService.class.getName()+"#createVerticesFor has not yet been implemented AT ALL");
  }

  /**
   * 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 void updateCollection(Property p, DataType 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) {
      // Get previously existing vertices
      Iterable<Edge> existingIterator = rootVertex.getOutEdges(GraphUtils.getEdgeNameFor(p));
      // Do not change previously existing vertices if they correspond to new ones
      Collection<Vertex> newVertices = createVerticesFor(value, cascade, objectsBeingAccessed);
      Map<Vertex, Edge> oldVertices = new HashMap<Vertex, Edge>();
      for(Edge e : existingIterator) {
        Vertex inVertex = e.getInVertex();
        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()) {
        database.removeEdge(entry.getValue());
      }
      // And finally add new vertices
      for(Vertex newVertex : newVertices) {
        database.addEdge(rootVertex.getId().toString()+"_to_"+newVertex.getId().toString()+"___"+UUID.randomUUID().toString(),
                rootVertex, newVertex, GraphUtils.getEdgeNameFor(p));
      }
    }
  }

  /**
   * 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> createVerticesFor(Collection value, CascadeType cascade, Map<String, Object> objectsBeingAccessed) {
    Collection<Vertex> returned = new HashSet<Vertex>();
    for(Object o : value) {
      returned.add(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 void updateSingle(Property p, DataType toUpdate, Vertex rootVertex, CascadeType cascade, Map<String, Object> objectsBeingAccessed) {
    Object value = p.get(toUpdate);
    // As a convention, null values are never stored
    if(value!=null) {
      Vertex valueVertex = getVertexFor(value, cascade, objectsBeingAccessed);
      Edge link = null;
      // Get previously existing vertex
      Iterator<Edge> existingIterator = rootVertex.getOutEdges(GraphUtils.getEdgeNameFor(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(existing.getInVertex().equals(valueVertex)) {
          // Nothing to do
          link = existing;
        } else {
          // delete edge (TODO maybe delete vertex)
          database.removeEdge(existing);
          link = database.addEdge(rootVertex.getId().toString()+"_to_"+valueVertex.getId().toString(),
                  rootVertex, valueVertex, GraphUtils.getEdgeNameFor(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().getInVertex().toString());
          }
          logger.log(Level.SEVERE, "Graph contains some incoherence :"+sOut.toString());
        }
      } else {
        if(link==null)
          // Now create edge
          link = database.addEdge(rootVertex.getId().toString()+"_to_"+valueVertex.getId().toString(),
                  rootVertex, valueVertex, GraphUtils.getEdgeNameFor(p));
      }
    }
  }

  /**
   * Get vertex associated to value. If object is managed by a service, we ask this service the value
   * @param value value we want the vertex for
   * @param cascade used cascade type, can be either {@link CascadeType#PERSIST} or {@link CascadeType#MERGE}
   * @param objectsBeingUpdated map of objects currently being updated, it avoid some loops during update, but is absolutely NOT a persistent cache
   * @return
   */
  public Vertex getVertexFor(Object value, CascadeType cascade, Map<String, Object> objectsBeingUpdated) {
    // Here we suppose the service is the right one for the job (which may not be the case)
    if(containedClass.isInstance(value)) {
      Vertex returned = getIdVertexFor(containedClass.cast(value));
      if(returned==null) {
        doUpdate(containedClass.cast(value), cascade, objectsBeingUpdated);
        returned = getIdVertexFor(containedClass.cast(value));
      } else {
        // vertex already exist, but maybe object needs an update
        if(CascadeType.PERSIST==cascade || CascadeType.MERGE==cascade) {
          doUpdate(containedClass.cast(value), cascade, objectsBeingUpdated);
        }
      }
      return returned;
    }
    Class<? extends Object> valueClass = value.getClass();
    if(repository.containsKey(valueClass)) {
      FinderCrudService service = repository.get(valueClass);
      if(service instanceof BluePrintsBackedFinderService) {
        return ((BluePrintsBackedFinderService) service).getVertexFor(value, cascade, objectsBeingUpdated);
      } else {
        throw new IncompatibleServiceException(service, valueClass);
      }
    } else if(Literals.containsKey(valueClass)){
      return GraphUtils.getVertexForBasicObject(database, value);
    } else {
/*      // OK, we will persist this object by ourselves, which is really error-prone, but we do we have any other solution ?
      // But notice object is by design consderie
      Vertex objectVertex =
      objectVertex.setProperty(Properties.vertexId.name(), getIdVertexId(toUpdate));
      objectVertex.setProperty(Properties.kind.name(), Kind.managed.name());
      objectVertex.setProperty(Properties.type.name(), toUpdate.getClass().getName());
*/
      throw new ObjectIsNotARealLiteralException(value, valueClass);
     
    }
  }

  /**
   * Object query is done by simply looking up all objects of that class using a standard query
   * @return an iterable over all objects of that class
   * @see com.dooapp.gaedo.finders.FinderCrudService#findAll()
   */
  @Override
  public Iterable<DataType> findAll() {
    return find().matching(new QueryBuilder<InformerType>() {

      /**
       * An empty and starts with an initial match of true, but degrades it for each failure.
       * So creating an empty and() is like creating a "true" statement, which in turn results into searching all objects of that class.
       * @param informer
       * @return an empty or matching all objects
       * @see com.dooapp.gaedo.finders.QueryBuilder#createMatchingExpression(com.dooapp.gaedo.finders.Informer)
       */
      @Override
      public QueryExpression createMatchingExpression(InformerType informer) {
        return Expressions.and();
      }
    }).getAll();
  }

  @Override
  protected QueryStatement<DataType, InformerType> createQueryStatement(QueryBuilder<InformerType> query) {
    return new BluePrintsGraphQueryStatement<DataType, InformerType>(query,
            this, database, repository);
  }

  /**
   * Load object starting with the given vertex root.
   * Notice object is added to the accessed set with a weak key, this way, it should be faster to load it and to maintain instance unicity
   * @param objectVertex
   *
   * @return loaded object
   * @param objectsBeingAccessed map of objects currently being accessed, it avoid some loops during loading, but is absolutely NOT a persistent cache
   * @see #loadObject(String, Vertex, Map)
   */
  public DataType loadObject(String objectVertexId, Map<String, Object> objectsBeingAccessed) {
    // If cast fails, well, that's some fuckin mess, no ?
    Vertex objectVertex = GraphUtils.locateVertex(database, Properties.vertexId, objectVertexId);
    return loadObject(objectVertexId, objectVertex, objectsBeingAccessed);
  }

  /**
   * Load object from a vertex
   * @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
   * @see #loadObject(String, Vertex, Map)
   */
  public DataType loadObject(Vertex objectVertex, Map<String, Object> objectsBeingAccessed) {
    String objectVertexId = objectVertex.getProperty(Properties.vertexId.name()).toString();
    return loadObject(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
   */
  private DataType loadObject(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 {
      DataType returned = (DataType) GraphUtils.createInstance(containedClass.getClassLoader(), objectVertex, repository);
      objectsBeingAccessed.put(objectVertexId, returned);
      Map<Property, Collection<CascadeType>> containedProperties = getContainedProperties(returned);
      for(Property p : containedProperties.keySet()) {
        if(!p.hasModifier(Modifier.STATIC) && !Annotations.TRANSIENT.is(p)) {
          Class<?> rawPropertyType = p.getType();
          if(Collection.class.isAssignableFrom(rawPropertyType)) {
            loadCollection(p, returned, objectVertex, objectsBeingAccessed);
            // each value should be written as an independant value
          } else if(Map.class.isAssignableFrom(rawPropertyType)) {
            loadMap(p, returned, objectVertex, objectsBeingAccessed);
          } else {
            loadSingle(p, returned, objectVertex, objectsBeingAccessed);
          }
        }
      }
      return returned;
    }
  }

  /**
   * Implementation tied to the future implementation of {@link #updateMap(Property, Object, Vertex, CascadeType)}
   * @param p
   * @param returned
   * @param objectVertex
   */
  private void loadMap(Property p, DataType returned, Vertex objectVertex, Map<String, Object> objectsBeingAccessed) {
  }

  /**
   * Load a single-valued property from graph
   * @param p
   * @param returned
   * @param objectVertex
   * @param objectsBeingAccessed
   */
  private void loadSingle(Property p, DataType returned, Vertex objectVertex, Map<String, Object> objectsBeingAccessed) {
    Iterator<Edge> iterator = objectVertex.getOutEdges(GraphUtils.getEdgeNameFor(p)).iterator();
    if(iterator.hasNext()) {
      // yeah, there is a value !
      Vertex firstVertex = iterator.next().getInVertex();
      Object value = GraphUtils.createInstance(containedClass.getClassLoader(), firstVertex, repository);
      if(repository.containsKey(value.getClass())) {
        // value requires fields loading
        BluePrintsBackedFinderService blueprints= (BluePrintsBackedFinderService) repository.get(value.getClass());
        p.set(returned, loadObject(firstVertex, objectsBeingAccessed));
      } else {
        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 p
   * @param returned
   * @param objectVertex
   */
  private void loadCollection(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());
      }
    }
    Collection<Object> generatedCollection = Utils.generateCollection((Class<?>) p.getType(), null);
    ClassLoader classLoader = containedClass.getClassLoader();
    CollectionLazyLoader handler = new CollectionLazyLoader(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));
    }
  }

  /**
   * we only consider first id element
   * @param id collection of id
   * @return object which has as vertexId the given property
   * @see com.dooapp.gaedo.finders.id.IdBasedService#findById(java.lang.Object[])
   */
  @Override
  public DataType findById(Object... id) {
    // make sure entered type is a valid one
    if(Utils.maybeObjectify(idProperty.getType()).isAssignableFrom(Utils.maybeObjectify(id[0].getClass()))) {
      String vertexIdValue = GraphUtils.getIdPropertyValue(database, containedClass, idProperty, id[0]).toString();
      return loadObject(vertexIdValue, new TreeMap<String, Object>());
    } else {
      throw new UnsupportedIdException(id[0].getClass(), idProperty.getType());
    }
  }

  @Override
  public Collection<Property> getIdProperties() {
    return Arrays.asList(idProperty);
  }

  /**
   * Get object associated to given key. Notice this method uses internal
   * cache ({@link #objectsBeingAccessed}) before to resolve call on
   * datastore.
   *
   * @param key
   * @return
   */
  public DataType getObjectFromKey(String key) {
    return loadObject(key, new TreeMap<String, Object>());
  }

}
TOP

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

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.