Package com.dooapp.gaedo.blueprints

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

package com.dooapp.gaedo.blueprints;

import java.util.Arrays;
import java.util.Collection;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.Map;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import javax.persistence.CascadeType;
import javax.persistence.Column;

import com.dooapp.gaedo.blueprints.annotations.GraphProperty;
import com.dooapp.gaedo.blueprints.indexable.IndexNames;
import com.dooapp.gaedo.blueprints.strategies.GraphMappingStrategy;
import com.dooapp.gaedo.blueprints.strategies.PropertyMappingStrategy;
import com.dooapp.gaedo.blueprints.strategies.UnableToGetVertexTypeException;
import com.dooapp.gaedo.blueprints.transformers.LiteralTransformer;
import com.dooapp.gaedo.blueprints.transformers.Literals;
import com.dooapp.gaedo.blueprints.transformers.TupleTransformer;
import com.dooapp.gaedo.blueprints.transformers.Tuples;
import com.dooapp.gaedo.finders.repository.ServiceRepository;
import com.dooapp.gaedo.properties.Property;
import com.tinkerpop.blueprints.Direction;
import com.tinkerpop.blueprints.Edge;
import com.tinkerpop.blueprints.Element;
import com.tinkerpop.blueprints.Graph;
import com.tinkerpop.blueprints.Index;
import com.tinkerpop.blueprints.IndexableGraph;
import com.tinkerpop.blueprints.Vertex;
import com.tinkerpop.blueprints.oupls.sail.GraphSail;

public class GraphUtils {

  /**
   * Log level used for "normal" removals. This is the best way to track some weird transaction bugs
   */
  private static final Level REMOVAL_LOG_LEVEL = Level.FINE;

  private static final String GAEDO_PREFIX = "https://github.com/Riduidel/gaedo/";

  /**
   * Ontologic context used by all gaedo graph elements.
   */
  public static final String GAEDO_CONTEXT = GAEDO_PREFIX + "visible";

  /**
   * Ontologic context used by gaedo graph elements that we want to keep
   * hidden. Those elements should never be exported. To make sure this works
   * well, this context is set to null. Crazy no ?
   */
  public static final String GAEDO_HIDDEN_CONTEXT = GAEDO_PREFIX + "hidden";

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

  public static String asSailProperty(String context) {
    if (GraphSail.NULL_CONTEXT_NATIVE.equals(context))
      return context;
    return GraphSail.URI_PREFIX + " " + context;
  }

  /**
   * Generate edge name from property infos. Notice generated edge name will
   * first be searched in property annotations, and only if none compatile
   * found by generating a basic property name
   *
   * @param p
   *            source property
   * @return an edge name (by default property container class name + "." +
   *         property name
   */
  public static String getEdgeNameFor(Property p) {
    if (p.getAnnotation(GraphProperty.class) != null) {
      GraphProperty graph = p.getAnnotation(GraphProperty.class);
      // Test added to avoid default value (which defaults name to "")
      if (graph.name() != null && graph.name().trim().length() > 0)
        return graph.name();
    }
    if (p.getAnnotation(Column.class) != null) {
      Column column = p.getAnnotation(Column.class);
      if (column.name() != null && column.name().trim().length() > 0)
        return column.name();
    }
    return getDefaultEdgeNameFor(p);
  }

  public static String getDefaultEdgeNameFor(Property p) {
    return p.getDeclaringClass().getName() + ":" + p.getName();
  }

  /**
   * Create a vertex out of a basic object. if object is of a simple type,
   * we'll use value as id. Elsewhere, we will generate an id for object
   *
   * @param database
   *            database in which vertex will be stored
   * @param value
   *            value to get
   * @param cascade
   *            cascade type. if nor PERSIST neither MERGE and vertex doesn't
   *            exist, null may be returned
   * @return vertex associated to literal or null if none found.
   */
  public static Vertex getVertexForLiteral(GraphDatabaseDriver database, Object value, CascadeType cascade) {
    Vertex returned = null;
    // Now distinct behaviour between known objects and unknown ones
    Class<? extends Object> valueClass = value.getClass();
    if (Literals.containsKey(valueClass)) {
      LiteralTransformer transformer = Literals.get(valueClass);
      returned = transformer.getVertexFor(database, valueClass.cast(value), cascade);
    } else {
      throw new ObjectIsNotARealLiteralException(value, valueClass);
      // TODO do not forget to set id property
    }
    return returned;
  }

  /**
   * Create an object instance from a literal vertex compatible with this
   * service contained class
   *
   * @param driver
   *            driver used to load data
   * @param strategy
   *            used graph mapping strategy
   * @param classLoader
   *            class loader used to find class
   * @param key
   *            vertex containing object id
   * @param repository
   *            service repository, used to disambiguate subclass of literal
   *            and managed class
   * @param objectsBeingAccessed
   * @param property
   *            property used to navigate to this value. it allows
   *            disambiguation for literal values (which may be linked to more
   *            than one type, the typical example being a saved float, say
   *            "3.0", which may also be refered as the string "3.0").
   * @return a fresh instance, with only id set
   */
  public static Object createInstance(GraphDatabaseDriver driver, GraphMappingStrategy strategy, ClassLoader classLoader, Vertex key, Class<?> defaultType,
          ServiceRepository repository, Map<String, Object> objectsBeingAccessed) {
    String effectiveType = null;
    Kind kind = getKindOf(key);
    /*
     * One literal node may be used according to different types. To
     * disambiguate, we check if effective type matches default one. If not
     * (typically type returns string and user wants number), prefer default
     * type.
     */
    try {
      effectiveType = driver.getEffectiveType(key);
    } catch (UnableToGetVertexTypeException untypedVertex) {
      try {
        // Don't remember the reason of that mess
        if (!Collection.class.isAssignableFrom(defaultType) && !defaultType.isAssignableFrom(Class.forName(effectiveType))) {
          effectiveType = defaultType.getName();
        }
      } catch (Exception unableToLoadClass) {
        // nothing to do : we use effective type - or try to
      }
      if (effectiveType == null) {
        // First alternative is here for untyped strings in uris nodes
        // (like uris themselves when treated as strings)
        // Second alternative is there for cases when we try to load a
        // collection of untyped thingies
        if (String.class.isAssignableFrom(defaultType) || Collection.class.isAssignableFrom(defaultType))
          effectiveType = GraphMappingStrategy.STRING_TYPE;

      }
    }
    if (classLoader == null) {
      throw new UnspecifiedClassLoader();
    }
    try {
      if (Literals.containsKey(classLoader, effectiveType) && !repository.containsKey(effectiveType)) {
        LiteralTransformer transformer = Literals.get(classLoader, effectiveType);
        return transformer.loadObject(driver, classLoader, effectiveType, key);
      } else {
        Class<?> type = classLoader.loadClass(effectiveType);
        if (Tuples.containsKey(type) && !repository.containsKey(type)) {
          // Tuples are handled the object way (easier, but more
          // dangerous
          TupleTransformer transformer = Tuples.get(type);
          return transformer.loadObject(driver, strategy, classLoader, type, key, repository, objectsBeingAccessed);
        } else {
          return type.newInstance();
        }
      }
    } catch (Exception e) {
      throw new UnableToCreateException(effectiveType, e);
    }
  }

  public static Kind getKindOf(Vertex key) {
    String kindName = key.getProperty(Properties.kind.name()).toString();
    Kind kind = Kind.valueOf(kindName);
    return kind;
  }

  /**
   * get an id value for the given object whatever the object is
   *
   * @param repository
   * @param value
   * @return
   */
  public static <DataType> String getIdOf(ServiceRepository repository, DataType value) {
    Class<? extends Object> valueClass = value.getClass();
    if (repository.containsKey(valueClass)) {
      AbstractBluePrintsBackedFinderService<IndexableGraph, DataType, ?> service = (AbstractBluePrintsBackedFinderService<IndexableGraph, DataType, ?>) repository
              .get(valueClass);
      // All ids are string, don't worry about it
      return service.getIdOf(value).toString();
    } else if (Literals.containsKey(valueClass)) {
      return getIdOfLiteral(valueClass, null, value);
    } else if (Tuples.containsKey(valueClass)) {
      return getIdOfTuple(repository, valueClass, value);
    } else {
      throw new ImpossibleToGetIdOfUnknownType(valueClass);
    }
  }

  /**
   * Get the value of the vertex id for the given literal
   *
   * @param database
   *            used graph
   * @param declaredClass
   *            declared object class
   * @param idProperty
   *            gives the declared type of id (which may differ from primitive
   *            types, where user may give an integer instead of a long, as an
   *            example). Notice that, contrary to most of gaedo code, this
   *            field can be null
   * @param objectId
   *            object id value
   * @return the value used by {@link Properties#vertexId} to identify the
   *         vertex associated to that object
   */
  public static String getIdOfLiteral(Class<?> declaredClass, Property idProperty, Object objectId) {
    PropertyMappingStrategy strategy = PropertyMappingStrategy.prefixed;
    if (idProperty != null && idProperty.getAnnotation(GraphProperty.class) != null) {
      strategy = idProperty.getAnnotation(GraphProperty.class).mapping();
    }
    return strategy.literalToId(declaredClass, idProperty, objectId);
  }

  /**
   * Get the value of the vertex id for the given object
   *
   * @param database
   *            used graph
   * @param declaredClass
   *            declared object class
   * @param idProperty
   *            gives the declared type of id (which may differ from primitive
   *            types, where user may give an integer instead of a long, as an
   *            example). Notice that, contrary to most of gaedo code, this
   *            field can be null
   * @param value
   *            object id value
   * @return the value used by {@link Properties#vertexId} to identify the
   *         vertex associated to that object
   */
  public static String getIdOfTuple(ServiceRepository repository, Class<?> declaredClass, Object value) {
    return Tuples.get(declaredClass).getIdOfTuple(repository, value);
  }

  /**
   * Generates a vertex for the given tuple
   *
   * @param bluePrintsBackedFinderService
   *            source service, some informations may be extracted from it
   * @param repository
   *            service repository for non literal values
   * @param value
   *            tuple to persist
   * @param cascade
   *            cascade type to be used for all operations
   * @param objectsBeingUpdated
   *            map of objects already being accessed. Links object id to
   *            object
   * @return the
   */
  public static Vertex getVertexForTuple(AbstractBluePrintsBackedFinderService<? extends Graph, ?, ?> service, ServiceRepository repository, Object value,
          CascadeType cascade, Map<String, Object> objectsBeingUpdated) {
    Vertex returned = null;
    // Now distinct behaviour between known objects and unknown ones
    Class<? extends Object> valueClass = value.getClass();
    if (Tuples.containsKey(valueClass)) {
      TupleTransformer transformer = Tuples.get(valueClass);
      returned = transformer.getVertexFor(service, valueClass.cast(value), cascade, objectsBeingUpdated);
    } else {
      throw new ObjectIsNotARealTupleException(value, valueClass);
      // TODO do not forget to set id property
    }
    return returned;
  }

