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

io.evitadb.api.requestResponse.EvitaResponse 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;

import io.evitadb.api.query.Query;
import io.evitadb.api.requestResponse.extraResult.QueryTelemetry;
import io.evitadb.dataType.DataChunk;
import io.evitadb.exception.GenericEvitaInternalError;

import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.io.Serializable;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;

/**
 * Evita response contains all results to single query. Results are divided to two parts - main results returned by
 * {@link #getRecordPage()} and set of extra results retrieved by {@link #getExtraResult(Class)}.
 *
 * Evita tries to take advantage of all possible intermediate results to minimize computational costs so that there
 * could be a variety of extra results attached to the base response data.
 *
 * The idea behind it is as follows: client requires data A and B, for computing result A we need to compute data C.
 * This data C is necessary even for computing the result B, so we can reuse this intermediate result C if both A and B
 * results are queried and returned within the same query. If computation of A takes 73ms, B takes 62ms and both require
 * intermediate computation C that takes 42ms, then we could achieve result computation in (73-42 + 62-42) = 93ms
 * instead of (73+62) = 135ms that would take when there were two separates queries.
 *
 * @author Jan Novotný ([email protected]), FG Forrest a.s. (c) 2021
 */
public abstract sealed class EvitaResponse
	permits EvitaBinaryEntityResponse, EvitaEntityReferenceResponse, EvitaEntityResponse {
	protected final Query sourceQuery;
	protected final DataChunk recordPage;
	protected final Map, EvitaResponseExtraResult> extraResults = new HashMap<>();

	protected EvitaResponse(@Nonnull Query sourceQuery, @Nonnull DataChunk recordPage) {
		this.sourceQuery = sourceQuery;
		this.recordPage = recordPage;
	}

	protected EvitaResponse(@Nonnull Query sourceQuery,
	                        @Nonnull DataChunk recordPage,
	                        @Nonnull EvitaResponseExtraResult... extraResults) {
		this.sourceQuery = sourceQuery;
		this.recordPage = recordPage;
		for (EvitaResponseExtraResult extraResult : extraResults) {
			addExtraResult(extraResult);
		}
	}

	/**
	 * Returns input query this response reacts to.
	 */
	@Nonnull
	public Query getSourceQuery() {
		return sourceQuery;
	}

	/**
	 * Returns page of records according to pagination rules in input query. If no pagination was defined in input
	 * query `page(1, 20)` is assumed.
	 */
	@Nonnull
	public DataChunk getRecordPage() {
		return recordPage;
	}

	/**
	 * Returns slice of data that belongs to the requested page.
	 */
	@Nonnull
	public List getRecordData() {
		return recordPage.getData();
	}

	/**
	 * Returns total count of available main records in entire result set (i.e. ignoring current pagination settings).
	 */
	public int getTotalRecordCount() {
		return recordPage.getTotalRecordCount();
	}

	/**
	 * Returns set of extra result types provided along with the base result.
	 */
	@Nonnull
	public Set> getExtraResultTypes() {
		return this.extraResults.keySet();
	}

	/**
	 * Adds extra result to be accompanied with standard record page.
	 */
	public void addExtraResult(@Nonnull EvitaResponseExtraResult extraResult) {
		this.extraResults.put(extraResult.getClass(), extraResult);
	}

	/**
	 * Returns extra result attached to the base data response of specified type. See documentation for this class.
	 */
	@Nullable
	public  S getExtraResult(Class resultType) {
		final Object extraResult = this.extraResults.get(resultType);
		if (extraResult != null && !resultType.isInstance(extraResult)) {
			throw new GenericEvitaInternalError("This should never happen!");
		}
		//noinspection unchecked
		return (S) extraResult;
	}

	/**
	 * Returns all attached extra results.
	 */
	@Nonnull
	public Map, EvitaResponseExtraResult> getExtraResults() {
		return Collections.unmodifiableMap(this.extraResults);
	}

	@Override
	public boolean equals(Object o) {
		if (this == o) return true;
		if (o == null || getClass() != o.getClass()) return false;
		EvitaResponse that = (EvitaResponse) o;
		return recordPage.equals(that.recordPage) &&
			extraResults.size() == ((EvitaResponse) o).extraResults.size() &&
			extraResults.entrySet()
				.stream()
				.filter(it -> !(it instanceof QueryTelemetry))
				.allMatch(it -> it.getValue().equals(((EvitaResponse) o).extraResults.get(it.getKey())));
	}

	@Override
	public int hashCode() {
		return Objects.hash(recordPage);
	}

	@Override
	public String toString() {
		return "EvitaResponse:" +
			"\nsourceQuery:\n" + sourceQuery.prettyPrint() +
			"\nresult:\n" + recordPage +
			(extraResults.isEmpty() ? "" : "\nextraResults\n: " + extraResults);
	}
}