All Downloads are FREE. Search and download functionalities are using the official Maven repository.

io.evitadb.api.requestResponse.data.structure.InitialEntityBuilder Maven / Gradle / Ivy

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.data.structure;

import io.evitadb.api.exception.ReferenceNotKnownException;
import io.evitadb.api.requestResponse.data.AssociatedDataContract;
import io.evitadb.api.requestResponse.data.AttributesContract;
import io.evitadb.api.requestResponse.data.EntityClassifierWithParent;
import io.evitadb.api.requestResponse.data.EntityEditor.EntityBuilder;
import io.evitadb.api.requestResponse.data.PriceContract;
import io.evitadb.api.requestResponse.data.PriceInnerRecordHandling;
import io.evitadb.api.requestResponse.data.PricesContract;
import io.evitadb.api.requestResponse.data.ReferenceContract;
import io.evitadb.api.requestResponse.data.ReferenceEditor.ReferenceBuilder;
import io.evitadb.api.requestResponse.data.Versioned;
import io.evitadb.api.requestResponse.data.mutation.EntityMutation;
import io.evitadb.api.requestResponse.data.mutation.EntityMutation.EntityExistence;
import io.evitadb.api.requestResponse.data.mutation.EntityUpsertMutation;
import io.evitadb.api.requestResponse.data.mutation.associatedData.AssociatedDataMutation;
import io.evitadb.api.requestResponse.data.mutation.associatedData.UpsertAssociatedDataMutation;
import io.evitadb.api.requestResponse.data.mutation.attribute.AttributeMutation;
import io.evitadb.api.requestResponse.data.mutation.attribute.UpsertAttributeMutation;
import io.evitadb.api.requestResponse.data.mutation.parent.SetParentMutation;
import io.evitadb.api.requestResponse.data.mutation.price.SetPriceInnerRecordHandlingMutation;
import io.evitadb.api.requestResponse.data.mutation.price.UpsertPriceMutation;
import io.evitadb.api.requestResponse.data.mutation.reference.InsertReferenceMutation;
import io.evitadb.api.requestResponse.data.mutation.reference.ReferenceAttributeMutation;
import io.evitadb.api.requestResponse.data.mutation.reference.ReferenceKey;
import io.evitadb.api.requestResponse.data.mutation.reference.SetReferenceGroupMutation;
import io.evitadb.api.requestResponse.schema.Cardinality;
import io.evitadb.api.requestResponse.schema.EntitySchemaContract;
import io.evitadb.api.requestResponse.schema.ReferenceSchemaContract;
import io.evitadb.api.requestResponse.schema.dto.EntitySchema;
import io.evitadb.dataType.DateTimeRange;
import lombok.experimental.Delegate;

import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.io.Serial;
import java.io.Serializable;
import java.math.BigDecimal;
import java.util.Collection;
import java.util.Currency;
import java.util.LinkedHashMap;
import java.util.Locale;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import static java.util.Optional.of;
import static java.util.Optional.ofNullable;

/**
 * Builder that is used to create the entity.
 * Due to performance reasons (see {@link DirectWriteOrOperationLog} microbenchmark) there is special implementation
 * for the situation when entity is newly created. In this case we know everything is new and we don't need to closely
 * monitor the changes so this can speed things up.
 *
 * @author Jan Novotný ([email protected]), FG Forrest a.s. (c) 2021
 */
public class InitialEntityBuilder implements EntityBuilder {
	@Serial private static final long serialVersionUID = -3674623071115207036L;

	private final String type;
	private final EntitySchemaContract schema;
	private final Integer primaryKey;
	@Delegate(types = AttributesContract.class)
	private final InitialEntityAttributesBuilder attributesBuilder;
	@Delegate(types = AssociatedDataContract.class)
	private final AssociatedDataBuilder associatedDataBuilder;
	@Delegate(types = PricesContract.class, excludes = Versioned.class)
	private final PricesBuilder pricesBuilder;
	private final Map references;
	private Integer parent;

	public InitialEntityBuilder(@Nonnull String type) {
		this.type = type;
		this.schema = EntitySchema._internalBuild(type);
		this.primaryKey = null;
		this.attributesBuilder = new InitialEntityAttributesBuilder(schema);
		this.associatedDataBuilder = new InitialAssociatedDataBuilder(schema);
		this.pricesBuilder = new InitialPricesBuilder(schema);
		this.references = new LinkedHashMap<>();
	}

