1 | 0 | |
2 | |
|
3 | |
|
4 | |
|
5 | |
|
6 | |
|
7 | |
|
8 | |
|
9 | |
|
10 | |
|
11 | |
|
12 | |
|
13 | |
|
14 | |
|
15 | |
|
16 | |
|
17 | |
|
18 | |
|
19 | |
|
20 | |
|
21 | |
|
22 | |
|
23 | |
|
24 | |
|
25 | |
|
26 | |
|
27 | |
|
28 | |
|
29 | |
|
30 | |
package com.jcabi.dynamo; |
31 | |
|
32 | |
import com.amazonaws.AmazonClientException; |
33 | |
import com.amazonaws.services.dynamodbv2.AmazonDynamoDB; |
34 | |
import com.amazonaws.services.dynamodbv2.model.AttributeValue; |
35 | |
import com.amazonaws.services.dynamodbv2.model.Condition; |
36 | |
import com.amazonaws.services.dynamodbv2.model.QueryRequest; |
37 | |
import com.amazonaws.services.dynamodbv2.model.QueryResult; |
38 | |
import com.amazonaws.services.dynamodbv2.model.ReturnConsumedCapacity; |
39 | |
import com.amazonaws.services.dynamodbv2.model.Select; |
40 | |
import com.google.common.collect.Iterables; |
41 | |
import com.jcabi.aspects.Immutable; |
42 | |
import com.jcabi.aspects.Loggable; |
43 | |
import com.jcabi.aspects.Tv; |
44 | |
import com.jcabi.log.Logger; |
45 | |
import java.io.IOException; |
46 | |
import java.util.ArrayList; |
47 | |
import java.util.Arrays; |
48 | |
import java.util.Collection; |
49 | |
import java.util.Collections; |
50 | |
import java.util.HashSet; |
51 | |
import java.util.List; |
52 | |
import java.util.Map; |
53 | |
import lombok.EqualsAndHashCode; |
54 | |
import lombok.ToString; |
55 | |
|
56 | |
|
57 | |
|
58 | |
|
59 | |
|
60 | |
|
61 | |
|
62 | |
|
63 | |
@Immutable |
64 | 0 | @ToString |
65 | |
@Loggable(Loggable.DEBUG) |
66 | 0 | @EqualsAndHashCode(of = { "limit", "forward" }) |
67 | |
@SuppressWarnings("PMD.TooManyMethods") |
68 | |
public final class QueryValve implements Valve { |
69 | |
|
70 | |
|
71 | |
|
72 | |
|
73 | |
private final transient int limit; |
74 | |
|
75 | |
|
76 | |
|
77 | |
|
78 | |
private final transient boolean forward; |
79 | |
|
80 | |
|
81 | |
|
82 | |
|
83 | |
@Immutable.Array |
84 | |
private final transient String[] attributes; |
85 | |
|
86 | |
|
87 | |
|
88 | |
|
89 | |
private final transient String index; |
90 | |
|
91 | |
|
92 | |
|
93 | |
|
94 | |
private final transient String select; |
95 | |
|
96 | |
|
97 | |
|
98 | |
|
99 | |
private final transient boolean consistent; |
100 | |
|
101 | |
|
102 | |
|
103 | |
|
104 | |
public QueryValve() { |
105 | 1 | this( |
106 | |
Tv.TWENTY, true, new ArrayList<String>(0), |
107 | |
"", Select.SPECIFIC_ATTRIBUTES.toString(), true |
108 | |
); |
109 | 1 | } |
110 | |
|
111 | |
|
112 | |
|
113 | |
|
114 | |
|
115 | |
|
116 | |
|
117 | |
|
118 | |
|
119 | |
|
120 | |
|
121 | |
private QueryValve(final int lmt, final boolean fwd, |
122 | |
final Iterable<String> attrs, final String idx, |
123 | 1 | final String slct, final boolean cnst) { |
124 | 1 | this.limit = lmt; |
125 | 1 | this.forward = fwd; |
126 | 1 | this.attributes = Iterables.toArray(attrs, String.class); |
127 | 1 | this.index = idx; |
128 | 1 | this.select = slct; |
129 | 1 | this.consistent = cnst; |
130 | 1 | } |
131 | |
|
132 | |
|
133 | |
@Override |
134 | |
public Dosage fetch(final Credentials credentials, final String table, |
135 | |
final Map<String, Condition> conditions, final Collection<String> keys) |
136 | |
throws IOException { |
137 | 2 | final AmazonDynamoDB aws = credentials.aws(); |
138 | |
try { |
139 | 1 | final Collection<String> attrs = new HashSet<String>( |
140 | |
Arrays.asList(this.attributes) |
141 | |
); |
142 | 1 | attrs.addAll(keys); |
143 | 1 | QueryRequest request = new QueryRequest() |
144 | |
.withTableName(table) |
145 | |
.withReturnConsumedCapacity(ReturnConsumedCapacity.TOTAL) |
146 | |
.withKeyConditions(conditions) |
147 | |
.withConsistentRead(this.consistent) |
148 | |
.withScanIndexForward(this.forward) |
149 | |
.withSelect(this.select) |
150 | |
.withLimit(this.limit); |
151 | 1 | if (this.select.equals(Select.SPECIFIC_ATTRIBUTES.toString())) { |
152 | 1 | request = request.withAttributesToGet(attrs); |
153 | |
} |
154 | 1 | if (!this.index.isEmpty()) { |
155 | 0 | request = request.withIndexName(this.index); |
156 | |
} |
157 | 1 | final long start = System.currentTimeMillis(); |
158 | 1 | final QueryResult result = aws.query(request); |
159 | 1 | Logger.info( |
160 | |
this, |
161 | |
"#items(): loaded %d item(s) from '%s' using %s, %s, in %[ms]s", |
162 | |
result.getCount(), table, conditions, |
163 | |
new PrintableConsumedCapacity( |
164 | |
result.getConsumedCapacity() |
165 | |
).print(), |
166 | |
System.currentTimeMillis() - start |
167 | |
); |
168 | 1 | return new QueryValve.NextDosage(credentials, request, result); |
169 | 0 | } catch (final AmazonClientException ex) { |
170 | 0 | throw new IOException( |
171 | |
String.format( |
172 | |
"failed to fetch from \"%s\" by %s and %s", |
173 | |
table, conditions, keys |
174 | |
), |
175 | |
ex |
176 | |
); |
177 | |
} finally { |
178 | 1 | aws.shutdown(); |
179 | |
} |
180 | |
} |
181 | |
|
182 | |
@Override |
183 | |
public int count(final Credentials credentials, final String table, |
184 | |
final Map<String, Condition> conditions) throws IOException { |
185 | 0 | final AmazonDynamoDB aws = credentials.aws(); |
186 | |
try { |
187 | 0 | QueryRequest request = new QueryRequest() |
188 | |
.withTableName(table) |
189 | |
.withReturnConsumedCapacity(ReturnConsumedCapacity.TOTAL) |
190 | |
.withKeyConditions(conditions) |
191 | |
.withConsistentRead(this.consistent) |
192 | |
.withSelect(Select.COUNT) |
193 | |
.withLimit(Integer.MAX_VALUE); |
194 | 0 | if (!this.index.isEmpty()) { |
195 | 0 | request = request.withIndexName(this.index); |
196 | |
} |
197 | 0 | final long start = System.currentTimeMillis(); |
198 | 0 | final QueryResult rslt = aws.query(request); |
199 | 0 | final int count = rslt.getCount(); |
200 | 0 | Logger.info( |
201 | |
this, |
202 | |
|
203 | |
"#total(): COUNT=%d in '%s' using %s, %s, in %[ms]s", |
204 | |
count, request.getTableName(), request.getQueryFilter(), |
205 | |
new PrintableConsumedCapacity( |
206 | |
rslt.getConsumedCapacity() |
207 | |
).print(), |
208 | |
System.currentTimeMillis() - start |
209 | |
); |
210 | 0 | return count; |
211 | |
} finally { |
212 | 0 | aws.shutdown(); |
213 | |
} |
214 | |
} |
215 | |
|
216 | |
|
217 | |
|
218 | |
|
219 | |
|
220 | |
|
221 | |
|
222 | |
|
223 | |
|
224 | |
public QueryValve withConsistentRead(final boolean cnst) { |
225 | 0 | return new QueryValve( |
226 | |
this.limit, this.forward, |
227 | |
Arrays.asList(this.attributes), |
228 | |
this.index, this.select, cnst |
229 | |
); |
230 | |
} |
231 | |
|
232 | |
|
233 | |
|
234 | |
|
235 | |
|
236 | |
|
237 | |
|
238 | |
|
239 | |
|
240 | |
public QueryValve withIndexName(final String idx) { |
241 | 0 | return new QueryValve( |
242 | |
this.limit, this.forward, |
243 | |
Arrays.asList(this.attributes), |
244 | |
idx, this.select, this.consistent |
245 | |
); |
246 | |
} |
247 | |
|
248 | |
|
249 | |
|
250 | |
|
251 | |
|
252 | |
|
253 | |
|
254 | |
|
255 | |
|
256 | |
public QueryValve withSelect(final Select slct) { |
257 | 0 | return new QueryValve( |
258 | |
this.limit, this.forward, |
259 | |
Arrays.asList(this.attributes), this.index, |
260 | |
slct.toString(), this.consistent |
261 | |
); |
262 | |
} |
263 | |
|
264 | |
|
265 | |
|
266 | |
|
267 | |
|
268 | |
|
269 | |
|
270 | |
|
271 | |
public QueryValve withLimit(final int lmt) { |
272 | 0 | return new QueryValve( |
273 | |
lmt, this.forward, |
274 | |
Arrays.asList(this.attributes), |
275 | |
this.index, this.select, this.consistent |
276 | |
); |
277 | |
} |
278 | |
|
279 | |
|
280 | |
|
281 | |
|
282 | |
|
283 | |
|
284 | |
|
285 | |
|
286 | |
public QueryValve withScanIndexForward(final boolean fwd) { |
287 | 0 | return new QueryValve( |
288 | |
this.limit, fwd, |
289 | |
Arrays.asList(this.attributes), |
290 | |
this.index, this.select, this.consistent |
291 | |
); |
292 | |
} |
293 | |
|
294 | |
|
295 | |
|
296 | |
|
297 | |
|
298 | |
|
299 | |
|
300 | |
|
301 | |
public QueryValve withAttributeToGet(final String name) { |
302 | 0 | return new QueryValve( |
303 | |
this.limit, this.forward, |
304 | |
Iterables.concat( |
305 | |
Arrays.asList(this.attributes), |
306 | |
Collections.singleton(name) |
307 | |
), |
308 | |
this.index, this.select, this.consistent |
309 | |
); |
310 | |
} |
311 | |
|
312 | |
|
313 | |
|
314 | |
|
315 | |
|
316 | |
|
317 | |
|
318 | |
|
319 | |
public QueryValve withAttributesToGet(final String... names) { |
320 | 0 | return new QueryValve( |
321 | |
this.limit, this.forward, |
322 | |
Iterables.concat( |
323 | |
Arrays.asList(this.attributes), |
324 | |
Arrays.asList(names) |
325 | |
), |
326 | |
this.index, |
327 | |
this.select, this.consistent |
328 | |
); |
329 | |
} |
330 | |
|
331 | |
|
332 | |
|
333 | |
|
334 | 1 | @ToString |
335 | |
@Loggable(Loggable.DEBUG) |
336 | 0 | @EqualsAndHashCode(of = { "credentials", "request", "result" }) |
337 | |
private final class NextDosage implements Dosage { |
338 | |
|
339 | |
|
340 | |
|
341 | |
private final transient Credentials credentials; |
342 | |
|
343 | |
|
344 | |
|
345 | |
private final transient QueryRequest request; |
346 | |
|
347 | |
|
348 | |
|
349 | |
private final transient QueryResult result; |
350 | |
|
351 | |
|
352 | |
|
353 | |
|
354 | |
|
355 | |
|
356 | |
NextDosage(final Credentials creds, final QueryRequest rqst, |
357 | 1 | final QueryResult rslt) { |
358 | 1 | this.credentials = creds; |
359 | 1 | this.request = rqst; |
360 | 1 | this.result = rslt; |
361 | 1 | } |
362 | |
@Override |
363 | |
public List<Map<String, AttributeValue>> items() { |
364 | 2 | return this.result.getItems(); |
365 | |
} |
366 | |
@Override |
367 | |
public boolean hasNext() { |
368 | 2 | return this.result.getLastEvaluatedKey() != null; |
369 | |
} |
370 | |
@Override |
371 | |
public Dosage next() { |
372 | 0 | if (!this.hasNext()) { |
373 | 0 | throw new IllegalStateException( |
374 | |
"nothing left in the iterator" |
375 | |
); |
376 | |
} |
377 | 0 | final AmazonDynamoDB aws = this.credentials.aws(); |
378 | |
try { |
379 | 0 | final QueryRequest rqst = |
380 | |
this.request.withExclusiveStartKey( |
381 | |
this.result.getLastEvaluatedKey() |
382 | |
); |
383 | 0 | final long start = System.currentTimeMillis(); |
384 | 0 | final QueryResult rslt = aws.query(rqst); |
385 | 0 | Logger.info( |
386 | |
this, |
387 | |
|
388 | |
"#next(): loaded %d item(s) from '%s' using %s, %s, in %[ms]s", |
389 | |
rslt.getCount(), rqst.getTableName(), |
390 | |
rqst.getKeyConditions(), |
391 | |
new PrintableConsumedCapacity( |
392 | |
rslt.getConsumedCapacity() |
393 | |
).print(), |
394 | |
System.currentTimeMillis() - start |
395 | |
); |
396 | 0 | return new QueryValve.NextDosage(this.credentials, rqst, rslt); |
397 | |
} finally { |
398 | 0 | aws.shutdown(); |
399 | |
} |
400 | |
} |
401 | |
} |
402 | |
} |