View Javadoc

1   /**
2    * Copyright © 2018 spring-data-dynamodb (https://github.com/derjust/spring-data-dynamodb)
3    *
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    *     http://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  package org.socialsignin.spring.data.dynamodb.repository.query;
17  
18  import com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBMapper;
19  import org.socialsignin.spring.data.dynamodb.core.DynamoDBOperations;
20  import org.socialsignin.spring.data.dynamodb.domain.UnpagedPageImpl;
21  import org.socialsignin.spring.data.dynamodb.exception.BatchDeleteException;
22  import org.socialsignin.spring.data.dynamodb.query.Query;
23  import org.socialsignin.spring.data.dynamodb.utils.ExceptionHandler;
24  import org.springframework.data.domain.Page;
25  import org.springframework.data.domain.PageImpl;
26  import org.springframework.data.domain.Pageable;
27  import org.springframework.data.domain.Slice;
28  import org.springframework.data.domain.SliceImpl;
29  import org.springframework.data.repository.query.ParameterAccessor;
30  import org.springframework.data.repository.query.Parameters;
31  import org.springframework.data.repository.query.ParametersParameterAccessor;
32  import org.springframework.data.repository.query.RepositoryQuery;
33  
34  import java.util.ArrayList;
35  import java.util.Collections;
36  import java.util.Iterator;
37  import java.util.List;
38  
39  /**
40   * @author Michael Lavelle
41   * @author Sebastian Just
42   */
43  public abstract class AbstractDynamoDBQuery<T, ID> implements RepositoryQuery, ExceptionHandler {
44  
45  	protected final DynamoDBOperations dynamoDBOperations;
46  	private final DynamoDBQueryMethod<T, ID> method;
47  
48  	public AbstractDynamoDBQuery(DynamoDBOperations dynamoDBOperations, DynamoDBQueryMethod<T, ID> method) {
49  		this.dynamoDBOperations = dynamoDBOperations;
50  		this.method = method;
51  	}
52  
53  	protected QueryExecution<T, ID> getExecution() {
54  		if (method.isCollectionQuery() && !isSingleEntityResultsRestriction()) {
55  			return new CollectionExecution();
56  		} else if (method.isSliceQuery() && !isSingleEntityResultsRestriction()) {
57  			return new SlicedExecution(method.getParameters());
58  		} else if (method.isPageQuery() && !isSingleEntityResultsRestriction()) {
59  			return new PagedExecution(method.getParameters());
60  		} else if (method.isModifyingQuery()) {
61  			throw new UnsupportedOperationException("Modifying queries not yet supported");
62  		} else if (isSingleEntityResultsRestriction()) {
63  			return new SingleEntityLimitedExecution();
64  		} else if (isDeleteQuery()) {
65  			return new DeleteExecution();
66  		} else {
67  			return new SingleEntityExecution();
68  		}
69  	}
70  
71  	protected abstract Query<T> doCreateQuery(Object[] values);
72  	protected abstract Query<Long> doCreateCountQuery(Object[] values, boolean pageQuery);
73  	protected abstract boolean isCountQuery();
74  	protected abstract boolean isExistsQuery();
75  	protected abstract boolean isDeleteQuery();
76  
77  	protected abstract Integer getResultsRestrictionIfApplicable();
78  	protected abstract boolean isSingleEntityResultsRestriction();
79  
80  	protected Query<T> doCreateQueryWithPermissions(Object values[]) {
81  		Query<T> query = doCreateQuery(values);
82  		query.setScanEnabled(method.isScanEnabled());
83  		return query;
84  	}
85  
86  	protected Query<Long> doCreateCountQueryWithPermissions(Object values[], boolean pageQuery) {
87  		Query<Long> query = doCreateCountQuery(values, pageQuery);
88  		query.setScanCountEnabled(method.isScanCountEnabled());
89  		return query;
90  	}
91  
92  	private interface QueryExecution<T, ID> {
93  		Object execute(AbstractDynamoDBQuery<T, ID> query, Object[] values);
94  	}
95  
96  	class CollectionExecution implements QueryExecution<T, ID> {
97  
98  		@Override
99  		public Object execute(AbstractDynamoDBQuery<T, ID> dynamoDBQuery, Object[] values) {
100 			Query<T> query = dynamoDBQuery.doCreateQueryWithPermissions(values);
101 			if (getResultsRestrictionIfApplicable() != null) {
102 				return restrictMaxResultsIfNecessary(query.getResultList().iterator());
103 			} else
104 				return query.getResultList();
105 		}
106 
107 		private List<T> restrictMaxResultsIfNecessary(Iterator<T> iterator) {
108 			int processed = 0;
109 			List<T> resultsPage = new ArrayList<>();
110 			while (iterator.hasNext() && processed < getResultsRestrictionIfApplicable()) {
111 				resultsPage.add(iterator.next());
112 				processed++;
113 			}
114 			return resultsPage;
115 		}
116 
117 	}
118 
119 	/**
120 	 * Executes the {@link AbstractDynamoDBQuery} to return a
121 	 * {@link org.springframework.data.domain.Page} of entities.
122 	 */
123 	class PagedExecution implements QueryExecution<T, ID> {
124 
125 		private final Parameters<?, ?> parameters;
126 
127 		public PagedExecution(Parameters<?, ?> parameters) {
128 
129 			this.parameters = parameters;
130 		}
131 
132 		private long scanThroughResults(Iterator<T> iterator, long resultsToScan) {
133 			long processed = 0;
134 			while (iterator.hasNext() && processed < resultsToScan) {
135 				iterator.next();
136 				processed++;
137 			}
138 			return processed;
139 		}
140 
141 		private List<T> readPageOfResultsRestrictMaxResultsIfNecessary(Iterator<T> iterator, int pageSize) {
142 			int processed = 0;
143 			int toProcess = getResultsRestrictionIfApplicable() != null
144 					? Math.min(pageSize, getResultsRestrictionIfApplicable())
145 					: pageSize;
146 			List<T> resultsPage = new ArrayList<>();
147 			while (iterator.hasNext() && processed < toProcess) {
148 				resultsPage.add(iterator.next());
149 				processed++;
150 			}
151 			return resultsPage;
152 		}
153 
154 		@Override
155 		public Object execute(AbstractDynamoDBQuery<T, ID> dynamoDBQuery, Object[] values) {
156 
157 			ParameterAccessor accessor = new ParametersParameterAccessor(parameters, values);
158 			Pageable pageable = accessor.getPageable();
159 			Query<T> query = dynamoDBQuery.doCreateQueryWithPermissions(values);
160 
161 			List<T> results = query.getResultList();
162 			return createPage(results, pageable, dynamoDBQuery, values);
163 		}
164 
165 		private Page<T> createPage(List<T> allResults, Pageable pageable, AbstractDynamoDBQuery<T, ID> dynamoDBQuery,
166 				Object[] values) {
167 
168 			// Get the result = this list might be a lazy list
169 			Iterator<T> iterator = allResults.iterator();
170 
171 			// Check if the pageable request is 'beyond' the result set
172 			if (!pageable.isUnpaged() && pageable.getOffset() > 0) {
173 				long processedCount = scanThroughResults(iterator, pageable.getOffset());
174 				if (processedCount < pageable.getOffset()) {
175 					return new PageImpl<>(Collections.emptyList());
176 				}
177 			}
178 
179 			// Then Count the result set size
180 			Query<Long> countQuery = dynamoDBQuery.doCreateCountQueryWithPermissions(values, true);
181 			long count = countQuery.getSingleResult();
182 
183 			// Finally wrap the result in a page -
184 			if (!pageable.isUnpaged()) {
185 				// either seek to the proper part of the result set
186 				if (getResultsRestrictionIfApplicable() != null) {
187 					count = Math.min(count, getResultsRestrictionIfApplicable());
188 				}
189 
190 				List<T> results = readPageOfResultsRestrictMaxResultsIfNecessary(iterator, pageable.getPageSize());
191 				return new PageImpl<>(results, pageable, count);
192 			} else {
193 				// or treat the whole (lazy) list as the result page if it's unpaged
194 				return new UnpagedPageImpl<>(allResults, count);
195 			}
196 		}
197 	}
198 
199 	class SlicedExecution implements QueryExecution<T, ID> {
200 
201 		private final Parameters<?, ?> parameters;
202 
203 		public SlicedExecution(Parameters<?, ?> parameters) {
204 
205 			this.parameters = parameters;
206 		}
207 
208 		private long scanThroughResults(Iterator<T> iterator, long resultsToScan) {
209 			long processed = 0;
210 			while (iterator.hasNext() && processed < resultsToScan) {
211 				iterator.next();
212 				processed++;
213 			}
214 			return processed;
215 		}
216 
217 		private List<T> readPageOfResultsRestrictMaxResultsIfNecessary(Iterator<T> iterator, int pageSize) {
218 			int processed = 0;
219 			int toProcess = getResultsRestrictionIfApplicable() != null
220 					? Math.min(pageSize, getResultsRestrictionIfApplicable())
221 					: pageSize;
222 
223 			List<T> resultsPage = new ArrayList<>();
224 			while (iterator.hasNext() && processed < toProcess) {
225 				resultsPage.add(iterator.next());
226 				processed++;
227 			}
228 			return resultsPage;
229 		}
230 
231 		@Override
232 		public Object execute(AbstractDynamoDBQuery<T, ID> dynamoDBQuery, Object[] values) {
233 
234 			ParameterAccessor accessor = new ParametersParameterAccessor(parameters, values);
235 			Pageable pageable = accessor.getPageable();
236 			Query<T> query = dynamoDBQuery.doCreateQueryWithPermissions(values);
237 			List<T> results = query.getResultList();
238 			return createSlice(results, pageable);
239 		}
240 
241 		private Slice<T> createSlice(List<T> allResults, Pageable pageable) {
242 
243 			Iterator<T> iterator = allResults.iterator();
244 			if (pageable.getOffset() > 0) {
245 				long processedCount = scanThroughResults(iterator, pageable.getOffset());
246 				if (processedCount < pageable.getOffset())
247 					return new SliceImpl<>(new ArrayList<T>());
248 			}
249 			List<T> results = readPageOfResultsRestrictMaxResultsIfNecessary(iterator, pageable.getPageSize());
250 			// Scan ahead to retrieve the next page count
251 			boolean hasMoreResults = scanThroughResults(iterator, 1) > 0;
252 			if (getResultsRestrictionIfApplicable() != null
253 					&& getResultsRestrictionIfApplicable().intValue() <= results.size())
254 				hasMoreResults = false;
255 			return new SliceImpl<>(results, pageable, hasMoreResults);
256 		}
257 	}
258 
259 	class DeleteExecution implements QueryExecution<T, ID> {
260 
261 		@Override
262 		public Object execute(AbstractDynamoDBQuery<T, ID> dynamoDBQuery, Object[] values) throws BatchDeleteException {
263 			List<T> entities = dynamoDBQuery.doCreateQueryWithPermissions(values).getResultList();
264 			List<DynamoDBMapper.FailedBatch> failedBatches = dynamoDBOperations.batchDelete(entities);
265 			if (failedBatches.isEmpty()) {
266 				return entities;
267 			} else {
268 				throw repackageToException(failedBatches, BatchDeleteException.class);
269 			}
270 		}
271 	}
272 
273 	class SingleEntityExecution implements QueryExecution<T, ID> {
274 
275 		@Override
276 		public Object execute(AbstractDynamoDBQuery<T, ID> dynamoDBQuery, Object[] values) {
277 			if (isCountQuery()) {
278 				return dynamoDBQuery.doCreateCountQueryWithPermissions(values, false).getSingleResult();
279 			} else if (isExistsQuery()) {
280 				return !dynamoDBQuery.doCreateQueryWithPermissions(values).getResultList().isEmpty();
281 			} else {
282 				return dynamoDBQuery.doCreateQueryWithPermissions(values).getSingleResult();
283 			}
284 
285 		}
286 	}
287 
288 	class SingleEntityLimitedExecution implements QueryExecution<T, ID> {
289 
290 		@Override
291 		public Object execute(AbstractDynamoDBQuery<T, ID> dynamoDBQuery, Object[] values) {
292 			if (isCountQuery()) {
293 				return dynamoDBQuery.doCreateCountQueryWithPermissions(values, false).getSingleResult();
294 			} else {
295 				List<T> resultList = dynamoDBQuery.doCreateQueryWithPermissions(values).getResultList();
296 				return resultList.size() == 0 ? null : resultList.get(0);
297 
298 			}
299 
300 		}
301 	}
302 
303 	/*
304 	 * (non-Javadoc)
305 	 * 
306 	 * @see org.springframework.data.repository.query.RepositoryQuery#execute(java
307 	 * .lang.Object[])
308 	 */
309 	public Object execute(Object[] parameters) {
310 
311 		return getExecution().execute(this, parameters);
312 	}
313 
314 	@Override
315 	public DynamoDBQueryMethod<T, ID> getQueryMethod() {
316 		return this.method;
317 	}
318 
319 }