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

io.evitadb.api.requestResponse.data.EntityContract Maven / Gradle / Ivy

The newest version!
/*
 *
 *                         _ _        ____  ____
 *               _____   _(_) |_ __ _|  _ \| __ )
 *              / _ \ \ / / | __/ _` | | | |  _ \
 *             |  __/\ V /| | || (_| | |_| | |_) |
 *              \___| \_/ |_|\__\__,_|____/|____/
 *
 *   Copyright (c) 2023
 *
 *   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;

import io.evitadb.api.exception.ContextMissingException;
import io.evitadb.api.exception.EntityIsNotHierarchicalException;
import io.evitadb.api.exception.ReferenceNotFoundException;
import io.evitadb.api.query.require.HierarchyContent;
import io.evitadb.api.query.require.ReferenceContent;
import io.evitadb.api.requestResponse.EvitaRequest;
import io.evitadb.api.requestResponse.data.mutation.reference.ReferenceKey;
import io.evitadb.api.requestResponse.data.structure.Entity;
import io.evitadb.api.requestResponse.data.structure.Reference;
import io.evitadb.api.requestResponse.schema.EntityAttributeSchemaContract;
import io.evitadb.api.requestResponse.schema.EntitySchemaContract;
import io.evitadb.utils.MemoryMeasuringConstants;

import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.util.Collection;
import java.util.Collections;
import java.util.Locale;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;

import static java.util.Optional.of;

/**
 * Contract for classes that allow reading information about {@link Entity} instance.
 *
 * @author Jan Novotný ([email protected]), FG Forrest a.s. (c) 2021
 */
public interface EntityContract extends EntityClassifierWithParent,
	ContentComparator,
	AttributesContract,
	AssociatedDataContract,
	PricesContract,
	Versioned,
	Droppable {

	/**
	 * Returns schema of the entity, that fully describes its structure and capabilities. Schema is up-to-date to the
	 * moment entity was fetched from evitaDB.
	 */
	@Nonnull
	EntitySchemaContract getSchema();

	/**
	 * Returns primary key of the entity that is UNIQUE among all other entities of the same type.
	 * Primary key may be null only when entity is created in case evitaDB is responsible for automatically assigning
	 * new primary key. Once entity is stored into evitaDB it MUST have non-null primary key. So the NULL can be
	 * returned only in the rare case when new entity is created in the client code and hasn't yet been stored to
	 * evitaDB.
	 */
	@Nullable
	Integer getPrimaryKey();

	/**
	 * Returns true if entity hierarchy was fetched along with the entity. Calling this method before calling any
	 * other method that requires prices to be fetched will allow you to avoid {@link ContextMissingException}.
	 *
	 * Method also returns false if the entity is not allowed to be hierarchical by the schema. Checking this method
	 * also allows you to avoid {@link EntityIsNotHierarchicalException} in such case.
	 */
	boolean parentAvailable();

	/**
	 * Returns parent entity body. The entity fetch needs to be triggered using {@link HierarchyContent} requirement.
	 * The property allows to fetch entire parent axis of the entity to the root if requested.
	 *
	 * @throws EntityIsNotHierarchicalException when {@link EntitySchemaContract#isWithHierarchy()} is false
	 * @throws ContextMissingException          when {@link HierarchyContent} is not part of the query requirements
	 */
	@Nonnull
	Optional getParentEntity()
		throws EntityIsNotHierarchicalException, ContextMissingException;

	/**
	 * Returns true if entity references were fetched along with the entity. Calling this method before calling any
	 * other method that requires references to be fetched will allow you to avoid {@link ContextMissingException}.
	 */
	boolean referencesAvailable();

	/**
	 * Returns true if references of particular name was fetched along with the entity. Calling this method
	 * before calling any other method that requires references to be fetched will allow you to avoid
	 * {@link ContextMissingException}.
	 */
	boolean referencesAvailable(@Nonnull String referenceName);

	/**
	 * Returns collection of {@link Reference} of this entity. The references represent relations to other evitaDB
	 * entities or external entities in different systems.
	 *
	 * @throws ContextMissingException when {@link ReferenceContent} is not part of the query requirements
	 */
	@Nonnull
	Collection getReferences()
		throws ContextMissingException;

	/**
	 * Returns collection of {@link Reference} to certain type of other entities. References represent relations to
	 * other evitaDB entities or external entities in different systems.
	 *
	 * @throws ReferenceNotFoundException when reference with given name is not defined in the schema
	 * @throws ContextMissingException    when {@link ReferenceContent} is not part of the query requirements
	 */
	@Nonnull
	Collection getReferences(@Nonnull String referenceName)
		throws ContextMissingException, ReferenceNotFoundException;

	/**
	 * Returns single {@link Reference} instance that is referencing passed entity type with certain primary key.
	 * The references represent relations to other evitaDB entities or external entities in different systems.
	 *
	 * @throws ReferenceNotFoundException when reference with given name is not defined in the schema
	 * @throws ContextMissingException    when {@link ReferenceContent} is not part of the query requirements
	 */
	@Nonnull
	Optional getReference(@Nonnull String referenceName, int referencedEntityId)
		throws ContextMissingException, ReferenceNotFoundException;

	;

	/**
	 * Returns set of locales this entity has any of localized data in. Although {@link EntitySchemaContract#getLocales()} may
	 * support wider range of the locales, this method returns only those that are used by data of this very entity
	 * instance.
	 */
	@Nonnull
	Set getAllLocales();

	/**
	 * Returns set of locales this entity has any of localized data in. The method further limits the output of
	 * {@link #getAllLocales()} by returning only those locales that were requested by the query. The locales here
	 * reflect the {@link EvitaRequest#getLocale()} and {@link EvitaRequest#getRequiredLocales()}.
	 */
	@Nonnull
	Set getLocales();

	/**
	 * Method returns gross estimation of the in-memory size of this instance. The estimation is expected not to be
	 * a precise one. Please use constants from {@link MemoryMeasuringConstants} for size computation.
	 */
	default int estimateSize() {
		return MemoryMeasuringConstants.OBJECT_HEADER_SIZE +
			// primary key
			MemoryMeasuringConstants.INT_SIZE +
			// version
			MemoryMeasuringConstants.INT_SIZE +
			// reference to the schema
			MemoryMeasuringConstants.REFERENCE_SIZE +
			// dropped
			MemoryMeasuringConstants.BYTE_SIZE +
			// type - we should assume the key is stored in memory only once (should be enum or String)
			MemoryMeasuringConstants.REFERENCE_SIZE +
			// hierarchical placement
			(parentAvailable() && getParentEntity().isPresent() ? MemoryMeasuringConstants.INT_SIZE : 0) +
			// locales
			getLocales().stream().mapToInt(it -> MemoryMeasuringConstants.REFERENCE_SIZE).sum() +
			// attributes
			(!attributesAvailable() ? 0 : getAttributeValues().stream().mapToInt(AttributeValue::estimateSize).sum()) +
			// attributes
			(!associatedDataAvailable() ? 0 : getAssociatedDataValues().stream().mapToInt(AssociatedDataValue::estimateSize).sum()) +
			// price inner record handling
			MemoryMeasuringConstants.BYTE_SIZE +
			// prices
			(!pricesAvailable() ? 0 : getPrices().stream().mapToInt(PriceContract::estimateSize).sum()) +
			// references
			(!referencesAvailable() ? 0 : getReferences().stream().mapToInt(ReferenceContract::estimateSize).sum());

	}

	/**
	 * Method returns true if any entity inner data differs from other entity.
	 */
	@Override
	default boolean differsFrom(@Nullable EntityContract otherEntity) {
		if (this == otherEntity) return false;
		if (otherEntity == null) return true;

		if (!Objects.equals(getPrimaryKey(), otherEntity.getPrimaryKey())) return true;
		if (version() != otherEntity.version()) return true;
		if (dropped() != otherEntity.dropped()) return true;
		if (!getType().equals(otherEntity.getType())) return true;
		if (parentAvailable() != otherEntity.parentAvailable()) return true;
		if (parentAvailable()) {
			if (getParentEntity().isPresent() != otherEntity.getParentEntity().isPresent()) return true;
			if (getParentEntity().isPresent() && !Objects.equals(getParentEntity().get().getPrimaryKey(), otherEntity.getParentEntity().get().getPrimaryKey())) return true;
		}
		if (AttributesContract.anyAttributeDifferBetween(this, otherEntity)) return true;
		if (AssociatedDataContract.anyAssociatedDataDifferBetween(this, otherEntity)) return true;
		if (getPriceInnerRecordHandling() != otherEntity.getPriceInnerRecordHandling()) return true;
		if (PricesContract.anyPriceDifferBetween(this, otherEntity)) return true;
		if (!getLocales().equals(otherEntity.getLocales())) return true;

		final Collection thisReferences = referencesAvailable() ? getReferences() : Collections.emptyList();
		final Collection otherReferences = otherEntity.referencesAvailable() ? otherEntity.getReferences() : Collections.emptyList();
		if (thisReferences.size() != otherReferences.size()) return true;
		for (ReferenceContract thisReference : thisReferences) {
			final ReferenceKey thisKey = thisReference.getReferenceKey();
			if (otherEntity.getReference(thisKey.referenceName(), thisKey.primaryKey())
				.map(thisReference::differsFrom)
				.orElse(true)) {
				return true;
			}
		}
		return false;
	}

	/**
	 * Method prints details about entity as single line. Use in {@link Object#toString()} implementations.
	 */
	default String describe() {
		final Set locales = getLocales();
		return (dropped() ? "❌ " : "") +
			"Entity " + getType() + " ID=" + getPrimaryKey() +
			(parentAvailable() ? getParentEntity().map(it -> ", ↰ " + it.getPrimaryKey()).orElse("") : "") +
			(referencesAvailable() ? ", " + of(getReferences()).filter(it -> !it.isEmpty()).map(it -> it.stream().map(ReferenceContract::toString).collect(Collectors.joining(", "))).orElse("") : "") +
			(attributesAvailable() ? of(getAttributeValues()).filter(it -> !it.isEmpty()).map(it -> ", " + it.stream().map(AttributeValue::toString).collect(Collectors.joining(", "))).orElse("") : "") +
			(associatedDataAvailable() ? of(getAssociatedDataValues()).filter(it -> !it.isEmpty()).map(it -> ", " + it.stream().map(AssociatedDataValue::toString).collect(Collectors.joining(", "))).orElse("") : "") +
			(pricesAvailable() ? of(getPrices()).filter(it -> !it.isEmpty()).map(it -> ", " + it.stream().map(Object::toString).collect(Collectors.joining(", "))).orElse(null) : "") +
			(locales.isEmpty() ? "" : ", localized to " + locales.stream().map(Locale::toString).collect(Collectors.joining(", ")));
	}
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy