1 package net.technearts.rip;
2
3 import static java.nio.charset.StandardCharsets.UTF_8;
4 import static java.util.Arrays.asList;
5 import static java.util.Optional.ofNullable;
6 import static net.technearts.rip.OP.AND;
7 import static net.technearts.rip.OP.OR;
8 import static org.eclipse.jetty.http.HttpStatus.NOT_FOUND_404;
9 import static org.eclipse.jetty.http.HttpStatus.OK_200;
10
11 import java.io.IOException;
12 import java.nio.file.Files;
13 import java.nio.file.Path;
14 import java.util.Arrays;
15 import java.util.HashMap;
16 import java.util.LinkedHashMap;
17 import java.util.Map;
18 import java.util.Optional;
19 import java.util.function.BiFunction;
20 import java.util.function.Consumer;
21 import java.util.function.Function;
22 import java.util.function.Predicate;
23
24 import org.slf4j.Logger;
25 import org.slf4j.LoggerFactory;
26
27 import net.sf.jmimemagic.Magic;
28 import net.sf.jmimemagic.MagicException;
29 import net.sf.jmimemagic.MagicMatch;
30 import net.sf.jmimemagic.MagicMatchNotFoundException;
31 import net.sf.jmimemagic.MagicParseException;
32 import spark.ModelAndView;
33 import spark.Request;
34 import spark.Response;
35
36 enum OP {
37 AND, OR;
38 }
39
40
41
42
43 public class RipResponseBuilder {
44
45 private static final Map<RipRoute, Map<Predicate<Request>, RipResponse>> CONDITIONS = new LinkedHashMap<>();
46 private static final Map<RipRoute, BiFunction<Request, Response, String>> LOGS = new LinkedHashMap<>();
47 private static final Logger LOG = LoggerFactory
48 .getLogger(RipResponseBuilder.class);
49
50 private RipRoute route;
51 private Predicate<Request> condition;
52 private OP op = AND;
53
54 RipResponseBuilder(final RipRoute route) {
55 LOG.info("Criando RipResponseBuilder para requisição {} {}",
56 route.getMethod(), route.getPath());
57 this.route = route;
58 if (!CONDITIONS.containsKey(route)) {
59 CONDITIONS.put(route,
60 new LinkedHashMap<Predicate<Request>, RipResponse>());
61 }
62
63 route.route = (req, res) -> {
64 final Optional<Map.Entry<Predicate<Request>, RipResponse>> optional = CONDITIONS
65 .get(route).entrySet().stream()
66 .filter(entry -> entry.getKey().test(req)).findFirst();
67 RipResponse response;
68 String result;
69 if (optional.isPresent()) {
70 response = optional.get().getValue();
71 LOG.debug("Requisição para {}:\n{}", req.pathInfo(), req.body());
72 LOG.debug("Respondendo com \n{}", response.getContent());
73 res.status(response.getStatus());
74 result = response.getContent();
75 if (response.getContentType() == null) {
76 res.header("Content-Type", contentType(result.getBytes(UTF_8)));
77 } else {
78 res.header("Content-Type", response.getContentType());
79 }
80 } else {
81 res.status(NOT_FOUND_404);
82 LOG.warn("Resposta para {} {} não encontrada", route.getMethod(),
83 route.getPath());
84 result = "";
85 }
86 ofNullable(LOGS.get(route)).ifPresent(f -> f.apply(req, res));
87 return result;
88 };
89 route.templateRoute = (req, res) -> {
90 final Optional<Map.Entry<Predicate<Request>, RipResponse>> optional = CONDITIONS
91 .get(route).entrySet().stream()
92 .filter(entry -> entry.getKey().test(req)).findFirst();
93 RipResponse response;
94 ModelAndView result;
95 if (optional.isPresent()) {
96 response = optional.get().getValue();
97 LOG.debug("Respondendo com \n{}", response.getContent());
98 res.status(response.getStatus());
99 final Map<String, Object> attributes = new HashMap<>();
100 for (final Map.Entry<String, Function<Request, String>> f : response
101 .getAttributes().entrySet()) {
102 attributes.put(f.getKey(), f.getValue().apply(req));
103 }
104 if (response.getContentType() != null) {
105 res.header("Content-Type", response.getContentType());
106 } else {
107 res.header("Content-Type", "text/plain");
108 }
109 result = new ModelAndView(attributes, response.getContent());
110 } else {
111 res.status(NOT_FOUND_404);
112 LOG.debug("Resposta para {} {} não encontrada", route.getMethod(),
113 route.getPath());
114 result = null;
115 }
116 return result;
117 };
118 }
119
120
121
122
123
124
125 public RipResponseBuilder and() {
126 op = AND;
127 return this;
128 }
129
130
131
132
133
134
135
136
137
138
139 @SafeVarargs
140 public final void buildResponse(final String template,
141 final Consumer<Map<String, Function<Request, String>>>... consumers) {
142 buildResponse(template, OK_200, consumers);
143 }
144
145 @SafeVarargs
146 public final void buildResponse(final String template, final int status,
147 final Consumer<Map<String, Function<Request, String>>>... consumers) {
148 buildResponse(template, status, null, consumers);
149 }
150
151
152
153
154
155
156
157
158
159
160
161 @SafeVarargs
162 public final void buildResponse(final String template, final int status,
163 final String contentType,
164 final Consumer<Map<String, Function<Request, String>>>... consumers) {
165 final Map<String, Function<Request, String>> attributes = new HashMap<>();
166 for (final Consumer<Map<String, Function<Request, String>>> consumer : consumers) {
167 consumer.accept(attributes);
168 }
169 buildResponse(template, status, contentType, attributes);
170 }
171
172
173
174
175
176
177
178
179
180 public final void buildResponse(final String template, final int status,
181 final String contentType,
182 final Map<String, Function<Request, String>> attributes) {
183 if (condition == null) {
184 condition = s -> true;
185 }
186 final RipResponse res = new RipResponse(attributes, template, status,
187 contentType);
188 CONDITIONS.get(route).put(condition, res);
189 route.createTemplateMethod();
190 }
191
192
193
194
195
196
197
198
199 public final void buildResponse(final String template,
200 final Map<String, Function<Request, String>> attributes) {
201 buildResponse(template, OK_200, null, attributes);
202 }
203
204 @SafeVarargs
205 public final void buildResponse(final String template,
206 final String contentType,
207 final Consumer<Map<String, Function<Request, String>>>... consumers) {
208 buildResponse(template, OK_200, contentType, consumers);
209 }
210
211 public final void buildResponse(final String template,
212 final String contentType,
213 final Map<String, Function<Request, String>> attributes) {
214 buildResponse(template, OK_200, contentType, attributes);
215 }
216
217
218
219
220
221
222
223 public RipResponseBuilder contains(final String content) {
224 final Predicate<Request> newCondition = req -> req.body().contains(content);
225 updateConditions(newCondition);
226 return this;
227 }
228
229
230
231
232
233
234
235 public RipResponseBuilder containsAll(final String... contents) {
236 final Predicate<Request> newCondition = req -> Arrays.asList(contents)
237 .stream().allMatch(req.body()::contains);
238 updateConditions(newCondition);
239 return this;
240 }
241
242
243
244
245
246
247
248
249 public RipResponseBuilder containsAny(final String... contents) {
250 final Predicate<Request> newCondition = req -> Arrays.asList(contents)
251 .stream().anyMatch(req.body()::contains);
252 updateConditions(newCondition);
253 return this;
254 }
255
256 private String contentType(final byte[] stream) {
257 MagicMatch match = null;
258 try {
259 match = Magic.getMagicMatch(stream, false);
260 if (match.getSubMatches().size() > 0) {
261 return match.getSubMatches().toArray()[0].toString();
262 }
263 } catch (MagicParseException | MagicMatchNotFoundException | MagicException
264 | NullPointerException e) {
265 return "text/html;charset=utf-8";
266 }
267 return match.getMimeType();
268 }
269
270
271
272
273
274
275
276
277 public RipResponseBuilder log(final BiFunction<Request, Response, String> f) {
278 LOGS.put(route, f);
279 return this;
280 }
281
282
283
284
285
286
287
288 public RipResponseBuilder matches(final Predicate<Request> condition) {
289 updateConditions(condition);
290 return this;
291 }
292
293
294
295
296
297
298
299 public RipResponseBuilder matchesAll(
300 @SuppressWarnings("unchecked") final Predicate<Request>... conditions) {
301 final Predicate<Request> newCondition = asList(conditions).stream()
302 .reduce(req -> true, Predicate::and);
303 updateConditions(newCondition);
304 return this;
305 }
306
307
308
309
310
311
312
313
314 public RipResponseBuilder matchesAny(
315 @SuppressWarnings("unchecked") final Predicate<Request>... conditions) {
316 final Predicate<Request> newCondition = asList(conditions).stream()
317 .reduce(req -> false, Predicate::or);
318 updateConditions(newCondition);
319 return this;
320 }
321
322
323
324
325
326
327 public RipResponseBuilder or() {
328 op = OR;
329 return this;
330 }
331
332
333
334
335
336
337
338
339 public void respond(final Path withFile) {
340 respond(withFile, OK_200);
341 }
342
343
344
345
346
347
348
349
350
351 public void respond(final Path withFile, final int status) {
352 respond(withFile, status, null);
353 }
354
355 public void respond(final Path withFile, final int status,
356 final String contentType) {
357 try {
358 respond(new String(Files.readAllBytes(withFile)), status, contentType);
359 } catch (final IOException e) {
360 respond("Arquivo não encontrado.", NOT_FOUND_404);
361 }
362 }
363
364 public void respond(final Path withFile, final String contentType) {
365 respond(withFile, OK_200, contentType);
366 }
367
368
369
370
371
372
373
374 public void respond(final String response) {
375 respond(response, OK_200);
376 }
377
378
379
380
381
382
383
384
385 public void respond(final String response, final int status) {
386 respond(response, status, null);
387 }
388
389 public void respond(final String response, final int status,
390 final String contentType) {
391 if (condition == null) {
392 condition = s -> true;
393 }
394 final RipResponse res = new RipResponse(response, status, contentType);
395 CONDITIONS.get(route).put(condition, res);
396 route.createMethod();
397 }
398
399 public void respond(final String response, final String contentType) {
400 respond(response, OK_200, contentType);
401 }
402
403 private void updateConditions(final Predicate<Request> newCondition) {
404 if (condition == null) {
405 condition = newCondition;
406 } else {
407 switch (op) {
408 case OR:
409 condition = condition.or(newCondition);
410 break;
411 case AND:
412 condition = condition.and(newCondition);
413 break;
414 }
415 }
416 }
417
418 }