  public static 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;
  }

  /**
   * Converts a vertex to a string by outputing all its properties values
   *
   * @param objectVertex
   * @return
   */
  public static String toString(Vertex objectVertex) {
    StringBuilder sOut = new StringBuilder("{");
    toString(objectVertex, sOut);
    return sOut.append("}").toString();
  }

  public static void toString(Element objectVertex, StringBuilder sOut) {
    for (String s : objectVertex.getPropertyKeys()) {
      if (sOut.length() > 1)
        sOut.append("; ");
      sOut.append(s).append("=").append(objectVertex.getProperty(s));
    }
  }

  public static String toString(Edge existing) {
    StringBuilder sOut = new StringBuilder();
    sOut.append("{{{");
    sOut.append("\n").append("fromVertex (aka outVertex) => ").append(toString(existing.getVertex(Direction.OUT)));
    sOut.append("\n\t").append(existing.getLabel());
    toString(existing, sOut);
    sOut.append("\n").append("toVertex (aka inVertex) => ").append(toString(existing.getVertex(Direction.IN)));
    sOut.append("\n}}}");
    return sOut.toString();
  }

  /**
   * Find all contexts in given edge by looking, in
   * {@link GraphSail#CONTEXT_PROP} property, what are the contexts. These
   * contexts are extracted by iteratively calling {@link #CONTEXTS_MATCHER}
   * and {@link Matcher#find(int)} method.
   *
   * @param edge
   *            input edge
   * @return collection of declared contexts.
   */
  public static Collection<String> getContextsOf(Edge edge) {
    String contextsString = edge.getProperty(GraphSail.CONTEXT_PROP).toString();
    Matcher matcher = CONTEXTS_MATCHER.matcher(contextsString);
    Collection<String> output = new LinkedList<String>();
    int character = 0;
    while (matcher.find(character)) {
      if (GraphSail.NULL_CONTEXT_NATIVE.equals(matcher.group(1))) {
        // the null context is a low-level view. It should be associated
        // with "no named graph" (that's to say an empty collection).
        return output;
      } else if (matcher.group(1).startsWith(GraphSail.URI_PREFIX + "")) {
        output.add(matcher.group(2));
      }
      character = matcher.end();
    }
    return output;
  }

  /**
   * Compiled pattern used to match strings such as
   *
   * <pre>
   * U https://github.com/Riduidel/gaedo/visible  U http://purl.org/dc/elements/1.1/description
   * </pre>
   *
   * or
   *
   * <pre>
   * N U http://purl.org/dc/elements/1.1/description
   * </pre>
   *
   * or even
   *
   * <pre>
   * N
   * </pre>
   *
   * You know why I do such a pattern matching ? Because sail graph named
   * graph definintion goes by concatenating contexts URI in edges properties.
   * This is really douchebag code !
   */
  public static final Pattern CONTEXTS_MATCHER = Pattern.compile("(N|U ([\\S]+))+");

  /**
   * Check if edge has the required named graphs list
   *
   * @param e
   *            edge to test
   * @param namedGraphs
   *            named graphs the edge must have
   * @return true if any of edge contexts is in namedGraphs. This
   *         implementation differs from previous one but, as stated in
   */
  public static boolean isInNamedGraphs(Edge e, Collection<String> namedGraphs) {
    if (namedGraphs.isEmpty())
      return true;
    Collection<String> contexts = getContextsOf(e);
    // Only analyse edge if it is in named graph, and only in named graphs
    for (String s : contexts) {
      if (namedGraphs.contains(s))
        return true;
    }
    return false;
  }

  /**
   * Remove an edge "safely". That's to say with prior existence check.
   *
   * @param database
   *            database from which edge should be removed
   * @param existing
   *            edge to remove
   */
  public static void removeSafely(Graph database, Edge existing) {
    if (logger.isLoggable(REMOVAL_LOG_LEVEL)) {
      logger.log(REMOVAL_LOG_LEVEL, "removing safely " + existing);
    }
    Edge toRemove = null;
    if ((toRemove = database.getEdge(existing.getId())) == null) {
      if (logger.isLoggable(Level.WARNING)) {
        logger.log(Level.WARNING, "We tried to remove non existing edge " + toString(existing));
      }
    } else {
      database.removeEdge(toRemove);
      if (logger.isLoggable(REMOVAL_LOG_LEVEL)) {
        logger.log(REMOVAL_LOG_LEVEL, "REMOVED " + toRemove);
      }
    }
  }

  public static void removeSafely(Graph database, Vertex existing) {
    if (logger.isLoggable(REMOVAL_LOG_LEVEL)) {
      logger.log(REMOVAL_LOG_LEVEL, "removing safely " + existing);
    }
    Vertex toRemove = null;
    if ((toRemove = database.getVertex(existing.getId())) == null) {
      if (logger.isLoggable(Level.WARNING)) {
        logger.log(Level.WARNING, "We tried to remove non existing vertex " + toString(existing));
      }
    } else {
      database.removeVertex(toRemove);
      if (logger.isLoggable(REMOVAL_LOG_LEVEL)) {
        logger.log(REMOVAL_LOG_LEVEL, "REMOVED " + toRemove);
      }
    }
  }

  /**
   * Define if a vertex can be created when using the given cascade type
   *
   * @param cascade
   * @return true for PERSIST and MERGE, false otherwise
   */
  public static boolean canCreateVertex(CascadeType cascade) {
    switch (cascade) {
    case PERSIST:
    case MERGE:
      return true;
    default:
      return false;
    }
  }

  /**
   * Set an indexed property on any graph element, updating the given list of indices
   * @param graph graph from which indices should be loaded
   * @param graphElement graph element to update
   * @param propertyName property to set
   * @param propertyValue value to set
   * @param indexName index to update. Notice removing value from index on mutation is not supported, as gaedo vertices and edges are not considered as mutable
   */
  public static <ElementType extends Element> void setIndexedProperty(IndexableGraph graph, ElementType graphElement, String propertyName, Object propertyValue, IndexNames indexName) {
    graphElement.setProperty(propertyName, propertyValue);
    Index<ElementType> index = graph.getIndex(indexName.getIndexName(), (Class<ElementType>) indexName.getIndexed());
    index.put(propertyName, propertyValue, graphElement);
  }
}
TOP

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

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.