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

com.arangodb.springframework.repository.ArangoExampleConverter Maven / Gradle / Ivy

The newest version!
/*
 * DISCLAIMER
 *
 * Copyright 2017 ArangoDB GmbH, Cologne, Germany
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * 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.
 *
 * Copyright holder is ArangoDB GmbH, Cologne, Germany
 */

package com.arangodb.springframework.repository;

import java.lang.annotation.Annotation;
import java.util.HashSet;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;

import com.arangodb.springframework.core.util.AqlUtils;
import org.springframework.data.domain.Example;
import org.springframework.data.domain.ExampleMatcher;
import org.springframework.data.mapping.AssociationHandler;
import org.springframework.data.mapping.PersistentPropertyAccessor;

import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;

import com.arangodb.springframework.core.convert.resolver.ReferenceResolver;
import com.arangodb.springframework.core.convert.resolver.ResolverFactory;
import com.arangodb.springframework.core.mapping.ArangoMappingContext;
import com.arangodb.springframework.core.mapping.ArangoPersistentEntity;
import com.arangodb.springframework.core.mapping.ArangoPersistentProperty;

/**
 * Converts Example to String representing predicate expression and puts
 * necessary bindings in the given bindVars Map
 */
public class ArangoExampleConverter {

	private final ArangoMappingContext context;
	private final ResolverFactory resolverFactory;

	public ArangoExampleConverter(final ArangoMappingContext context, final ResolverFactory resolverFactory) {
		this.context = context;
		this.resolverFactory = resolverFactory;
	}

	public String convertExampleToPredicate(final Example example, final Map bindVars) {
		final StringBuilder predicateBuilder = new StringBuilder();
		final ArangoPersistentEntity persistentEntity = context.getRequiredPersistentEntity(example.getProbeType());
		Assert.notNull(example.getProbe(), "Probe in Example cannot be null");
		final String bindEntintyName = "e";
		traversePropertyTree(example, predicateBuilder, bindVars, "", "", persistentEntity, example.getProbe(),
				bindEntintyName);
		return predicateBuilder.toString();
	}

	private void traversePropertyTree(final Example example, final StringBuilder predicateBuilder,
			final Map bindVars, final String path, final String javaPath,
			final ArangoPersistentEntity entity, final Object object, final String bindEntintyName) {
		final PersistentPropertyAccessor accessor = entity.getPropertyAccessor(object);
		entity.doWithProperties((final ArangoPersistentProperty property) -> {
			if (property.getFrom().isPresent() || property.getTo().isPresent() || property.getRelations().isPresent()) {
				return;
			}
			final String fullPath = path + (path.length() == 0 ? "" : ".") + AqlUtils.buildFieldName(property.getFieldName());
			final String fullJavaPath = javaPath + (javaPath.length() == 0 ? "" : ".") + property.getName();
			final Object value = accessor.getProperty(property);

			if (property.isCollectionLike() && value != null) {
				final Class actualType = property.getActualType();
				final ArangoPersistentEntity persistentEntity = context.getPersistentEntity(actualType);
				final StringBuilder predicateBuilderArray = new StringBuilder();
				for (Object item : (Iterable) value) {
					final StringBuilder predicateBuilderArrayItem = new StringBuilder();
					if (ClassUtils.isPrimitiveOrWrapper(item.getClass()) || item.getClass() == String.class) {
						addPredicate(example, predicateBuilderArrayItem, bindVars, null, fullJavaPath, item, "CURRENT");
					} else {
						Assert.notNull(persistentEntity, "Missing persistent entity for " + actualType);
						traversePropertyTree(example, predicateBuilderArrayItem, bindVars, "", fullJavaPath,
								persistentEntity, item, "CURRENT");
					}

					if (predicateBuilderArray.length() > 0) {
						predicateBuilderArray.append(" OR ");
					}
					predicateBuilderArray.append(predicateBuilderArrayItem);
				}
				final String delimiter = example.getMatcher().isAllMatching() ? " AND " : " OR ";
				if (predicateBuilder.length() > 0) {
					predicateBuilder.append(delimiter);
				}
				String clause = String.format("LENGTH(%s.%s[* FILTER %s ])>0", bindEntintyName, property.getName(),
						predicateBuilderArray);
				predicateBuilder.append(clause);

			} else if (property.isEntity() && value != null) {
				final ArangoPersistentEntity persistentEntity = context.getRequiredPersistentEntity(property.getType());
				traversePropertyTree(example, predicateBuilder, bindVars, fullPath, fullJavaPath, persistentEntity,
						value, bindEntintyName);
			} else if (!example.getMatcher().isIgnoredPath(fullJavaPath) && (value != null
					|| example.getMatcher().getNullHandler().equals(ExampleMatcher.NullHandler.INCLUDE))) {
				addPredicate(example, predicateBuilder, bindVars, fullPath, fullJavaPath, value, bindEntintyName);
			}
		});

		entity.doWithAssociations((AssociationHandler) association -> {

			final ArangoPersistentProperty property = association.getInverse();
			final String fullPath = path + (path.length() == 0 ? "" : ".") + AqlUtils.buildFieldName(property.getFieldName());
			final String fullJavaPath = javaPath + (javaPath.length() == 0 ? "" : ".") + property.getName();
			final Object value = accessor.getProperty(property);

			if (property.isEntity() && property.isAssociation() && value != null) {
				final ArangoPersistentEntity persistentEntity = context.getRequiredPersistentEntity(property.getType());
				final PersistentPropertyAccessor associatedAccessor = persistentEntity.getPropertyAccessor(value);
				final Object idValue = associatedAccessor.getProperty(persistentEntity.getIdProperty());
				String refIdValue = null;
				if (property.getRef().isPresent()) {
					final Optional> resolver = resolverFactory
							.getReferenceResolver(property.getRef().get());
					refIdValue = resolver.get().write(value, persistentEntity, idValue);
				} else {
					refIdValue = String.format("%s/%s", persistentEntity.getCollection(), idValue);
				}
				addPredicate(example, predicateBuilder, bindVars, fullPath, fullJavaPath, refIdValue, bindEntintyName);
			}
		});
	}

	private void addPredicate(final Example example, final StringBuilder predicateBuilder,
			final Map bindVars, final String fullPath, final String fullJavaPath, Object value,
							  final String bindEntintyName) {
		final String delimiter = example.getMatcher().isAllMatching() ? " AND " : " OR ";
		if (predicateBuilder.length() > 0) {
			predicateBuilder.append(delimiter);
		}
		final String binding = Integer.toString(bindVars.size());
		String clause;
		final ExampleMatcher.PropertySpecifier specifier = example.getMatcher().getPropertySpecifiers()
				.getForPath(fullJavaPath);
		if (specifier != null && value != null) {
			value = specifier.transformValue(Optional.of(value)).orElse(null);
		}
		if (value == null) {
			clause = String.format("%s.%s == null", bindEntintyName, fullPath);
		} else if (String.class.isAssignableFrom(value.getClass())) {
			final boolean ignoreCase = specifier == null ? example.getMatcher().isIgnoreCaseEnabled()
					: (specifier.getIgnoreCase() == null ? false : specifier.getIgnoreCase());
			final ExampleMatcher.StringMatcher stringMatcher = (specifier == null
					|| specifier.getStringMatcher() == ExampleMatcher.StringMatcher.DEFAULT)
							? example.getMatcher().getDefaultStringMatcher()
							: specifier.getStringMatcher();
			final String string = (String) value;
			if (stringMatcher == ExampleMatcher.StringMatcher.REGEX) {
				if (Objects.isNull(fullPath)) {
					clause = String.format("REGEX_TEST(%s, @%s, %b)", bindEntintyName, binding, ignoreCase);
				} else {
					clause = String.format("REGEX_TEST(%s.%s, @%s, %b)", bindEntintyName, fullPath, binding,
							ignoreCase);
				}
			} else {
				if (Objects.isNull(fullPath)) {
					clause = String.format("LIKE(%s, @%s, %b)", bindEntintyName, binding, ignoreCase);
				} else {
					clause = String.format("LIKE(%s.%s, @%s, %b)", bindEntintyName, fullPath, binding, ignoreCase);
				}
			}
			switch (stringMatcher) {
			case STARTING:
				value = escape(string) + "%";
				break;
			case ENDING:
				value = "%" + escape(string);
				break;
			case CONTAINING:
				value = "%" + escape(string) + "%";
				break;
			case REGEX:
				value = escape(string);
				break;
			case DEFAULT:
			case EXACT:
			default:
				value = escape(string);
				break;
			}
		} else {
			if (Objects.isNull(fullPath)) {
				clause = String.format("%s == @%s", bindEntintyName, binding);
			} else {
				clause = String.format("%s.%s == @%s", bindEntintyName, fullPath, binding);
			}

		}
		predicateBuilder.append(clause);
		if (value != null) {
			bindVars.put(binding, value);
		}
	}

	private static final Set SPECIAL_CHARACTERS = new HashSet<>();

	static {
		SPECIAL_CHARACTERS.add('\\');
		SPECIAL_CHARACTERS.add('_');
		SPECIAL_CHARACTERS.add('%');
	}

	private static String escape(final String string) {
		final StringBuilder stringBuilder = new StringBuilder();
		for (final Character character : string.toCharArray()) {
			if (SPECIAL_CHARACTERS.contains(character)) {
				stringBuilder.append('\\');
			}
			stringBuilder.append(character);
		}
		return stringBuilder.toString();
	}
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy