/* * Copyright (c) [2011-2017] "Pivotal Software, Inc." / "Neo Technology" / "Graph Aware Ltd." * * This product is licensed to you under the Apache License, Version 2.0 (the "License"). * You may not use this product except in compliance with the License. * * This product may include a number of subcomponents with * separate copyright notices and license terms. Your use of the source * code for these subcomponents is subject to the terms and * conditions of the subcomponent's license, as noted in the LICENSE file. * */ /* * Copyright (c) [2011-2017] "Pivotal Software, Inc." / "Neo Technology" / "Graph Aware Ltd." * * This product is licensed to you under the Apache License, Version 2.0 (the "License"). * You may not use this product except in compliance with the License. * * This product may include a number of subcomponents with * separate copyright notices and license terms. Your use of the source * code for these subcomponents is subject to the terms and * conditions of the subcomponent's license, as noted in the LICENSE file. * */ package org.springframework.data.neo4j.repository.query; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.function.LongSupplier; import org.neo4j.ogm.session.Session; import org.springframework.dao.IncorrectResultSizeDataAccessException; import org.springframework.data.domain.Pageable; import org.springframework.data.domain.SliceImpl; import org.springframework.data.neo4j.annotation.QueryResult; import org.springframework.data.repository.support.PageableExecutionUtils; import org.springframework.util.Assert; /** * Classes intended to pilot query execution according to the type of the query. * The type of the query is determined by looking at the result class of the method. * @see AbstractGraphRepositoryQuery#getExecution(org.springframework.data.neo4j.repository.query.GraphParameterAccessor) * * @author Nicolas Mervaillie */ public interface GraphQueryExecution { Object execute(Query query, Class<?> type); final class SingleEntityExecution implements GraphQueryExecution { private final Session session; private final GraphParameterAccessor accessor; SingleEntityExecution(Session session, GraphParameterAccessor accessor) { this.session = session; this.accessor = accessor; } @Override public Object execute(Query query, Class<?> type) { Iterable<?> result; if (query.isFilterQuery()) { result = session.loadAll(type, query.getFilters(), accessor.getDepth()); } else { // not using queryForObject here because it raises too generic RuntimeException // if more than one result found if (type.getAnnotation(QueryResult.class) != null) { result = session.query(query.getCypherQuery(), query.getParameters()).queryResults(); } else { result = session.query(type, query.getCypherQuery(), query.getParameters()); } } Iterator<?> iterator = result.iterator(); if (! iterator.hasNext()) { return null; } Object ret = iterator.next(); if (iterator.hasNext()) { throw new IncorrectResultSizeDataAccessException("Incorrect result size: expected at most 1", 1); } return ret; } } final class CollectionExecution implements GraphQueryExecution { private final Session session; private final GraphParameterAccessor accessor; CollectionExecution(Session session, GraphParameterAccessor accessor) { this.session = session; this.accessor = accessor; } @Override public Object execute(Query query, Class<?> type) { if (query.isFilterQuery()) { return session.loadAll(type, query.getFilters(), accessor.getOgmSort()); } else { if (type.getAnnotation(QueryResult.class) != null || Map.class.isAssignableFrom(type)) { return session.query(query.getCypherQuery(accessor.getSort()), query.getParameters()).queryResults(); } else { return session.query(type, query.getCypherQuery(accessor.getSort()), query.getParameters()); } } } } final class QueryResultExecution implements GraphQueryExecution { private final Session session; private final GraphParameterAccessor accessor; QueryResultExecution(Session session, GraphParameterAccessor accessor) { this.session = session; this.accessor = accessor; } @Override public Object execute(Query query, Class<?> type) { return session.query(query.getCypherQuery(accessor.getSort()), query.getParameters()); } } final class PagedExecution implements GraphQueryExecution { private final Session session; private final Pageable pageable; private final GraphParameterAccessor accessor; PagedExecution(Session session, GraphParameterAccessor accessor) { this.session = session; this.pageable = accessor.getPageable(); this.accessor = accessor; } @Override public Object execute(Query query, Class<?> type) { List<?> result; long count; if (query.isFilterQuery()) { result = (List<?>) session.loadAll(type, query.getFilters(), accessor.getOgmSort() , query.getPagination(pageable, false), accessor.getDepth()); count = session.count(type, query.getFilters()); } else { if (type.getAnnotation(QueryResult.class) != null) { result = (List<?>) session.query(query.getCypherQuery(pageable, false), query.getParameters()).queryResults(); } else { result = (List<?>) session.query(type, query.getCypherQuery(pageable, false), query.getParameters()); } count = (result.size() > 0) ? countTotalNumberOfElements(query) : 0; } return PageableExecutionUtils.getPage(result, pageable, (LongSupplier) () -> count); } private Integer countTotalNumberOfElements(Query query) { Assert.hasText(query.getCountQuery(), "Must specify a count query to get pagination info."); return session.queryForObject(Integer.class, query.getCountQuery(), query.getParameters()); } } final class SlicedExecution implements GraphQueryExecution { private final Session session; private final GraphParameterAccessor accessor; private final Pageable pageable; SlicedExecution(Session session, GraphParameterAccessor accessor) { this.session = session; this.accessor = accessor; this.pageable = accessor.getPageable(); } @Override public Object execute(Query query, Class<?> type) { int pageSize = pageable.getPageSize(); List<?> result; if (query.isFilterQuery()) { // For a slice, need one extra result to determine if there is a next page result = (List<?>) session.loadAll(type, query.getFilters(), accessor.getOgmSort() , query.getPagination(pageable, true), accessor.getDepth()); } else { String cypherQuery = query.getCypherQuery(pageable, true); if (type.getAnnotation(QueryResult.class) != null) { result = (List<?>) session.query(cypherQuery, query.getParameters()).queryResults(); } else { result = (List<?>) session.query(type, cypherQuery, query.getParameters()); } } boolean hasNext = result.size() > pageSize; return new SliceImpl(hasNext ? result.subList(0, pageSize) : result, pageable, hasNext); } } final class StreamExecution implements GraphQueryExecution { private final Session session; private final GraphParameterAccessor accessor; public StreamExecution(org.neo4j.ogm.session.Session session, GraphParameterAccessor accessor) { this.session = session; this.accessor = accessor; } @Override @SuppressWarnings("unchecked") public Object execute(Query query, Class<?> type) { // not a real support for streaming. for that need that the stack all the way down // supports streaming List<?> result; if (query.isFilterQuery()) { result = (List<?>) session.loadAll(type, query.getFilters(), accessor.getOgmSort(), accessor.getDepth()); } else { // TODO add support for QueryResults as above result = (List<?>) session.query(type, query.getCypherQuery(accessor.getSort()), query.getParameters()); } return result; } } final class CountByExecution implements GraphQueryExecution { private final Session session; CountByExecution(Session session, GraphParameterAccessor accessor) { this.session = session; } @Override public Object execute(Query query, Class<?> type) { return session.count(type, query.getFilters()); } } final class DeleteByExecution implements GraphQueryExecution { private final Session session; private final GraphQueryMethod graphQueryMethod; DeleteByExecution(Session session, GraphQueryMethod graphQueryMethod, GraphParameterAccessor accessor) { this.session = session; this.graphQueryMethod = graphQueryMethod; } @Override public Object execute(Query query, Class<?> type) { Class<?> returnType = graphQueryMethod.getReturnedObjectType(); if (returnType.equals(Long.class)) { return session.delete(type, query.getFilters(), graphQueryMethod.isCollectionQuery()); } throw new RuntimeException("Long or Iterable<Long> is required as the return type of a Delete query"); } } }