463 lines
18 KiB
Java
463 lines
18 KiB
Java
/**
|
|
*
|
|
*/
|
|
package org.gcube.informationsystem.resourceregistry.instances.type;
|
|
|
|
import java.util.ArrayList;
|
|
import java.util.Collection;
|
|
import java.util.Iterator;
|
|
import java.util.List;
|
|
import java.util.Set;
|
|
import java.util.concurrent.Callable;
|
|
import java.util.concurrent.ExecutorService;
|
|
import java.util.concurrent.Executors;
|
|
import java.util.concurrent.Future;
|
|
|
|
import javax.activation.UnsupportedDataTypeException;
|
|
|
|
import org.gcube.informationsystem.base.reference.AccessType;
|
|
import org.gcube.informationsystem.base.reference.ISManageable;
|
|
import org.gcube.informationsystem.base.reference.entities.BaseEntity;
|
|
import org.gcube.informationsystem.base.reference.properties.BaseProperty;
|
|
import org.gcube.informationsystem.base.reference.relations.BaseRelation;
|
|
import org.gcube.informationsystem.model.reference.entities.Entity;
|
|
import org.gcube.informationsystem.model.reference.entities.Resource;
|
|
import org.gcube.informationsystem.model.reference.properties.Property;
|
|
import org.gcube.informationsystem.model.reference.relations.Relation;
|
|
import org.gcube.informationsystem.resourceregistry.api.exceptions.ResourceRegistryException;
|
|
import org.gcube.informationsystem.resourceregistry.api.exceptions.schema.SchemaAlreadyPresentException;
|
|
import org.gcube.informationsystem.resourceregistry.api.exceptions.schema.SchemaCreationException;
|
|
import org.gcube.informationsystem.resourceregistry.api.exceptions.schema.SchemaException;
|
|
import org.gcube.informationsystem.resourceregistry.api.exceptions.schema.SchemaNotFoundException;
|
|
import org.gcube.informationsystem.resourceregistry.instances.context.ContextUtility;
|
|
import org.gcube.informationsystem.resourceregistry.security.AdminSecurityContext;
|
|
import org.gcube.informationsystem.resourceregistry.security.SecurityContext.PermissionMode;
|
|
import org.gcube.informationsystem.types.Type;
|
|
import org.gcube.informationsystem.types.TypeBinder;
|
|
import org.gcube.informationsystem.types.reference.TypeDefinition;
|
|
import org.gcube.informationsystem.types.reference.entities.EntityTypeDefinition;
|
|
import org.gcube.informationsystem.types.reference.properties.PropertyDefinition;
|
|
import org.gcube.informationsystem.types.reference.properties.PropertyTypeDefinition;
|
|
import org.gcube.informationsystem.types.reference.relations.RelationTypeDefinition;
|
|
import org.slf4j.Logger;
|
|
import org.slf4j.LoggerFactory;
|
|
|
|
import com.fasterxml.jackson.databind.JsonNode;
|
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
|
import com.fasterxml.jackson.databind.node.ArrayNode;
|
|
import com.fasterxml.jackson.databind.node.ObjectNode;
|
|
import com.orientechnologies.orient.core.db.ODatabaseSession;
|
|
import com.orientechnologies.orient.core.exception.OSchemaException;
|
|
import com.orientechnologies.orient.core.metadata.OMetadata;
|
|
import com.orientechnologies.orient.core.metadata.schema.OClass;
|
|
import com.orientechnologies.orient.core.metadata.schema.OClassImpl;
|
|
import com.orientechnologies.orient.core.metadata.schema.OProperty;
|
|
import com.orientechnologies.orient.core.metadata.schema.OSchema;
|
|
import com.orientechnologies.orient.core.metadata.schema.OType;
|
|
import com.orientechnologies.orient.core.record.impl.ODocument;
|
|
import com.tinkerpop.blueprints.impls.orient.OrientElementType;
|
|
|
|
/**
|
|
* @author Luca Frosini (ISTI - CNR)
|
|
*
|
|
* TODO Create an instance for each Registered Type in a management SecurityContext so that that management context
|
|
* can be used to see Entity and Relations as graph.
|
|
*
|
|
*/
|
|
public class SchemaManagementImpl implements SchemaManagement {
|
|
|
|
private static Logger logger = LoggerFactory.getLogger(SchemaManagementImpl.class);
|
|
|
|
protected String typeName;
|
|
|
|
protected static OClass getOClass(OSchema oSchema, String type) throws SchemaException {
|
|
return oSchema.getClass(type);
|
|
}
|
|
|
|
public void setTypeName(String typeName) {
|
|
this.typeName = typeName;
|
|
}
|
|
|
|
public static OClass getTypeSchema(ODatabaseSession oDatabaseSession, String type, AccessType accessType)
|
|
throws SchemaException, SchemaNotFoundException {
|
|
OMetadata oMetadata = oDatabaseSession.getMetadata();
|
|
OSchema oSchema = oMetadata.getSchema();
|
|
return getTypeSchema(oSchema, type, accessType);
|
|
}
|
|
|
|
public static OClass getTypeSchema(OSchema oSchema, String type, AccessType accessType) throws SchemaException, SchemaNotFoundException {
|
|
try {
|
|
OClass oClass = oSchema.getClass(type);
|
|
if(oClass == null) {
|
|
throw new SchemaNotFoundException(type + " was not registered");
|
|
}
|
|
if(accessType != null && type.compareTo(accessType.getName()) != 0) {
|
|
if(!oClass.isSubClassOf(accessType.getName())) {
|
|
throw new SchemaException(type + " is not a " + accessType.getName());
|
|
}
|
|
}
|
|
return oClass;
|
|
} catch(SchemaNotFoundException snfe) {
|
|
throw snfe;
|
|
} catch(Exception e) {
|
|
throw new SchemaException(e.getMessage());
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
public static OClass getTypeSchema(String type, AccessType accessType)
|
|
throws SchemaException, ResourceRegistryException {
|
|
// TODO Add a Type cache
|
|
try {
|
|
ExecutorService es = Executors.newSingleThreadExecutor();
|
|
Future<OClass> result = es.submit(new Callable<OClass>() {
|
|
public OClass call() throws Exception {
|
|
ODatabaseSession oDatabaseSession = null;
|
|
try {
|
|
logger.debug("Getting {} Type {} schema", accessType != null ? accessType.getName() : "", type);
|
|
|
|
AdminSecurityContext adminSecurityContext = ContextUtility.getAdminSecurityContext();
|
|
oDatabaseSession = adminSecurityContext.getDatabaseSession(PermissionMode.READER);
|
|
|
|
return getTypeSchema(oDatabaseSession, type, accessType);
|
|
} catch(ResourceRegistryException e) {
|
|
throw e;
|
|
}catch(Exception e) {
|
|
throw new ResourceRegistryException(e);
|
|
} finally {
|
|
if(oDatabaseSession != null) {
|
|
oDatabaseSession.close();
|
|
}
|
|
}
|
|
}
|
|
});
|
|
return result.get();
|
|
} catch(Exception e) {
|
|
throw new ResourceRegistryException(e);
|
|
}
|
|
}
|
|
|
|
protected static TypeDefinition getTypeDefinition(OClass oClass) throws SchemaException {
|
|
// TODO OrientSerilizatoin is not acceptable anymore since TypeDefiniton introduction
|
|
|
|
ODocument oDocument = ((OClassImpl) oClass).toStream();
|
|
String json = oDocument.toJSON();
|
|
try {
|
|
ObjectMapper mapper = new ObjectMapper();
|
|
ObjectNode node = (ObjectNode) mapper.readTree(json);
|
|
|
|
if(oClass.isSubClassOf(BaseProperty.NAME)) {
|
|
node.put(ISManageable.CLASS_PROPERTY, PropertyTypeDefinition.NAME);
|
|
} else if(oClass.isSubClassOf(BaseEntity.NAME)) {
|
|
node.put(ISManageable.CLASS_PROPERTY, EntityTypeDefinition.NAME);
|
|
} else if(oClass.isSubClassOf(BaseRelation.NAME)) {
|
|
node.put(ISManageable.CLASS_PROPERTY, RelationTypeDefinition.NAME);
|
|
}
|
|
|
|
|
|
ArrayNode arrayNode = (ArrayNode) node.get(TypeDefinition.PROPERTIES_PROPERTY);
|
|
Iterator<JsonNode> iterator = arrayNode.iterator();
|
|
while(iterator.hasNext()) {
|
|
ObjectNode propertyNode = (ObjectNode) iterator.next();
|
|
propertyNode.put(ISManageable.CLASS_PROPERTY, PropertyDefinition.NAME);
|
|
}
|
|
|
|
String managedJson = mapper.writeValueAsString(node);
|
|
logger.trace("{} -> {}", json, managedJson);
|
|
|
|
return TypeBinder.deserializeTypeDefinition(managedJson);
|
|
} catch(Exception e) {
|
|
throw new SchemaException(e);
|
|
}
|
|
}
|
|
|
|
protected static String getTypeDefinitionAsString(OClass oClass) throws SchemaException {
|
|
try {
|
|
TypeDefinition typeDefinition = getTypeDefinition(oClass);
|
|
return TypeBinder.serializeTypeDefinition(typeDefinition);
|
|
} catch(Exception e) {
|
|
throw new SchemaException(e);
|
|
}
|
|
}
|
|
|
|
protected List<OClass> getSuperclassesAndCheckCompliancy(ODatabaseSession oDatabaseSession,
|
|
TypeDefinition typeDefinition, String baseType) throws SchemaException, SchemaNotFoundException {
|
|
|
|
Set<String> superClasses = typeDefinition.getSuperClasses();
|
|
if(baseType != null) {
|
|
if(superClasses == null || superClasses.size() == 0) {
|
|
throw new RuntimeException(
|
|
String.format("No Superclass found in schema %s. The Type Definition must extend %s",
|
|
typeDefinition, baseType));
|
|
}
|
|
}
|
|
|
|
OMetadata oMetadata = oDatabaseSession.getMetadata();
|
|
OSchema oSchema = oMetadata.getSchema();
|
|
|
|
List<OClass> oSuperclasses = new ArrayList<>();
|
|
for(String superClass : superClasses) {
|
|
OClass oSuperClass = getOClass(oSchema, superClass);
|
|
if(oSuperClass == null) {
|
|
throw new SchemaNotFoundException("Superclass " + superClass + " does not exists");
|
|
}
|
|
if(baseType != null) {
|
|
if(typeDefinition.getName().compareTo(baseType) != 0) {
|
|
if(!oSuperClass.isSubClassOf(baseType)) {
|
|
throw new RuntimeException(superClass + " is not a subsclass of " + baseType
|
|
+ ". Each Superclass MUST be a subclass of " + baseType);
|
|
}
|
|
}
|
|
}
|
|
oSuperclasses.add(oSuperClass);
|
|
}
|
|
|
|
return oSuperclasses;
|
|
}
|
|
|
|
private static List<String> baseTypes;
|
|
private static List<String> typeDefinitionTypes;
|
|
|
|
static {
|
|
baseTypes = new ArrayList<String>();
|
|
baseTypes.add(BaseProperty.NAME);
|
|
baseTypes.add(BaseEntity.NAME);
|
|
baseTypes.add(BaseRelation.NAME);
|
|
|
|
typeDefinitionTypes = new ArrayList<String>();
|
|
typeDefinitionTypes.add(PropertyTypeDefinition.NAME);
|
|
typeDefinitionTypes.add(EntityTypeDefinition.NAME);
|
|
typeDefinitionTypes.add(RelationTypeDefinition.NAME);
|
|
}
|
|
|
|
protected String registerTypeSchema(String jsonSchema, AccessType baseType) throws SchemaException {
|
|
|
|
ODatabaseSession oDatabaseSession = null;
|
|
try {
|
|
TypeDefinition typeDefinition = null;
|
|
try {
|
|
typeDefinition = TypeBinder.deserializeTypeDefinition(jsonSchema);
|
|
logger.info("Trying to register {} {} : {}", baseType.getName(), typeDefinition.getName(), jsonSchema);
|
|
}catch (Exception e) {
|
|
logger.error("Error while trying to register {} {}", baseType.getName(), jsonSchema);
|
|
throw e;
|
|
}
|
|
|
|
|
|
if(typeName.compareTo(typeDefinition.getName())!=0) {
|
|
String error = String.format("Provided type name path argument %s does not match with the type name in the definition %S. Please be coherent.", typeName, typeDefinition.getName());
|
|
throw new SchemaCreationException(error);
|
|
}
|
|
|
|
|
|
AdminSecurityContext adminSecurityContext = ContextUtility.getAdminSecurityContext();
|
|
oDatabaseSession = adminSecurityContext.getDatabaseSession(PermissionMode.WRITER);
|
|
|
|
OMetadata oMetadata = oDatabaseSession.getMetadata();
|
|
OSchema oSchema = oMetadata.getSchema();
|
|
|
|
OClass oClass = null;
|
|
|
|
if(BaseEntity.class.isAssignableFrom(baseType.getTypeClass())) {
|
|
oClass = oDatabaseSession.createVertexClass(typeDefinition.getName());
|
|
} else if(BaseRelation.class.isAssignableFrom(baseType.getTypeClass())) {
|
|
oClass = oDatabaseSession.createEdgeClass(typeDefinition.getName());
|
|
|
|
/*
|
|
* This information are persisted in Management Context String outBaseType =
|
|
* typeDefinition.getOutBaseType(); String inBaseType =
|
|
* typeDefinition.getInBaseType();
|
|
*/
|
|
|
|
} else if(BaseProperty.class.isAssignableFrom(baseType.getTypeClass())) {
|
|
oClass = oSchema.createClass(typeDefinition.getName());
|
|
} else {
|
|
String error = String.format("Allowed superclass are %s, %s, %s, or any subclasses of them.",
|
|
Entity.NAME, Relation.NAME, Property.NAME);
|
|
throw new SchemaCreationException(error);
|
|
}
|
|
|
|
try {
|
|
|
|
String description = typeDefinition.getDescription();
|
|
if(description!=null && description.compareTo("")!=0) {
|
|
try {
|
|
oClass.setDescription(description);
|
|
} catch(Exception e) {
|
|
logger.warn(
|
|
"Unable to set description. This is an orient bug. See https://github.com/orientechnologies/orientdb/issues/7065");
|
|
}
|
|
}
|
|
|
|
try {
|
|
// oClass.setAbstract(false); // Used to allow to persist Schema in Context
|
|
// Management
|
|
oClass.setAbstract(typeDefinition.isAbstract());
|
|
} catch(Exception e) {
|
|
logger.error(
|
|
"Unable to set the Vertex Type {} as abstract. This is an OrientDB <= 2.2.12 bug. The Type will be created as it is not abstract.",
|
|
typeDefinition.getName());
|
|
}
|
|
|
|
if(! baseTypes.contains(typeDefinition.getName())) {
|
|
List<OClass> oSuperclasses = getSuperclassesAndCheckCompliancy(oDatabaseSession, typeDefinition,
|
|
baseType.getName());
|
|
oClass.setSuperClasses(oSuperclasses);
|
|
}
|
|
|
|
Set<PropertyDefinition> propertyDefinitions = typeDefinition.getProperties();
|
|
if(Resource.class.isAssignableFrom(baseType.getTypeClass())) {
|
|
if(propertyDefinitions != null && propertyDefinitions.size() > 0) {
|
|
throw new SchemaCreationException("A Resource cannot contains any properties.");
|
|
}
|
|
} else {
|
|
for(PropertyDefinition propertyDefinition : propertyDefinitions) {
|
|
|
|
OType oType = OType.getById(propertyDefinition.getType().byteValue());
|
|
|
|
/*
|
|
* Types update is not allowed,
|
|
* hence bug https://github.com/orientechnologies/orientdb/issues/7354 cannot occur
|
|
* Excluding the check from types used for type definition
|
|
*
|
|
*/
|
|
if(!typeDefinitionTypes.contains(typeDefinition.getName())) {
|
|
switch(oType) {
|
|
case EMBEDDEDLIST:
|
|
throw new UnsupportedDataTypeException(Type.OType.PROPERTYLIST
|
|
+ " support is currently disabled due to OrientDB bug see https://github.com/orientechnologies/orientdb/issues/7354");
|
|
case EMBEDDEDSET:
|
|
throw new UnsupportedDataTypeException(Type.OType.PROPERTYSET
|
|
+ " support is currently disabled due to OrientDB bug see https://github.com/orientechnologies/orientdb/issues/7354");
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
OProperty op = oClass.createProperty(propertyDefinition.getName(), oType);
|
|
op.setDescription(propertyDefinition.getDescription());
|
|
|
|
/*
|
|
* Mandatory and notNull does not work in distributed mode: so that on Type
|
|
* declaration they are forced to false
|
|
* ovp.setMandatory(property.isMandatory());
|
|
* ovp.setNotNull(property.isNotnull()); This information are persisted in
|
|
* Management Context
|
|
*/
|
|
op.setMandatory(false);
|
|
op.setNotNull(false);
|
|
|
|
op.setReadonly(propertyDefinition.isReadonly());
|
|
op.setRegexp(propertyDefinition.getRegexp());
|
|
|
|
if(propertyDefinition.getLinkedClass() != null) {
|
|
OClass linkedClass = getOClass(oSchema, propertyDefinition.getLinkedClass());
|
|
if(linkedClass == null) {
|
|
logger.trace("class {} not found in schema", propertyDefinition.getLinkedClass());
|
|
throw new Exception("class " + propertyDefinition.getLinkedClass() + " not found in schema");
|
|
}
|
|
|
|
if(linkedClass.isEdgeType() || linkedClass.isVertexType()) {
|
|
throw new Exception("A Property Field cannot be an Entity or a Relation");
|
|
}
|
|
|
|
op.setLinkedClass(linkedClass);
|
|
} else if(propertyDefinition.getLinkedType() != null) {
|
|
op.setLinkedType(OType.getById(propertyDefinition.getLinkedType().byteValue()));
|
|
}
|
|
}
|
|
}
|
|
|
|
OClass toBeSerializedOClass = oClass;
|
|
if(oClass instanceof OrientElementType) {
|
|
toBeSerializedOClass = getOClass(oSchema, typeDefinition.getName());
|
|
}
|
|
|
|
/*
|
|
* SchemaContextManagement managementUtility = new SchemaContextManagement();
|
|
* String ret = managementUtility.create(jsonSchema, baseType);
|
|
*/
|
|
|
|
// TODO Remove when the previous has been implemented
|
|
String ret = getTypeDefinitionAsString(toBeSerializedOClass);
|
|
|
|
logger.info("{} type registered successfully: {}", baseType.getName(), jsonSchema);
|
|
return ret;
|
|
} catch(Exception e) {
|
|
oSchema.dropClass(typeDefinition.getName());
|
|
throw e;
|
|
}
|
|
} catch(OSchemaException ex) {
|
|
if(ex.getMessage().contains("already exists")) {
|
|
throw new SchemaAlreadyPresentException(ex);
|
|
}
|
|
throw new SchemaException(ex);
|
|
} catch(SchemaException e) {
|
|
throw e;
|
|
} catch(Exception ex) {
|
|
throw new SchemaCreationException(ex);
|
|
} finally {
|
|
if(oDatabaseSession != null) {
|
|
oDatabaseSession.close();
|
|
}
|
|
}
|
|
}
|
|
|
|
protected String getSchema(String type, boolean includeSubtypes) throws SchemaNotFoundException, SchemaException {
|
|
ODatabaseSession oDatabaseSession = null;
|
|
try {
|
|
AdminSecurityContext adminSecurityContext = ContextUtility.getAdminSecurityContext();
|
|
oDatabaseSession = adminSecurityContext.getDatabaseSession(PermissionMode.WRITER);
|
|
|
|
OMetadata oMetadata = oDatabaseSession.getMetadata();
|
|
OSchema oSchema = oMetadata.getSchema();
|
|
OClass baseOClass = getTypeSchema(oSchema, type, null);
|
|
|
|
List<TypeDefinition> typeDefinitions = new ArrayList<>();
|
|
typeDefinitions.add(getTypeDefinition(baseOClass));
|
|
|
|
if(includeSubtypes) {
|
|
Collection<OClass> subClasses = baseOClass.getAllSubclasses();
|
|
for(OClass oClass : subClasses) {
|
|
typeDefinitions.add(getTypeDefinition(oClass));
|
|
}
|
|
}
|
|
|
|
return TypeBinder.serializeTypeDefinitions(typeDefinitions);
|
|
} catch(SchemaException e) {
|
|
throw e;
|
|
} catch(Exception e) {
|
|
throw new SchemaException(e);
|
|
} finally {
|
|
if(oDatabaseSession != null) {
|
|
oDatabaseSession.close();
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
@Override
|
|
public String create(String jsonSchema, AccessType accessType) throws SchemaException {
|
|
return registerTypeSchema(jsonSchema, accessType);
|
|
}
|
|
|
|
@Override
|
|
public String read(String entityType, boolean includeSubtypes) throws SchemaNotFoundException, SchemaException {
|
|
return getSchema(entityType, includeSubtypes);
|
|
}
|
|
|
|
@Override
|
|
public String update(String entityType, AccessType accessType, String jsonSchema)
|
|
throws SchemaNotFoundException, SchemaException {
|
|
throw new UnsupportedOperationException("Not Yet implemented");
|
|
}
|
|
|
|
@Override
|
|
public String delete(String entityType, AccessType accessType) throws SchemaNotFoundException {
|
|
throw new UnsupportedOperationException("Not Yet implemented");
|
|
}
|
|
|
|
}
|