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

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

There is a newer version: 4.5.0
Show 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 com.arangodb.ArangoCursor;
import com.arangodb.ArangoDBException;
import com.arangodb.entity.DocumentDeleteEntity;
import com.arangodb.entity.ErrorEntity;
import com.arangodb.entity.MultiDocumentEntity;
import com.arangodb.model.AqlQueryOptions;
import com.arangodb.model.DocumentDeleteOptions;
import com.arangodb.springframework.core.DocumentNotFoundException;
import com.arangodb.springframework.core.convert.ArangoConverter;
import com.arangodb.springframework.core.mapping.ArangoMappingContext;
import com.arangodb.springframework.core.mapping.ArangoPersistentEntity;
import com.arangodb.springframework.core.template.ArangoTemplate;
import com.arangodb.springframework.core.util.AqlUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.dao.OptimisticLockingFailureException;
import org.springframework.data.domain.*;
import org.springframework.data.repository.query.FluentQuery;
import org.springframework.lang.Nullable;
import org.springframework.stereotype.Repository;

import java.util.*;
import java.util.function.Function;

/**
 * The implementation of all CRUD, paging and sorting functionality in
 * ArangoRepository from the Spring Data Commons CRUD repository and
 * PagingAndSorting repository
 */
@Repository
@SuppressWarnings({ "rawtypes", "unchecked" })
public class SimpleArangoRepository implements ArangoRepository {

	private static final Logger LOGGER = LoggerFactory.getLogger(SimpleArangoRepository.class);

	private final ArangoTemplate arangoTemplate;
	private final ArangoConverter converter;
	private final ArangoMappingContext mappingContext;
	private final ArangoExampleConverter exampleConverter;
	private final Class domainClass;
	private final boolean returnOriginalEntities;
    private final ArangoPersistentEntity persistentEntity;

	/**
	 * @param arangoTemplate       The template used to execute much of the
	 *                               functionality of this class
	 * @param domainClass            the class type of this repository
	 * @param returnOriginalEntities whether save and saveAll should return the
	 *                               original entities or new ones
	 */
	public SimpleArangoRepository(final ArangoTemplate arangoTemplate, final Class domainClass, boolean returnOriginalEntities) {
		super();
		this.arangoTemplate = arangoTemplate;
		this.domainClass = domainClass;
		this.returnOriginalEntities = returnOriginalEntities;
		converter = arangoTemplate.getConverter();
		mappingContext = (ArangoMappingContext) converter.getMappingContext();
		exampleConverter = new ArangoExampleConverter(mappingContext, arangoTemplate.getResolverFactory());
        persistentEntity = mappingContext.getRequiredPersistentEntity(domainClass);
	}

	/**
	 * Saves the passed entity to the database using repsert from the template
	 *
	 * @param entity the entity to be saved to the database
	 * @return the updated entity with any id/key/rev saved
	 */
	@Override
	public  S save(final S entity) {
		S saved = arangoTemplate.repsert(entity);
		return returnOriginalEntities ? entity : saved;
	}

	/**
	 * Saves the given iterable of entities to the database using repsert from the template
	 *
	 * @param entities the iterable of entities to be saved to the database
	 * @return the iterable of updated entities with any id/key/rev saved in each
	 *         entity
	 */
	@Override
	public  Iterable saveAll(final Iterable entities) {
		Iterable saved = arangoTemplate.repsertAll(entities, domainClass);
		return returnOriginalEntities ? entities : saved;
	}

	/**
	 * Finds if a document with the given id exists in the database
	 *
	 * @param id the id of the document to search for
	 * @return the object representing the document if found
	 */
	@Override
	public Optional findById(final ID id) {
		return arangoTemplate.find(id, domainClass);
	}

	/**
	 * Checks if a document exists or not based on the given id or key
	 *
	 * @param id represents either the key or id of a document to check for
	 * @return returns true if the document is found, false otherwise
	 */
	@Override
	public boolean existsById(final ID id) {
		return arangoTemplate.exists(id, domainClass);
	}

	/**
	 * Gets all documents in the collection for the class type of this repository
	 *
	 * @return an iterable with all the documents in the collection
	 */
	@Override
	public Iterable findAll() {
		return arangoTemplate.findAll(domainClass);
	}

	/**
	 * Finds all documents with the an id or key in the argument
	 *
	 * @param ids an iterable with ids/keys of documents to get
	 * @return an iterable with documents in the collection which have a id/key in
	 *         the argument
	 */
	@Override
	public Iterable findAllById(final Iterable ids) {
		return arangoTemplate.findAll(ids, domainClass);
	}

	/**
	 * Counts the number of documents in the collection for the type of this
	 * repository
	 *
	 * @return long with number of documents
	 */
	@Override
	public long count() {
		return arangoTemplate.collection(domainClass).count();
	}

	/**
	 * Deletes the document with the given id or key
	 *
	 * @param id id or key of document to be deleted
	 */
	@Override
	public void deleteById(final ID id) {
		try {
			arangoTemplate.delete(id, domainClass);
		} catch (DocumentNotFoundException unknown) {
//			 silently ignored
		}
	}

	/**
	 * Deletes document in the database representing the given object, by getting
	 * it's id
	 *
	 * @param entity the entity to be deleted from the database
	 */
    @Override
    public void delete(final T entity) {
		Object id = persistentEntity.getIdentifierAccessor(entity).getRequiredIdentifier();
        DocumentDeleteOptions opts = new DocumentDeleteOptions();
		persistentEntity.getRevProperty()
                .map(persistentEntity.getPropertyAccessor(entity)::getProperty)
				.map(r -> converter.convertIfNecessary(r, String.class))
                .ifPresent(opts::ifMatch);

        try {
            arangoTemplate.delete(id, opts, domainClass);
        } catch (DocumentNotFoundException e) {
            throw new OptimisticLockingFailureException(e.getMessage(), e);
        }
    }

    /**
     * Deletes all instances of the type {@code T} with the given IDs.
	 * @implNote do not add @Override annotation to keep backwards compatibility with spring-data-commons 2.4
     */
	public void deleteAllById(Iterable ids) {
		MultiDocumentEntity> res = arangoTemplate.deleteAllById(ids, domainClass);
		for (ErrorEntity error : res.getErrors()) {
			// Entities that aren't found in the persistence store are silently ignored.
			if (error.getErrorNum() != 1202) {
				throw arangoTemplate.translateException(new ArangoDBException(error));
			}
		}
	}

	/**
	 * Deletes all the given documents from the database
	 *
	 * @param entities iterable of entities to be deleted from the database
	 */
	@Override
	public void deleteAll(final Iterable entities) {
		entities.forEach(this::delete);
	}

	/**
	 * Deletes all documents in the collection for this repository
	 */
	@Override
	public void deleteAll() {
		arangoTemplate.collection(domainClass).truncate();
	}

	/**
	 * Gets all documents in the collection for the class type of this repository,
	 * with the given sort applied
	 *
	 * @param sort the sort object to use for sorting
	 * @return an iterable with all the documents in the collection
	 */
	@Override
	public Iterable findAll(final Sort sort) {
		return new Iterable() {
			@Override
			public Iterator iterator() {
				return findAllInternal(sort, null, new HashMap<>());
			}
		};
	}

	/**
	 * Gets all documents in the collection for the class type of this repository,
	 * with pagination
	 *
	 * @param pageable the pageable object to use for pagination of the results
	 * @return an iterable with all the documents in the collection
	 */
	@Override
	public Page findAll(final Pageable pageable) {
		if (pageable == null) {
			LOGGER.debug("Pageable in findAll(Pageable) is null");
		}

		final ArangoCursor result = findAllInternal(pageable, null, new HashMap<>());
		final List content = result.asListRemaining();
		return new PageImpl<>(content, pageable, ((Number) result.getStats().getFullCount()).longValue());
	}

	/**
	 * Gets the name of the collection for this repository
	 *
	 * @return the name of the collection
	 */
	private String getCollectionName() {
		return persistentEntity.getCollection();
	}

	/**
	 * Finds one document which matches the given example object
	 *
	 * @param example example object to construct query with
	 * @param 
	 * @return An object representing the example if it exists, else null
	 */
	@Override
	public  Optional findOne(final Example example) {
		final ArangoCursor cursor = findAllInternal((Pageable) null, example, new HashMap());
		return cursor.hasNext() ? Optional.ofNullable((S) cursor.next()) : Optional.empty();
	}

	/**
	 * Finds all documents which match with the given example
	 *
	 * @param example example object to construct query with
	 * @param 
	 * @return iterable of all matching documents
	 */
	@Override
	public  Iterable findAll(final Example example) {
		return (ArangoCursor) findAllInternal((Pageable) null, example, new HashMap<>());
	}

	/**
	 * Finds all documents which match with the given example, then apply the given
	 * sort to results
	 *
	 * @param example example object to construct query with
	 * @param sort    sort object to sort results
	 * @param 
	 * @return sorted iterable of all matching documents
	 */
	@Override
	public  Iterable findAll(final Example example, final Sort sort) {
		return findAllInternal(sort, example, new HashMap());
	}

	/**
	 * Finds all documents which match with the given example, with pagination
	 *
	 * @param example  example object to construct query with
	 * @param pageable pageable object to apply pagination with
	 * @param 
	 * @return iterable of all matching documents, with pagination
	 */
	@Override
	public  Page findAll(final Example example, final Pageable pageable) {
		final ArangoCursor cursor = findAllInternal(pageable, example, new HashMap());
		final List content = cursor.asListRemaining();
		return new PageImpl<>((List) content, pageable, ((Number) cursor.getStats().getFullCount()).longValue());
	}

	/**
	 * Counts the number of documents in the collection which match with the given
	 * example
	 *
	 * @param example example object to construct query with
	 * @param 
	 * @return number of matching documents found
	 */
	@Override
	public  long count(final Example example) {
		final Map bindVars = new HashMap<>();
		bindVars.put("@col", getCollectionName());
		final String predicate = exampleConverter.convertExampleToPredicate(example, bindVars);
		final String filter = predicate.length() == 0 ? "" : " FILTER " + predicate;
		final String query = String.format("FOR e IN @@col %s COLLECT WITH COUNT INTO length RETURN length", filter);
		arangoTemplate.collection(domainClass);
		final ArangoCursor cursor = arangoTemplate.query(query, bindVars, null, Long.class);
		return cursor.next();
	}

	/**
	 * Checks if any documents match with the given example
	 *
	 * @param example
	 * @param 
	 * @return true if any matches are found, else false
	 */
	@Override
	public  boolean exists(final Example example) {
		return count(example) > 0;
	}

	public  R findBy(Example example, Function, R> queryFunction) {
		throw new UnsupportedOperationException();
	}

	private  ArangoCursor findAllInternal(final Sort sort, @Nullable final Example example,
			final Map bindVars) {
		bindVars.put("@col", getCollectionName());
		final String query = String.format("FOR e IN @@col %s %s RETURN e",
				buildFilterClause(example, bindVars), buildSortClause(sort, "e"));
		arangoTemplate.collection(domainClass);
		return arangoTemplate.query(query, bindVars, null, domainClass);
	}

	private  ArangoCursor findAllInternal(final Pageable pageable, @Nullable final Example example,
			final Map bindVars) {
		bindVars.put("@col", getCollectionName());
		final String query = String.format("FOR e IN @@col %s %s RETURN e",
				buildFilterClause(example, bindVars), buildPageableClause(pageable, "e"));
		arangoTemplate.collection(domainClass);
		return arangoTemplate.query(query, bindVars,
				pageable != null ? new AqlQueryOptions().fullCount(true) : null, domainClass);
	}

	private  String buildFilterClause(final Example example, final Map bindVars) {
		if (example == null) {
			return "";
		}

		final String predicate = exampleConverter.convertExampleToPredicate(example, bindVars);
		return predicate == null ? "" : "FILTER " + predicate;
	}

    private String buildPageableClause(final Pageable pageable, final String varName) {
        if (pageable == null) return "";
        Sort persistentSort = AqlUtils.toPersistentSort(pageable.getSort(), mappingContext, domainClass);
		Pageable persistentPageable;
		if (pageable.isPaged()) {
			persistentPageable = PageRequest.of(pageable.getPageNumber(), pageable.getPageSize(), persistentSort);
		} else {
			persistentPageable = pageable;
		}
        return AqlUtils.buildPageableClause(persistentPageable, varName);
    }

    private String buildSortClause(final Sort sort, final String varName) {
        return sort == null ? "" : AqlUtils.buildSortClause(AqlUtils.toPersistentSort(sort, mappingContext, domainClass), varName);
    }

}