![JAR search and dependency download from the Maven repository](/logo.png)
io.evitadb.api.requestResponse.schema.dto.ReferenceSchema 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.
/*
*
* _ _ ____ ____
* _____ _(_) |_ __ _| _ \| __ )
* / _ \ \ / / | __/ _` | | | | _ \
* | __/\ 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.SchemaAlteringException;
import io.evitadb.api.requestResponse.schema.AttributeSchemaContract;
import io.evitadb.api.requestResponse.schema.Cardinality;
import io.evitadb.api.requestResponse.schema.CatalogSchemaContract;
import io.evitadb.api.requestResponse.schema.EntitySchemaContract;
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.ClassifierType;
import io.evitadb.exception.EvitaInternalError;
import io.evitadb.utils.Assert;
import io.evitadb.utils.ClassifierUtils;
import io.evitadb.utils.NamingConvention;
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.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Objects;
import java.util.Optional;
import java.util.function.Function;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import static io.evitadb.api.requestResponse.schema.dto.EntitySchema._internalGenerateNameVariantIndex;
import static io.evitadb.api.requestResponse.schema.dto.EntitySchema.toReferenceAttributeSchema;
import static io.evitadb.api.requestResponse.schema.dto.EntitySchema.toSortableAttributeCompoundSchema;
import static java.util.Optional.ofNullable;
/**
* Internal implementation of {@link ReferenceSchemaContract}.
*
* @author Jan Novotný ([email protected]), FG Forrest a.s. (c) 2022
* @see ReferenceSchemaContract
*/
@Immutable
@ThreadSafe
public sealed class ReferenceSchema implements ReferenceSchemaContract permits ReflectedReferenceSchema {
@Serial private static final long serialVersionUID = 2018566260261489037L;
@Getter @Nonnull protected final String name;
@Getter @Nonnull protected final Map nameVariants;
@Getter @Nullable protected final String description;
@Getter @Nullable protected final String deprecationNotice;
@Getter @Nonnull protected final Cardinality cardinality;
@Getter @Nonnull protected final String referencedEntityType;
@Nonnull protected final Map entityTypeNameVariants;
@Getter protected final boolean referencedEntityTypeManaged;
@Getter @Nullable protected final String referencedGroupType;
@Nonnull protected final Map groupTypeNameVariants;
@Getter protected final boolean referencedGroupTypeManaged;
@Getter protected final boolean indexed;
@Getter protected final boolean faceted;
/**
* Contains index of all {@link SortableAttributeCompoundSchema} that could be used as sortable attribute compounds
* of reference 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.
*/
@Nonnull 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.
*/
@Nonnull private final Map attributeNameIndex;
/**
* Contains all definitions of the attributes that contain default value.
*/
@Getter @Nonnull private final Map nonNullableOrDefaultValueAttributes;
/**
* 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;
/**
* This method is for internal purposes only. It could be used for reconstruction of ReferenceSchema 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 ReferenceSchema _internalBuild(
@Nonnull String name,
@Nonnull String entityType,
boolean referencedEntityTypeManaged,
@Nonnull Cardinality cardinality,
@Nullable String groupType,
boolean referencedGroupTypeManaged,
boolean indexed,
boolean faceted
) {
ClassifierUtils.validateClassifierFormat(ClassifierType.ENTITY, entityType);
if (groupType != null) {
ClassifierUtils.validateClassifierFormat(ClassifierType.ENTITY, groupType);
}
if (faceted) {
Assert.isTrue(indexed, "When reference is marked as faceted, it needs also to be indexed.");
}
return new ReferenceSchema(
name, NamingConvention.generate(name),
null, null, cardinality,
entityType,
referencedEntityTypeManaged ? Collections.emptyMap() : NamingConvention.generate(entityType),
referencedEntityTypeManaged,
groupType,
groupType != null && groupType.isBlank() && !referencedGroupTypeManaged ?
NamingConvention.generate(groupType) : Collections.emptyMap(),
referencedGroupTypeManaged,
indexed,
faceted,
Collections.emptyMap(),
Collections.emptyMap()
);
}
/**
* This method is for internal purposes only. It could be used for reconstruction of ReferenceSchema 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 ReferenceSchema _internalBuild(
@Nonnull String name,
@Nullable String description,
@Nullable String deprecationNotice,
@Nonnull String entityType,
boolean referencedEntityTypeManaged,
@Nonnull Cardinality cardinality,
@Nullable String groupType,
boolean referencedGroupTypeManaged,
boolean indexed,
boolean faceted,
@Nonnull Map attributes,
@Nonnull Map sortableAttributeCompounds
) {
ClassifierUtils.validateClassifierFormat(ClassifierType.ENTITY, entityType);
if (groupType != null) {
ClassifierUtils.validateClassifierFormat(ClassifierType.ENTITY, groupType);
}
if (faceted) {
Assert.isTrue(indexed, "When reference is marked as faceted, it needs also to be indexed.");
}
return new ReferenceSchema(
name, NamingConvention.generate(name),
description, deprecationNotice, cardinality,
entityType,
referencedEntityTypeManaged ? Collections.emptyMap() : NamingConvention.generate(entityType),
referencedEntityTypeManaged,
groupType,
groupType != null && groupType.isBlank() && !referencedGroupTypeManaged ?
NamingConvention.generate(groupType) : Collections.emptyMap(),
referencedGroupTypeManaged,
indexed,
faceted,
attributes,
sortableAttributeCompounds
);
}
/**
* This method is for internal purposes only. It could be used for reconstruction of ReferenceSchema 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 ReferenceSchema _internalBuild(
@Nonnull String name,
@Nonnull Map nameVariants,
@Nullable String description,
@Nullable String deprecationNotice,
@Nonnull String entityType,
@Nonnull Map entityTypeNameVariants,
boolean referencedEntityTypeManaged,
@Nonnull Cardinality cardinality,
@Nullable String groupType,
@Nullable Map groupTypeNameVariants,
boolean referencedGroupTypeManaged,
boolean indexed,
boolean faceted,
@Nonnull Map attributes,
@Nonnull Map sortableAttributeCompounds
) {
ClassifierUtils.validateClassifierFormat(ClassifierType.ENTITY, entityType);
if (groupType != null) {
ClassifierUtils.validateClassifierFormat(ClassifierType.ENTITY, groupType);
}
if (faceted) {
Assert.isTrue(indexed, "When reference is marked as faceted, it needs also to be indexed.");
}
return new ReferenceSchema(
name, nameVariants,
description, deprecationNotice, cardinality,
entityType,
entityTypeNameVariants,
referencedEntityTypeManaged,
groupType,
ofNullable(groupTypeNameVariants).orElse(Collections.emptyMap()),
referencedGroupTypeManaged,
indexed,
faceted,
attributes,
sortableAttributeCompounds
);
}
protected ReferenceSchema(
@Nonnull String name,
@Nonnull Map nameVariants,
@Nullable String description,
@Nullable String deprecationNotice,
@Nullable Cardinality cardinality,
@Nonnull String referencedEntityType,
@Nonnull Map entityTypeNameVariants,
boolean referencedEntityTypeManaged,
@Nullable String referencedGroupType,
@Nonnull Map groupTypeNameVariants,
boolean referencedGroupTypeManaged,
boolean indexed,
boolean faceted,
@Nonnull Map attributes,
@Nonnull Map sortableAttributeCompounds
) {
ClassifierUtils.validateClassifierFormat(ClassifierType.ENTITY, referencedEntityType);
this.name = name;
this.nameVariants = Collections.unmodifiableMap(nameVariants);
this.description = description;
this.deprecationNotice = deprecationNotice;
this.cardinality = cardinality;
this.referencedEntityType = referencedEntityType;
this.entityTypeNameVariants = Collections.unmodifiableMap(entityTypeNameVariants);
this.referencedEntityTypeManaged = referencedEntityTypeManaged;
this.referencedGroupType = referencedGroupType;
this.groupTypeNameVariants = Collections.unmodifiableMap(groupTypeNameVariants);
this.referencedGroupTypeManaged = referencedGroupTypeManaged;
this.indexed = indexed;
this.faceted = faceted;
this.attributes = Collections.unmodifiableMap(
attributes.entrySet()
.stream()
.collect(
Collectors.toMap(
Entry::getKey,
it -> toReferenceAttributeSchema(it.getValue())
)
)
);
this.attributeNameIndex = _internalGenerateNameVariantIndex(
this.attributes.values(), AttributeSchemaContract::getNameVariants
);
this.nonNullableOrDefaultValueAttributes = this.attributes
.values()
.stream()
.filter(it -> !it.isNullable() || it.getDefaultValue() != null)
.collect(
Collectors.toMap(
AttributeSchema::getName,
Function.identity()
)
);
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)
)
)
);
}
@Override
@Nonnull
public String getNameVariant(@Nonnull NamingConvention namingConvention) {
return this.nameVariants.get(namingConvention);
}
@Nonnull
@Override
public Map getEntityTypeNameVariants(@Nonnull Function entitySchemaFetcher) {
return referencedEntityTypeManaged ?
Objects.requireNonNull(entitySchemaFetcher.apply(referencedEntityType)).getNameVariants() :
this.entityTypeNameVariants;
}
@Override
@Nonnull
public String getReferencedEntityTypeNameVariant(@Nonnull NamingConvention namingConvention, @Nonnull Function entitySchemaFetcher) {
return referencedEntityTypeManaged ?
Objects.requireNonNull(entitySchemaFetcher.apply(referencedEntityType)).getNameVariant(namingConvention) :
this.entityTypeNameVariants.get(namingConvention);
}
@Nonnull
@Override
public Map getGroupTypeNameVariants(@Nonnull Function entitySchemaFetcher) {
return referencedGroupTypeManaged ?
Objects.requireNonNull(entitySchemaFetcher.apply(referencedGroupType)).getNameVariants() :
this.groupTypeNameVariants;
}
@Override
@Nonnull
public String getReferencedGroupTypeNameVariant(@Nonnull NamingConvention namingConvention, @Nonnull Function entitySchemaFetcher) {
return referencedGroupTypeManaged ?
Objects.requireNonNull(entitySchemaFetcher.apply(referencedGroupType)).getNameVariant(namingConvention) :
this.groupTypeNameVariants.get(namingConvention);
}
@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 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)this.attributes;
}
@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)this.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 void validate(@Nonnull CatalogSchemaContract catalogSchema, @Nonnull EntitySchema entitySchema) throws SchemaAlteringException {
final Optional referencedEntityTypeSchema = catalogSchema.getEntitySchema(this.referencedEntityType);
Stream referenceErrors = Stream.empty();
if (this.referencedEntityTypeManaged && referencedEntityTypeSchema.isEmpty()) {
referenceErrors = Stream.concat(
referenceErrors,
Stream.of("Referenced entity type `" + this.referencedEntityType + "` is not present in catalog `" + catalogSchema.getName() + "` schema!"));
} else if (!this.referencedEntityTypeManaged && referencedEntityTypeSchema.isPresent()) {
referenceErrors = Stream.concat(
referenceErrors,
Stream.of("Referenced entity type `" + this.referencedEntityType + "` is present in catalog `" + catalogSchema.getName() + "` schema, but it's marked as not managed!"));
}
if (this.referencedGroupTypeManaged &&
catalogSchema.getEntitySchema(this.referencedGroupType).isEmpty()
) {
referenceErrors = Stream.concat(
referenceErrors,
Stream.of("Referenced group entity type `" + this.referencedGroupType + "` is not present in catalog `" + catalogSchema.getName() + "` schema!"));
} else if (!this.referencedGroupTypeManaged &&
catalogSchema.getEntitySchema(this.referencedGroupType).isPresent()
) {
referenceErrors = Stream.concat(
referenceErrors,
Stream.of("Referenced group entity type `" + this.referencedGroupType + "` is present in catalog `" + catalogSchema.getName() + "` schema, but it's marked as not managed!"));
}
referenceErrors = Stream.concat(
referenceErrors,
validateAttributes(this.getAttributes())
);
final List errors = referenceErrors.map(it -> "\t" + it).toList();
if (!errors.isEmpty()) {
throw new InvalidSchemaMutationException(
"Reference schema `" + this.name + "` contains validation errors:\n" + String.join("\n", errors)
);
}
}
/**
* Updates the referenced entity type for managed entity types, but leaves all other properties unchanged.
*
* @param newReferencedEntityType the new referenced entity type, must not be null
* @return a new ReferenceSchema with the updated referenced entity type, never null
* @throws EvitaInternalError if the referenced entity type is not managed
*/
@Nonnull
public ReferenceSchemaContract withUpdatedReferencedEntityType(@Nonnull String newReferencedEntityType) {
Assert.isPremiseValid(
this.referencedEntityTypeManaged,
"The new referenced entity type can be changed only for managed entity types!"
);
return new ReferenceSchema(
this.name,
this.nameVariants,
this.description,
this.deprecationNotice,
this.cardinality,
newReferencedEntityType,
// is always empty for managed types
Map.of(),
true,
this.referencedGroupType,
this.groupTypeNameVariants,
this.referencedGroupTypeManaged,
this.indexed,
this.faceted,
this.getAttributes(),
this.getSortableAttributeCompounds()
);
}
/**
* Updates the referenced group type for managed group types, but leaves all other properties unchanged.
*
* @param newReferencedGroupType the new referenced group type, must not be null
* @return a new ReferenceSchema with the updated referenced group type, never null
* @throws EvitaInternalError if the referenced group type is not managed
*/
@Nonnull
public ReferenceSchemaContract withUpdatedReferencedGroupType(@Nonnull String newReferencedGroupType) {
Assert.isPremiseValid(
this.referencedGroupTypeManaged,
"The new referenced entity group type can be changed only for managed entity types!"
);
return new ReferenceSchema(
this.name,
this.nameVariants,
this.description,
this.deprecationNotice,
this.cardinality,
this.referencedEntityType,
this.entityTypeNameVariants,
this.referencedEntityTypeManaged,
newReferencedGroupType,
// is always empty for managed types
Map.of(),
true,
this.indexed,
this.faceted,
this.getAttributes(),
this.getSortableAttributeCompounds()
);
}
/**
* Collects errors for reference attributes.
*
* @param attributes a map of attribute schemas
* @return returns errors for reference attribute schemas as a stream
*/
@Nonnull
protected Stream validateAttributes(@Nonnull Map attributes) {
Stream attributeErrors = Stream.empty();
if (!this.isIndexed()) {
for (AttributeSchemaContract attribute : attributes.values()) {
if (attribute.isFilterable()) {
attributeErrors = Stream.concat(
attributeErrors,
Stream.of("Attribute `" + attribute.getName() + "` of reference schema `" + this.name + "` is filterable but reference schema is not indexed!")
);
}
if (attribute.isSortable()) {
attributeErrors = Stream.concat(
attributeErrors,
Stream.of("Attribute `" + attribute.getName() + "` of reference schema `" + this.name + "` is sortable but reference schema is not indexed!")
);
}
if (attribute.isUnique()) {
attributeErrors = Stream.concat(
attributeErrors,
Stream.of("Attribute `" + attribute.getName() + "` of reference schema `" + this.name + "` is unique but reference schema is not indexed!")
);
}
}
}
return attributeErrors;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
ReferenceSchema that = (ReferenceSchema) o;
return referencedEntityTypeManaged == that.referencedEntityTypeManaged && referencedGroupTypeManaged == that.referencedGroupTypeManaged && indexed == that.indexed && faceted == that.faceted && name.equals(that.name) && nameVariants.equals(that.nameVariants) && Objects.equals(description, that.description) && Objects.equals(deprecationNotice, that.deprecationNotice) && cardinality == that.cardinality && referencedEntityType.equals(that.referencedEntityType) && entityTypeNameVariants.equals(that.entityTypeNameVariants) && Objects.equals(referencedGroupType, that.referencedGroupType) && groupTypeNameVariants.equals(that.groupTypeNameVariants) && sortableAttributeCompounds.equals(that.sortableAttributeCompounds) && attributes.equals(that.attributes);
}
@Override
public int hashCode() {
int result = name.hashCode();
result = 31 * result + nameVariants.hashCode();
result = 31 * result + Objects.hashCode(description);
result = 31 * result + Objects.hashCode(deprecationNotice);
result = 31 * result + cardinality.hashCode();
result = 31 * result + referencedEntityType.hashCode();
result = 31 * result + entityTypeNameVariants.hashCode();
result = 31 * result + Boolean.hashCode(referencedEntityTypeManaged);
result = 31 * result + Objects.hashCode(referencedGroupType);
result = 31 * result + groupTypeNameVariants.hashCode();
result = 31 * result + Boolean.hashCode(referencedGroupTypeManaged);
result = 31 * result + Boolean.hashCode(indexed);
result = 31 * result + Boolean.hashCode(faceted);
result = 31 * result + sortableAttributeCompounds.hashCode();
result = 31 * result + attributes.hashCode();
return result;
}
/**
* 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
) {
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy