io.evitadb.api.requestResponse.schema.dto.EntitySchema Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of evita_api Show documentation
Show all versions of evita_api Show documentation
Module contains external API of the evitaDB.
The newest version!
/*
*
* _ _ ____ ____
* _____ _(_) |_ __ _| _ \| __ )
* / _ \ \ / / | __/ _` | | | | _ \
* | __/\ V /| | || (_| | |_| | |_) |
* \___| \_/ |_|\__\__,_|____/|____/
*
* Copyright (c) 2023-2024
*
* Licensed under the Business Source License, Version 1.1 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://github.com/FgForrest/evitaDB/blob/master/LICENSE
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.evitadb.api.requestResponse.schema.dto;
import io.evitadb.api.exception.InvalidSchemaMutationException;
import io.evitadb.api.exception.ReferenceNotFoundException;
import io.evitadb.api.exception.SchemaAlteringException;
import io.evitadb.api.requestResponse.schema.AssociatedDataSchemaContract;
import io.evitadb.api.requestResponse.schema.AttributeSchemaContract;
import io.evitadb.api.requestResponse.schema.CatalogSchemaContract;
import io.evitadb.api.requestResponse.schema.EntityAttributeSchemaContract;
import io.evitadb.api.requestResponse.schema.EntitySchemaContract;
import io.evitadb.api.requestResponse.schema.EvolutionMode;
import io.evitadb.api.requestResponse.schema.ReferenceSchemaContract;
import io.evitadb.api.requestResponse.schema.SortableAttributeCompoundSchemaContract;
import io.evitadb.api.requestResponse.schema.SortableAttributeCompoundSchemaContract.AttributeElement;
import io.evitadb.dataType.ReferencedEntityPredecessor;
import io.evitadb.exception.EvitaInvalidUsageException;
import io.evitadb.utils.Assert;
import io.evitadb.utils.CollectionUtils;
import io.evitadb.utils.ComparatorUtils;
import io.evitadb.utils.NamingConvention;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import javax.annotation.concurrent.Immutable;
import javax.annotation.concurrent.ThreadSafe;
import java.io.Serial;
import java.io.Serializable;
import java.lang.reflect.Array;
import java.util.*;
import java.util.Map.Entry;
import java.util.function.Function;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import static io.evitadb.utils.Assert.isTrue;
import static java.util.Optional.ofNullable;
/**
* Internal implementation of {@link EntitySchemaContract}.
*/
@Immutable
@ThreadSafe
@EqualsAndHashCode(of = {"version", "name"})
public final class EntitySchema implements EntitySchemaContract {
@Serial private static final long serialVersionUID = -209500573660545111L;
private final int version;
@Getter @Nonnull private final String name;
@Getter @Nonnull private final Map nameVariants;
@Getter @Nullable private final String description;
@Getter @Nullable private final String deprecationNotice;
@Getter private final boolean withGeneratedPrimaryKey;
@Getter private final boolean withHierarchy;
@Getter private final boolean withPrice;
@Getter private final int indexedPricePlaces;
@Getter @Nonnull private final Set locales;
@Getter @Nonnull private final Set currencies;
/**
* Contains index of all {@link SortableAttributeCompoundSchema} that could be used as sortable attribute compounds
* of entity of this type.
*/
@Nonnull private final Map sortableAttributeCompounds;
/**
* Index of attribute names that allows to quickly lookup sortable attribute compound schemas by name in specific
* naming convention. Key is the name in specific name convention, value is array of size {@link NamingConvention#values()}
* where reference to {@link SortableAttributeCompoundSchema} is placed on index of naming convention that matches
* the key.
*/
private final Map sortableAttributeCompoundNameIndex;
/**
* Contains index of all {@link AttributeSchema} that could be used as attributes of entity of this type.
*/
private final Map attributes;
/**
* Index of attribute names that allows to quickly lookup attribute schemas by attribute name in specific naming
* convention. Key is the name in specific name convention, value is array of size {@link NamingConvention#values()}
* where reference to {@link AttributeSchema} is placed on index of naming convention that matches the key.
*/
private final Map attributeNameIndex;
/**
* Contains index of all {@link AssociatedDataSchema} that could be used as associated data of entity of this type.
*/
private final Map associatedData;
/**
* Index of associated data names that allows to quickly lookup attribute schemas by associated data name in
* specific naming convention. Key is the name in specific name convention, value is array of size
* {@link NamingConvention#values()} where reference to {@link AssociatedDataSchema} is placed on index of naming
* convention that matches the key.
*/
private final Map associatedDataNameIndex;
/**
* Contains index of all {@link ReferenceSchema} that could be used as references of entity of this type.
*/
private final Map references;
/**
* Index of associated data names that allows to quickly lookup reference schemas by reference data name in
* specific naming convention. Key is the name in specific name convention, value is array of size
* {@link NamingConvention#values()} where reference to {@link ReferenceSchema} is placed on index of naming
* convention that matches the key.
*/
private final Map referenceNameIndex;
/**
* List of all reflected reference schemas within {@link #references}, prepared for quick lookup.
*/
private final Map reflectedReferences;
/**
* Contains allowed evolution modes for the entity schema.
*/
@Getter private final Set evolutionMode;
/**
* Contains all definitions of the attributes that return false in method {@link AttributeSchema#isNullable()}.
*/
@Getter private final Collection nonNullableOrDefaultValueAttributes;
/**
* Contains all definitions of the associated data that return false in method {@link AssociatedDataSchema#isNullable()}.
*/
@Getter private final Collection nonNullableAssociatedData;
/**
* Index contains collections of sortable attribute compounds that reference the attribute with the name equal
* to a key of this index.
*/
@Nonnull private final Map> attributeToSortableAttributeCompoundIndex;
/**
* Method generates name variant index used for quickly looking up for schemas by name in specific name convention.
*/
@Nonnull
public static Map _internalGenerateNameVariantIndex(
@Nonnull Collection items,
@Nonnull Function> nameVariantsFetcher
) {
if (items.isEmpty()) {
return new HashMap<>();
}
final Map nameIndex = CollectionUtils.createHashMap(NamingConvention.values().length * items.size());
for (T schema : items) {
_internalAddNameVariantsToIndex(nameIndex, schema, nameVariantsFetcher);
}
return Collections.unmodifiableMap(nameIndex);
}
/**
* This method is for internal purposes only. It could be used for reconstruction of original Entity from different
* package than current, but still internal code of the Evita ecosystems.
*
* Do not use this method from in the client code!
*/
@Nonnull
public static EntitySchema _internalBuild(@Nonnull String name) {
return new EntitySchema(
1,
name, NamingConvention.generate(name),
null, null, false, false, false,
2,
Collections.emptySet(),
Collections.emptySet(),
Collections.emptyMap(),
Collections.emptyMap(),
Collections.emptyMap(),
EnumSet.allOf(EvolutionMode.class),
Collections.emptyMap()
);
}
/**
* This method is for internal purposes only. It could be used for reconstruction of original Entity from different
* package than current, but still internal code of the Evita ecosystems.
*
* Do not use this method from in the client code!
*/
@Nonnull
public static EntitySchema _internalBuild(
int version,
@Nonnull String name,
@Nullable String description,
@Nullable String deprecationNotice,
boolean withGeneratedPrimaryKey,
boolean withHierarchy,
boolean withPrice,
int indexedPricePlaces,
@Nonnull Set locales,
@Nonnull Set currencies,
@Nonnull Map attributes,
@Nonnull Map associatedData,
@Nonnull Map references,
@Nonnull Set evolutionMode,
@Nonnull Map sortableAttributeCompounds
) {
return new EntitySchema(
version, name, NamingConvention.generate(name),
description, deprecationNotice,
withGeneratedPrimaryKey, withHierarchy, withPrice,
indexedPricePlaces,
locales,
currencies,
attributes,
associatedData,
references,
evolutionMode,
sortableAttributeCompounds
);
}
/**
* This method is for internal purposes only. It could be used for reconstruction of original Entity from different
* package than current, but still internal code of the Evita ecosystems.
*
* Do not use this method from in the client code!
*/
@Nonnull
public static EntitySchema _internalBuild(
int version,
@Nonnull String name,
@Nonnull Map nameVariants,
@Nullable String description,
@Nullable String deprecationNotice,
boolean withGeneratedPrimaryKey,
boolean withHierarchy,
boolean withPrice,
int indexedPricePlaces,
@Nonnull Set locales,
@Nonnull Set currencies,
@Nonnull Map attributes,
@Nonnull Map associatedData,
@Nonnull Map references,
@Nonnull Set evolutionMode,
@Nonnull Map sortableAttributeCompounds
) {
return new EntitySchema(
version, name, nameVariants,
description, deprecationNotice,
withGeneratedPrimaryKey, withHierarchy, withPrice,
indexedPricePlaces,
locales,
currencies,
attributes,
associatedData,
references,
evolutionMode,
sortableAttributeCompounds
);
}
/**
* Ensures that the specified attribute is not of type {@link ReferencedEntityPredecessor}. This type is allowed
* only on references.
*
* @param attributeName The name of the attribute being checked.
* @param theType The type of the attribute being checked.
*/
public static void assertNotReferencedEntityPredecessor(
@Nonnull String attributeName,
@Nonnull Class extends Serializable> theType
) {
final Class> plainType = theType.isArray() ?
theType.getComponentType() : theType;
isTrue(
!ReferencedEntityPredecessor.class.equals(plainType),
() -> new InvalidSchemaMutationException(
"Attribute " + attributeName + " cannot be of type " + theType + "!"
)
);
}
/**
* Method generates name variant index used for quickly looking up for schemas by name in specific name convention.
*/
static void _internalAddNameVariantsToIndex(
@Nonnull Map nameIndex,
@Nonnull T schema,
@Nonnull Function> nameVariantsFetcher
) {
for (Entry entry : nameVariantsFetcher.apply(schema).entrySet()) {
nameIndex.compute(
entry.getValue(),
(theName, existingArray) -> {
@SuppressWarnings("unchecked") final T[] result = existingArray == null ?
(T[]) Array.newInstance(schema.getClass(), NamingConvention.values().length) : existingArray;
result[entry.getKey().ordinal()] = schema;
return result;
}
);
}
}
/**
* Method converts the "unknown" contract implementation and converts it to the "known" {@link AttributeSchema}
* so that the entity schema can access the internal API of it.
*/
@Nonnull
static EntityAttributeSchemaContract toEntityAttributeSchema(@Nonnull EntityAttributeSchemaContract attributeSchemaContract) {
if (attributeSchemaContract instanceof EntityAttributeSchema attributeSchema) {
return attributeSchema;
} else if (attributeSchemaContract instanceof GlobalAttributeSchema globalAttributeSchema) {
return globalAttributeSchema;
} else {
//noinspection unchecked,rawtypes
return EntityAttributeSchema._internalBuild(
attributeSchemaContract.getName(),
attributeSchemaContract.getNameVariants(),
attributeSchemaContract.getDescription(),
attributeSchemaContract.getDeprecationNotice(),
attributeSchemaContract.getUniquenessType(),
attributeSchemaContract.isFilterable(),
attributeSchemaContract.isSortable(),
attributeSchemaContract.isLocalized(),
attributeSchemaContract.isNullable(),
attributeSchemaContract.isRepresentative(),
(Class) attributeSchemaContract.getType(),
attributeSchemaContract.getDefaultValue(),
attributeSchemaContract.getIndexedDecimalPlaces()
);
}
}
/**
* Method converts the "unknown" contract implementation and converts it to the "known" {@link AttributeSchema}
* so that the entity schema can access the internal API of it.
*/
@Nonnull
static AttributeSchema toReferenceAttributeSchema(@Nonnull AttributeSchemaContract attributeSchemaContract) {
//noinspection unchecked,rawtypes
return attributeSchemaContract instanceof AttributeSchema attributeSchema ?
attributeSchema :
AttributeSchema._internalBuild(
attributeSchemaContract.getName(),
attributeSchemaContract.getNameVariants(),
attributeSchemaContract.getDescription(),
attributeSchemaContract.getDeprecationNotice(),
attributeSchemaContract.getUniquenessType(),
attributeSchemaContract.isFilterable(),
attributeSchemaContract.isSortable(),
attributeSchemaContract.isLocalized(),
attributeSchemaContract.isNullable(),
(Class) attributeSchemaContract.getType(),
attributeSchemaContract.getDefaultValue(),
attributeSchemaContract.getIndexedDecimalPlaces()
);
}
/**
* Method converts the "unknown" contract implementation and converts it to the "known"
* {@link SortableAttributeCompoundSchema} so that the entity schema can access the internal API of it.
*/
@Nonnull
static SortableAttributeCompoundSchema toSortableAttributeCompoundSchema(@Nonnull SortableAttributeCompoundSchemaContract sortableAttributeCompoundSchemaContract) {
return sortableAttributeCompoundSchemaContract instanceof SortableAttributeCompoundSchema sortableAttributeCompoundSchema ?
sortableAttributeCompoundSchema :
SortableAttributeCompoundSchema._internalBuild(
sortableAttributeCompoundSchemaContract.getName(),
sortableAttributeCompoundSchemaContract.getNameVariants(),
sortableAttributeCompoundSchemaContract.getDescription(),
sortableAttributeCompoundSchemaContract.getDeprecationNotice(),
sortableAttributeCompoundSchemaContract.getAttributeElements()
);
}
/**
* Method converts the "unknown" contract implementation and converts it to the "known" {@link AssociatedDataSchema}
* so that the entity schema can access the internal API of it.
*/
@Nonnull
private static AssociatedDataSchema toAssociatedDataSchema(@Nonnull AssociatedDataSchemaContract associatedDataSchemaContract) {
return associatedDataSchemaContract instanceof AssociatedDataSchema associatedDataSchema ?
associatedDataSchema :
AssociatedDataSchema._internalBuild(
associatedDataSchemaContract.getName(),
associatedDataSchemaContract.getNameVariants(),
associatedDataSchemaContract.getDescription(),
associatedDataSchemaContract.getDeprecationNotice(),
associatedDataSchemaContract.getType(),
associatedDataSchemaContract.isLocalized(),
associatedDataSchemaContract.isNullable()
);
}
/**
* Method converts the "unknown" contract implementation and converts it to the "known" {@link ReferenceSchema}
* so that the entity schema can access the internal API of it.
*/
@Nonnull
private static ReferenceSchema toReferenceSchema(@Nonnull ReferenceSchemaContract referenceSchemaContract) {
return referenceSchemaContract instanceof ReferenceSchema referenceSchema ?
referenceSchema :
ReferenceSchema._internalBuild(
referenceSchemaContract.getName(),
referenceSchemaContract.getNameVariants(),
referenceSchemaContract.getDescription(),
referenceSchemaContract.getDeprecationNotice(),
referenceSchemaContract.getReferencedEntityType(),
referenceSchemaContract.getEntityTypeNameVariants(entityType -> null),
referenceSchemaContract.isReferencedEntityTypeManaged(),
referenceSchemaContract.getCardinality(),
referenceSchemaContract.getReferencedGroupType(),
referenceSchemaContract.getGroupTypeNameVariants(entityType -> null),
referenceSchemaContract.isReferencedGroupTypeManaged(),
referenceSchemaContract.isIndexed(),
referenceSchemaContract.isFaceted(),
referenceSchemaContract.getAttributes(),
referenceSchemaContract.getSortableAttributeCompounds()
);
}
private EntitySchema(
int version,
@Nonnull String name,
@Nonnull Map nameVariants,
@Nullable String description,
@Nullable String deprecationNotice,
boolean withGeneratedPrimaryKey,
boolean withHierarchy,
boolean withPrice,
int indexedPricePlaces,
@Nonnull Set locales,
@Nonnull Set currencies,
@Nonnull Map attributes,
@Nonnull Map associatedData,
@Nonnull Map references,
@Nonnull Set evolutionMode,
@Nonnull Map sortableAttributeCompounds
) {
this.version = version;
this.name = name;
this.nameVariants = Collections.unmodifiableMap(nameVariants);
this.description = description;
this.deprecationNotice = deprecationNotice;
this.withGeneratedPrimaryKey = withGeneratedPrimaryKey;
this.withHierarchy = withHierarchy;
this.withPrice = withPrice;
this.indexedPricePlaces = indexedPricePlaces;
this.locales = Collections.unmodifiableSet(locales.stream().collect(Collectors.toCollection(() -> new TreeSet<>(ComparatorUtils.localeComparator()))));
this.currencies = Collections.unmodifiableSet(currencies.stream().collect(Collectors.toCollection(() -> new TreeSet<>(ComparatorUtils.currencyComparator()))));
this.attributes = Collections.unmodifiableMap(
attributes.entrySet()
.stream()
.collect(
Collectors.toMap(
Entry::getKey,
it -> toEntityAttributeSchema(it.getValue()),
(a, b) -> {
throw new IllegalStateException("Duplicate key " + a);
},
LinkedHashMap::new
)
)
);
this.attributeNameIndex = _internalGenerateNameVariantIndex(this.attributes.values(), AttributeSchemaContract::getNameVariants);
this.associatedData = Collections.unmodifiableMap(
associatedData.entrySet()
.stream()
.collect(
Collectors.toMap(
Entry::getKey,
it -> toAssociatedDataSchema(it.getValue()),
(a, b) -> {
throw new IllegalStateException("Duplicate key " + a.getName());
},
LinkedHashMap::new
)
)
);
this.associatedDataNameIndex = _internalGenerateNameVariantIndex(this.associatedData.values(), AssociatedDataSchemaContract::getNameVariants);
this.references = Collections.unmodifiableMap(
references.entrySet()
.stream()
.collect(
Collectors.toMap(
Entry::getKey,
it -> toReferenceSchema(it.getValue()),
(a, b) -> {
throw new IllegalStateException("Duplicate key " + a.getName());
},
LinkedHashMap::new
)
)
);
;
this.referenceNameIndex = _internalGenerateNameVariantIndex(this.references.values(), ReferenceSchemaContract::getNameVariants);
this.reflectedReferences = this.references
.values()
.stream()
.filter(ReflectedReferenceSchema.class::isInstance)
.map(ReflectedReferenceSchema.class::cast)
.collect(
Collectors.toMap(
ReflectedReferenceSchema::getReflectedReferenceName,
Function.identity()
)
);
this.evolutionMode = Collections.unmodifiableSet(evolutionMode);
this.nonNullableOrDefaultValueAttributes = this.attributes
.values()
.stream()
.filter(it -> !it.isNullable() || it.getDefaultValue() != null)
.toList();
this.nonNullableAssociatedData = this.associatedData
.values()
.stream()
.filter(it -> !it.isNullable())
.toList();
this.sortableAttributeCompounds = Collections.unmodifiableMap(
sortableAttributeCompounds
.entrySet()
.stream()
.collect(
Collectors.toMap(
Entry::getKey,
it -> toSortableAttributeCompoundSchema(it.getValue())
)
)
);
this.sortableAttributeCompoundNameIndex = _internalGenerateNameVariantIndex(
this.sortableAttributeCompounds.values(), SortableAttributeCompoundSchemaContract::getNameVariants
);
this.attributeToSortableAttributeCompoundIndex = this.sortableAttributeCompounds
.values()
.stream()
.flatMap(it -> it.getAttributeElements().stream().map(attribute -> new AttributeToCompound(attribute, it)))
.collect(
Collectors.groupingBy(
rec -> rec.attribute().attributeName(),
Collectors.mapping(
AttributeToCompound::compoundSchema,
Collectors.toCollection(ArrayList::new)
)
)
);
}
@Nonnull
@Override
public String getNameVariant(@Nonnull NamingConvention namingConvention) {
return this.nameVariants.get(namingConvention);
}
@Override
@Nonnull
public Map getAttributes() {
// we need EntitySchema to provide access to internal representations - i.e. whoever has
// reference to EntitySchema should have access to other internal schema representations as well
// unfortunately, the Generics in Java is just stupid, and we cannot provide subtype at the place of supertype
// collection, so we have to work around that issue using generics stripping
//noinspection unchecked,rawtypes
return (Map) attributes;
}
@Nonnull
@Override
public Optional getAttribute(@Nonnull String attributeName) {
return ofNullable(this.attributes.get(attributeName));
}
@Nonnull
@Override
public Optional getAttributeByName(@Nonnull String attributeName, @Nonnull NamingConvention namingConvention) {
return ofNullable(attributeNameIndex.get(attributeName))
.map(it -> it[namingConvention.ordinal()]);
}
@Nonnull
@Override
public Map getSortableAttributeCompounds() {
// we need EntitySchema to provide access to internal representations - i.e. whoever has
// reference to EntitySchema should have access to other internal schema representations as well
// unfortunately, the Generics in Java is just stupid, and we cannot provide subtype at the place of supertype
// collection, so we have to work around that issue using generics stripping
//noinspection unchecked,rawtypes
return (Map) sortableAttributeCompounds;
}
@Nonnull
@Override
public Optional getSortableAttributeCompound(@Nonnull String name) {
return ofNullable(sortableAttributeCompounds.get(name));
}
@Nonnull
@Override
public Optional getSortableAttributeCompoundByName(@Nonnull String name, @Nonnull NamingConvention namingConvention) {
return ofNullable(sortableAttributeCompoundNameIndex.get(name))
.map(it -> it[namingConvention.ordinal()]);
}
@Nonnull
@Override
public Collection getSortableAttributeCompoundsForAttribute(@Nonnull String attributeName) {
return ofNullable(attributeToSortableAttributeCompoundIndex.get(attributeName))
.orElse(Collections.emptyList());
}
@Override
public int version() {
return version;
}
@Override
public boolean isBlank() {
return this.version == 1 && !this.withGeneratedPrimaryKey && !this.withHierarchy && !this.withPrice &&
this.indexedPricePlaces == 2 && this.locales.isEmpty() && this.references.isEmpty() &&
this.attributes.isEmpty() && this.associatedData.isEmpty() &&
this.evolutionMode.size() == EvolutionMode.values().length;
}
@Nonnull
@Override
public Optional getAssociatedData(@Nonnull String dataName) {
return ofNullable(this.associatedData.get(dataName));
}
@Nonnull
@Override
public AssociatedDataSchemaContract getAssociatedDataOrThrowException(@Nonnull String dataName) {
return ofNullable(this.associatedData.get(dataName))
.orElseThrow(() -> new EvitaInvalidUsageException("Associated data `" + dataName + "` is not known in entity `" + getName() + "` schema!"));
}
@Nonnull
@Override
public Optional getAssociatedDataByName(@Nonnull String dataName, @Nonnull NamingConvention namingConvention) {
return ofNullable(associatedDataNameIndex.get(dataName))
.map(it -> it[namingConvention.ordinal()]);
}
@Override
@Nonnull
public Map getAssociatedData() {
// we need EntitySchema to provide access to provide access to internal representations - i.e. whoever has
// reference to EntitySchema should have access to other internal schema representations as well
// unfortunately, the Generics in Java is just stupid, and we cannot provide subtype at the place of supertype
// collection, so we have to work around that issue using generics stripping
//noinspection unchecked,rawtypes
return (Map) associatedData;
}
@Nonnull
@Override
public Optional getReference(@Nonnull String referenceName) {
return ofNullable(this.references.get(referenceName));
}
@Nonnull
@Override
public Optional getReferenceByName(@Nonnull String referenceName, @Nonnull NamingConvention namingConvention) {
return ofNullable(referenceNameIndex.get(referenceName))
.map(it -> it[namingConvention.ordinal()]);
}
@Override
@Nonnull
public Map getReferences() {
// we need EntitySchema to provide access to provide access to internal representations - i.e. whoever has
// reference to EntitySchema should have access to other internal schema representations as well
// unfortunately, the Generics in Java is just stupid, and we cannot provide subtype at the place of supertype
// collection, so we have to work around that issue using generics stripping
//noinspection unchecked,rawtypes
return (Map) references;
}
@Nonnull
@Override
public ReferenceSchema getReferenceOrThrowException(@Nonnull String referenceName) {
return getReference(referenceName)
.map(it -> (ReferenceSchema) it)
.orElseThrow(() -> new ReferenceNotFoundException(referenceName, this));
}
@Override
public void validate(@Nonnull CatalogSchemaContract catalogSchema) throws SchemaAlteringException {
for (EntityAttributeSchemaContract attribute : attributes.values()) {
assertNotReferencedEntityPredecessor(attribute.getName(), attribute.getType());
}
final List errors = getReferences()
.values()
.stream()
.flatMap(ref -> {
try {
ref.validate(catalogSchema, this);
return Stream.empty();
} catch (SchemaAlteringException e) {
return Stream.of(e.getMessage());
}
})
.map(it -> "\t" + it)
.toList();
if (!errors.isEmpty()) {
throw new InvalidSchemaMutationException(
"Schema `" + getName() + "` contains validation errors:\n" + String.join("\n", errors)
);
}
}
/**
* Returns true if this schema differs in any way from other schema. Executes full comparison logic of all contents.
*/
@Override
public boolean differsFrom(@Nullable EntitySchemaContract otherSchema) {
if (this == otherSchema) return false;
if (otherSchema == null) return true;
if (version != otherSchema.version()) return true;
if (withGeneratedPrimaryKey != otherSchema.isWithGeneratedPrimaryKey()) return true;
if (withHierarchy != otherSchema.isWithHierarchy()) return true;
if (withPrice != otherSchema.isWithPrice()) return true;
if (!name.equals(otherSchema.getName())) return true;
if (!locales.equals(otherSchema.getLocales())) return true;
if (!currencies.equals(otherSchema.getCurrencies())) return true;
if (attributes.size() != otherSchema.getAttributes().size()) return true;
for (Entry entry : attributes.entrySet()) {
final Optional otherAttributeSchema = otherSchema.getAttribute(entry.getKey());
if (otherAttributeSchema.map(it -> !Objects.equals(it, entry.getValue())).orElse(true)) {
return true;
}
}
if (associatedData.size() != otherSchema.getAssociatedData().size()) return true;
for (Entry entry : associatedData.entrySet()) {
if (otherSchema.getAssociatedData(entry.getKey()).map(it -> !Objects.equals(entry.getValue(), it)).orElse(true)) {
return true;
}
}
if (references.size() != otherSchema.getReferences().size()) return true;
for (Entry entry : references.entrySet()) {
if (otherSchema.getReference(entry.getKey()).map(it -> !Objects.equals(entry.getValue(), it)).orElse(true)) {
return true;
}
}
return !evolutionMode.equals(otherSchema.getEvolutionMode());
}
/**
* Replaces the given reference schema in the current EntitySchema.
*
* @param referenceSchema the reference schema to replace
* @return a new instance of EntitySchema with the replaced reference schema
*/
@Nonnull
public EntitySchema withReplacedReferenceSchema(@Nonnull ReferenceSchemaContract... referenceSchema) {
final Stream newSchemaStream;
if (referenceSchema.length == 1) {
newSchemaStream = Stream.concat(
this.references.values().stream().filter(it -> !it.getName().equals(referenceSchema[0].getName())),
Stream.of(referenceSchema)
);
} else if (referenceSchema.length == 0) {
return this;
} else {
final Set reflectedReferenceSchemaNames = Arrays.stream(referenceSchema)
.map(ReferenceSchemaContract::getName)
.collect(Collectors.toSet());
newSchemaStream = Stream.concat(
this.references.values().stream().filter(it -> !reflectedReferenceSchemaNames.contains(it.getName())),
Stream.of(referenceSchema)
);
}
final Map replacedReferenceIndex = newSchemaStream.collect(
Collectors.toMap(
ReferenceSchemaContract::getName,
Function.identity()
)
);
// sanity check
Assert.isPremiseValid(
this.references.size() == replacedReferenceIndex.size(),
"Reflected reference schema was not found in the current EntitySchema!"
);
return EntitySchema._internalBuild(
this.version,
this.name,
this.nameVariants,
this.description,
this.deprecationNotice,
this.withGeneratedPrimaryKey,
this.withHierarchy,
this.withPrice,
this.indexedPricePlaces,
this.locales,
this.currencies,
this.attributes,
this.getAssociatedData(),
replacedReferenceIndex,
this.evolutionMode,
this.getSortableAttributeCompounds()
);
}
/**
* Retrieves the reflected reference schema for the given reference name.
*
* @param referenceName The name of the reference for which to retrieve the reflected reference schema.
* Must not be null.
* @return The optional reflected reference schema for the given reference name. If the reference name is not found,
* an empty optional is returned.
*/
@Nonnull
public Optional getReflectedReferenceFor(@Nonnull String referenceName) {
return ofNullable(this.reflectedReferences.get(referenceName));
}
/**
* Helper DTO to envelope relation between {@link AttributeElement} and {@link SortableAttributeCompoundSchemaContract}.
*
* @param attribute {@link SortableAttributeCompoundSchemaContract#getAttributeElements()} item
* @param compoundSchema {@link SortableAttributeCompoundSchemaContract} enveloping compound
*/
private record AttributeToCompound(
@Nonnull AttributeElement attribute,
@Nonnull SortableAttributeCompoundSchema compoundSchema
) {
}
}