com.dooapp.gaedo.blueprints.GraphUtils Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of gaedo-blueprints Show documentation
Show all versions of gaedo-blueprints Show documentation
Implementation of gaedo mechanisms backed by blueprints graph layer
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.TreeSet;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.regex.Matcher;
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;
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());
/**
* 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 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 cache of objects, allowing faster operations sometimes ..
* @param defaultType default type for that vertex if none better was found
* @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, ObjectCache objectsBeingAccessed) {
String effectiveType = null;
effectiveType = disambiguateEffectiveType(driver, key, defaultType, effectiveType);
if (classLoader == null) {
throw new UnspecifiedClassLoader();
}
try {
// for legacy reasons, we support loading a detached literal value
if (Literals.containsKey(classLoader, effectiveType) && !repository.containsKey(effectiveType)) {
Class> type = classLoader.loadClass(effectiveType);
LiteralTransformer> transformer = Literals.get(type);
return transformer.fromString(key.getProperty(Properties.value.name()).toString(), type, classLoader, objectsBeingAccessed);
} else {
Class> type = loadClass(classLoader, 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 UnableToCreateException.dueTo(key, effectiveType, e);
}
}
/**
* 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.
* @param driver
* driver used to load data
* @param key
* vertex containing object id
* @return resolved effective type for vertex
*/
private static String disambiguateEffectiveType(GraphDatabaseDriver driver, Vertex key, Class> defaultType, String effectiveType) {
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;
}
}
return effectiveType;
}
/**
* Load given class if possible.
* If not, a clear message should be output (or at least a special exception should be used)
* @param classLoader
* @param effectiveType the type we want to load
* @return
* @throws ClassNotFoundException
*/
public static Class> loadClass(ClassLoader classLoader, String effectiveType) throws ClassNotFoundException {
try {
Class> returned = classLoader.loadClass(effectiveType);
if(returned==null) {
throw new UnableToLoadClassException("unable to load class \""+effectiveType+"\"");
}
return returned;
} catch(Exception e) {
throw new UnableToLoadClassException("unable to load class \""+effectiveType+"\"", e);
}
}
public static Kind getKindOf(Vertex key) {
Object kindObject = key.getProperty(Properties.kind.name());
if(kindObject==null) {
throw new VertexHasNoPropertyException("vertex "+toString(key)+" has no "+Properties.kind+" property defined.");
}
String kindName = kindObject.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 id of object. may be null if value has no known id (typically the case for non persisted managed objects)
* @see https://github.com/Riduidel/gaedo/issues/80
*/
public static String getIdOf(ServiceRepository repository, DataType value) {
Class extends Object> valueClass = value.getClass();
if (repository.containsKey(valueClass)) {
AbstractBluePrintsBackedFinderService service = (AbstractBluePrintsBackedFinderService) repository
.get(valueClass);
// All ids are string, don't worry about it
Object idOf = service.getIdOf(value);
if(idOf==null)
return null;
else
return idOf.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, CascadeType.REFRESH, new ObjectCache());
}
/**
* Generates a vertex for the given tuple
* @param driver TODO
* @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
* @param bluePrintsBackedFinderService
* source service, some informations may be extracted from it
*
* @return the
*/
public static Vertex getVertexForTuple(AbstractBluePrintsBackedFinderService extends Graph, ?, ?> service, GraphDatabaseDriver driver, ServiceRepository repository,
Object value, CascadeType cascade, ObjectCache 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, driver, valueClass.cast(value), cascade, objectsBeingUpdated);
} else {
throw new ObjectIsNotARealTupleException(value, valueClass);
// TODO do not forget to set id property
}
return returned;
}
public static Collection extractCascadeOf(CascadeType[] cascade) {
Set returned = new HashSet();
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();
}
/**
* List all vertex properties in alphabetical order
* @param element element for which we want some details
* @param sOut output buffer
*/
public static void toString(Element element, StringBuilder sOut) {
sOut.append("graph id=").append(element.getId().toString());
for (String s : new TreeSet(element.getPropertyKeys())) {
sOut.append("\n\t");
sOut.append(s).append("=").append(element.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();
}
/**
* 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 {
removeFromIndex(database, existing);
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 {
removeFromIndex(database, existing);
database.removeVertex(toRemove);
if (logger.isLoggable(REMOVAL_LOG_LEVEL)) {
logger.log(REMOVAL_LOG_LEVEL, "REMOVED " + toRemove);
}
}
}
/**
* Safely set property of entity to the given value.
* Notice that, if graph is indexable,
* @param database
* @param entity
* @param propertyName
* @param newValue
*/
public static void setIndexedProperty(Graph database, Type entity, String propertyName, Object newValue) {
Object oldValue = entity.getProperty(propertyName);
entity.setProperty(propertyName, newValue);
if(database instanceof IndexableGraph) {
IndexableGraph indexable = (IndexableGraph) database;
IndexNames indexName = IndexNames.forElement(entity);
Index index = (Index) indexable.getIndex(indexName.getIndexName(), indexName.getIndexed());
if(oldValue!=null) {
index.remove(propertyName, oldValue, entity);
}
if(newValue!=null) {
index.put(propertyName, newValue, entity);
}
}
}
/**
* Remove given element from index by removing all bindings from its properties names to its properties values
* @param database graph on which remove operation will be performed (must be indexable)
* @param existing element to remove index entries
*/
public static void removeFromIndex(Graph database, Type existing) {
if (database instanceof IndexableGraph) {
IndexableGraph indexable = (IndexableGraph) database;
IndexNames indexName = IndexNames.forElement(existing);
if(indexName.isUsable()) {
Index index = (Index) indexable.getIndex(indexName.getIndexName(), indexName.getIndexed());
for(String propertyName : existing.getPropertyKeys()) {
index.remove(propertyName, existing.getProperty(propertyName), existing);
}
}
}
}
/**
* 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;
}
}
/**
* Creates an empty vertex with some interesting properties set
* @param database database in which the vertex is to be created
* @param repository service repository allowing us to set specific properties ({@link Properties#kind} , namely)
* @param vertexId expected vertex id, which will be set in the {@link Properties#value}
* @param valueClass class of value for which a vertex is expected.
* @param value the value for which a vertex is to be created.
* @return
*/
public static Vertex createEmptyVertexFor(Graph database, ServiceRepository repository, String vertexId, Class extends Object> valueClass, Object value) {
Vertex returned = database.addVertex(valueClass.getName() + ":" + vertexId);
setIndexedProperty(database, returned, Properties.value.name(), vertexId);
if (Literals.containsKey(valueClass)) {
throw new CantCreateAVertexForALiteralException("Impossible to create a vertex for "+value);
} else {
if (repository.containsKey(valueClass)) {
setIndexedProperty(database, returned, Properties.kind.name(), Kind.uri.name());
} else if (Tuples.containsKey(valueClass)) {
// some literals aren't so ... literal, as they can accept
// incoming connections (like classes)
setIndexedProperty(database, returned, Properties.kind.name(), Tuples.get(valueClass).getKind().name());
}
// no more edge to class creation : as TYPE and ClassCollectionProperty now are literals, one should rely upon classical index search
}
// Yup, this if has no default else statement, and that's normal.
if (logger.isLoggable(Level.FINE)) {
logger.log(Level.FINE, "created vertex " + toString(returned));
}
return returned;
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy