package com.constellio.model.services.records; import static com.constellio.model.entities.schemas.entries.DataEntryType.MANUAL; import static com.constellio.model.entities.schemas.entries.DataEntryType.SEQUENCE; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.Objects; import org.apache.commons.lang3.StringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.constellio.data.dao.dto.records.RecordDTO; import com.constellio.data.dao.dto.records.RecordDeltaDTO; import com.constellio.data.utils.LangUtils; import com.constellio.model.entities.EnumWithSmallCode; import com.constellio.model.entities.records.Record; import com.constellio.model.entities.records.RecordRuntimeException; import com.constellio.model.entities.records.RecordRuntimeException.InvalidMetadata; import com.constellio.model.entities.records.RecordRuntimeException.RecordIsAlreadySaved; import com.constellio.model.entities.records.RecordRuntimeException.RecordRuntimeException_CannotModifyId; import com.constellio.model.entities.records.RecordRuntimeException.RequiredMetadataArgument; import com.constellio.model.entities.records.wrappers.RecordWrapper; import com.constellio.model.entities.schemas.Metadata; import com.constellio.model.entities.schemas.MetadataSchema; import com.constellio.model.entities.schemas.MetadataSchemaTypes; import com.constellio.model.entities.schemas.MetadataSchemasRuntimeException.NoSuchMetadata; import com.constellio.model.entities.schemas.MetadataValueType; import com.constellio.model.entities.schemas.MetadataTransiency; import com.constellio.model.entities.schemas.ModifiableStructure; import com.constellio.model.entities.schemas.Schemas; import com.constellio.model.services.encrypt.EncryptionServices; import com.constellio.model.services.records.RecordImplRuntimeException.CannotGetListForSingleValue; import com.constellio.model.services.records.RecordImplRuntimeException.RecordImplException_CannotBuildStructureValue; import com.constellio.model.services.records.RecordImplRuntimeException.RecordImplException_PopulatorReturnedNullValue; import com.constellio.model.services.records.RecordImplRuntimeException.RecordImplException_RecordCannotHaveTwoParents; import com.constellio.model.services.records.RecordImplRuntimeException.RecordImplException_UnsupportedOperationOnUnsavedRecord; import com.constellio.model.services.schemas.MetadataList; import com.constellio.model.services.schemas.SchemaUtils; import com.constellio.model.utils.EnumWithSmallCodeUtils; public class RecordImpl implements Record { private static final Logger LOGGER = LoggerFactory.getLogger(RecordImpl.class); protected final Map<String, Object> modifiedValues = new HashMap<String, Object>(); private String schemaCode; private final String collection; private final String id; private long version; private boolean disconnected = false; private RecordDTO recordDTO; private Map<String, Object> lazyTransientValues = new HashMap<String, Object>(); private Map<String, Object> eagerTransientValues = new HashMap<String, Object>(); private Map<String, Object> structuredValues; private List<String> followers; private boolean fullyLoaded; public RecordImpl(String schemaCode, String collection, String id) { if (schemaCode == null) { throw new IllegalArgumentException("Require schema code"); } if (collection == null) { throw new IllegalArgumentException("Require collection code"); } this.collection = collection; this.id = id; this.schemaCode = schemaCode; this.version = -1; this.recordDTO = null; this.followers = new ArrayList<String>(); this.fullyLoaded = true; } public RecordImpl(RecordDTO recordDTO, Map<String, Object> eagerTransientValues) { this(recordDTO, true); this.eagerTransientValues = new HashMap<>(eagerTransientValues); } public RecordImpl(RecordDTO recordDTO) { this(recordDTO, true); } public RecordImpl(RecordDTO recordDTO, boolean fullyLoaded) { this.fullyLoaded = fullyLoaded; this.id = recordDTO.getId(); this.version = recordDTO.getVersion(); this.schemaCode = (String) recordDTO.getFields().get("schema_s"); this.collection = (String) recordDTO.getFields().get("collection_s"); if (collection == null) { throw new IllegalArgumentException("Require collection code for record '" + id + "'"); } this.followers = (List<String>) recordDTO.getFields().get("followers_ss"); if (this.followers == null) { this.followers = new ArrayList<>(); } this.recordDTO = recordDTO; } public boolean isFullyLoaded() { return fullyLoaded; } public Record updateAutomaticValue(Metadata metadata, Object value) { get(metadata); Object convertedRecord; if (metadata.getEnumClass() != null) { if (metadata.isMultivalue()) { convertedRecord = EnumWithSmallCodeUtils.toSmallCodeList((List<Enum<?>>) value); } else { convertedRecord = EnumWithSmallCodeUtils.toSmallCode((Enum<?>) value); } } else { convertedRecord = value; } if (value instanceof List) { return setModifiedValue(metadata, Collections.unmodifiableList((List<?>) convertedRecord)); } else { return setModifiedValue(metadata, convertedRecord); } } @Override public Record set(Metadata metadata, Object value) { if ("".equals(value)) { value = null; } // Get may parse some metadata, and this is required later get(metadata); validateMetadata(metadata); Object convertedRecord; if (value instanceof Record) { convertedRecord = ((Record) value).getId(); } else if (value instanceof RecordWrapper) { convertedRecord = ((RecordWrapper) value).getWrappedRecord().getId(); } else if (metadata.getEnumClass() != null) { if (metadata.isMultivalue()) { convertedRecord = EnumWithSmallCodeUtils.toSmallCodeList((List<Enum<?>>) value); } else { convertedRecord = EnumWithSmallCodeUtils.toSmallCode((Enum<?>) value); } } else if (value instanceof List) { List<Object> convertedRecordList = new ArrayList<>(); for (Object item : (List) value) { if (item instanceof Record) { convertedRecordList.add(((Record) item).getId()); } else if (item instanceof RecordWrapper) { convertedRecordList.add(((RecordWrapper) item).getWrappedRecord().getId()); } else { convertedRecordList.add(item); } } convertedRecord = convertedRecordList; } else { convertedRecord = value; } // if (metadata.getInputMask() != null) { // try { // convertedRecord = MaskUtils.format(metadata.getInputMask(), (String) convertedRecord); // } catch (MaskUtilsException e) { // //Cannot convert the value, setting the raw value (will fail in further validations) // LOGGER.info("Value '" + convertedRecord + "' in metadata '" + metadata.getCode() + "' is incompatible with mask '" // + metadata.getInputMask() + "'"); // // } // } return setModifiedValue(metadata, convertedRecord); } private void validateMetadata(Metadata metadata) { if (metadata == null) { throw new RequiredMetadataArgument(); } String code = metadata.getCode(); if (code == null) { throw new InvalidMetadata("null"); } if (code.startsWith("global_default")) { return; } if (!code.startsWith(schemaCode)) { throw new InvalidMetadata(code); } if (metadata.getDataEntry().getType() != MANUAL && metadata.getDataEntry().getType() != SEQUENCE) { throw new RecordRuntimeException.CannotSetManualValueInAutomaticField(metadata); } if (metadata.getLocalCode().equals("id")) { throw new RecordRuntimeException_CannotModifyId(); } } private Record setModifiedValue(Metadata metadata, Object value) { validateSetArguments(metadata, value); Map<String, Object> map = modifiedValues; if (metadata.getTransiency() == MetadataTransiency.TRANSIENT_EAGER) { map = eagerTransientValues; } else if (metadata.getTransiency() == MetadataTransiency.TRANSIENT_LAZY) { map = lazyTransientValues; } Object correctedValue = correctValue(value); String codeAndType = metadata.getDataStoreCode(); if (structuredValues != null && structuredValues.containsKey(codeAndType)) { if (!structuredValues.get(codeAndType).equals(correctedValue)) { map.put(codeAndType, correctedValue); } else { map.remove(codeAndType); } } else { if (!isSameValueThanDTO(metadata, correctedValue, codeAndType)) { map.put(codeAndType, correctedValue); } else { map.remove(codeAndType); } } return this; } private boolean isSameValueThanDTO(Metadata metadata, Object value, String codeAndType) { boolean sameAsDTOValue = false; if (recordDTO != null) { Object dtoValue = recordDTO.getFields().get(codeAndType); if (value instanceof List && ((List) value).isEmpty()) { sameAsDTOValue = dtoValue == null || (dtoValue instanceof List && ((List) dtoValue).isEmpty()); } else { sameAsDTOValue = Objects.equals(dtoValue, value); } } return sameAsDTOValue; } private void validateSetArguments(Metadata metadata, Object value) { if (disconnected) { throw new RecordRuntimeException.CannotModifyADisconnectedRecord(id); } if (metadata == null) { throw new RecordRuntimeException.RequiredMetadataArgument(); } if (value == null) { return; } if (metadata.isMultivalue() && !(value instanceof List)) { throw new RecordRuntimeException.CannotSetNonListValueInMultivalueMetadata(metadata, value.getClass()); } if (!metadata.isMultivalue() && (value instanceof Collection)) { throw new RecordRuntimeException.CannotSetCollectionInSingleValueMetadata(metadata); } } @Override @SuppressWarnings("unchecked") public <T> T get(Metadata metadata) { if (metadata == null) { throw new RecordRuntimeException.RequiredMetadataArgument(); } if (Schemas.IDENTIFIER.getLocalCode().equals(metadata.getLocalCode())) { return (T) id; } String codeAndType = metadata.getDataStoreCode(); T returnedValue; if (metadata.getTransiency() == MetadataTransiency.TRANSIENT_LAZY) { returnedValue = (T) lazyTransientValues.get(codeAndType); } else if (metadata.getTransiency() == MetadataTransiency.TRANSIENT_EAGER) { returnedValue = (T) eagerTransientValues.get(codeAndType); } else if (modifiedValues.containsKey(codeAndType)) { returnedValue = (T) modifiedValues.get(codeAndType); } else if (recordDTO != null) { returnedValue = (T) getConvertedValue(recordDTO.getFields().get(codeAndType), metadata); } else { returnedValue = null; } if (metadata.getEnumClass() != null && returnedValue != null) { if (metadata.isMultivalue()) { returnedValue = (T) EnumWithSmallCodeUtils.toEnumList(metadata.getEnumClass(), (List<String>) returnedValue); } else { returnedValue = (T) EnumWithSmallCodeUtils.toEnum(metadata.getEnumClass(), (String) returnedValue); } } if (metadata.isMultivalue()) { if (returnedValue == null) { returnedValue = (T) Collections.unmodifiableList(Collections.emptyList()); } else { returnedValue = (T) Collections.unmodifiableList((List<? extends T>) returnedValue); } } return returnedValue; } private Object getConvertedValue(Object rawValue, Metadata metadata) { if (!isConvertedValue(metadata)) { if (metadata.getType() == MetadataValueType.DATE && "".equals(rawValue)) { return null; } return rawValue; } if (rawValue == null) { return null; } if (metadata.isEncrypted()) { EncryptionServices encryptionServices = metadata.getEncryptionServicesFactory().get(); return encryptionServices.decrypt(rawValue); } if (structuredValues == null) { structuredValues = new HashMap<>(); } Object convertedValue = structuredValues.get(metadata.getDataStoreCode()); if (convertedValue == null) { try { convertedValue = convertToStructuredValue(rawValue, metadata); } catch (RecordImplException_CannotBuildStructureValue e) { LOGGER.error("Error while building a structure value", e); convertedValue = null; } structuredValues.put(metadata.getDataStoreCode(), convertedValue); } return convertedValue; } private Object convertToStructuredValue(Object rawValue, Metadata metadata) { if (rawValue instanceof List) { List<Object> convertedValues = new ArrayList<>(); for (Object value : (List) rawValue) { if (value == null) { convertedValues.add(null); } else { convertedValues.add(convertToStructuredValue(value, metadata)); } } return convertedValues; } else { try { return metadata.getStructureFactory().build((String) rawValue); } catch (RuntimeException e) { throw new RecordImplException_CannotBuildStructureValue(id, (String) rawValue, e); } } } private Object convertStructuredValueToString(Object structureValue, Metadata metadata) { if (structureValue instanceof List) { List<Object> convertedValues = new ArrayList<>(); for (Object value : (List) structureValue) { convertedValues.add(convertStructuredValueToString(value, metadata)); } return convertedValues; } else { ModifiableStructure structure = (ModifiableStructure) structureValue; return structure == null ? null : metadata.getStructureFactory().toString(structure); } } private boolean isConvertedValue(Metadata metadata) { return metadata.getStructureFactory() != null || metadata.isEncrypted(); } @Override public <T> T getNonNullValueIn(List<Metadata> metadatas) { T nonNullValue = null; for (Metadata metadata : metadatas) { Object value = get(metadata); if (value != null) { if (nonNullValue == null) { nonNullValue = (T) value; } else { throw new RecordImplException_RecordCannotHaveTwoParents(id); } } } return nonNullValue; } @Override public <T> List<T> getList(Metadata metadata) { Object value = get(metadata); if (value == null) { return Collections.emptyList(); } else { if (metadata.isMultivalue()) { return (List<T>) value; } else { throw new CannotGetListForSingleValue(metadata.getLocalCode()); } } } public void refresh(long version, RecordDTO recordDTO) { if (recordDTO == null) { throw new RecordRuntimeException.RecordDTORequired(); } if (recordDTO.getFields().get("collection_s") == null) { throw new IllegalArgumentException("Argument recordDTO requires a collection_s value"); } if (recordDTO.getFields().get("schema_s") == null) { throw new IllegalArgumentException("Argument recordDTO requires a schema_s value"); } this.version = version; this.recordDTO = recordDTO; this.modifiedValues.clear(); if (structuredValues != null) { this.structuredValues.clear(); } } @Override public String getId() { return id; } @Override public long getVersion() { return version; } @Override public String getSchemaCode() { return schemaCode; } @Override public String getTypeCode() { return SchemaUtils.getSchemaTypeCode(schemaCode); } public RecordDTO getRecordDTO() { return recordDTO; } public Map<String, Object> getModifiedValues() { addDirtyStructuresToModifiedMap(); return Collections.unmodifiableMap(modifiedValues); } public void markAsDisconnected() { this.disconnected = true; } @Override public boolean isDirty() { addDirtyStructuresToModifiedMap(); for (String modifiedMetadata : modifiedValues.keySet()) { if (!isCreationOrModificationInfo(modifiedMetadata)) { return true; } } return false; } private void addDirtyStructuresToModifiedMap() { if (structuredValues != null) { for (Map.Entry<String, Object> structureMetadataKeyValue : structuredValues.entrySet()) { Object structureMetadataValue = structureMetadataKeyValue.getValue(); String structureMetadataKey = structureMetadataKeyValue.getKey(); if (!modifiedValues.containsKey(structureMetadataKey)) { if (structureMetadataValue != null && isDirtyStructuredValue(structureMetadataValue)) { modifiedValues.put(structureMetadataKey, structureMetadataValue); } } } } } private boolean isDirtyStructuredValue(Object value) { if (value instanceof List) { for (Object structureMetadataValueItem : (List) value) { ModifiableStructure modifiableStructure = (ModifiableStructure) structureMetadataValueItem; if (modifiableStructure != null && modifiableStructure.isDirty()) { return true; } } return false; } else { ModifiableStructure modifiableStructure = (ModifiableStructure) value; return modifiableStructure.isDirty(); } } public RecordDTO toNewDocumentDTO(MetadataSchema schema, List<FieldsPopulator> copyfieldsPopulators) { if (version != -1) { throw new RecordIsAlreadySaved(id); } return toDocumentDTO(schema, copyfieldsPopulators); } public RecordDTO toDocumentDTO(MetadataSchema schema, List<FieldsPopulator> copyfieldsPopulators) { Map<String, Object> fields = new HashMap<String, Object>(); if (recordDTO != null) { fields.putAll(recordDTO.getFields()); } for (Map.Entry<String, Object> entry : modifiedValues.entrySet()) { String metadataAtomicCode = new SchemaUtils().getLocalCodeFromDataStoreCode(entry.getKey()); Metadata metadata = schema.getMetadata(metadataAtomicCode); if (metadata.getTransiency() == MetadataTransiency.PERSISTED || metadata.getTransiency() == null) { Object value = entry.getValue(); if (metadata.isEncrypted() && value != null) { EncryptionServices encryptionServices = metadata.getEncryptionServicesFactory().get(); fields.put(entry.getKey(), encryptionServices.encrypt(value)); } else if (metadata.getStructureFactory() != null) { fields.put(entry.getKey(), convertStructuredValueToString(value, metadata)); } else { fields.put(entry.getKey(), value); } } } Map<String, Object> copyfields = new HashMap<>(); for (FieldsPopulator populator : copyfieldsPopulators) { for (Map.Entry<String, Object> entry : populator.populateCopyfields(schema, this).entrySet()) { if (entry.getValue() == null) { throw new RecordImplException_PopulatorReturnedNullValue(populator, entry.getKey()); } copyfields.put(entry.getKey(), entry.getValue()); } } for (String copiedKey : copyfields.keySet()) { fields.remove(copiedKey); } fields.remove("_version_"); fields.put("schema_s", schemaCode); fields.put("collection_s", collection); return new RecordDTO(id, version, null, fields, copyfields); } @Override public MetadataList getModifiedMetadatas(MetadataSchemaTypes schemaTypes) { MetadataList modifiedMetadatas = new MetadataList(); for (String modifiedMetadataDataStoreCode : getModifiedValues().keySet()) { String localCode = SchemaUtils.underscoreSplitWithCache(modifiedMetadataDataStoreCode)[0]; try { modifiedMetadatas.add(schemaTypes.getSchema(schemaCode).getMetadata(localCode)); } catch (NoSuchMetadata e) { Record originalRecord = getCopyOfOriginalRecord(); try { modifiedMetadatas.add(schemaTypes.getSchema(originalRecord.getSchemaCode()).getMetadata(localCode)); } catch (NoSuchMetadata e2) { } } } return modifiedMetadatas.unModifiable(); } public RecordDeltaDTO toRecordDeltaDTO(MetadataSchema schema, List<FieldsPopulator> copyfieldsPopulators) { Map<String, Object> modifiedValues = getModifiedValues(); Map<String, Object> convertedValues = new HashMap<>(modifiedValues); Map<String, Object> copyfields = new HashMap<>(); for (FieldsPopulator populator : copyfieldsPopulators) { for (Map.Entry<String, Object> entry : populator.populateCopyfields(schema, this).entrySet()) { if (entry.getValue() == null) { throw new RecordImplException_PopulatorReturnedNullValue(populator, entry.getKey()); } copyfields.put(entry.getKey(), entry.getValue()); } } for (Map.Entry<String, Object> entry : modifiedValues.entrySet()) { String localCode = new SchemaUtils().getLocalCodeFromDataStoreCode(entry.getKey()); try { Metadata metadata = schema.getMetadata(localCode); if (metadata.getTransiency() == MetadataTransiency.PERSISTED || metadata.getTransiency() == null) { Object value = entry.getValue(); if (metadata.isEncrypted() && value != null) { EncryptionServices encryptionServices = metadata.getEncryptionServicesFactory().get(); convertedValues.put(entry.getKey(), encryptionServices.encrypt(value)); } if (metadata.getStructureFactory() != null) { convertedValues.put(entry.getKey(), convertStructuredValueToString(value, metadata)); } } } catch (NoSuchMetadata e) { convertedValues.put(entry.getKey(), null); } } if (!schemaCode.equals(recordDTO.getFields().get("schema_s"))) { convertedValues.put("schema_s", schemaCode); } return new RecordDeltaDTO(id, version, convertedValues, recordDTO.getFields(), copyfields); } @Override public String toString() { return id; } // @Override // public int hashCode() { // return HashCodeBuilder.reflectionHashCode(this, "recordDTO"); // } // // @Override // public boolean equals(Object obj) { // return EqualsBuilder.reflectionEquals(this, obj, "recordDTO"); // } @Override public boolean equals(Object o) { if (this == o) return true; if (!(o instanceof RecordImpl)) return false; RecordImpl record = (RecordImpl) o; if (version != record.version) return false; if (disconnected != record.disconnected) return false; if (fullyLoaded != record.fullyLoaded) return false; if (modifiedValues != null ? !modifiedValues.equals(record.modifiedValues) : record.modifiedValues != null) return false; if (schemaCode != null ? !schemaCode.equals(record.schemaCode) : record.schemaCode != null) return false; if (collection != null ? !collection.equals(record.collection) : record.collection != null) return false; if (!id.equals(record.id)) return false; if (structuredValues != null ? !structuredValues.equals(record.structuredValues) : record.structuredValues != null) return false; return true; } @Override public int hashCode() { int result = modifiedValues != null ? modifiedValues.hashCode() : 0; result = 31 * result + (schemaCode != null ? schemaCode.hashCode() : 0); result = 31 * result + (collection != null ? collection.hashCode() : 0); result = 31 * result + id.hashCode(); result = 31 * result + (int) (version ^ (version >>> 32)); result = 31 * result + (disconnected ? 1 : 0); result = 31 * result + (structuredValues != null ? structuredValues.hashCode() : 0); result = 31 * result + (fullyLoaded ? 1 : 0); return result; } @Override public boolean isModified(Metadata metadata) { return getModifiedValues().containsKey(metadata.getDataStoreCode()); } @Override public boolean isSaved() { return version != -1; } public void merge(RecordImpl otherVersion, MetadataSchema schema) { RecordDTO otherVersionRecordDTO = otherVersion.recordDTO; List<String> removedKeys = new ArrayList<>(); for (Entry<String, Object> entry : modifiedValues.entrySet()) { String key = entry.getKey(); boolean specialField = key.equals("schema_s") || key.equals("id") || key.equals("_version_") || key.equals("autocomplete_ss") || key.equals("collection_s") || key.equals("modifiedOn_dt") || key.equals("createdOn_dt") || key.equals("modifiedById_s"); if (!specialField) { String metadataCode = new SchemaUtils().getLocalCodeFromDataStoreCode(key); if (!specialField && schema.getMetadata(metadataCode).getDataEntry().getType() == MANUAL) { Object initialValue = recordDTO.getFields().get(entry.getKey()); Object modifiedValue = entry.getValue(); Object currentValue = otherVersionRecordDTO.getFields().get(entry.getKey()); if (LangUtils.areNullableEqual(currentValue, modifiedValue)) { //Both transactions made the same change on that field removedKeys.add(entry.getKey()); } else { if (!LangUtils.areNullableEqual(currentValue, initialValue)) { throw new RecordRuntimeException.CannotMerge(schema.getCode(), id, key, currentValue, initialValue); } } } } } for (String removedKey : removedKeys) { modifiedValues.remove(removedKey); } // for (Entry<String, Object> entry : otherVersionRecordDTO.getFields().entrySet()) { // String key = entry.getKey(); // if (!isCreationOrModificationInfo(key)) { // Object dataStoreValue = correctValue(otherVersionRecordDTO.getFields().get(key)); // Object value = correctValue(recordDTO.getFields().get(key)); // // String metadataCode = new SchemaUtils().getLocalCodeFromDataStoreCode(key); // // boolean specialField = // key.equals("schema_s") || key.equals("id") || key.equals("_version_") || key.equals("autocomplete_ss") // || key.equals("collection_s"); // // if (!specialField && schema.getMetadata(metadataCode).getDataEntry().getType() != DataEntryType.CALCULATED) { // if (!LangUtils.areNullableEqual(dataStoreValue, value)) { // // if (modifiedValues.containsKey(key) && !modifiedValues.get(key).equals(dataStoreValue)) { // throw new RecordRuntimeException.CannotMerge(); // } else { // toMerge.put(entry.getKey(), dataStoreValue); // } // } // } // } // } this.version = otherVersion.getVersion(); this.recordDTO = otherVersion.getRecordDTO(); } private boolean isCreationOrModificationInfo(String key) { return Schemas.MODIFIED_BY.getDataStoreCode().equals(key) || Schemas.MODIFIED_ON.getDataStoreCode().equals(key) || Schemas.CREATED_BY.getDataStoreCode().equals(key) || Schemas.CREATED_ON.getDataStoreCode().equals(key); } private Object correctValue(Object value) { if (value instanceof Number) { return ((Number) value).doubleValue(); } else if (value instanceof EnumWithSmallCode) { return ((EnumWithSmallCode) value).getCode(); } return value; } public void markAsSaved(long version, MetadataSchema schema) { if (!isSaved()) { RecordDTO dto = toNewDocumentDTO(schema, new ArrayList<FieldsPopulator>()).withVersion( version); refresh(version, dto); } else { RecordDTO dto = recordDTO .createCopyWithDelta(toRecordDeltaDTO(schema, new ArrayList<FieldsPopulator>())) .withVersion(version); refresh(version, dto); } } @Override public String getCollection() { return collection; } @Override public String getParentId() { for (Map.Entry<String, Object> entry : modifiedValues.entrySet()) { if (entry.getKey().contains("PId_") && entry.getValue() != null) { return (String) entry.getValue(); } } if (recordDTO != null) { for (Map.Entry<String, Object> entry : recordDTO.getFields().entrySet()) { if (entry.getKey().contains("PId_") && entry.getValue() != null) { return (String) entry.getValue(); } } } return null; } public Map<String, Object> getLoadedStructuredValues() { return structuredValues; } @Override public boolean isActive() { return Boolean.TRUE != get(Schemas.LOGICALLY_DELETED_STATUS); } @Override public boolean isDisconnected() { return disconnected; } @Override public List<String> getFollowers() { if (modifiedValues.containsKey("followers_ss")) { followers = (List<String>) modifiedValues.get("followers_ss"); } return followers; } @Override public Record getCopyOfOriginalRecord() { if (recordDTO == null) { throw new RecordImplException_UnsupportedOperationOnUnsavedRecord("getCopyOfOriginalRecord", id); } return new RecordImpl(recordDTO, eagerTransientValues); } @Override public String getIdTitle() { String title = getTitle(); return id + (title == null ? "" : (":" + title)); } @Override public String getTitle() { return get(Schemas.TITLE); } @Override public void removeAllFieldsStartingWith(String prefix) { if (recordDTO != null) { for (String entry : recordDTO.getFields().keySet()) { if (entry.startsWith(prefix)) { modifiedValues.put(entry, null); } } for (String entry : recordDTO.getCopyFields().keySet()) { if (entry.startsWith(prefix)) { modifiedValues.put(entry, null); } } } } @Override public void markAsModified(Metadata metadata) { modifiedValues.put(metadata.getDataStoreCode(), get(metadata)); } // @Override // public void changeSchemaTo(String newSchemaCodeOrLocalCode) { // SchemaUtils schemaUtils = new SchemaUtils(); // if (isSaved()) { // throw new RecordImplException_CannotChangeSchemaOfSavedRecord(id); // } // String currentType = schemaUtils.getSchemaTypeCode(schemaCode); // String newSchemaCode; // if (newSchemaCodeOrLocalCode.contains("_")) { // String newType = schemaUtils.getSchemaTypeCode(newSchemaCodeOrLocalCode); // if (!currentType.equals(newType)) { // throw new RecordImplException_CannotChangeTypeOfRecord(id); // } // newSchemaCode = newSchemaCodeOrLocalCode; // } else { // newSchemaCode = currentType + "_" + newSchemaCodeOrLocalCode; // } // // this.schemaCode = newSchemaCode; // } @Override public void changeSchema(MetadataSchema wasSchema, MetadataSchema newSchema) { LOGGER.info("changeSchema (" + wasSchema.getCode() + "=>" + newSchema.getCode() + ")"); Map<String, Metadata> newSchemasMetadatas = new HashMap<>(); for (Metadata metadata : newSchema.getMetadatas()) { newSchemasMetadatas.put(metadata.getLocalCode(), metadata); } for (Metadata wasMetadata : wasSchema.getMetadatas()) { if (wasMetadata.getDataEntry().getType() == MANUAL) { Metadata newMetadata = newSchemasMetadatas.get(wasMetadata.getLocalCode()); if (newMetadata == null || !newMetadata.isSameValueThan(wasMetadata)) { set(wasMetadata, null); } else { Object value = get(wasMetadata); if (value == null || isBlankString(value) || isEmptyList(value) || isDefaultValue(value, wasMetadata)) { set(wasMetadata, null); } } } } this.schemaCode = newSchema.getCode(); for (Metadata metadata : newSchema.getMetadatas()) { if (metadata.getDataEntry().getType() == MANUAL) { if (metadata.isMultivalue()) { List<Object> value = getList(metadata); if (value.isEmpty()) { set(metadata, metadata.getDefaultValue()); } } else { Object value = get(metadata); if (value == null) { set(metadata, metadata.getDefaultValue()); } } } } markAsModified(Schemas.SCHEMA); } @Override public boolean isOfSchemaType(String type) { return schemaCode.startsWith(type + "_"); } private static boolean isDefaultValue(Object value, Metadata metadata) { if (metadata.isMultivalue()) { Object defaultValue = metadata.getDefaultValue(); if (defaultValue == null || isEmptyList(defaultValue)) { return isEmptyList(value); } else { List<Object> defaultListValues = new ArrayList<>((List<?>) metadata.getDefaultValue()); if (isEmptyList(value)) { return false; } else if (value instanceof List) { List<?> listValue = new ArrayList<>((List<?>) value); return listValue.equals(defaultListValues); } else { return false; } } } else { return LangUtils.isEqual(value, metadata.getDefaultValue()); } } public static boolean isListWithSameContentEqual(Object value1, Object value2) { if (value1 == null) { return value2 == null; } else { if (value2 == null) { return false; } else { if (value1 instanceof List) ; } return value1.equals(value2); } } private static boolean isEmptyList(Object value) { return (value instanceof List) && ((List) value).isEmpty(); } private boolean isBlankString(Object value) { return (value instanceof String) && StringUtils.isBlank((String) value); } public Map<String, Object> getLazyTransientValues() { return lazyTransientValues; } public Map<String, Object> getEagerTransientValues() { return eagerTransientValues; } @Override public <T> void addValueToList(Metadata metadata, T value) { List<T> values = new ArrayList<>(this.<T>getList(metadata)); values.add(value); set(metadata, values); } @Override public <T> void removeValueFromList(Metadata metadata, T value) { List<T> values = new ArrayList<>(this.<T>getList(metadata)); values.remove(value); set(metadata, values); } }