	public InitialEntityBuilder(@Nonnull EntitySchemaContract schema) {
		this.type = schema.getName();
		this.schema = schema;
		this.primaryKey = null;
		this.attributesBuilder = new InitialEntityAttributesBuilder(schema);
		this.associatedDataBuilder = new InitialAssociatedDataBuilder(schema);
		this.pricesBuilder = new InitialPricesBuilder(schema);
		this.references = new LinkedHashMap<>();
	}

	public InitialEntityBuilder(@Nonnull String type, @Nullable Integer primaryKey) {
		this.type = type;
		this.primaryKey = primaryKey;
		this.schema = EntitySchema._internalBuild(type);
		this.attributesBuilder = new InitialEntityAttributesBuilder(schema);
		this.associatedDataBuilder = new InitialAssociatedDataBuilder(schema);
		this.pricesBuilder = new InitialPricesBuilder(schema);
		this.references = new LinkedHashMap<>();
	}

	public InitialEntityBuilder(@Nonnull EntitySchemaContract schema, @Nullable Integer primaryKey) {
		this.type = schema.getName();
		this.schema = schema;
		this.primaryKey = primaryKey;
		this.attributesBuilder = new InitialEntityAttributesBuilder(schema);
		this.associatedDataBuilder = new InitialAssociatedDataBuilder(schema);
		this.pricesBuilder = new InitialPricesBuilder(schema);
		this.references = new LinkedHashMap<>();
	}

	public InitialEntityBuilder(
		@Nonnull EntitySchemaContract entitySchema,
		@Nullable Integer primaryKey,
		@Nonnull Collection attributeValues,
		@Nonnull Collection associatedDataValues,
		@Nonnull Collection referenceContracts,
		@Nullable PriceInnerRecordHandling priceInnerRecordHandling,
		@Nonnull Collection prices
	) {
		this.type = entitySchema.getName();
		this.schema = entitySchema;
		this.primaryKey = primaryKey;
		this.attributesBuilder = new InitialEntityAttributesBuilder(schema);
		for (AttributeValue attributeValue : attributeValues) {
			final AttributeKey attributeKey = attributeValue.key();
			if (attributeKey.localized()) {
				this.attributesBuilder.setAttribute(
					attributeKey.attributeName(),
					attributeKey.locale(),
					attributeValue.value()
				);
			} else {
				this.attributesBuilder.setAttribute(
					attributeKey.attributeName(),
					attributeValue.value()
				);
			}
		}
		this.associatedDataBuilder = new InitialAssociatedDataBuilder(schema);
		for (AssociatedDataValue associatedDataValue : associatedDataValues) {
			final AssociatedDataKey associatedDataKey = associatedDataValue.key();
			if (associatedDataKey.localized()) {
				this.associatedDataBuilder.setAssociatedData(
					associatedDataKey.associatedDataName(),
					associatedDataKey.locale(),
					associatedDataValue.value()
				);
			} else {
				this.associatedDataBuilder.setAssociatedData(
					associatedDataKey.associatedDataName(),
					associatedDataValue.value()
				);
			}
		}
		this.pricesBuilder = new InitialPricesBuilder(schema);
		ofNullable(priceInnerRecordHandling)
			.ifPresent(this.pricesBuilder::setPriceInnerRecordHandling);
		for (PriceContract price : prices) {
			this.pricesBuilder.setPrice(
				price.priceId(),
				price.priceList(),
				price.currency(),
				price.innerRecordId(),
				price.priceWithoutTax(),
				price.taxRate(),
				price.priceWithTax(),
				price.validity(),
				price.indexed()
			);
		}

		this.references = referenceContracts.stream()
			.collect(
				Collectors.toMap(
					ReferenceContract::getReferenceKey,
					Function.identity(),
					(o, o2) -> {
						throw new IllegalStateException("Duplicate key " + o);
					},
					LinkedHashMap::new
				)
			);
	}

	@Override
	public boolean dropped() {
		return false;
	}

	@Override
	public int version() {
		return 1;
	}

	@Override
	@Nonnull
	public String getType() {
		return type;
	}

	@Override
	@Nonnull
	public EntitySchemaContract getSchema() {
		return schema;
	}

	@Override
	@Nullable
	public Integer getPrimaryKey() {
		return primaryKey;
	}

	@Override
	public boolean parentAvailable() {
		return true;
	}

	@Nonnull
	@Override
	public Optional getParentEntity() {
		return ofNullable(parent)
			.map(it -> new EntityReferenceWithParent(type, it, null));
	}

	@Override
	public boolean referencesAvailable() {
		return true;
	}

	@Override
	public boolean referencesAvailable(@Nonnull String referenceName) {
		return true;
	}
	@Nonnull
	@Override
	public Collection getReferences() {
		return references.values();
	}

	@Nonnull
	@Override
	public Collection getReferences(@Nonnull String referenceName) {
		return references
			.values()
			.stream()
			.filter(it -> Objects.equals(referenceName, it.getReferenceName()))
			.collect(Collectors.toList());
	}

	@Nonnull
	@Override
	public Optional getReference(@Nonnull String referenceName, int referencedEntityId) {
		return ofNullable(references.get(new ReferenceKey(referenceName, referencedEntityId)));
	}

	@Nonnull
	public Set getAllLocales() {
		return Stream.concat(
				attributesBuilder.getAttributeLocales().stream(),
				associatedDataBuilder.getAssociatedDataLocales().stream()
			)
			.collect(Collectors.toSet());
	}

	@Nonnull
	public Set getLocales() {
		return getAllLocales();
	}

	@Nonnull
	@Override
	public EntityBuilder removeAttribute(@Nonnull String attributeName) {
		attributesBuilder.removeAttribute(attributeName);
		return this;
	}

	@Nonnull
	@Override
	public  EntityBuilder setAttribute(@Nonnull String attributeName, @Nullable T attributeValue) {
		attributesBuilder.setAttribute(attributeName, attributeValue);
		return this;
	}

	@Nonnull
	@Override
	public  EntityBuilder setAttribute(@Nonnull String attributeName, @Nullable T[] attributeValue) {
		attributesBuilder.setAttribute(attributeName, attributeValue);
		return this;
	}

	@Nonnull
	@Override
	public EntityBuilder removeAttribute(@Nonnull String attributeName, @Nonnull Locale locale) {
		attributesBuilder.removeAttribute(attributeName, locale);
		return this;
	}

	@Nonnull
	@Override
	public  EntityBuilder setAttribute(@Nonnull String attributeName, @Nonnull Locale locale, @Nullable T attributeValue) {
		attributesBuilder.setAttribute(attributeName, locale, attributeValue);
		return this;
	}

	@Nonnull
	@Override
	public  EntityBuilder setAttribute(@Nonnull String attributeName, @Nonnull Locale locale, @Nullable T[] attributeValue) {
		attributesBuilder.setAttribute(attributeName, locale, attributeValue);
		return this;
	}

	@Nonnull
	@Override
	public EntityBuilder mutateAttribute(@Nonnull AttributeMutation mutation) {
		attributesBuilder.mutateAttribute(mutation);
		return this;
	}

	@Nonnull
	@Override
	public EntityBuilder removeAssociatedData(@Nonnull String associatedDataName) {
		associatedDataBuilder.removeAssociatedData(associatedDataName);
		return this;
	}

	@Nonnull
	@Override
	public  EntityBuilder setAssociatedData(@Nonnull String associatedDataName, @Nullable T associatedDataValue) {
		associatedDataBuilder.setAssociatedData(associatedDataName, associatedDataValue);
		return this;
	}

	@Nonnull
	@Override
	public  EntityBuilder setAssociatedData(@Nonnull String associatedDataName, @Nonnull T[] associatedDataValue) {
		associatedDataBuilder.setAssociatedData(associatedDataName, associatedDataValue);
		return this;
	}

	@Nonnull
	@Override
	public EntityBuilder removeAssociatedData(@Nonnull String associatedDataName, @Nonnull Locale locale) {
		associatedDataBuilder.removeAssociatedData(associatedDataName, locale);
		return this;
	}

	@Nonnull
	@Override
	public  EntityBuilder setAssociatedData(@Nonnull String associatedDataName, @Nonnull Locale locale, @Nullable T associatedDataValue) {
		associatedDataBuilder.setAssociatedData(associatedDataName, locale, associatedDataValue);
		return this;
	}

	@Nonnull
	@Override
	public  EntityBuilder setAssociatedData(@Nonnull String associatedDataName, @Nonnull Locale locale, @Nullable T[] associatedDataValue) {
		associatedDataBuilder.setAssociatedData(associatedDataName, locale, associatedDataValue);
		return this;
	}

	@Nonnull
	@Override
	public EntityBuilder mutateAssociatedData(@Nonnull AssociatedDataMutation mutation) {
		associatedDataBuilder.mutateAssociatedData(mutation);
		return this;
	}

	@Override
	public EntityBuilder setParent(int parentPrimaryKey) {
		this.parent = parentPrimaryKey;
		return this;
	}

	@Override
	public EntityBuilder removeParent() {
		this.parent = null;
		return this;
	}

	@Override
	public EntityBuilder setReference(@Nonnull String referenceName, int referencedPrimaryKey) {
		final ReferenceSchemaContract referenceSchema = getReferenceSchemaOrThrowException(referenceName);
		return setReference(referenceName, referenceSchema.getReferencedEntityType(), referenceSchema.getCardinality(), referencedPrimaryKey, null);
	}

	@Override
	public EntityBuilder setReference(@Nonnull String referenceName, int referencedPrimaryKey, @Nullable Consumer whichIs) {
		final ReferenceSchemaContract referenceSchema = getReferenceSchemaOrThrowException(referenceName);
		return setReference(referenceName, referenceSchema.getReferencedEntityType(), referenceSchema.getCardinality(), referencedPrimaryKey, whichIs);
	}

	@Override
	public EntityBuilder setReference(@Nonnull String referenceName, @Nonnull String referencedEntityType, @Nonnull Cardinality cardinality, int referencedPrimaryKey) {
		return setReference(referenceName, referencedEntityType, cardinality, referencedPrimaryKey, null);
	}

	@Override
	public EntityBuilder setReference(@Nonnull String referenceName, @Nonnull String referencedEntityType, @Nonnull Cardinality cardinality, int referencedPrimaryKey, @Nullable Consumer whichIs) {
		final InitialReferenceBuilder builder = new InitialReferenceBuilder(this.schema, referenceName, referencedPrimaryKey, cardinality, referencedEntityType);
		ofNullable(whichIs).ifPresent(it -> it.accept(builder));
		final Reference reference = builder.build();
		references.put(new ReferenceKey(referenceName, referencedPrimaryKey), reference);
		return this;
	}

	@Override
	public void addOrReplaceReferenceMutations(@Nonnull ReferenceBuilder referenceBuilder) {
		references.put(referenceBuilder.getReferenceKey(), referenceBuilder.build());
	}

	@Override
	public EntityBuilder removeReference(@Nonnull String referenceName, int referencedPrimaryKey) {
		this.references.remove(new ReferenceKey(referenceName, referencedPrimaryKey));
		return this;
	}

	@Override
	public EntityBuilder setPrice(int priceId, @Nonnull String priceList, @Nonnull Currency currency, @Nonnull BigDecimal priceWithoutTax, @Nonnull BigDecimal taxRate, @Nonnull BigDecimal priceWithTax, boolean indexed) {
		pricesBuilder.setPrice(priceId, priceList, currency, priceWithoutTax, taxRate, priceWithTax, indexed);
		return this;
	}

	@Override
	public EntityBuilder setPrice(int priceId, @Nonnull String priceList, @Nonnull Currency currency, @Nullable Integer innerRecordId, @Nonnull BigDecimal priceWithoutTax, @Nonnull BigDecimal taxRate, @Nonnull BigDecimal priceWithTax, boolean indexed) {
		pricesBuilder.setPrice(priceId, priceList, currency, innerRecordId, priceWithoutTax, taxRate, priceWithTax, indexed);
		return this;
	}

