package org.molgenis.util; import org.molgenis.data.DataService; import org.molgenis.data.Entity; import org.molgenis.data.EntityManager; import org.molgenis.data.MolgenisDataException; import org.molgenis.data.meta.model.Attribute; import org.molgenis.data.meta.model.EntityType; import org.molgenis.data.meta.model.Package; import org.molgenis.data.meta.model.Tag; import org.molgenis.data.support.EntityTypeUtils; import java.text.ParseException; import java.util.Iterator; import java.util.List; import java.util.Objects; import java.util.stream.Stream; import static com.google.common.collect.Iterables.*; import static com.google.common.collect.Lists.newArrayList; import static java.lang.String.format; import static java.util.stream.Collectors.toList; import static java.util.stream.StreamSupport.stream; import static org.molgenis.data.meta.AttributeType.COMPOUND; import static org.molgenis.util.MolgenisDateFormat.getDateFormat; import static org.molgenis.util.MolgenisDateFormat.getDateTimeFormat; public class EntityUtils { /** * Convert a string value to a typed value based on a non-entity-referencing attribute data type. * * @param valueStr string value * @param attr non-entity-referencing attribute * @return typed value * @throws MolgenisDataException if attribute references another entity */ public static Object getTypedValue(String valueStr, Attribute attr) { //Reference types cannot be processed because we lack an entityManager in this route. if (EntityTypeUtils.isReferenceType(attr)) { throw new MolgenisDataException( "getTypedValue(String, AttributeMetadata) can't be used for attributes referencing entities"); } return getTypedValue(valueStr, attr, null); } /** * Convert a string value to a typed value based on the attribute data type. * * @param valueStr string value * @param attr attribute * @param entityManager entity manager used to convert referenced entity values * @return typed value */ public static Object getTypedValue(String valueStr, Attribute attr, EntityManager entityManager) { if (valueStr == null) return null; switch (attr.getDataType()) { case BOOL: return Boolean.valueOf(valueStr); case CATEGORICAL: case FILE: case XREF: EntityType xrefEntity = attr.getRefEntity(); Object xrefIdValue = getTypedValue(valueStr, xrefEntity.getIdAttribute(), entityManager); return entityManager.getReference(xrefEntity, xrefIdValue); case CATEGORICAL_MREF: case MREF: case ONE_TO_MANY: EntityType mrefEntity = attr.getRefEntity(); List<String> mrefIdStrValues = ListEscapeUtils.toList(valueStr); return mrefIdStrValues.stream() .map(mrefIdStrValue -> getTypedValue(mrefIdStrValue, mrefEntity.getIdAttribute(), entityManager)).map(mrefIdValue -> entityManager.getReference(mrefEntity, mrefIdValue)) .collect(toList()); case COMPOUND: throw new IllegalArgumentException("Compound attribute has no value"); case DATE: try { return getDateFormat().parse(valueStr); } catch (ParseException e) { throw new MolgenisDataException(e); } case DATE_TIME: try { return getDateTimeFormat().parse(valueStr); } catch (ParseException e) { throw new MolgenisDataException(e); } case DECIMAL: return Double.valueOf(valueStr); case EMAIL: case ENUM: case HTML: case HYPERLINK: case SCRIPT: case STRING: case TEXT: return valueStr; case INT: return Integer.valueOf(valueStr); case LONG: return Long.valueOf(valueStr); default: throw new RuntimeException(format("Unknown attribute type [%s]", attr.getDataType().toString())); } } /** * Checks if an entity contains data or not * * @param entity */ public static boolean isEmpty(Entity entity) { for (String attr : entity.getAttributeNames()) { if (entity.get(attr) != null) return false; } return true; } public static List<Pair<EntityType, List<Attribute>>> getReferencingEntityType(EntityType entityType, DataService dataService) { List<Pair<EntityType, List<Attribute>>> referencingEntityType = newArrayList(); // get entity types that referencing the given entity (including self) String entityName = entityType.getName(); dataService.getEntityNames().forEach(otherEntityName -> { EntityType otherEntityType = dataService.getEntityType(otherEntityName); // get referencing attributes for other entity List<Attribute> referencingAttributes = null; for (Attribute attribute : otherEntityType.getAtomicAttributes()) { EntityType refEntityType = attribute.getRefEntity(); if (refEntityType != null && refEntityType.getName().equals(entityName)) { if (referencingAttributes == null) referencingAttributes = newArrayList(); referencingAttributes.add(attribute); } } // store references if (referencingAttributes != null) { referencingEntityType.add(new Pair<>(otherEntityType, referencingAttributes)); } }); return referencingEntityType; } /** * Gets all attribute names of an EntityType (atomic + compound) * * @param entityType * @return */ public static Iterable<String> getAttributeNames(EntityType entityType) { // atomic Iterable<String> atomicAttributes = transform(entityType.getAtomicAttributes(), Attribute::getName); // compound Iterable<String> compoundAttributes = transform( filter(entityType.getAttributes(), attribute -> attribute.getDataType() == COMPOUND), Attribute::getName); // all = atomic + compound return concat(atomicAttributes, compoundAttributes); } /** * Checks if an entity has another entity as one of its parents * * @param entityType * @param entityName * @return */ public static boolean doesExtend(EntityType entityType, String entityName) { EntityType parent = entityType.getExtends(); while (parent != null) { if (parent.getName().equalsIgnoreCase(entityName)) return true; parent = parent.getExtends(); } return false; } /** * Get an Iterable of entities as a stream of entities * * @param entities entities iterable * @return entities stream */ public static <E extends Entity> Stream<E> asStream(Iterable<E> entities) { return stream(entities.spliterator(), false); } /** * Returns true if entity metadata equals another entity metadata. TODO docs * * @param entityType * @param otherEntityType * @return */ public static boolean equals(EntityType entityType, EntityType otherEntityType) { if (entityType == null && otherEntityType != null) return false; if (entityType != null && otherEntityType == null) return false; if (!entityType.getName().equals(otherEntityType.getName())) return false; if (!entityType.getSimpleName().equals(otherEntityType.getSimpleName())) return false; if (!Objects.equals(entityType.getLabel(), otherEntityType.getLabel())) return false; if (!Objects.equals(entityType.getDescription(), otherEntityType.getDescription())) return false; if (entityType.isAbstract() != otherEntityType.isAbstract()) return false; //NB Thsi is at such a low level that we do not know the default backend // so we don't check if the other one is the default if the backend is null. String backend = entityType.getBackend(); String otherBackend = otherEntityType.getBackend(); if (backend == null && otherBackend != null) return false; else if (backend != null && otherBackend == null) return false; else if (backend != null && !backend.equals(otherBackend)) return false; // compare package identifiers Package package_ = entityType.getPackage(); Package otherPackage = otherEntityType.getPackage(); if (package_ == null && otherPackage != null) return false; if (package_ != null && otherPackage == null) return false; //FIXME //if (!package_.getIdValue().equals(otherPackage.getIdValue())) //{ // return false; //} // compare id attribute identifier (identifier might be null if id attribute hasn't been persisted yet) Attribute ownIdAttribute = entityType.getOwnIdAttribute(); Attribute otherOwnIdAttribute = otherEntityType.getOwnIdAttribute(); if (ownIdAttribute == null && otherOwnIdAttribute != null) return false; if (ownIdAttribute != null && otherOwnIdAttribute == null) return false; if (ownIdAttribute != null && otherOwnIdAttribute != null && !Objects .equals(ownIdAttribute.getIdentifier(), otherOwnIdAttribute.getIdentifier())) return false; // compare label attribute identifier (identifier might be null if id attribute hasn't been persisted yet) Attribute ownLabelAttribute = entityType.getOwnLabelAttribute(); Attribute otherOwnLabelAttribute = otherEntityType.getOwnLabelAttribute(); if (ownLabelAttribute == null && otherOwnLabelAttribute != null) return false; if (ownLabelAttribute != null && otherOwnLabelAttribute == null) return false; if (ownLabelAttribute != null && otherOwnLabelAttribute != null && !Objects .equals(ownLabelAttribute.getIdentifier(), otherOwnLabelAttribute.getIdentifier())) return false; // compare lookup attribute identifiers List<Attribute> lookupAttrs = newArrayList(entityType.getOwnLookupAttributes()); List<Attribute> otherLookupAttrs = newArrayList(otherEntityType.getOwnLookupAttributes()); if (lookupAttrs.size() != otherLookupAttrs.size()) return false; for (int i = 0; i < lookupAttrs.size(); ++i) { // identifier might be null if id attribute hasn't been persisted yet if (!Objects.equals(lookupAttrs.get(i).getIdentifier(), otherLookupAttrs.get(i).getIdentifier())) { return false; } } // compare extends entity identifier EntityType extendsEntityType = entityType.getExtends(); EntityType otherExtendsEntityType = otherEntityType.getExtends(); if (extendsEntityType == null && otherExtendsEntityType != null) return false; if (extendsEntityType != null && otherExtendsEntityType == null) return false; if (extendsEntityType != null && otherExtendsEntityType != null && !extendsEntityType.getName() .equals(otherExtendsEntityType.getName())) return false; // compare attributes if (!equals(entityType.getOwnAttributes(), otherEntityType.getOwnAttributes())) return false; // compare tag identifiers List<Tag> tags = newArrayList(entityType.getTags()); List<Tag> otherTags = newArrayList(otherEntityType.getTags()); if (tags.size() != otherTags.size()) return false; for (int i = 0; i < tags.size(); ++i) { if (!tags.get(i).getId().equals(otherTags.get(i).getId())) return false; } return true; } /** * Returns true if an Iterable equals another Iterable. * * @param attrsIt * @param otherAttrsIt * @return */ public static boolean equals(Iterable<Attribute> attrsIt, Iterable<Attribute> otherAttrsIt) { List<Attribute> attrs = newArrayList(attrsIt); List<Attribute> otherAttrs = newArrayList(otherAttrsIt); if (attrs.size() != otherAttrs.size()) return false; for (int i = 0; i < attrs.size(); ++i) { if (!equals(attrs.get(i), otherAttrs.get(i))) return false; } return true; } /** * Returns true if a Tag equals another Tag. * * @param tag * @param otherTag * @return */ public static boolean equals(Tag tag, Tag otherTag) { if (!Objects.equals(tag.getId(), otherTag.getId())) return false; if (!Objects.equals(tag.getObjectIri(), otherTag.getObjectIri())) return false; if (!Objects.equals(tag.getLabel(), otherTag.getLabel())) return false; if (!Objects.equals(tag.getRelationIri(), otherTag.getRelationIri())) return false; if (!Objects.equals(tag.getRelationLabel(), otherTag.getRelationLabel())) return false; if (!Objects.equals(tag.getCodeSystem(), otherTag.getCodeSystem())) return false; return true; } /** * Returns true if an attribute equals another attribute. * * @param attr * @param otherAttr * @return */ public static boolean equals(Attribute attr, Attribute otherAttr) { return equals(attr, otherAttr, true); } /** * Returns true if an attribute equals another attribute. * Skips the identifier if checkIdentifier is set to false * <p> * Other attribute identifiers can be null when importing and this attribute * has not been persisted to the db yet * </p> * * @param attr * @param otherAttr * @param checkIdentifier * @return */ public static boolean equals(Attribute attr, Attribute otherAttr, boolean checkIdentifier) { if (attr == null || otherAttr == null) { if (attr == null && otherAttr == null) return true; return false; } if (checkIdentifier) if (!Objects.equals(attr.getIdentifier(), otherAttr.getIdentifier())) return false; if (!Objects.equals(attr.getName(), otherAttr.getName())) return false; if (!Objects.equals(attr.getLabel(), otherAttr.getLabel())) return false; if (!Objects.equals(attr.getDescription(), otherAttr.getDescription())) return false; if (!Objects.equals(attr.getDataType(), otherAttr.getDataType())) return false; if (!Objects.equals(attr.isIdAttribute(), otherAttr.isIdAttribute())) return false; if (!Objects.equals(attr.isLabelAttribute(), otherAttr.isLabelAttribute())) return false; if (!Objects.equals(attr.getLookupAttributeIndex(), otherAttr.getLookupAttributeIndex())) return false; // recursively compare attribute parts if (!EntityUtils.equals(attr.getChildren(), otherAttr.getChildren())) return false; // compare entity identifier EntityType refEntity = attr.getRefEntity(); EntityType otherRefEntity = otherAttr.getRefEntity(); if (refEntity == null && otherRefEntity != null) return false; if (refEntity != null && otherRefEntity == null) return false; if (refEntity != null && otherRefEntity != null && !refEntity.getName().equals(otherRefEntity.getName())) return false; if (!Objects.equals(attr.getExpression(), otherAttr.getExpression())) return false; if (!Objects.equals(attr.isNillable(), otherAttr.isNillable())) return false; if (!Objects.equals(attr.isAuto(), otherAttr.isAuto())) return false; if (!Objects.equals(attr.isVisible(), otherAttr.isVisible())) return false; if (!Objects.equals(attr.isAggregatable(), otherAttr.isAggregatable())) return false; if (!Objects.equals(attr.getEnumOptions(), otherAttr.getEnumOptions())) return false; if (!Objects.equals(attr.getRangeMin(), otherAttr.getRangeMin())) return false; if (!Objects.equals(attr.getRangeMax(), otherAttr.getRangeMax())) return false; if (!Objects.equals(attr.isReadOnly(), otherAttr.isReadOnly())) return false; if (!Objects.equals(attr.isUnique(), otherAttr.isUnique())) return false; if (!Objects.equals(attr.getVisibleExpression(), otherAttr.getVisibleExpression())) return false; if (!Objects.equals(attr.getValidationExpression(), otherAttr.getValidationExpression())) return false; if (!Objects.equals(attr.getDefaultValue(), otherAttr.getDefaultValue())) return false; // compare tag identifiers List<Tag> tags = newArrayList(attr.getTags()); List<Tag> otherTags = newArrayList(otherAttr.getTags()); if (tags.size() != otherTags.size()) return false; for (int i = 0; i < tags.size(); ++i) { if (!Objects.equals(tags.get(i).getId(), otherTags.get(i).getId())) return false; } return true; } /** * Returns true if entity equals another entity. For referenced entities compares the referenced entity ids. * * @param entity * @param otherEntity * @return true if entity equals another entity */ public static boolean equals(Entity entity, Entity otherEntity) { if (entity == null && otherEntity != null) return false; if (entity != null && otherEntity == null) return false; if (!entity.getEntityType().getName().equals(otherEntity.getEntityType().getName())) return false; for (Attribute attr : entity.getEntityType().getAtomicAttributes()) { String attrName = attr.getName(); switch (attr.getDataType()) { case BOOL: if (!Objects.equals(entity.getBoolean(attrName), otherEntity.getBoolean(attrName))) return false; break; case CATEGORICAL: case FILE: case XREF: Entity xrefValue = entity.getEntity(attrName); Entity otherXrefValue = otherEntity.getEntity(attrName); if (xrefValue == null && otherXrefValue != null) return false; if (xrefValue != null && otherXrefValue == null) return false; if (xrefValue != null && otherXrefValue != null && !xrefValue.getIdValue() .equals(otherXrefValue.getIdValue())) return false; break; case CATEGORICAL_MREF: case ONE_TO_MANY: case MREF: List<Entity> entities = newArrayList(entity.getEntities(attrName)); List<Entity> otherEntities = newArrayList(otherEntity.getEntities(attrName)); if (entities.size() != otherEntities.size()) return false; for (int i = 0; i < entities.size(); ++i) { Entity mrefValue = entities.get(i); Entity otherMrefValue = otherEntities.get(i); if (mrefValue == null && otherMrefValue != null) return false; if (mrefValue != null && otherMrefValue == null) return false; if (mrefValue != null && otherMrefValue != null && !mrefValue.getIdValue() .equals(otherMrefValue.getIdValue())) return false; } break; case COMPOUND: throw new RuntimeException(format("Invalid data type [%s]", attr.getDataType())); case DATE: if (!Objects.equals(entity.getDate(attrName), otherEntity.getDate(attrName))) return false; break; case DATE_TIME: if (!Objects.equals(entity.getTimestamp(attrName), otherEntity.getTimestamp(attrName))) return false; break; case DECIMAL: if (!Objects.equals(entity.getDouble(attrName), otherEntity.getDouble(attrName))) return false; break; case EMAIL: case ENUM: case HTML: case HYPERLINK: case SCRIPT: case STRING: case TEXT: if (!Objects.equals(entity.getString(attrName), otherEntity.getString(attrName))) return false; break; case INT: if (!Objects.equals(entity.getInt(attrName), otherEntity.getInt(attrName))) return false; break; case LONG: if (!Objects.equals(entity.getLong(attrName), otherEntity.getLong(attrName))) return false; break; default: throw new RuntimeException(format("Unknown data type [%s]", attr.getDataType())); } } return true; } public static int hashCode(Entity entity) { int h = 0; for (Attribute attr : entity.getEntityType().getAtomicAttributes()) { int hValue = 0; String attrName = attr.getName(); switch (attr.getDataType()) { case BOOL: hValue = Objects.hashCode(entity.getBoolean(attrName)); break; case CATEGORICAL: case FILE: case XREF: Entity xrefValue = entity.getEntity(attrName); Object xrefIdValue = xrefValue != null ? xrefValue.getIdValue() : null; hValue = Objects.hashCode(xrefIdValue); break; case CATEGORICAL_MREF: case ONE_TO_MANY: case MREF: for (Entity mrefValue : entity.getEntities(attrName)) { Object mrefIdValue = mrefValue != null ? mrefValue.getIdValue() : null; hValue += Objects.hashCode(mrefIdValue); } break; case COMPOUND: throw new RuntimeException(format("Invalid data type [%s]", attr.getDataType())); case DATE: hValue = Objects.hashCode(entity.getDate(attrName)); break; case DATE_TIME: hValue = Objects.hashCode(entity.getTimestamp(attrName)); break; case DECIMAL: hValue = Objects.hashCode(entity.getDouble(attrName)); break; case EMAIL: case ENUM: case HTML: case HYPERLINK: case SCRIPT: case STRING: case TEXT: hValue = Objects.hashCode(entity.getString(attrName)); break; case INT: hValue = Objects.hashCode(entity.getInt(attrName)); break; case LONG: hValue = Objects.hashCode(entity.getLong(attrName)); break; default: throw new RuntimeException(format("Unknown data type [%s]", attr.getDataType())); } h += Objects.hashCode(attrName) ^ hValue; } int result = entity.getEntityType().getName().hashCode(); return 31 * result + h; } public static boolean entitiesEquals(Iterable<Entity> entities, Iterable<Entity> other) { if (size(entities) != size(other)) return false; Iterator<Entity> otherIt = other.iterator(); for (Entity entity : entities) { if (!equals(entity, otherIt.next())) return false; } return true; } }