package org.jboss.windup.graph;
import com.thinkaurelius.titan.core.TitanEdge;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.ListIterator;
import java.util.Map;
import java.util.Set;
import com.thinkaurelius.titan.graphdb.internal.AbstractElement;
import com.thinkaurelius.titan.graphdb.relations.StandardEdge;
import com.tinkerpop.blueprints.util.wrappers.event.EventEdge;
import org.jboss.forge.furnace.Furnace;
import org.jboss.forge.furnace.container.simple.lifecycle.SimpleContainer;
import org.jboss.windup.graph.model.WindupFrame;
import org.jboss.windup.graph.model.WindupVertexFrame;
import org.jboss.windup.util.furnace.FurnaceClasspathScanner;
import com.thinkaurelius.titan.core.TitanProperty;
import com.thinkaurelius.titan.graphdb.vertices.StandardVertex;
import com.tinkerpop.blueprints.Edge;
import com.tinkerpop.blueprints.Element;
import com.tinkerpop.blueprints.Vertex;
import com.tinkerpop.blueprints.util.wrappers.event.EventVertex;
import com.tinkerpop.frames.EdgeFrame;
import com.tinkerpop.frames.FrameInitializer;
import com.tinkerpop.frames.FramedGraph;
import com.tinkerpop.frames.FramedGraphConfiguration;
import com.tinkerpop.frames.VertexFrame;
import com.tinkerpop.frames.modules.AbstractModule;
import com.tinkerpop.frames.modules.Module;
import com.tinkerpop.frames.modules.TypeResolver;
import com.tinkerpop.frames.modules.typedgraph.TypeField;
import com.tinkerpop.frames.modules.typedgraph.TypeRegistry;
import com.tinkerpop.frames.modules.typedgraph.TypeValue;
import java.util.Arrays;
import java.util.logging.Logger;
import org.jboss.windup.util.exception.WindupException;
/**
* Windup's implementation of extended type handling for TinkerPop Frames. This allows storing multiple types based on the @TypeValue.value(), also in
* the type property (see {@link WindupVertexFrame#TYPE_PROP}.
*/
public class GraphTypeManager implements TypeResolver, FrameInitializer
{
private static final Logger LOG = Logger.getLogger(GraphTypeManager.class.getName());
private Map<String, Class<? extends WindupFrame<?>>> registeredTypes;
private TypeRegistry typeRegistry;
public GraphTypeManager()
{
}
private void initRegistry()
{
Furnace furnace = SimpleContainer.getFurnace(GraphTypeManager.class.getClassLoader());
FurnaceClasspathScanner furnaceClasspathScanner = furnace.getAddonRegistry().getServices(FurnaceClasspathScanner.class).get();
this.registeredTypes = new HashMap<>();
this.typeRegistry = new TypeRegistry();
GraphModelScanner.loadFrames(furnaceClasspathScanner).forEach(this::addTypeToRegistry);
}
public Set<Class<? extends WindupFrame<?>>> getRegisteredTypes()
{
return Collections.unmodifiableSet(new HashSet<>(getRegisteredTypeMap().values()));
}
private synchronized Map<String, Class<? extends WindupFrame<?>>> getRegisteredTypeMap()
{
if (registeredTypes == null)
initRegistry();
return registeredTypes;
}
private synchronized TypeRegistry getTypeRegistry()
{
if (typeRegistry == null)
initRegistry();
return typeRegistry;
}
private void addTypeToRegistry(Class<? extends WindupFrame<?>> frameType)
{
LOG.info(" Adding type to registry: " + frameType.getName());
TypeValue typeValueAnnotation = frameType.getAnnotation(TypeValue.class);
// Do not attempt to add types without @TypeValue. We use
// *Model types with no @TypeValue to function as essentially
// "abstract" models that would never exist on their own (only as subclasses).
if (typeValueAnnotation == null)
{
String msg = String.format("@%s is missing on type %s", TypeValue.class.getSimpleName(), frameType.getName());
LOG.warning(msg);
return;
}
if (getRegisteredTypeMap().containsKey(typeValueAnnotation.value()))
{
throw new IllegalArgumentException("Type value for model '" + frameType.getCanonicalName()
+ "' is already registered with model "
+ getRegisteredTypeMap().get(typeValueAnnotation.value()).getName());
}
getRegisteredTypeMap().put(typeValueAnnotation.value(), frameType);
getTypeRegistry().add(frameType);
}
/**
* Remove the given type from the provided {@link Element}.
*/
public void removeTypeFromElement(Class<? extends WindupFrame<?>> kind, Element element)
{
Class<?> typeHoldingTypeField = getTypeRegistry().getTypeHoldingTypeField(kind);
if (typeHoldingTypeField == null)
return;
String typeFieldName = typeHoldingTypeField.getAnnotation(TypeField.class).value();
TypeValue typeValueAnnotation = kind.getAnnotation(TypeValue.class);
if (typeValueAnnotation == null)
return;
String typeValue = typeValueAnnotation.value();
AbstractElement abstractElement = GraphTypeManager.asTitanElement(element);
List<String> newTypes = new ArrayList<>();
for (String existingType : (Iterable<String>)abstractElement.getProperty(typeFieldName))
{
if (!existingType.toString().equals(typeValue))
{
newTypes.add(typeValue);
}
}
abstractElement.removeProperty(typeFieldName);
for (String newType : newTypes)
addProperty(abstractElement, typeFieldName, newType);
addSuperclassType(kind, element);
}
private void addProperty(AbstractElement abstractElement, String propertyName, String propertyValue)
{
// This uses the direct Titan API which is indexed. See GraphContextImpl.
if (abstractElement instanceof StandardVertex)
((StandardVertex) abstractElement).addProperty(propertyName, propertyValue);
// StandardEdge doesn't have addProperty().
else if (abstractElement instanceof StandardEdge)
//((StandardEdge) abstractElement).setProperty(propertyName, propertyValue);
addTokenProperty(abstractElement, propertyName, propertyValue);
// For all others, we resort to storing a list
else
{
List<String> existingList = abstractElement.getProperty(propertyName);
if (existingList == null)
{
abstractElement.setProperty(propertyName, Collections.singletonList(propertyValue));
}
else
{
List<String> newList = new ArrayList<>(existingList);
newList.add(propertyValue);
abstractElement.setProperty(propertyName, newList);
}
}
}
private void addTokenProperty(AbstractElement el, String propertyName, String propertyValue)
{
Object val = el.getProperty(propertyName);
if (val == null)
el.setProperty(propertyName, propertyValue);
else
el.setProperty(propertyName, val + "|" + propertyValue);
}
/**
* Returns the type identifier for given type - the value in the property discriminating this type.
*/
public static String getTypeIdentifier(Class<? extends VertexFrame> modelInterface)
{
TypeValue typeValueAnnotation = modelInterface.getAnnotation(TypeValue.class);
if (typeValueAnnotation == null)
return null;
return typeValueAnnotation.value();
}
/**
* Adds the type value to the field denoting which type the element represents. This is similar
* to {@link GraphTypeManager#addTypeToElement(Class, Element)}, however it uses a String type instead. The
* String type will be looked up from the type registry to determine the class type to use.
*/
public void addTypeToElement(String typeString, Element element)
{
Class<? extends WindupFrame<?>> kind = getRegisteredTypeMap().get(typeString);
if (kind == null)
throw new IllegalArgumentException("Unrecognized type: " + typeString);
addTypeToElement(kind, element);
}
/**
* Adds the type value to the field denoting which type the element represents.
*/
public void addTypeToElement(Class<? extends WindupFrame<?>> kind, Element element)
{
Class<?> typeHoldingTypeField = getTypeRegistry().getTypeHoldingTypeField(kind);
if (typeHoldingTypeField == null)
return;
TypeValue typeValueAnnotation = kind.getAnnotation(TypeValue.class);
if (typeValueAnnotation == null)
return;
String typeFieldName = typeHoldingTypeField.getAnnotation(TypeField.class).value();
String typeValue = typeValueAnnotation.value();
AbstractElement abstractElement = GraphTypeManager.asTitanElement(element);
Object typeProp = abstractElement.getProperty(typeFieldName);
if (typeProp != null)
{
if (!(typeProp instanceof Iterable))
throw new RuntimeException("Discriminators property is not Iterable, but " + typeProp.getClass() + ": " + typeProp);
for (String existingType : (Iterable<String>)typeProp)
{
if (existingType.equals(typeValue))
{
// this is already in the list, so just exit now
return;
}
}
}
addProperty(abstractElement, typeFieldName, typeValue);
addSuperclassType(kind, element);
}
@SuppressWarnings("unchecked")
private void addSuperclassType(Class<? extends WindupFrame<?>> kind, Element element)
{
for (Class<?> superInterface : kind.getInterfaces())
{
if (WindupFrame.class.isAssignableFrom(superInterface))
{
addTypeToElement((Class<? extends WindupFrame<?>>) superInterface, element);
}
}
}
/**
* Returns the classes which this edge represents, typically subclasses.
*/
@Override
public Class<?>[] resolveTypes(Edge e, Class<?> defaultType)
{
return resolve(e, defaultType);
}
/**
* Returns the classes which this vertex represents, typically subclasses.
*/
@Override
public Class<?>[] resolveTypes(Vertex v, Class<?> defaultType)
{
return resolve(v, defaultType);
}
public static boolean hasType(Class<? extends WindupVertexFrame> type, WindupVertexFrame frame)
{
return hasType(type, frame.asVertex());
}
public static boolean hasType(Class<? extends WindupVertexFrame> type, Vertex v)
{
TypeValue typeValueAnnotation = type.getAnnotation(TypeValue.class);
if (typeValueAnnotation == null)
{
throw new IllegalArgumentException("Class " + type.getCanonicalName() + " lacks a @TypeValue annotation");
}
AbstractElement abstractElement= GraphTypeManager.asTitanElement(v);
Iterable<String> vertexTypes = abstractElement.getProperty(WindupVertexFrame.TYPE_PROP);
for (String typeValue : vertexTypes)
{
if (typeValue.equals(typeValueAnnotation.value()))
{
return true;
}
}
return false;
}
public static AbstractElement asTitanElement(Element e)
{
if (e instanceof StandardVertex)
{
return (StandardVertex) e;
}
else if (e instanceof EventVertex)
{
return (AbstractElement) ((EventVertex) e).getBaseVertex();
} else if (e instanceof StandardEdge)
{
return (StandardEdge)e;
} else if (e instanceof EventEdge)
{
return (AbstractElement) ((EventEdge) e).getBaseEdge();
}
else
{
throw new IllegalArgumentException("Unrecognized element type: " + e.getClass());
}
}
/**
* Returns the classes which this vertex/edge represents, typically subclasses. This will only return the lowest level subclasses (no superclasses
* of types in the type list will be returned). This prevents Annotation resolution issues between superclasses and subclasses (see also:
* WINDUP-168).
*/
private Class<?>[] resolve(Element e, Class<?> defaultType)
{
// The class field holding the name of the type holding property.
Class<?> typeHoldingTypeField = getTypeRegistry().getTypeHoldingTypeField(defaultType);
if (typeHoldingTypeField == null)
return new Class[] { defaultType, VertexFrame.class };
// Name of the graph element property holding the type list.
String propName = typeHoldingTypeField.getAnnotation(TypeField.class).value();
AbstractElement abstractElement = GraphTypeManager.asTitanElement(e);
final Object typeValue = abstractElement.getProperty(propName);
if (typeValue == null)
return new Class[] { defaultType, VertexFrame.class };
Iterable<String> valuesAll = null;
if (abstractElement instanceof StandardVertex)
{
if (!Iterable.class.isAssignableFrom(typeValue.getClass()))
throw new WindupException(String.format("Expected Iterable stored in vertex's %s, was %s: %s", propName, typeValue.getClass().getName(), typeValue.toString()));
valuesAll = (Iterable<String>) typeValue;
}
else if (abstractElement instanceof TitanEdge)
{
if (!String.class.isAssignableFrom(typeValue.getClass()))
throw new WindupException(String.format("Expected String with tokens stored in edge's %s, was %s: %s", propName, typeValue.getClass().getName(), typeValue.toString()));
valuesAll = Arrays.asList(((String)typeValue).split("|"));
}
else
throw new WindupException(String.format("Unknown element type: %s", abstractElement.getClass().getName()));
List<Class<?>> resultClasses = new ArrayList<>();
for (String value : valuesAll)
{
Class<?> type = getTypeRegistry().getType(typeHoldingTypeField, value);
if (type != null)
{
// first check that no subclasses have already been added
ListIterator<Class<?>> previouslyAddedIterator = resultClasses.listIterator();
boolean shouldAdd = true;
while (previouslyAddedIterator.hasNext())
{
Class<?> previouslyAdded = previouslyAddedIterator.next();
if (previouslyAdded.isAssignableFrom(type))
{
// Remove the previously added superclass
previouslyAddedIterator.remove();
}
else if (type.isAssignableFrom(previouslyAdded))
{
// The current type is a superclass of a previously added type, don't add it
shouldAdd = false;
}
}
if (shouldAdd)
resultClasses.add(type);
}
}
if (!resultClasses.isEmpty())
{
resultClasses.add(VertexFrame.class);
return resultClasses.toArray(new Class<?>[resultClasses.size()]);
}
return new Class[] { defaultType, VertexFrame.class };
}
@Override
@SuppressWarnings("unchecked")
public void initElement(Class<?> kind, FramedGraph<?> framedGraph, Element element)
{
if (VertexFrame.class.isAssignableFrom(kind) || EdgeFrame.class.isAssignableFrom(kind))
{
addTypeToElement((Class<? extends WindupFrame<?>>) kind, element);
}
}
/**
* Build TinkerPop Frames module - a collection of models.
*/
public Module build()
{
return new AbstractModule()
{
@Override
public void doConfigure(FramedGraphConfiguration config)
{
config.addTypeResolver(GraphTypeManager.this);
config.addFrameInitializer(GraphTypeManager.this);
}
};
}
}