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

com.sap.cds.util.CdsSearchUtils Maven / Gradle / Ivy

There is a newer version: 3.8.0
Show newest version
/************************************************************************
 * © 2020-2023 SAP SE or an SAP affiliate company. All rights reserved. *
 ************************************************************************/
package com.sap.cds.util;

import static com.sap.cds.impl.builder.model.Conjunction.and;
import static com.sap.cds.impl.parser.token.CqnBoolLiteral.FALSE;

import java.util.Collection;
import java.util.HashSet;
import java.util.Set;
import java.util.function.BiConsumer;
import java.util.function.Predicate;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import com.sap.cds.impl.builder.model.Connective;
import com.sap.cds.impl.builder.model.CqnNull;
import com.sap.cds.impl.builder.model.Disjunction;
import com.sap.cds.impl.builder.model.ElementRefImpl;
import com.sap.cds.impl.builder.model.LiteralImpl;
import com.sap.cds.impl.builder.model.Negation;
import com.sap.cds.impl.util.Stack;
import com.sap.cds.ql.CQL;
import com.sap.cds.ql.cqn.CqnComparisonPredicate;
import com.sap.cds.ql.cqn.CqnConnectivePredicate;
import com.sap.cds.ql.cqn.CqnElementRef;
import com.sap.cds.ql.cqn.CqnNegation;
import com.sap.cds.ql.cqn.CqnPredicate;
import com.sap.cds.ql.cqn.CqnSearchPredicate;
import com.sap.cds.ql.cqn.CqnSelect;
import com.sap.cds.ql.cqn.CqnVisitor;
import com.sap.cds.ql.impl.SelectBuilder;
import com.sap.cds.reflect.CdsAnnotatable;
import com.sap.cds.reflect.CdsAnnotation;
import com.sap.cds.reflect.CdsAssociationType;
import com.sap.cds.reflect.CdsBaseType;
import com.sap.cds.reflect.CdsElement;
import com.sap.cds.reflect.CdsStructuredType;

public class CdsSearchUtils {

	private static final String DEFAULT_SEARCH_ELEMENT = "Search.defaultSearchElement";
	private static final String CDS_SEARCH = "cds.search";

	@Deprecated
	private static final String SEARCH_CASCADE = "Search.cascade";

	private CdsSearchUtils() {
	}

	public static Collection searchableElementRefs(CdsStructuredType targetType) {
		return searchableRefs(targetType);
	}

	public static void moveSearchToWhere(CqnSelect select, CqnPredicate filter) {
		SelectBuilder selectBuilder = (SelectBuilder) select;

		selectBuilder.where(select.where().map(w -> and(w, filter)).orElse(filter));
		selectBuilder.search((CqnPredicate) null);
	}

	public static CqnPredicate searchToLikeExpression(Collection elements, CqnPredicate expression) {
		if (elements.isEmpty()) {
			return FALSE;
		}

		Stack stack = new Stack<>();
		CqnVisitor visitor = new CqnVisitor() {
			@Override
			public void visit(CqnSearchPredicate search) {
				stack.push(anyElementContains(search.searchTerm()));
			}

			@Override
			public void visit(CqnConnectivePredicate connective) {
				int n = connective.predicates().size();
				stack.push(Connective.create(connective.operator(), stack.pop(n)));
			}

			@Override
			public void visit(CqnNegation cqnNegation) {
				stack.push(Negation.not(stack.pop()));
			}

			private CqnPredicate anyElementContains(String searchTerm) {
				return elements.stream().map(e -> containsCaseInsensitive(e, searchTerm)).collect(Disjunction.or());
			}
		};
		expression.accept(visitor);
		return stack.pop();
	}

	public static Collection getSearchableElements(CqnSelect select, CdsStructuredType targetType) {
		Collection searchableElements = ((SelectBuilder) select).searchableElements();
		if (!searchableElements.isEmpty()) {
			// are searchable elements set manually?
			return searchableElements.stream().map(ElementRefImpl::parse).collect(Collectors.toList());
		}
		return searchableElementRefs(targetType);
	}

	private static Collection searchableRefs(CdsStructuredType targetType) {
		Set searchablePaths = searchablePaths(targetType);

		return searchablePaths.stream().map(ElementRefImpl::parse).collect(Collectors.toList());
	}

	private static Set searchablePaths(CdsStructuredType targetType) {
		Set searchablePaths = new HashSet<>();

		// @cds.search
		onCdsSearch(targetType, (element, path) -> {
			if (!element.getType().isAssociation() && !element.isVirtual()) {
				searchablePaths.add(path);
			}
		});

		// Old style search
		targetType.elements().filter(annotatedWith(DEFAULT_SEARCH_ELEMENT)).filter(e -> !e.isVirtual())
			.map(CdsElement::getName)
			.forEach(searchablePaths::add);

		// TODO - remove with 2.0 - https://github.wdf.sap.corp/cds-java/home/issues/574
		targetType.associations().filter(annotatedWith(SEARCH_CASCADE))
				.forEach(element -> onAssociation(searchablePaths, element));

		// Default elements not explicitly disabled with @cds.search: false
		if (searchablePaths.isEmpty()) {
			defaultSearchElements(targetType).map(CdsElement::getName)
					.filter(n -> targetType.getAnnotationValue(CDS_SEARCH + "." + n, true))
					.forEach(searchablePaths::add);
		}

		onCdsSearch(targetType, ((element, path) -> onAssociation(searchablePaths, element)));

		return searchablePaths;
	}

	private static void onCdsSearch(CdsStructuredType targetType, BiConsumer action) {
		targetType.annotations()
				.filter(annotation -> annotation.getName().startsWith(CDS_SEARCH)
						&& Boolean.TRUE.equals(annotation.getValue()))
				.map(CdsSearchUtils::path)
				.forEach(path -> targetType.findElement(path).ifPresent(element -> action.accept(element, path)));
	}

	private static void onAssociation(Set searchablePaths, CdsElement element) {
		if (element.getType().isAssociation()) {
			String prefix = element.getName();
			searchablePaths(element.getType().as(CdsAssociationType.class).getTarget()).stream()
					.map(suffix -> prefix + "." + suffix).forEach(searchablePaths::add);
		}
	}

	private static String path(CdsAnnotation a) {
		String name = a.getName();
		return name.substring(CDS_SEARCH.length() + 1);
	}

	private static Stream defaultSearchElements(CdsStructuredType type) {
		return type.elements().filter(e -> !e.isVirtual() && e.getType().isSimpleType(CdsBaseType.STRING));
	}

	private static Predicate annotatedWith(String annotationName) {
		return t -> t.getAnnotationValue(annotationName, false);

	}

	private static CqnPredicate containsCaseInsensitive(CqnElementRef element, String searchTerm) {
		return CQL.and(CQL.comparison(element, CqnComparisonPredicate.Operator.IS_NOT, CqnNull.NULL),
				CQL.contains(element, LiteralImpl.val(searchTerm), true));
	}

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy