package com.constellio.model.services.schemas.builders;
import static com.constellio.model.entities.schemas.MetadataValueType.REFERENCE;
import static com.constellio.model.services.search.query.logical.LogicalSearchQueryOperators.from;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.constellio.data.dao.services.DataStoreTypesFactory;
import com.constellio.model.entities.Language;
import com.constellio.model.entities.calculators.InitializedMetadataValueCalculator;
import com.constellio.model.entities.calculators.MetadataValueCalculator;
import com.constellio.model.entities.calculators.dependencies.Dependency;
import com.constellio.model.entities.calculators.dependencies.LocalDependency;
import com.constellio.model.entities.calculators.dependencies.ReferenceDependency;
import com.constellio.model.entities.schemas.Metadata;
import com.constellio.model.entities.schemas.MetadataNetwork;
import com.constellio.model.entities.schemas.MetadataNetworkBuilder;
import com.constellio.model.entities.schemas.MetadataSchema;
import com.constellio.model.entities.schemas.MetadataSchemaType;
import com.constellio.model.entities.schemas.MetadataSchemaTypes;
import com.constellio.model.entities.schemas.MetadataSchemasRuntimeException.InvalidCodeFormat;
import com.constellio.model.entities.schemas.MetadataValueType;
import com.constellio.model.entities.schemas.entries.CalculatedDataEntry;
import com.constellio.model.entities.schemas.entries.CopiedDataEntry;
import com.constellio.model.entities.schemas.entries.DataEntryType;
import com.constellio.model.services.factories.ModelLayerFactory;
import com.constellio.model.services.schemas.SchemaComparators;
import com.constellio.model.services.schemas.SchemaUtils;
import com.constellio.model.services.schemas.builders.MetadataSchemaTypesBuilderRuntimeException.CannotDeleteSchemaTypeSinceItHasRecords;
import com.constellio.model.services.search.SearchServices;
import com.constellio.model.utils.ClassProvider;
import com.constellio.model.utils.DependencyUtils;
import com.constellio.model.utils.DependencyUtilsRuntimeException;
public class MetadataSchemaTypesBuilder {
private static final Logger LOGGER = LoggerFactory.getLogger(MetadataSchemaTypesBuilder.class);
private static final String UNDERSCORE = "_";
private static final String DEFAULT = "default";
private int version;
private final List<MetadataSchemaTypeBuilder> schemaTypes = new ArrayList<>();
private final String collection;
private ClassProvider classProvider;
private List<Language> languages = new ArrayList<>();
private MetadataSchemaTypesBuilder(String collection, int version, ClassProvider classProvider, List<Language> languages) {
super();
this.collection = collection;
this.version = version;
this.classProvider = classProvider;
this.languages = Collections.unmodifiableList(languages);
}
public static MetadataSchemaTypesBuilder modify(MetadataSchemaTypes types, ClassProvider classProvider) {
MetadataSchemaTypesBuilder typesBuilder = new MetadataSchemaTypesBuilder(types.getCollection(), types.getVersion(),
classProvider, types.getLanguages());
for (MetadataSchemaType type : types.getSchemaTypes()) {
typesBuilder.schemaTypes.add(MetadataSchemaTypeBuilder.modifySchemaType(type, classProvider));
}
return typesBuilder;
}
public static MetadataSchemaTypesBuilder createWithVersion(String collection, int version, ClassProvider classProvider,
List<Language> languages) {
return new MetadataSchemaTypesBuilder(collection, version, classProvider, languages);
}
public MetadataSchemaTypes build(DataStoreTypesFactory typesFactory, ModelLayerFactory modelLayerFactory) {
List<String> dependencies = validateNoCyclicDependenciesBetweenSchemas();
validateAutomaticMetadatas();
List<MetadataSchemaType> buildedSchemaTypes = new ArrayList<>();
for (MetadataSchemaTypeBuilder schemaType : schemaTypes) {
buildedSchemaTypes.add(schemaType.build(typesFactory, modelLayerFactory));
}
List<String> referenceDefaultValues = new ArrayList<>();
for (MetadataSchemaType buildedSchemaType : buildedSchemaTypes) {
for (Metadata metadata : buildedSchemaType.getAllMetadatas().onlyWithType(MetadataValueType.REFERENCE)
.onlyWithDefaultValue()) {
if (metadata.getDefaultValue() instanceof List) {
referenceDefaultValues.addAll((List) metadata.getDefaultValue());
} else if (metadata.getDefaultValue() instanceof String) {
referenceDefaultValues.add((String) metadata.getDefaultValue());
}
}
}
Collections.sort(buildedSchemaTypes, SchemaComparators.SCHEMA_TYPE_COMPARATOR_BY_ASC_CODE);
MetadataSchemaTypes tempTypes = new MetadataSchemaTypes(collection, version + 1, buildedSchemaTypes, dependencies,
referenceDefaultValues, languages, MetadataNetwork.EMPTY());
for (MetadataSchemaType type : tempTypes.getSchemaTypes()) {
for (MetadataSchema schema : type.getAllSchemas()) {
for (Metadata metadata : schema.getMetadatas().onlyCalculated().onlyWithoutInheritance()) {
MetadataValueCalculator<?> calculator = ((CalculatedDataEntry) metadata.getDataEntry())
.getCalculator();
if (calculator instanceof InitializedMetadataValueCalculator) {
((InitializedMetadataValueCalculator) calculator).initialize(tempTypes, schema, metadata);
}
}
}
}
MetadataSchemaTypes types = new MetadataSchemaTypes(collection, version + 1, buildedSchemaTypes, dependencies,
referenceDefaultValues, languages, MetadataNetworkBuilder.buildFrom(buildedSchemaTypes));
return types;
}
public MetadataSchemaTypeBuilder createNewSchemaType(String code) {
return createNewSchemaType(code, true);
}
public MetadataSchemaTypeBuilder createNewSchemaType(String code, boolean initialize) {
MetadataSchemaTypeBuilder typeBuilder;
if (hasSchemaType(code)) {
throw new MetadataSchemaTypesBuilderRuntimeException.SchemaTypeExistent(code);
}
typeBuilder = MetadataSchemaTypeBuilder.createNewSchemaType(collection, code, this, initialize);
schemaTypes.add(typeBuilder);
return typeBuilder;
}
public boolean hasSchemaType(String code) {
for (MetadataSchemaTypeBuilder schemaType : schemaTypes) {
if (schemaType.getCode().equals(code)) {
return true;
}
}
return false;
}
public MetadataSchemaTypeBuilder getSchemaType(String code) {
for (MetadataSchemaTypeBuilder schemaType : schemaTypes) {
if (schemaType.getCode().equals(code)) {
return schemaType;
}
}
throw new MetadataSchemaTypesBuilderRuntimeException.NoSuchSchemaType(code);
}
public List<MetadataSchemaTypeBuilder> getTypes() {
List<MetadataSchemaTypeBuilder> types = new ArrayList<>();
types.addAll(schemaTypes);
return Collections.unmodifiableList(types);
}
public MetadataSchemaTypeBuilder getOrCreateNewSchemaType(String code) {
try {
return getSchemaType(code);
} catch (Exception e) {
LOGGER.debug("No schema type with code '{}', creating one", code, e);
return createNewSchemaType(code);
}
}
public MetadataSchemaBuilder getDefaultSchema(String typeCode) {
return getSchemaType(typeCode).getDefaultSchema();
}
public MetadataSchemaBuilder getSchema(String code) {
String[] parsedCode = code.split(UNDERSCORE);
if (parsedCode.length > 2) {
throw new InvalidCodeFormat(code);
}
String typeCode = parsedCode[0];
String schemaCode = parsedCode[1];
MetadataSchemaTypeBuilder schemaType = getSchemaType(typeCode);
MetadataSchemaBuilder schema = null;
if (schemaCode.equals(DEFAULT)) {
schema = schemaType.getDefaultSchema();
} else {
schema = schemaType.getCustomSchema(schemaCode);
}
if (schema == null) {
throw new MetadataSchemaTypesBuilderRuntimeException.NoSuchSchema(code);
} else {
return schema;
}
}
public MetadataBuilder getMetadata(String code) {
String[] parsedCode = code.split(UNDERSCORE);
String typeCode;
String schemaCode;
String metadataCode;
if (parsedCode.length == 3) {
typeCode = parsedCode[0];
schemaCode = parsedCode[1];
metadataCode = parsedCode[2];
} else {
throw new InvalidCodeFormat(code);
}
MetadataSchemaTypeBuilder schemaType = getSchemaType(typeCode);
MetadataBuilder metadata = null;
if (schemaCode.equals(DEFAULT)) {
metadata = schemaType.getDefaultSchema().getMetadata(metadataCode);
} else {
metadata = schemaType.getCustomSchema(schemaCode).getMetadata(metadataCode);
}
if (metadata == null) {
throw new MetadataSchemaTypesBuilderRuntimeException.NoSuchMetadata(metadataCode);
} else {
return metadata;
}
}
public int getVersion() {
return version;
}
public List<Language> getLanguages() {
return languages;
}
@Override
public String toString() {
return "MetadataSchemaTypesBuilder [version=" + version + ", schemaTypes=" + schemaTypes + "]";
}
public Set<MetadataBuilder> getAllMetadatas() {
Set<MetadataBuilder> metadatas = new HashSet<>();
for (MetadataSchemaTypeBuilder schemaType : schemaTypes) {
add(metadatas, schemaType);
}
return metadatas;
}
public Set<MetadataBuilder> getAllCopiedMetadatas() {
Set<MetadataBuilder> copiedMetadatas = new HashSet<>();
for (MetadataBuilder metadataBuilder : getAllMetadatas()) {
if (metadataBuilder.getDataEntry() != null && metadataBuilder.getDataEntry().getType() == DataEntryType.COPIED) {
copiedMetadatas.add(metadataBuilder);
}
}
return copiedMetadatas;
}
public Set<MetadataBuilder> getAllCalculatedMetadatas() {
Set<MetadataBuilder> calculatedMetadatas = new HashSet<>();
for (MetadataBuilder metadataBuilder : getAllMetadatas()) {
if (metadataBuilder.getDataEntry() != null && metadataBuilder.getDataEntry().getType() == DataEntryType.CALCULATED) {
calculatedMetadatas.add(metadataBuilder);
}
}
return calculatedMetadatas;
}
List<String> validateNoCyclicDependenciesBetweenSchemas() {
Map<String, Set<String>> typesDependencies = new HashMap<>();
for (MetadataSchemaTypeBuilder metadataSchemaType : schemaTypes) {
Set<String> types = new HashSet<>();
for (MetadataBuilder metadata : metadataSchemaType.getAllMetadatas()) {
if (metadata.getType() == REFERENCE) {
if (metadata.allowedReferencesBuilder == null) {
throw new MetadataSchemaTypesBuilderRuntimeException.NoAllowedReferences(metadata.getCode());
}
types.add(metadata.allowedReferencesBuilder.getSchemaType());
for (String schema : metadata.allowedReferencesBuilder.getSchemas()) {
types.add(newSchemaUtils().getSchemaTypeCode(schema));
}
}
}
typesDependencies.put(metadataSchemaType.getCode(), types);
}
try {
return newDependencyUtils().sortByDependency(typesDependencies);
} catch (DependencyUtilsRuntimeException.CyclicDependency e) {
throw new MetadataSchemaTypesBuilderRuntimeException.CyclicDependenciesInSchemas(e);
}
}
SchemaUtils newSchemaUtils() {
return new SchemaUtils();
}
DependencyUtils<String> newDependencyUtils() {
return new DependencyUtils<>();
}
public Map<String, Set<String>> getTypesDependencies() {
Map<String, Set<String>> typesDepencencies = new HashMap<>();
for (MetadataSchemaTypeBuilder type : this.schemaTypes) {
Set<String> dependencies = getSchemaDependenciesOf(type);
if (!dependencies.isEmpty()) {
typesDepencencies.put(type.getCode(), dependencies);
}
}
return typesDepencencies;
}
public Set<String> getSchemaDependenciesOf(MetadataSchemaTypeBuilder type) {
Set<String> otherSchemaTypesReferences = new HashSet<>();
for (MetadataBuilder metadata : type.getAllMetadatas()) {
if (metadata.getType() == REFERENCE) {
otherSchemaTypesReferences.addAll(getSchemaTypeReferences(metadata));
}
}
return otherSchemaTypesReferences;
}
private void add(Set<MetadataBuilder> metadatas, MetadataSchemaTypeBuilder schemaType) {
for (MetadataSchemaBuilder schemaBuilder : schemaType.getAllSchemas()) {
metadatas.addAll(schemaBuilder.getMetadatas());
}
}
private void validateAutomaticMetadatas() {
validateCopiedMetadatas();
validateCalculedMetadatas();
}
private void validateCopiedMetadatas() {
for (MetadataBuilder metadataBuilder : getAllCopiedMetadatas()) {
CopiedDataEntry copiedDataEntry = (CopiedDataEntry) metadataBuilder.getDataEntry();
String referenceMetadataCode = copiedDataEntry.getReferenceMetadata();
MetadataBuilder referenceMetadata = getMetadata(referenceMetadataCode);
String copiedMetadataCode = copiedDataEntry.getCopiedMetadata();
MetadataBuilder copiedMetadata = getMetadata(copiedMetadataCode);
validateCopiedMetadataMultiValues(metadataBuilder, referenceMetadataCode, referenceMetadata, copiedMetadataCode,
copiedMetadata);
validateCopiedMetadataType(metadataBuilder, copiedMetadata);
}
}
private void validateCalculedMetadatas() {
for (MetadataBuilder metadataBuilder : getAllCalculatedMetadatas()) {
CalculatedDataEntry calculatedDataEntry = (CalculatedDataEntry) metadataBuilder.getDataEntry();
if (!(calculatedDataEntry.getCalculator() instanceof InitializedMetadataValueCalculator)) {
validateCalculatedMultivalue(metadataBuilder, calculatedDataEntry);
MetadataValueType valueTypeMetadataCalculated = calculatedDataEntry.getCalculator().getReturnType();
List<? extends Dependency> dependencies = calculatedDataEntry.getCalculator().getDependencies();
boolean needToBeInitialized = calculatedDataEntry.getCalculator() instanceof InitializedMetadataValueCalculator;
if (!needToBeInitialized && (dependencies == null || dependencies.size() == 0)) {
throw new MetadataSchemaTypesBuilderRuntimeException.NoDependenciesInCalculator(calculatedDataEntry
.getCalculator().getClass().getName());
}
if (metadataBuilder.getType() != valueTypeMetadataCalculated) {
throw new MetadataSchemaTypesBuilderRuntimeException.CannotCalculateDifferentValueTypeInValueMetadata(
metadataBuilder.getCode(), metadataBuilder.getType(), valueTypeMetadataCalculated);
}
try {
validateDependenciesTypes(metadataBuilder, dependencies);
} catch (MetadataSchemaBuilderRuntimeException.NoSuchMetadata e) {
throw new MetadataSchemaTypesBuilderRuntimeException.CalculatorHasInvalidMetadataDependency(
calculatedDataEntry.getCalculator().getClass(), metadataBuilder.getCode(), e.getMetadataCode(), e);
}
}
}
}
private void validateCalculatedMultivalue(MetadataBuilder metadataBuilder, CalculatedDataEntry calculatedDataEntry) {
if (metadataBuilder.isMultivalue() && !calculatedDataEntry.getCalculator().isMultiValue()) {
throw new MetadataSchemaTypesBuilderRuntimeException.CannotCalculateASingleValueInAMultiValueMetadata(
metadataBuilder.getCode(), calculatedDataEntry.getCalculator().getClass().getName());
} else if (!metadataBuilder.isMultivalue() && calculatedDataEntry.getCalculator().isMultiValue()) {
throw new MetadataSchemaTypesBuilderRuntimeException.CannotCalculateAMultiValueInASingleValueMetadata(
metadataBuilder.getCode(), calculatedDataEntry.getCalculator().getClass().getName());
}
}
private void validateDependenciesTypes(MetadataBuilder metadataBuilder, List<? extends Dependency> dependencies) {
for (Dependency dependency : dependencies) {
if (dependency instanceof ReferenceDependency) {
validateReferencedDependency(metadataBuilder, dependency);
} else if (dependency instanceof LocalDependency) {
validateLocalDependency(metadataBuilder, dependency);
}
}
}
@SuppressWarnings("rawtypes")
private void validateLocalDependency(MetadataBuilder calculatedMetadataBuilder, Dependency dependency) {
LocalDependency localDependency = (LocalDependency) dependency;
String schemaCompleteCode = new SchemaUtils().getSchemaCode(calculatedMetadataBuilder);
if (!((LocalDependency) dependency).isMetadataCreatedLater()) {
MetadataBuilder dependencyMetadataBuilder = getMetadata(schemaCompleteCode + "_" + dependency.getLocalMetadataCode());
if (dependencyMetadataBuilder.getType() != localDependency.getReturnType()) {
throw new MetadataSchemaTypesBuilderRuntimeException.CalculatorDependencyHasInvalidValueType(
calculatedMetadataBuilder.getCode(), dependencyMetadataBuilder.getCode(),
dependencyMetadataBuilder.getType(),
localDependency.getReturnType());
}
}
}
@SuppressWarnings("rawtypes")
private void validateReferencedDependency(MetadataBuilder calculatedMetadataBuilder, Dependency dependency) {
ReferenceDependency referenceDependency = (ReferenceDependency) dependency;
String schemaCompleteCode = new SchemaUtils().getSchemaCode(calculatedMetadataBuilder);
if (!((ReferenceDependency) dependency).isMetadataCreatedLater()) {
MetadataBuilder dependencyRefMetadataBuilder = getMetadata(
schemaCompleteCode + "_" + dependency.getLocalMetadataCode());
if (dependencyRefMetadataBuilder.getAllowedReferencesBuider() != null) {
String dependencyMetaCompleteCode = dependencyRefMetadataBuilder.getAllowedReferencesBuider()
.getMetadataCompleteCode(referenceDependency.getDependentMetadataCode());
MetadataBuilder dependencyMetadata;
try {
dependencyMetadata = getMetadata(dependencyMetaCompleteCode);
} catch (MetadataSchemaBuilderRuntimeException e) {
throw new MetadataSchemaTypesBuilderRuntimeException.InvalidDependencyMetadata(dependencyMetaCompleteCode, e);
}
if (dependencyMetadata.getType() != referenceDependency.getReturnType()
|| dependencyRefMetadataBuilder.getType() != MetadataValueType.REFERENCE) {
throw new MetadataSchemaTypesBuilderRuntimeException.CalculatorDependencyHasInvalidValueType(
calculatedMetadataBuilder.getCode(), dependencyMetadata.getCode(), dependencyMetadata.getType(),
referenceDependency.getReturnType());
} else if (!dependencyMetadata.getCode().contains(DEFAULT)) {
throw new MetadataSchemaTypesBuilderRuntimeException.CannotUseACustomMetadataForCalculation(
dependencyMetadata.getCode());
}
} else {
throw new MetadataSchemaTypesBuilderRuntimeException.NoAllowedReferences(
dependencyRefMetadataBuilder.getCode());
}
}
}
private void validateCopiedMetadataType(MetadataBuilder metadataBuilder, MetadataBuilder copiedMetadata) {
if (metadataBuilder.getType() != copiedMetadata.getType()) {
throw new MetadataSchemaTypesBuilderRuntimeException.CannotCopyADifferentTypeInMetadata(
metadataBuilder.getCode(), metadataBuilder.getType().name(), copiedMetadata.getCode(),
copiedMetadata.getType().name());
} else if (!copiedMetadata.getCode().contains(DEFAULT)) {
throw new MetadataSchemaTypesBuilderRuntimeException.CannotCopyACustomMetadata(copiedMetadata.getCode());
}
}
private void validateCopiedMetadataMultiValues(MetadataBuilder metadataBuilder, String referenceMetadataCode,
MetadataBuilder referenceMetadata, String copiedMetadataCode, MetadataBuilder copiedMetadata) {
if (!metadataBuilder.isMultivalue() && (referenceMetadata.isMultivalue() || copiedMetadata.isMultivalue())) {
throw new MetadataSchemaTypesBuilderRuntimeException.CannotCopyMultiValueInSingleValueMetadata(
metadataBuilder.getCode(), referenceMetadataCode, copiedMetadataCode);
} else if (metadataBuilder.isMultivalue() && !referenceMetadata.isMultivalue() && !copiedMetadata.isMultivalue()) {
throw new MetadataSchemaTypesBuilderRuntimeException.CannotCopySingleValueInMultiValueMetadata(
metadataBuilder.getCode(), referenceMetadataCode, copiedMetadataCode);
}
}
private Set<String> getSchemaTypeReferences(MetadataBuilder metadata) {
Set<String> schemas = new HashSet<>();
for (String schemaCode : metadata.allowedReferencesBuilder.getSchemas()) {
schemas.add(schemaCode.split("_")[0]);
}
if (metadata.allowedReferencesBuilder.getSchemaType() != null) {
schemas.add(metadata.allowedReferencesBuilder.getSchemaType());
}
return schemas;
}
public String getCollection() {
return collection;
}
public void deleteSchemaType(MetadataSchemaType type, SearchServices searchServices) {
if (searchServices.hasResults(from(type).returnAll())) {
throw new CannotDeleteSchemaTypeSinceItHasRecords(type.getCode());
} else {
try {
schemaTypes.remove(getSchemaType(type.getCode()));
} catch (MetadataSchemaTypesBuilderRuntimeException.NoSuchSchemaType e) {
//OK
}
}
}
public ClassProvider getClassProvider() {
return classProvider;
}
public void setVersion(int version) {
this.version = version;
}
}