	@Override
	public EntityBuilder setPrice(int priceId, @Nonnull String priceList, @Nonnull Currency currency, @Nonnull BigDecimal priceWithoutTax, @Nonnull BigDecimal taxRate, @Nonnull BigDecimal priceWithTax, DateTimeRange validity, boolean indexed) {
		pricesBuilder.setPrice(priceId, priceList, currency, priceWithoutTax, taxRate, priceWithTax, validity, indexed);
		return this;
	}

	@Override
	public EntityBuilder setPrice(int priceId, @Nonnull String priceList, @Nonnull Currency currency, @Nullable Integer innerRecordId, @Nonnull BigDecimal priceWithoutTax, @Nonnull BigDecimal taxRate, @Nonnull BigDecimal priceWithTax, @Nullable DateTimeRange validity, boolean indexed) {
		pricesBuilder.setPrice(priceId, priceList, currency, innerRecordId, priceWithoutTax, taxRate, priceWithTax, validity, indexed);
		return this;
	}

	@Override
	public EntityBuilder removePrice(int priceId, @Nonnull String priceList, @Nonnull Currency currency) {
		pricesBuilder.removePrice(priceId, priceList, currency);
		return this;
	}

	@Override
	public EntityBuilder setPriceInnerRecordHandling(@Nonnull PriceInnerRecordHandling priceInnerRecordHandling) {
		pricesBuilder.setPriceInnerRecordHandling(priceInnerRecordHandling);
		return this;
	}

	@Override
	public EntityBuilder removePriceInnerRecordHandling() {
		pricesBuilder.removePriceInnerRecordHandling();
		return this;
	}

	@Override
	public EntityBuilder removeAllNonTouchedPrices() {
		pricesBuilder.removeAllNonTouchedPrices();
		return this;
	}

	@Nonnull
	@Override
	public Optional toMutation() {
		return of(
			new EntityUpsertMutation(
				getType(),
				getPrimaryKey(),
				EntityExistence.MUST_NOT_EXIST,
				Stream.of(
						ofNullable(parent)
							.map(SetParentMutation::new)
							.stream(),
						references.values()
							.stream()
							.flatMap(it -> Stream.concat(
									Stream.of(
											new InsertReferenceMutation(it.getReferenceKey(), it.getReferenceCardinality(), it.getReferencedEntityType()),
											it.getGroup().map(group -> new SetReferenceGroupMutation(it.getReferenceKey(), group.getType(), group.getPrimaryKey())).orElse(null)
										)
										.filter(Objects::nonNull),
									it.getAttributeValues()
										.stream()
										.filter(x -> Objects.nonNull(x.value()))
										.map(x ->
											new ReferenceAttributeMutation(
												it.getReferenceKey(),
												new UpsertAttributeMutation(x.key(), x.value())
											)
										)
								)
							),
						attributesBuilder
							.getAttributeValues()
							.stream()
							.filter(it -> it.value() != null)
							.map(it -> new UpsertAttributeMutation(it.key(), it.value())),
						associatedDataBuilder
							.getAssociatedDataValues()
							.stream()
							.map(it -> new UpsertAssociatedDataMutation(it.key(), it.value())),
						Stream.of(
							new SetPriceInnerRecordHandlingMutation(pricesBuilder.getPriceInnerRecordHandling())
						),
						pricesBuilder
							.getPrices()
							.stream()
							.map(it -> new UpsertPriceMutation(it.priceKey(), it))
					)
					.flatMap(it -> it)
					.filter(Objects::nonNull)
					.collect(Collectors.toList())
			)
		);
	}

	@Nonnull
	@Override
	public Entity toInstance() {
		return Entity._internalBuild(
			primaryKey,
			version(),
			schema,
			parent,
			references.values(),
			attributesBuilder.build(),
			associatedDataBuilder.build(),
			pricesBuilder.build(),
			getAllLocales(),
			Stream.concat(
				schema.getReferences().keySet().stream(),
				references.keySet()
					.stream()
					.map(ReferenceKey::referenceName)
			).collect(Collectors.toSet()),
			schema.isWithHierarchy() || parent != null,
			false
		);
	}

	@Nonnull
	private ReferenceSchemaContract getReferenceSchemaOrThrowException(@Nonnull String referenceName) {
		return getSchema()
			.getReference(referenceName)
			.orElseThrow(() -> new ReferenceNotKnownException(referenceName));
	}

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy