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

io.evitadb.api.requestResponse.data.AssociatedDataContract 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.query.QueryUtils;
import io.evitadb.api.requestResponse.schema.AssociatedDataSchemaContract;
import io.evitadb.dataType.EvitaDataTypes;
import io.evitadb.dataType.exception.IncompleteDeserializationException;
import io.evitadb.utils.Assert;
import io.evitadb.utils.MemoryMeasuringConstants;
import io.evitadb.utils.ReflectionLookup;

import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.io.Serial;
import java.io.Serializable;
import java.util.Arrays;
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 io.evitadb.utils.ComparatorUtils.compareLocale;

/**
 * This interface prescribes a set of methods that must be implemented by the object, that maintains set of associated data.
 *
 * @author Jan Novotný ([email protected]), FG Forrest a.s. (c) 2021
 */
public interface AssociatedDataContract extends Serializable, AssociatedDataAvailabilityChecker {

	/**
	 * Returns true if single associated data differs between first and second instance.
	 */
	static boolean anyAssociatedDataDifferBetween(AssociatedDataContract first, AssociatedDataContract second) {
		final Collection thisValues = first.associatedDataAvailable() ? first.getAssociatedDataValues() : Collections.emptyList();
		final Collection otherValues = second.associatedDataAvailable() ? second.getAssociatedDataValues() : Collections.emptyList();

		if (thisValues.size() != otherValues.size()) {
			return true;
		} else {
			return thisValues
				.stream()
				.anyMatch(it -> {
					final AssociatedDataKey key = it.key();
					final Serializable thisValue = it.value();
					final Serializable otherValue = second.getAssociatedData(
						key.associatedDataName(), key.locale()
					);
					return QueryUtils.valueDiffers(thisValue, otherValue);
				});
		}
	}

	/**
	 * Returns value associated with the key or null when the associatedData is missing.
	 *
	 * @throws ClassCastException when associatedData is of different type than expected
	 */
	@Nullable
	 T getAssociatedData(@Nonnull String associatedDataName);

	/**
	 * Returns value associated with the key or null when the associatedData is missing.
	 *
	 * @throws ClassCastException                 when associatedData is of different type than expected
	 * @throws IncompleteDeserializationException when only part of the complex object data was deserialized
	 */
	@Nullable
	 T getAssociatedData(@Nonnull String associatedDataName, @Nonnull Class dtoType, @Nonnull ReflectionLookup reflectionLookup);

	/**
	 * Returns array of values associated with the key or null when the associatedData is missing.
	 *
	 * @throws ClassCastException when associatedData is of different type than expected or is not an array
	 */
	@Nullable
	 T[] getAssociatedDataArray(@Nonnull String associatedDataName);

	/**
	 * Returns array of values associated with the key or null when the associated data is missing.
	 *
	 * Method returns wrapper dto for the associated data that contains information about the associated data version
	 * and state.
	 */
	@Nonnull
	Optional getAssociatedDataValue(@Nonnull String associatedDataName);

	/**
	 * Returns value associated with the key or null when the associatedData is missing.
	 * When localized associatedData is not found it is looked up in generic (non-localized) associatedDatas. This makes this
	 * method safest way how to lookup for associatedData if caller doesn't know whether it is localized or not.
	 *
	 * @throws ClassCastException when associatedData is of different type than expected
	 */
	@Nullable
	 T getAssociatedData(@Nonnull String associatedDataName, @Nonnull Locale locale);

	/**
	 * Returns value associated with the key or null when the associatedData is missing.
	 *
	 * @throws ClassCastException                 when associatedData is of different type than expected
	 * @throws IncompleteDeserializationException when only part of the complex object data was deserialized
	 */
	@Nullable
	 T getAssociatedData(@Nonnull String associatedDataName, @Nonnull Locale locale, @Nonnull Class dtoType, @Nonnull ReflectionLookup reflectionLookup);

	/**
	 * Returns array of values associated with the key or null when the associatedData is missing.
	 * When localized associatedData is not found it is looked up in generic (non-localized) associatedDatas. This makes this
	 * method safest way how to lookup for associatedData if caller doesn't know whether it is localized or not.
	 *
	 * @throws ClassCastException when associatedData is of different type than expected or is not an array
	 */
	@Nullable
	 T[] getAssociatedDataArray(@Nonnull String associatedDataName, @Nonnull Locale locale);

	/**
	 * Returns array of values associated with the key or null when the associated data is missing.
	 * When localized associated data is not found it is looked up in generic (non-localized) associated data. This
	 * makes this method safest way how to lookup for associated data if caller doesn't know whether it is localized
	 * or not.
	 *
	 * Method returns wrapper dto for the associated data that contains information about the associated data version
	 * and state.
	 */
	@Nonnull
	Optional getAssociatedDataValue(@Nonnull String associatedDataName, @Nonnull Locale locale);

	/**
	 * Returns definition for the associatedData of specified name.
	 */
	@Nonnull
	Optional getAssociatedDataSchema(@Nonnull String associatedDataName);

	/**
	 * Returns set of all keys registered in this associatedData set. The result set is not limited to the set
	 * of currently fetched associated data.
	 */
	@Nonnull
	Set getAssociatedDataNames();

	/**
	 * Returns set of all keys (combination of associated data name and locale) registered in this associated data.
	 */
	@Nonnull
	Set getAssociatedDataKeys();

	/**
	 * Returns array of values associated with the key or null when the associated data is missing.
	 * When localized associated data is not found it is looked up in generic (non-localized) associated data.
	 * This makes this method the safest way how to lookup for associated data if caller doesn't know whether it is
	 * localized or not.
	 *
	 * Method returns wrapper dto for the associated data that contains information about the associated data version
	 * and state.
	 */
	@Nonnull
	Optional getAssociatedDataValue(@Nonnull AssociatedDataKey associatedDataKey);

	/**
	 * Returns collection of all values present in this object.
	 */
	@Nonnull
	Collection getAssociatedDataValues();

	/**
	 * Returns collection of all values of `associatedDataName` present in this object. This method has usually sense
	 * only when the associated data is present in multiple localizations.
	 */
	@Nonnull
	Collection getAssociatedDataValues(@Nonnull String associatedDataName);

	/**
	 * Method returns set of locales used in the localized associated data. The result set is not limited to the set
	 * of currently fetched associated data.
	 */
	@Nonnull
	Set getAssociatedDataLocales();

	/**
	 * Inner implementation used in {@link AssociatedDataContract} to represent a proper key in hash map.
	 *
	 * @param associatedDataName Unique name of the associatedData. Case-sensitive. Distinguishes one associated data
	 *                           item from another within single entity instance.
	 * @param locale             Contains locale in case the associatedData is locale specific
	 *                           (i.e. {@link AssociatedDataSchemaContract#isLocalized()}
	 * @author Jan Novotný ([email protected]), FG Forrest a.s. (c) 2021
	 */
	record AssociatedDataKey(
		@Nonnull String associatedDataName,
		@Nullable Locale locale
	) implements Serializable, Comparable {
		@Serial private static final long serialVersionUID = -8516513307116598241L;

		/**
		 * Constructor for the locale specific associatedData.
		 */
		public AssociatedDataKey {
			Assert.notNull(associatedDataName, "AssociatedData name cannot be null!");
		}

		/**
		 * Construction for the locale agnostics associatedData.
		 */
		public AssociatedDataKey(@Nonnull String associatedDataName) {
			this(associatedDataName, null);
		}

		/**
		 * Returns true if associatedData is localized.
		 */
		public boolean localized() {
			return locale != null;
		}

		@Override
		public int compareTo(@Nonnull AssociatedDataKey o) {
			return compareLocale(locale, o.locale, () -> associatedDataName.compareTo(o.associatedDataName));
		}

		/**
		 * 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.
		 */
		public int estimateSize() {
			return MemoryMeasuringConstants.OBJECT_HEADER_SIZE +
				// data name
				MemoryMeasuringConstants.REFERENCE_SIZE + MemoryMeasuringConstants.computeStringSize(associatedDataName) +
				// locale
				MemoryMeasuringConstants.REFERENCE_SIZE;
		}

		@Override
		public String toString() {
			return associatedDataName + (locale == null ? "" : ":" + locale);
		}
	}

	/**
	 * Associated data holder is used internally to keep track of associated data as well as its unique identification that
	 * is assigned new everytime associated data changes somehow.
	 *
	 * @param version Contains version of this object and gets increased with any associatedData update. Allows to execute
	 *                optimistic locking i.e. avoiding parallel modifications.
	 * @param key     Uniquely identifies the associated data value among other associated data in the same entity instance.
	 * @param value   Returns associated data value contents.
	 * @param dropped Contains TRUE if associatedData was dropped - i.e. removed. Such associated data are not removed
	 *                (unless tidying process does it), but are lying among other associated data with tombstone flag.
	 *                Dropped associated data can be overwritten by a new value continuing with the versioning where it
	 *                was stopped for the last time.
	 * @author Jan Novotný ([email protected]), FG Forrest a.s. (c) 2021
	 */
	record AssociatedDataValue(
		int version,
		@Nonnull AssociatedDataKey key,
		@Nullable Serializable value,
		boolean dropped
	) implements Versioned, Droppable, Serializable, ContentComparator {
		@Serial private static final long serialVersionUID = -4732226749198696974L;

		public AssociatedDataValue(@Nonnull AssociatedDataKey key, @Nullable Serializable value) {
			this(1, key, value, false);
		}

		public AssociatedDataValue(int version, @Nonnull AssociatedDataKey key, @Nullable Serializable value) {
			this(version, key, value, false);
		}

		@Override
		public boolean equals(Object o) {
			if (this == o) return true;
			if (o == null || getClass() != o.getClass()) return false;

			AssociatedDataValue that = (AssociatedDataValue) o;

			if (version != that.version) return false;
			return key.equals(that.key);
		}

		@Override
		public int hashCode() {
			int result = version;
			result = 31 * result + key.hashCode();
			return result;
		}

		/**
		 * 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.
		 */
		public int estimateSize() {
			// version
			// dropped
			// key
			// value size estimate
			return MemoryMeasuringConstants.OBJECT_HEADER_SIZE
				// version
				+ MemoryMeasuringConstants.INT_SIZE +
				// dropped
				+MemoryMeasuringConstants.BYTE_SIZE +
				// key
				+key.estimateSize()
				// value size estimate
				+ MemoryMeasuringConstants.REFERENCE_SIZE + EvitaDataTypes.estimateSize(value);
		}

		/**
		 * Returns true if this associatedData differs in key factors from the passed associatedData.
		 */
		@Override
		public boolean differsFrom(@Nullable AssociatedDataValue otherAssociatedDataValue) {
			if (otherAssociatedDataValue == null) return true;
			if (!Objects.equals(key, otherAssociatedDataValue.key)) return true;
			if (QueryUtils.valueDiffers(value, otherAssociatedDataValue.value)) return true;
			return dropped != otherAssociatedDataValue.dropped;
		}

		@Override
		public String toString() {
			return (dropped ? "❌ " : "") +
				"\uD83D\uDD11 " + key.associatedDataName() + " " +
				(key.locale() == null ? "" : "(" + key.locale() + ")") +
				": " +
				(
					value instanceof Object[] ?
						"[" + Arrays.stream((Object[]) value).filter(Objects::nonNull).map(Object::toString).collect(Collectors.joining(",")) + "]" :
						value
				);
		}
	}
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy