package org.molgenis.data.cache.utils; import org.molgenis.data.Entity; import org.molgenis.data.EntityManager; import org.molgenis.data.meta.AttributeType; import org.molgenis.data.meta.model.Attribute; import org.molgenis.data.meta.model.EntityType; import org.molgenis.data.support.EntityWithComputedAttributes; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import java.util.Date; import java.util.List; import java.util.Map; import static com.google.common.collect.Lists.newArrayList; import static com.google.common.collect.Maps.newHashMap; import static java.lang.String.format; import static java.util.Objects.requireNonNull; import static org.molgenis.data.EntityManager.CreationMode.NO_POPULATE; import static org.molgenis.data.support.EntityTypeUtils.isMultipleReferenceType; import static org.molgenis.data.support.EntityTypeUtils.isSingleReferenceType; /** * Hydrates and dehydrates entities. */ @Component public class EntityHydration { private static final Logger LOG = LoggerFactory.getLogger(EntityHydration.class); private final EntityManager entityManager; @Autowired public EntityHydration(EntityManager entityManager) { this.entityManager = requireNonNull(entityManager); } /** * Rehydrate an entity. Entity can be an {@link EntityWithComputedAttributes} * if there are attributes present with an expression * * @param entityType metadata of the entity to rehydrate * @param dehydratedEntity map with key value pairs representing this entity * @return hydrated entity */ @SuppressWarnings("unchecked") public Entity hydrate(Map<String, Object> dehydratedEntity, EntityType entityType) { LOG.trace("Hydrating entity: {} for entity {}", dehydratedEntity, entityType.getName()); Entity hydratedEntity = entityManager.create(entityType, NO_POPULATE); for (Attribute attribute : entityType.getAtomicAttributes()) { // Only hydrate the attribute if it is NOT computed. // Computed attributes will be calculated based on the metadata if (attribute.getExpression() == null) { String name = attribute.getName(); Object value = dehydratedEntity.get(name); if (value != null) { if (isMultipleReferenceType(attribute)) { // We can do this cast because during dehydration, mrefs and categorical mrefs are stored as a List of Object value = entityManager.getReferences(attribute.getRefEntity(), (List<Object>) value); } else if (isSingleReferenceType(attribute)) { value = entityManager.getReference(attribute.getRefEntity(), value); } } hydratedEntity.set(name, value); } } return hydratedEntity; } /** * Creates a Map containing the values required to rebuild this entity. * For references to other entities only stores the ids. * * @param entity the {@link Entity} to dehydrate * @return Map representation of the entity */ public Map<String, Object> dehydrate(Entity entity) { LOG.trace("Dehydrating entity {}", entity); Map<String, Object> dehydratedEntity = newHashMap(); EntityType entityType = entity.getEntityType(); entityType.getAtomicAttributes().forEach(attribute -> { // Only dehydrate if the attribute is NOT computed if (!attribute.hasExpression()) { String name = attribute.getName(); AttributeType type = attribute.getDataType(); dehydratedEntity.put(name, getValueBasedOnType(entity, name, type)); } }); return dehydratedEntity; } private static Object getValueBasedOnType(Entity entity, String name, AttributeType type) { Object value; switch (type) { case CATEGORICAL: case FILE: case XREF: Entity xrefEntity = entity.getEntity(name); value = xrefEntity != null ? xrefEntity.getIdValue() : null; break; case CATEGORICAL_MREF: case MREF: case ONE_TO_MANY: List<Object> mrefIdentifiers = newArrayList(); entity.getEntities(name).forEach(mrefEntity -> { if (mrefEntity != null) mrefIdentifiers.add(mrefEntity.getIdValue()); }); value = mrefIdentifiers; break; case DATE: Date date = entity.getUtilDate(name); value = date != null ? date : null; break; case DATE_TIME: Date dateTime = entity.getUtilDate(name); value = dateTime != null ? dateTime : null; break; case BOOL: case DECIMAL: case EMAIL: case ENUM: case HTML: case HYPERLINK: case INT: case LONG: case SCRIPT: case STRING: case TEXT: value = entity.get(name); break; case COMPOUND: throw new RuntimeException(format("Illegal attribute type [%s]", type.toString())); default: throw new RuntimeException(String.format("Unknown attribute type [%s]", type)); } LOG.trace("Dehydrating attribute '{}' of type [{}] resulted in value: {}", name, type.toString(), value); return value; } }