resource-registry/src/main/java/org/gcube/informationsystem/resourceregistry/instances/base/ElementManagement.java

1362 lines
40 KiB
Java

package org.gcube.informationsystem.resourceregistry.instances.base;
import java.io.IOException;
import java.text.DateFormat;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.UUID;
import javax.activation.UnsupportedDataTypeException;
import org.gcube.com.fasterxml.jackson.core.JsonProcessingException;
import org.gcube.com.fasterxml.jackson.databind.JsonNode;
import org.gcube.com.fasterxml.jackson.databind.ObjectMapper;
import org.gcube.com.fasterxml.jackson.databind.node.ArrayNode;
import org.gcube.com.fasterxml.jackson.databind.node.BooleanNode;
import org.gcube.com.fasterxml.jackson.databind.node.JsonNodeType;
import org.gcube.com.fasterxml.jackson.databind.node.ObjectNode;
import org.gcube.com.fasterxml.jackson.databind.node.TextNode;
import org.gcube.informationsystem.base.reference.AccessType;
import org.gcube.informationsystem.base.reference.Element;
import org.gcube.informationsystem.base.reference.IdentifiableElement;
import org.gcube.informationsystem.model.reference.properties.Header;
import org.gcube.informationsystem.resourceregistry.api.contexts.ContextCache;
import org.gcube.informationsystem.resourceregistry.api.exceptions.AlreadyPresentException;
import org.gcube.informationsystem.resourceregistry.api.exceptions.AvailableInAnotherContextException;
import org.gcube.informationsystem.resourceregistry.api.exceptions.NotFoundException;
import org.gcube.informationsystem.resourceregistry.api.exceptions.ResourceRegistryException;
import org.gcube.informationsystem.resourceregistry.api.exceptions.contexts.ContextException;
import org.gcube.informationsystem.resourceregistry.api.exceptions.types.SchemaException;
import org.gcube.informationsystem.resourceregistry.api.exceptions.types.SchemaViolationException;
import org.gcube.informationsystem.resourceregistry.contexts.ContextUtility;
import org.gcube.informationsystem.resourceregistry.contexts.security.AdminSecurityContext;
import org.gcube.informationsystem.resourceregistry.contexts.security.SecurityContext;
import org.gcube.informationsystem.resourceregistry.contexts.security.SecurityContext.PermissionMode;
import org.gcube.informationsystem.resourceregistry.instances.base.properties.PropertyElementManagement;
import org.gcube.informationsystem.resourceregistry.instances.model.Operation;
import org.gcube.informationsystem.resourceregistry.types.CachedType;
import org.gcube.informationsystem.resourceregistry.types.TypesCache;
import org.gcube.informationsystem.resourceregistry.utils.HeaderOrient;
import org.gcube.informationsystem.resourceregistry.utils.HeaderUtility;
import org.gcube.informationsystem.resourceregistry.utils.Utility;
import org.gcube.informationsystem.types.reference.Type;
import org.gcube.informationsystem.types.reference.entities.ResourceType;
import org.gcube.informationsystem.types.reference.properties.PropertyDefinition;
import org.gcube.smartgears.utils.InnerMethodName;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.orientechnologies.orient.core.db.document.ODatabaseDocument;
import com.orientechnologies.orient.core.metadata.schema.OClass;
import com.orientechnologies.orient.core.metadata.schema.OProperty;
import com.orientechnologies.orient.core.metadata.schema.OType;
import com.orientechnologies.orient.core.record.OElement;
import com.orientechnologies.orient.core.record.impl.ODocument;
import com.orientechnologies.orient.core.util.ODateHelper;
/**
* @author Luca Frosini (ISTI - CNR)
*/
public abstract class ElementManagement<El extends OElement, T extends Type> {
protected Logger logger = LoggerFactory.getLogger(this.getClass());
public final static String DELETED = "deleted";
public final static String AT = "@";
public final static String UNDERSCORE = "_";
protected final Set<String> ignoreKeys;
protected final Set<String> ignoreStartWithKeys;
protected Class<El> elementClass;
protected final AccessType accessType;
protected ODatabaseDocument oDatabaseDocument;
protected UUID uuid;
protected JsonNode jsonNode;
protected OClass oClass;
protected String typeName;
protected JsonNode self;
protected JsonNode complete;
protected CachedType<T> cachedType;
protected El element;
protected boolean reload;
/**
* Some operation, e.g. delete has a cascade impact which is not know a priori by the client.
* Setting this variable to false the system just simulate the operation so an interested client
* could know the impact in advance.
*
* By the default the system execute effectively the requested operation.
* So this variable is initialised as false.
*
*/
protected boolean dryRun;
/**
* An operation can affects multiple instances (e.g. create, update)
* We need to know if the instance is the entry point of the operation
* or if it is just a subordinated operation.
* This is required for example in delete to trigger sanity check
* in the Resource when the action is performed directly on the Facet.
* Resource sanity check is not required to be triggered by the Facet
* because the entry point of the action already take care
* of triggering this action (e.g. the delete was invoked on the Resource and
* each describing Facets must not trigger multiple time the sanityCheck).
*/
protected boolean entryPoint;
/**
* It is assigned only in the entry point
*/
protected Operation operation;
/**
* A Delete operation has a cascade impact we could want to know the impact
* Instances affected by a delete
*/
protected final Map<UUID,JsonNode> affectedInstances;
protected ElementManagement(AccessType accessType) {
this.accessType = accessType;
this.ignoreKeys = new HashSet<String>();
this.ignoreStartWithKeys = new HashSet<String>();
this.ignoreStartWithKeys.add(ElementManagement.AT);
this.ignoreStartWithKeys.add(ElementManagement.UNDERSCORE);
this.reload = false;
this.entryPoint = false;
this.operation = null;
/*
* By the default the system execute the operation
* which has a cascade impact so this variable is initialised as false.
*/
this.dryRun = false;
this.affectedInstances = new HashMap<>();
}
public Map<UUID,JsonNode> getAffectedInstances() {
return affectedInstances;
}
public boolean isDryRun() {
return dryRun;
}
public void setDryRun(boolean dryRun) {
this.dryRun = dryRun;
}
protected void setAsEntryPoint() {
this.entryPoint = true;
}
public void setOperation(Operation operation) {
this.operation = operation;
}
protected void cleanCachedSerialization() {
this.self = null;
this.complete = null;
}
public UUID getUUID() {
return uuid;
}
public boolean isReload() {
return reload;
}
public void setReload(boolean reload) {
this.reload = reload;
}
public AccessType getAccessType() {
return accessType;
}
protected SecurityContext workingContext;
protected SecurityContext getWorkingContext() throws ResourceRegistryException {
if(workingContext == null) {
workingContext = ContextUtility.getCurrentSecurityContext();
}
return workingContext;
}
public void setWorkingContext(SecurityContext workingContext) {
this.workingContext = workingContext;
}
public void setUUID(UUID uuid) throws ResourceRegistryException {
this.uuid = uuid;
if(jsonNode != null) {
checkUUIDMatch();
}
}
public void setJsonNode(JsonNode jsonNode) throws ResourceRegistryException {
this.jsonNode = jsonNode;
checkJsonNode();
}
public void setJson(String json) throws ResourceRegistryException {
ObjectMapper mapper = new ObjectMapper();
try {
this.jsonNode = mapper.readTree(json);
} catch(IOException e) {
throw new ResourceRegistryException(e);
}
checkJsonNode();
}
public void setODatabaseDocument(ODatabaseDocument oDatabaseDocument) {
this.oDatabaseDocument = oDatabaseDocument;
}
public void setOClass(OClass oClass) {
this.oClass = oClass;
}
protected OClass getOClass() throws SchemaException, ResourceRegistryException {
if(oClass == null) {
if(element != null) {
try {
oClass = ElementManagementUtility.getOClass(element);
if(typeName==null) {
typeName = oClass.getName();
}
getCachedType().setOClass(oClass);
}catch (ResourceRegistryException e) {
try {
oClass = getCachedType().getOClass();
if(typeName==null) {
typeName = oClass.getName();
}
}catch (Exception e1) {
throw e;
}
}
} else {
if(typeName==null) {
throw new SchemaException("Unknown type name. Please set it first.");
}
oClass = getCachedType().getOClass();
AccessType gotAccessType = cachedType.getAccessType();
if(accessType!=gotAccessType) {
throw new SchemaException(typeName + " is not a " + accessType.getName());
}
}
}
return oClass;
}
@SuppressWarnings("unchecked")
protected CachedType<T> getCachedType(){
if(cachedType==null) {
TypesCache typesCache = TypesCache.getInstance();
cachedType = (CachedType<T>) typesCache.getCachedType(typeName);
}
return cachedType;
}
public void setElementType(String elementType) throws ResourceRegistryException {
if(this.typeName == null) {
if(elementType == null || elementType.compareTo("") == 0) {
elementType = accessType.getName();
}
this.typeName = elementType;
} else {
if(elementType.compareTo(elementType) != 0) {
throw new ResourceRegistryException(
"Provided type " + elementType + " does not match with the one already known " + this.accessType);
}
}
if(jsonNode != null) {
checkERMatch();
}
}
public String getTypeName() throws NotFoundException, AvailableInAnotherContextException, ResourceRegistryException {
if(typeName==null) {
if(element!=null) {
typeName = element.getProperty(Element.CLASS_PROPERTY);
}
if(typeName==null && jsonNode!=null) {
this.typeName = getClassProperty(jsonNode);
}
if(typeName==null) {
typeName = getOClass().getName();
}
}
return typeName;
}
protected void checkJsonNode() throws ResourceRegistryException {
if(uuid == null) {
try {
uuid = org.gcube.informationsystem.utils.Utility.getUUIDFromJsonNode(jsonNode);
} catch(Exception e) {
}
} else {
checkUUIDMatch();
}
if(this.typeName == null) {
this.typeName = getClassProperty(jsonNode);
getOClass();
} else {
checkERMatch();
}
}
protected void checkERMatch() throws ResourceRegistryException {
if(jsonNode != null) {
String type = getClassProperty(jsonNode);
if(type != null && type.compareTo(typeName) != 0) {
String error = String.format("Requested type does not match with json representation %s!=%s",
typeName, type);
logger.trace(error);
throw new ResourceRegistryException(error);
}
}
getOClass();
}
protected void checkUUIDMatch() throws ResourceRegistryException {
Header header = null;
try {
header = HeaderUtility.getHeader(jsonNode, false);
} catch(Exception e) {
throw new ResourceRegistryException(e);
}
if(header != null) {
UUID resourceUUID = header.getUUID();
if(resourceUUID.compareTo(uuid) != 0) {
String error = String.format(
"UUID provided in header (%s) differs from the one (%s) used to identify the %s instance",
resourceUUID.toString(), uuid.toString(), typeName);
throw new ResourceRegistryException(error);
}
}
}
private JsonNode createSelfJsonNode() throws ResourceRegistryException {
try {
ObjectMapper objectMapper = new ObjectMapper();
ObjectNode objectNode = objectMapper.createObjectNode();
OElement element = getElement();
Set<String> keys = element.getPropertyNames();
for(String key : keys) {
Object object = element.getProperty(key);
if(object == null) {
objectNode.replace(key, null);
continue;
}
JsonNode jsonNode = getPropertyForJson(key, object);
if(jsonNode != null) {
objectNode.replace(key, jsonNode);
}
}
objectNode.put(Element.CLASS_PROPERTY, getTypeName());
Collection<String> superClasses = getCachedType().getSuperTypes();
ArrayNode arrayNode = objectMapper.valueToTree(superClasses);
objectNode.replace(Element.SUPERCLASSES_PROPERTY, arrayNode);
return objectNode;
} catch(ResourceRegistryException e) {
throw e;
} catch(Exception e) {
throw new ResourceRegistryException("Error while serializing " + getElement().toString(), e);
}
}
public JsonNode serializeAsAffectedInstance() throws ResourceRegistryException {
return serializeSelfAsJsonNode();
}
public JsonNode serializeSelfAsJsonNode() throws ResourceRegistryException {
try {
if(self==null || reload) {
self = createSelfJsonNode();
}
return self.deepCopy();
} catch(Exception e) {
throw new ResourceRegistryException(e);
}
}
protected abstract JsonNode createCompleteJsonNode() throws ResourceRegistryException;
public JsonNode serializeAsJsonNode() throws ResourceRegistryException {
try {
if(complete==null || reload) {
complete = createCompleteJsonNode();
}
return complete;
} catch(Exception e) {
throw new ResourceRegistryException(e);
}
}
protected abstract El reallyCreate() throws AlreadyPresentException, ResourceRegistryException;
public El internalCreate() throws AlreadyPresentException, ResourceRegistryException {
try {
setOperation(Operation.CREATE);
reallyCreate();
Header entityHeader = HeaderUtility.getHeader(jsonNode, true);
if(entityHeader != null) {
element.setProperty(IdentifiableElement.HEADER_PROPERTY, entityHeader);
} else {
entityHeader = HeaderUtility.addHeader(element, null);
uuid = entityHeader.getUUID();
}
getWorkingContext().addElement(element, oDatabaseDocument);
element.save();
sanityCheck();
return element;
} catch(ResourceRegistryException e) {
throw e;
} catch(Exception e) {
throw new ResourceRegistryException("Error Creating " + typeName + " with " + jsonNode, e);
}
}
protected abstract El reallyUpdate() throws NotFoundException, ResourceRegistryException;
public El internalUpdate() throws NotFoundException, ResourceRegistryException {
try {
setOperation(Operation.UPDATE);
reallyUpdate();
HeaderUtility.updateModifiedByAndLastUpdate(element);
element.save();
sanityCheck();
return element;
} catch(ResourceRegistryException e) {
throw e;
} catch(Exception e) {
throw new ResourceRegistryException("Error Updating " + typeName + " with " + jsonNode, e);
}
}
public El internalCreateOrUdate() throws ResourceRegistryException {
try {
return internalUpdate();
} catch(NotFoundException e) {
return internalCreate();
}
}
protected abstract void reallyDelete() throws NotFoundException, ResourceRegistryException;
public void internalDelete() throws NotFoundException, ResourceRegistryException {
setOperation(Operation.DELETE);
reallyDelete();
sanityCheck();
}
public void setElement(El element) throws ResourceRegistryException {
if(element == null) {
throw new ResourceRegistryException("Trying to set null " + elementClass.getSimpleName() + " in " + this);
}
this.element = element;
this.uuid = HeaderUtility.getHeader(element).getUUID();
OClass oClass = getOClass();
this.typeName = oClass.getName();
}
protected abstract NotFoundException getSpecificNotFoundException(NotFoundException e);
protected abstract AlreadyPresentException getSpecificAlreadyPresentException(String message);
public El getElement() throws NotFoundException, AvailableInAnotherContextException, ResourceRegistryException {
if(element == null) {
try {
element = retrieveElement();
} catch(NotFoundException e) {
throw e;
} catch(ResourceRegistryException e) {
throw e;
} catch(Exception e) {
throw new ResourceRegistryException(e);
}
} else {
if(reload) {
element.reload();
}
}
return element;
}
public El retrieveElement() throws NotFoundException, ResourceRegistryException {
try {
if(uuid == null) {
throw new NotFoundException("null UUID does not allow to retrieve the Element");
}
return Utility.getElementByUUID(oDatabaseDocument, typeName == null ? accessType.getName() : typeName, uuid,
elementClass);
} catch(NotFoundException e) {
throw getSpecificNotFoundException(e);
} catch(ResourceRegistryException e) {
throw e;
} catch(Exception e) {
throw new ResourceRegistryException(e);
}
}
public El retrieveElementFromAnyContext() throws NotFoundException, ResourceRegistryException {
try {
return Utility.getElementByUUIDAsAdmin(typeName == null ? accessType.getName() : typeName, uuid,
elementClass);
} catch(NotFoundException e) {
throw getSpecificNotFoundException(e);
} catch(ResourceRegistryException e) {
throw e;
} catch(Exception e) {
throw new ResourceRegistryException(e);
}
}
public abstract String reallyGetAll(boolean polymorphic) throws ResourceRegistryException;
public String all(boolean polymorphic) throws ResourceRegistryException {
ODatabaseDocument current = ContextUtility.getCurrentODatabaseDocumentFromThreadLocal();
try {
oDatabaseDocument = getWorkingContext().getDatabaseDocument(PermissionMode.READER);
setAsEntryPoint();
setOperation(Operation.QUERY);
return reallyGetAll(polymorphic);
} catch(ResourceRegistryException e) {
throw e;
} catch(Exception e) {
throw new ResourceRegistryException(e);
} finally {
if(oDatabaseDocument != null) {
oDatabaseDocument.close();
}
if(current!=null) {
current.activateOnCurrentThread();
}
}
}
public boolean exists() throws NotFoundException, AvailableInAnotherContextException, ResourceRegistryException {
ODatabaseDocument current = ContextUtility.getCurrentODatabaseDocumentFromThreadLocal();
try {
oDatabaseDocument = getWorkingContext().getDatabaseDocument(PermissionMode.READER);
setAsEntryPoint();
setOperation(Operation.EXISTS);
getElement();
return true;
} catch(ResourceRegistryException e) {
logger.error("Unable to find {} with UUID {}", accessType.getName(), uuid);
throw e;
} catch(Exception e) {
logger.error("Unable to find {} with UUID {}", accessType.getName(), uuid, e);
throw new ResourceRegistryException(e);
} finally {
if(oDatabaseDocument != null) {
oDatabaseDocument.close();
}
if(current!=null) {
current.activateOnCurrentThread();
}
}
}
public String createOrUpdate()
throws NotFoundException, AvailableInAnotherContextException, ResourceRegistryException {
ODatabaseDocument current = ContextUtility.getCurrentODatabaseDocumentFromThreadLocal();
try {
oDatabaseDocument = getWorkingContext().getDatabaseDocument(PermissionMode.WRITER);
oDatabaseDocument.begin();
boolean update = false;
try {
setAsEntryPoint();
getElement();
update = true;
internalUpdate();
} catch(NotFoundException e) {
setAsEntryPoint();
String calledMethod = InnerMethodName.instance.get();
calledMethod = calledMethod.replace("update", "create");
InnerMethodName.instance.set(calledMethod);
internalCreate();
}
oDatabaseDocument.commit();
if(update) {
setReload(true);
}
// TODO Notify to subscriptionNotification
return serializeAsJsonNode().toString();
} catch(ResourceRegistryException e) {
logger.error("Unable to update {} with UUID {}", accessType.getName(), uuid);
if(oDatabaseDocument != null) {
oDatabaseDocument.rollback();
}
throw e;
} catch(Exception e) {
logger.error("Unable to update {} with UUID {}", accessType.getName(), uuid, e);
if(oDatabaseDocument != null) {
oDatabaseDocument.rollback();
}
throw new ResourceRegistryException(e);
} finally {
if(oDatabaseDocument != null) {
oDatabaseDocument.close();
}
if(current!=null) {
current.activateOnCurrentThread();
}
}
}
public String create() throws AlreadyPresentException, ResourceRegistryException {
ODatabaseDocument current = ContextUtility.getCurrentODatabaseDocumentFromThreadLocal();
try {
oDatabaseDocument = getWorkingContext().getDatabaseDocument(PermissionMode.WRITER);
oDatabaseDocument.begin();
setAsEntryPoint();
internalCreate();
oDatabaseDocument.commit();
// TODO Notify to subscriptionNotification
return serializeAsJsonNode().toString();
} catch(ResourceRegistryException e) {
logger.error("Unable to create {}", accessType.getName());
if(oDatabaseDocument != null) {
oDatabaseDocument.rollback();
}
throw e;
} catch(Exception e) {
logger.error("Unable to create {}", accessType.getName(), e);
if(oDatabaseDocument != null) {
oDatabaseDocument.rollback();
}
throw new ResourceRegistryException(e);
} finally {
if(oDatabaseDocument != null) {
oDatabaseDocument.close();
}
if(current!=null) {
current.activateOnCurrentThread();
}
}
}
public String read() throws NotFoundException, AvailableInAnotherContextException, ResourceRegistryException {
ODatabaseDocument current = ContextUtility.getCurrentODatabaseDocumentFromThreadLocal();
try {
oDatabaseDocument = getWorkingContext().getDatabaseDocument(PermissionMode.READER);
setAsEntryPoint();
setOperation(Operation.READ);
getElement();
return serializeAsJsonNode().toString();
} catch(ResourceRegistryException e) {
logger.error("Unable to read {} with UUID {}", accessType.getName(), uuid);
throw e;
} catch(Exception e) {
logger.error("Unable to read {} with UUID {}", accessType.getName(), uuid, e);
throw new ResourceRegistryException(e);
} finally {
if(oDatabaseDocument != null) {
oDatabaseDocument.close();
}
if(current!=null) {
current.activateOnCurrentThread();
}
}
}
public String update() throws NotFoundException, AvailableInAnotherContextException, ResourceRegistryException {
ODatabaseDocument current = ContextUtility.getCurrentODatabaseDocumentFromThreadLocal();
try {
oDatabaseDocument = getWorkingContext().getDatabaseDocument(PermissionMode.WRITER);
oDatabaseDocument.begin();
setAsEntryPoint();
internalUpdate();
oDatabaseDocument.commit();
setReload(true);
// TODO Notify to subscriptionNotification
return serializeAsJsonNode().toString();
} catch(ResourceRegistryException e) {
logger.error("Unable to update {} with UUID {}", accessType.getName(), uuid);
if(oDatabaseDocument != null) {
oDatabaseDocument.rollback();
}
throw e;
} catch(Exception e) {
logger.error("Unable to update {} with UUID {}", accessType.getName(), uuid, e);
if(oDatabaseDocument != null) {
oDatabaseDocument.rollback();
}
throw new ResourceRegistryException(e);
} finally {
if(oDatabaseDocument != null) {
oDatabaseDocument.close();
}
if(current!=null) {
current.activateOnCurrentThread();
}
}
}
public void delete() throws NotFoundException, AvailableInAnotherContextException, SchemaViolationException, ResourceRegistryException {
logger.trace("Going to delete {} instance with UUID {}", accessType.getName(), uuid);
ODatabaseDocument current = ContextUtility.getCurrentODatabaseDocumentFromThreadLocal();
try {
// oDatabaseDocument = ContextUtility.getAdminSecurityContext().getDatabaseDocument(PermissionMode.WRITER);
oDatabaseDocument = getWorkingContext().getDatabaseDocument(PermissionMode.WRITER);
oDatabaseDocument.begin();
setAsEntryPoint();
internalDelete();
if(!dryRun) {
oDatabaseDocument.commit();
logger.info("{} with UUID {} was successfully deleted.", accessType.getName(), uuid);
}else {
oDatabaseDocument.rollback();
}
} catch(ResourceRegistryException e) {
logger.error("Unable to delete {} with UUID {}", accessType.getName(), uuid);
if(oDatabaseDocument != null) {
oDatabaseDocument.rollback();
}
throw e;
} catch(Exception e) {
logger.error("Unable to delete {} with UUID {}", accessType.getName(), uuid, e);
if(oDatabaseDocument != null) {
oDatabaseDocument.rollback();
}
throw new ResourceRegistryException(e);
} finally {
if(oDatabaseDocument != null) {
oDatabaseDocument.close();
}
if(current!=null) {
current.activateOnCurrentThread();
}
}
}
public Set<String> getContextsSet() throws NotFoundException, ContextException, ResourceRegistryException {
logger.trace("Going to get contexts for {} instance with UUID {}", typeName, uuid);
ODatabaseDocument current = ContextUtility.getCurrentODatabaseDocumentFromThreadLocal();
try {
AdminSecurityContext adminSecurityContext = ContextUtility.getAdminSecurityContext();
oDatabaseDocument = adminSecurityContext.getDatabaseDocument(PermissionMode.READER);
setAsEntryPoint();
setOperation(Operation.GET_METADATA);
Set<String> contexts = SecurityContext.getContexts(getElement());
return contexts;
} catch(ResourceRegistryException e) {
logger.error("Unable to get contexts for {} with UUID {}", typeName, uuid, e);
throw e;
} catch(Exception e) {
logger.error("Unable to get contexts for {} with UUID {}", typeName, uuid, e);
throw new ContextException(e);
} finally {
if(oDatabaseDocument != null) {
oDatabaseDocument.close();
}
if(current!=null) {
current.activateOnCurrentThread();
}
}
}
public String getContexts() throws NotFoundException, ContextException, ResourceRegistryException {
try {
ObjectMapper objectMapper = new ObjectMapper();
ObjectNode objectNode = getContextsAsObjectNode(objectMapper);
return objectMapper.writeValueAsString(objectNode);
} catch(ResourceRegistryException e) {
throw e;
} catch (Exception e) {
throw new ContextException(e);
}
}
private ObjectNode getContextsAsObjectNode(ObjectMapper objectMapper) throws NotFoundException, ContextException, ResourceRegistryException {
try {
Set<String> contexts = getContextsSet();
ContextCache contextCache = ContextCache.getInstance();
ObjectNode objectNode = objectMapper.createObjectNode();
for(String contextUUID : contexts) {
String contextFullName = contextCache.getContextFullNameByUUID(contextUUID);
objectNode.put(contextUUID, contextFullName);
}
return objectNode;
} catch(ResourceRegistryException e) {
throw e;
} catch (Exception e) {
throw new ContextException(e);
}
}
public ObjectNode getContextsAsObjectNode() throws NotFoundException, ContextException, ResourceRegistryException {
ObjectMapper objectMapper = new ObjectMapper();
return getContextsAsObjectNode(objectMapper);
}
public static String getClassProperty(JsonNode jsonNode) {
if(jsonNode.has(Element.CLASS_PROPERTY)) {
return jsonNode.get(Element.CLASS_PROPERTY).asText();
}
return null;
}
public static Object getObjectFromJsonNode(JsonNode value)
throws UnsupportedDataTypeException, ResourceRegistryException {
JsonNodeType jsonNodeType = value.getNodeType();
switch(jsonNodeType) {
case OBJECT:
return PropertyElementManagement.getPropertyDocument(value);
case ARRAY:
/*
* Due to bug https://github.com/orientechnologies/orientdb/issues/7354
* we should not support ArrayList
*/
List<Object> list = new ArrayList<>();
ArrayNode arrayNode = (ArrayNode) value;
for(JsonNode node : arrayNode) {
list.add(getObjectFromJsonNode(node));
}
return list;
case BINARY:
break;
case BOOLEAN:
return value.asBoolean();
case NULL:
break;
case NUMBER:
if(value.isDouble() || value.isFloat()) {
return value.asDouble();
}
if(value.isBigInteger() || value.isShort() || value.isInt()) {
return value.asInt();
}
if(value.isLong()) {
return value.asLong();
}
break;
case STRING:
return value.asText();
case MISSING:
break;
case POJO:
break;
default:
break;
}
return null;
}
public Map<String,JsonNode> getPropertyMap(JsonNode jsonNode, Set<String> ignoreKeys,
Set<String> ignoreStartWith) throws JsonProcessingException, IOException {
Map<String, JsonNode> map = new HashMap<>();
if(ignoreKeys == null) {
ignoreKeys = new HashSet<>();
}
if(ignoreStartWith == null) {
ignoreStartWith = new HashSet<>();
}
Iterator<Entry<String,JsonNode>> fields = jsonNode.fields();
OUTER_WHILE: while(fields.hasNext()) {
Entry<String,JsonNode> entry = fields.next();
String key = entry.getKey();
if(ignoreKeys.contains(key)) {
continue;
}
for(String prefix : ignoreStartWith) {
if(key.startsWith(prefix)) {
continue OUTER_WHILE;
}
}
JsonNode value = entry.getValue();
map.put(key, value);
}
return map;
}
public void setProperty(OProperty oProperty, String key, JsonNode value) throws Exception {
switch (oProperty.getType()) {
case EMBEDDED:
ODocument oDocument = PropertyElementManagement.getPropertyDocument(value);
element.setProperty(key, oDocument, OType.EMBEDDED);
break;
case EMBEDDEDLIST:
List<Object> list = new ArrayList<Object>();
Iterator<JsonNode> arrayElement = value.elements();
while(arrayElement.hasNext()) {
JsonNode elementOfArray = arrayElement.next();
Object object = null;
if(oProperty.getLinkedType()!=null) {
object = getObjectFromJsonNode(elementOfArray);
}else {
object = PropertyElementManagement.getPropertyDocument(elementOfArray);
}
list.add(object);
}
element.setProperty(key, list, OType.EMBEDDEDLIST);
break;
case EMBEDDEDSET:
Set<Object> set = new HashSet<Object>();
Iterator<JsonNode> setElement = value.elements();
while(setElement.hasNext()) {
JsonNode elementOfSet = setElement.next();
Object object = null;
if(oProperty.getLinkedType()!=null) {
object = getObjectFromJsonNode(elementOfSet);
}else {
object = PropertyElementManagement.getPropertyDocument(elementOfSet);
}
set.add(object);
}
element.setProperty(key, set, OType.EMBEDDEDSET);
break;
case EMBEDDEDMAP:
Map<String, Object> map = new HashMap<>();
Iterator<String> fieldNames = value.fieldNames();
while(fieldNames.hasNext()) {
String fieldKey = fieldNames.next();
Object object = null;
if(oProperty.getLinkedType()!=null) {
object = getObjectFromJsonNode(value.get(fieldKey));
}else {
object = PropertyElementManagement.getPropertyDocument(value.get(fieldKey));
}
map.put(fieldKey, object);
}
element.setProperty(key, map, OType.EMBEDDEDMAP);
break;
case STRING:
if(value.getNodeType() == JsonNodeType.OBJECT) {
element.setProperty(key, value.toString());
}else {
element.setProperty(key, getObjectFromJsonNode(value));
}
break;
default:
Object obj = getObjectFromJsonNode(value);
if(obj != null) {
element.setProperty(key, obj);
}
break;
}
}
public OElement updateProperties(OClass oClass, OElement element, JsonNode jsonNode, Set<String> ignoreKeys,
Set<String> ignoreStartWithKeys) throws ResourceRegistryException {
Set<String> oldKeys = element.getPropertyNames();
Map<String,JsonNode> properties;
try {
properties = getPropertyMap(jsonNode, ignoreKeys, ignoreStartWithKeys);
} catch(IOException e) {
throw new ResourceRegistryException(e);
}
oldKeys.removeAll(properties.keySet());
getOClass();
for(String key : properties.keySet()) {
try {
JsonNode value = properties.get(key);
OProperty oProperty = oClass.getProperty(key);
if(oProperty==null) {
Object object = getObjectFromJsonNode(value);
if(object != null) {
if(object instanceof ODocument) {
element.setProperty(key, object, OType.EMBEDDED);
/*
* Due to bug https://github.com/orientechnologies/orientdb/issues/7354
* we should not support ArrayList
*/
} else if(object instanceof List){
element.setProperty(key, object, OType.EMBEDDEDLIST);
} else {
element.setProperty(key, object);
}
}
}else {
setProperty(oProperty, key, value);
}
} catch(Exception e) {
String error = String.format("Error while setting property %s : %s (%s)", key,
properties.get(key).toString(), e.getMessage());
logger.error(error);
throw new ResourceRegistryException(error, e);
}
}
OUTER_FOR: for(String key : oldKeys) {
if(ignoreKeys.contains(key)) {
continue;
}
for(String prefix : ignoreStartWithKeys) {
if(key.startsWith(prefix)) {
continue OUTER_FOR;
}
}
element.removeProperty(key);
}
element.save();
return element;
}
protected JsonNode getPropertyForJson(String key, Object object) throws ResourceRegistryException {
try {
if(object == null) {
return null;
}
if(object instanceof JsonNode) {
return (JsonNode) object;
}
if(key.compareTo(IdentifiableElement.HEADER_PROPERTY) == 0) {
// Keeping the header
HeaderOrient headerOrient = HeaderUtility.getHeaderOrient((ODocument) object);
JsonNode headerJson = Utility.toJsonNode(headerOrient, false);
if(ContextUtility.getIncludeInstanceContexts().get()) {
((ObjectNode) headerJson).set(Header.__CONTEXTS, getContextsAsObjectNode());
}
return headerJson;
}
if(ignoreKeys.contains(key)) {
return null;
}
for(String prefix : ignoreStartWithKeys) {
if(key.startsWith(prefix)) {
return null;
}
}
if(object instanceof ODocument) {
ODocument oDocument = (ODocument) object;
return PropertyElementManagement.getJsonNode(oDocument);
}
if(object instanceof Date) {
OProperty oProperty = getOClass().getProperty(key);
OType oType = oProperty.getType();
DateFormat dateFormat = ODateHelper.getDateTimeFormatInstance();
switch(oType) {
case DATE:
dateFormat = ODateHelper.getDateFormatInstance();
break;
case DATETIME:
dateFormat = ODateHelper.getDateTimeFormatInstance();
break;
default:
break;
}
return new TextNode(dateFormat.format((Date) object));
}
if(object instanceof Collection) {
Collection<?> collection = (Collection<?>) object;
ObjectMapper objectMapper = new ObjectMapper();
ArrayNode arrayNode = objectMapper.createArrayNode();
for(Object o : collection) {
JsonNode obj = getPropertyForJson("PLACEHOLDER", o);
if(obj!=null) {
arrayNode.add(obj);
}
}
return arrayNode;
}
if(object instanceof Map) {
@SuppressWarnings("unchecked")
Map<String, Object> map = (Map<String, Object>) object;
ObjectMapper objectMapper = new ObjectMapper();
ObjectNode objectNode = objectMapper.createObjectNode();
for(String k : map.keySet()) {
JsonNode obj = getPropertyForJson("PLACEHOLDER", map.get(k));
objectNode.set(k, obj);
}
return objectNode;
}
if(object instanceof Boolean) {
return BooleanNode.valueOf((Boolean) object);
}
return new TextNode(object.toString());
} catch(Exception e) {
throw new ResourceRegistryException(
"Error while serializing " + key + "=" + object.toString() + " in " + getElement().toString(), e);
}
}
/*
protected List<String> getSuperclasses() throws SchemaException, ResourceRegistryException {
List<String> superClasses = new ArrayList<>();
List<OClass> allSuperClasses = getOClass().getSuperClasses();
while(allSuperClasses.size()>0) {
List<OClass> toBeAnalysed = new ArrayList<>(allSuperClasses);
allSuperClasses = new ArrayList<>();
for(OClass oSuperClass : toBeAnalysed) {
String name = oSuperClass.getName();
if(name.compareTo(StringFactory.V.toUpperCase()) == 0 || name.compareTo(StringFactory.E.toUpperCase()) == 0
|| name.compareTo(DatabaseEnvironment.O_RESTRICTED_CLASS) == 0) {
continue;
}
if(superClassesToBeExcluded.contains(name)) {
continue;
}
superClasses.add(superClasses.size(), name);
allSuperClasses.addAll(oSuperClass.getSuperClasses());
}
}
return superClasses;
}
*/
protected String getNotNullErrorMessage(String fieldName) {
StringBuffer stringBuffer = new StringBuffer();
stringBuffer.append("The type ");
stringBuffer.append(typeName);
stringBuffer.append(" defines the fields ");
stringBuffer.append(fieldName);
stringBuffer.append(" as not nullable. Null or no value has been provided instead.");
return stringBuffer.toString();
}
protected String getMandatoryErrorMessage(String fieldName) {
StringBuffer stringBuffer = new StringBuffer();
stringBuffer.append("The type ");
stringBuffer.append(typeName);
stringBuffer.append(" defines the fields ");
stringBuffer.append(fieldName);
stringBuffer.append(" as mandatory but no value has been provided.");
return stringBuffer.toString();
}
protected boolean typeSatified(TypesCache typesCache, String requiredType, String effectiveType) throws SchemaException, ResourceRegistryException {
if(requiredType.compareTo(effectiveType)==0) {
return true;
}
@SuppressWarnings("unchecked")
CachedType<ResourceType> cachedType = (CachedType<ResourceType>) typesCache.getCachedType(requiredType);
if(cachedType.getSubTypes().contains(effectiveType)) {
return true;
}
return false;
}
/*
* Get not only the properties defined in the type but also the properties
* defined in the super types
*/
protected Set<PropertyDefinition> getAllProperties() throws SchemaException, ResourceRegistryException{
TypesCache typesCache = TypesCache.getInstance();
Set<PropertyDefinition> definedProperties = getCachedType().getType().getProperties();
Set<CachedType<T>> cachedSuperTypes = new HashSet<>();
List<String> superTypes = cachedType.getSuperTypes();
for(String superTypeName : superTypes) {
@SuppressWarnings("unchecked")
CachedType<T> cachedSuperType = (CachedType<T>) typesCache.getCachedType(superTypeName);
cachedSuperTypes.add(cachedSuperType);
Type superType = cachedSuperType.getType();
Set<PropertyDefinition> properties = superType.getProperties();
if(properties!=null) {
definedProperties.addAll(properties);
}
}
return definedProperties;
}
public void sanityCheck() throws SchemaViolationException, ResourceRegistryException {
// OrientDB distributed mode does not support
// mandatory and notnull constraints due to technical problem
// https://www.orientdb.com/docs/last/java/Graph-Schema-Property.html#using-constraints
// Going to validate them here
if(operation.isSafe()) {
/*
* The sanity check is not required for a safe operation.
*/
return;
}
Set<PropertyDefinition> definedProperties = getAllProperties();
if(definedProperties==null) {
// The type could define no property
return;
}
Set<String> elementPropertyNames = getElement().getPropertyNames();
for(PropertyDefinition propertyDefinition : definedProperties) {
String fieldName = propertyDefinition.getName();
if(propertyDefinition.isMandatory() && !elementPropertyNames.contains(fieldName)) {
if(propertyDefinition.isNotnull()) {
// If the field is mandatory but null value is accepted I add the
// field as null value
element.setProperty(fieldName, null);
} else {
throw new SchemaViolationException(getMandatoryErrorMessage(fieldName));
}
}
/*
JsonNode jsonNode = instances.get(fieldName);
if(!propertyDefinition.isNotnull() && jsonNode==null) {
throw new SchemaViolationException(getNotNullErrorMessage(fieldName));
}
*/
// This validation was required to check if all mandatory fields are presents
// The validation of the values has been performed at create/update time.
}
}
}