1
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.http.mock;
31
32 import com.jcabi.log.Logger;
33 import java.io.OutputStreamWriter;
34 import java.io.PrintWriter;
35 import java.io.UnsupportedEncodingException;
36 import java.net.HttpURLConnection;
37 import java.util.Collection;
38 import java.util.Iterator;
39 import java.util.LinkedList;
40 import java.util.NoSuchElementException;
41 import java.util.Queue;
42 import java.util.concurrent.ConcurrentLinkedQueue;
43 import java.util.concurrent.atomic.AtomicInteger;
44 import lombok.EqualsAndHashCode;
45 import lombok.RequiredArgsConstructor;
46 import org.apache.http.HttpHeaders;
47 import org.glassfish.grizzly.http.server.HttpHandler;
48 import org.glassfish.grizzly.http.server.Request;
49 import org.glassfish.grizzly.http.server.Response;
50 import org.hamcrest.Matcher;
51
52
53
54
55
56
57
58 @SuppressWarnings("PMD.TooManyMethods")
59 final class MkGrizzlyAdapter extends HttpHandler {
60
61
62
63
64 private static final String ENCODING = "UTF-8";
65
66
67
68
69 private final transient Queue<QueryWithAnswer> queue =
70 new ConcurrentLinkedQueue<>();
71
72
73
74
75 private final transient Queue<Conditional> conditionals =
76 new ConcurrentLinkedQueue<>();
77
78
79 @Override
80 @SuppressWarnings
81 (
82 {
83 "PMD.AvoidCatchingThrowable",
84 "PMD.AvoidInstantiatingObjectsInLoops",
85 "rawtypes"
86 }
87 )
88 public void service(
89 final Request request,
90 final Response response
91 ) {
92 try {
93 final MkQuery query = new GrizzlyQuery(request);
94 final Iterator<Conditional> iter = this.conditionals.iterator();
95 boolean matched = false;
96 while (iter.hasNext()) {
97 final Conditional cond = iter.next();
98 if (cond.matches(query)) {
99 matched = true;
100 final MkAnswer answer = cond.answer();
101 this.queue.add(new QueryWithAnswer(query, answer));
102 for (final String name : answer.headers().keySet()) {
103
104 for (final String value : answer.headers().get(name)) {
105 response.addHeader(name, value);
106 }
107 }
108 response.addHeader(
109 HttpHeaders.SERVER,
110 String.format(
111 "%s query #%d, %d answer(s) left",
112 this.getClass().getName(),
113 this.queue.size(), this.conditionals.size()
114 )
115 );
116 response.setStatus(answer.status());
117 final byte[] body = answer.bodyBytes();
118 response.createOutputStream().write(body);
119 response.setContentLength(body.length);
120 if (cond.decrement() == 0) {
121 iter.remove();
122 }
123 break;
124 }
125 }
126 if (!matched) {
127 throw new NoSuchElementException("No matching answers found.");
128 }
129
130 } catch (final Throwable ex) {
131 MkGrizzlyAdapter.fail(response, ex);
132 }
133 }
134
135
136
137
138
139
140
141
142
143 public void next(
144 final MkAnswer answer, final Matcher<MkQuery> query,
145 final int count
146 ) {
147 this.conditionals.add(new Conditional(answer, query, count));
148 }
149
150
151
152
153
154 public MkQuery take() {
155 return this.queue.remove().que;
156 }
157
158
159
160
161
162
163
164
165 public MkQuery take(final Matcher<MkAnswer> matcher) {
166 return this.takeMatching(matcher).next();
167 }
168
169
170
171
172
173
174
175
176
177 public Collection<MkQuery> takeAll(final Matcher<MkAnswer> matcher) {
178 final Collection<MkQuery> results = new LinkedList<>();
179 final Iterator<MkQuery> iter = this.takeMatching(matcher);
180 while (iter.hasNext()) {
181 results.add(iter.next());
182 }
183 return results;
184 }
185
186
187
188
189
190 public int queries() {
191 return this.queue.size();
192 }
193
194
195
196
197
198
199
200
201 private Iterator<MkQuery> takeMatching(final Matcher<MkAnswer> matcher) {
202 final Iterator<QueryWithAnswer> iter = this.queue.iterator();
203 final Iterator<MkQuery> result = new MkQueryIterator(iter, matcher);
204 if (!result.hasNext()) {
205 throw new NoSuchElementException("No matching results found");
206 }
207 return result;
208 }
209
210
211
212
213
214
215 private static void fail(
216 final Response response,
217 final Throwable failure
218 ) {
219 response.setStatus(HttpURLConnection.HTTP_INTERNAL_ERROR);
220 final PrintWriter writer;
221 try {
222 writer = new PrintWriter(
223 new OutputStreamWriter(
224 response.createOutputStream(),
225 MkGrizzlyAdapter.ENCODING
226 )
227 );
228 } catch (final UnsupportedEncodingException ex) {
229 throw new IllegalStateException(ex);
230 }
231 try {
232 writer.print(Logger.format("%[exception]s", failure));
233 } finally {
234 writer.close();
235 }
236 }
237
238
239
240
241
242
243 @EqualsAndHashCode(of = {"answr", "condition"})
244 private static final class Conditional {
245
246
247
248 private final transient MkAnswer answr;
249
250
251
252
253 private final transient Matcher<MkQuery> condition;
254
255
256
257
258 private final transient AtomicInteger count;
259
260
261
262
263
264
265
266 Conditional(
267 final MkAnswer ans, final Matcher<MkQuery> matcher,
268 final int times
269 ) {
270 this.answr = ans;
271 this.condition = matcher;
272 this.count = Conditional.positiveAtomic(times);
273 }
274
275
276
277
278
279 public MkAnswer answer() {
280 return this.answr;
281 }
282
283
284
285
286
287
288 public boolean matches(final MkQuery query) {
289 return this.condition.matches(query);
290 }
291
292
293
294
295
296 public int decrement() {
297 return this.count.decrementAndGet();
298 }
299
300
301
302
303
304
305 private static AtomicInteger positiveAtomic(final int num) {
306 if (num < 1) {
307 throw new IllegalArgumentException(
308 "Answer must be returned at least once."
309 );
310 }
311 return new AtomicInteger(num);
312 }
313
314 }
315
316
317
318
319
320
321 @EqualsAndHashCode(of = {"answr", "que"})
322 private static final class QueryWithAnswer {
323
324
325
326 private final transient MkAnswer answr;
327
328
329
330
331 private final transient MkQuery que;
332
333
334
335
336
337
338 QueryWithAnswer(final MkQuery qry, final MkAnswer ans) {
339 this.answr = ans;
340 this.que = qry;
341 }
342
343
344
345
346
347 public MkQuery query() {
348 return this.que;
349 }
350
351
352
353
354
355 public MkAnswer answer() {
356 return this.answr;
357 }
358 }
359
360
361
362
363
364
365 @RequiredArgsConstructor
366 private static final class MkQueryIterator implements Iterator<MkQuery> {
367
368
369
370
371 private final Queue<MkQuery> results = new LinkedList<>();
372
373
374
375
376 private final Iterator<QueryWithAnswer> iter;
377
378
379
380
381 private final Matcher<MkAnswer> matcher;
382
383 @Override
384 public boolean hasNext() {
385 while (this.iter.hasNext()) {
386 final QueryWithAnswer candidate = this.iter.next();
387 if (this.matcher.matches(candidate.answer())) {
388 this.results.add(candidate.query());
389 this.iter.remove();
390 break;
391 }
392 }
393 return !this.results.isEmpty();
394 }
395
396 @Override
397 public MkQuery next() {
398 if (this.results.isEmpty()) {
399 throw new NoSuchElementException();
400 }
401 return this.results.remove();
402 }
403
404 @Override
405 public void remove() {
406 this.results.remove();
407 }
408 }
409 }