1 package net.avcompris.commons3.core.tests;
2
3 import static com.google.common.base.Preconditions.checkNotNull;
4 import static com.google.common.collect.Sets.newHashSet;
5 import static java.util.Locale.ENGLISH;
6 import static org.apache.commons.lang3.RandomStringUtils.randomAlphanumeric;
7 import static org.apache.commons.lang3.StringUtils.normalizeSpace;
8 import static org.apache.commons.lang3.StringUtils.substringBefore;
9 import static org.apache.commons.lang3.StringUtils.uncapitalize;
10 import static org.junit.jupiter.api.Assertions.fail;
11
12 import java.lang.reflect.InvocationTargetException;
13 import java.lang.reflect.Method;
14 import java.util.Comparator;
15 import java.util.Set;
16 import java.util.concurrent.Callable;
17
18 import javax.annotation.Nullable;
19
20 import org.apache.commons.lang3.NotImplementedException;
21
22 import net.avcompris.commons3.api.Entities;
23 import net.avcompris.commons3.api.EntitiesQuery.EnumSortBy;
24 import net.avcompris.commons3.api.Entity;
25 import net.avcompris.commons3.api.User;
26 import net.avcompris.commons3.api.exception.ServiceException;
27 import net.avcompris.commons3.api.exception.UnauthorizedException;
28 import net.avcompris.commons3.core.Permissions;
29 import net.avcompris.commons3.databeans.DataBeans;
30 import net.avcompris.commons3.types.DateTimeHolder;
31 import net.avcompris.commons3.utils.Clock;
32 import net.avcompris.commons3.utils.ClockImpl;
33 import net.avcompris.commons3.utils.LogFactory;
34
35 public abstract class CoreTestUtils {
36
37 private static final Set<String> randoms = newHashSet();
38
39 public static String newCorrelationId() {
40
41 final String correlationId = "C-"
42 + System.currentTimeMillis() + "-"
43 + randomAlphanumeric(20);
44
45 LogFactory.setCorrelationId(correlationId);
46
47 return correlationId;
48 }
49
50 public static Permissions grantAll() {
51
52 return new Permissions() {
53
54 @Override
55 public void assertAuthorized(final String correlationId, final User user,
56 final Object... contextNameValuePairs) throws UnauthorizedException {
57
58 return;
59 }
60 };
61 }
62
63 public static Clock defaultClock() {
64
65 return new ClockImpl();
66 }
67
68 private static String randomNoDuplicate(final Callable<String> c) {
69
70 for (final long startMs = System.currentTimeMillis(); System.currentTimeMillis() < startMs + 1000;) {
71
72 final String s;
73
74 try {
75
76 s = c.call();
77
78 } catch (final RuntimeException e) {
79
80 throw e;
81
82 } catch (final Exception e) {
83
84 throw new RuntimeException(e);
85 }
86
87 synchronized (randoms) {
88
89 if (!randoms.contains(s)) {
90
91 randoms.add(s);
92
93 return s;
94 }
95 }
96 }
97
98 throw new IllegalStateException("Timeout after 1000 ms.");
99 }
100
101 public static String random8() {
102
103 return randomNoDuplicate(() -> randomAlphanumeric(8));
104 }
105
106 public static String random20() {
107
108 return randomNoDuplicate(() -> randomAlphanumeric(20));
109 }
110
111 public static String random20(@Nullable final String prefix) {
112
113 return randomNoDuplicate(() -> random(20, prefix));
114 }
115
116 public static String random32(@Nullable final String prefix) {
117
118 return randomNoDuplicate(() -> random(32, prefix));
119 }
120
121 public static String random40(@Nullable final String prefix) {
122
123 return randomNoDuplicate(() -> random(40, prefix));
124 }
125
126 public static String randomLowercase40(@Nullable final String prefix) {
127
128 return randomNoDuplicate(() -> randomLowercase(40, prefix));
129 }
130
131 public static String randomEmail20() {
132
133 return randomNoDuplicate(()
134
135 -> randomAlphanumeric(8) + "@" + randomAlphanumeric(7) + ".avcompris.com");
136 }
137
138 private static String random(final int length, @Nullable final String prefix) {
139
140 return (prefix
141 + System.currentTimeMillis() + "-"
142 + randomAlphanumeric(length)
143 ).substring(0, length);
144 }
145
146 private static String randomLowercase(final int length, @Nullable final String prefix) {
147
148 return random(length, prefix).toLowerCase(ENGLISH);
149 }
150
151 public static <T extends Entity> void assertAreSorted(final EnumSortBy sortBy, final Entities<T> entities) {
152
153 checkNotNull(entities, "entities");
154 checkNotNull(sortBy, "sortBy");
155
156 DataBeans.validate(entities);
157
158 final StringBuilder sb = new StringBuilder();
159
160 final String name = sortBy.name();
161
162 final boolean isDesc = name.endsWith("_DESC");
163
164 for (int i = 8; i < (isDesc ? name.length() - 5 : name.length()); ++i) {
165
166 final char c = name.charAt(i);
167
168 if (c == '_') {
169
170 ++i;
171
172 sb.append(Character.toUpperCase(name.charAt(i)));
173
174 } else {
175
176 sb.append(Character.toLowerCase(c));
177 }
178 }
179
180 if (isDesc) {
181 sb.append(" DESC");
182 }
183
184 assertAreSorted(sb.toString(), entities.getResults());
185 }
186
187 public static <T extends Entity> void assertAreSorted(final String sortBy, final Entities<T> entities) {
188
189 checkNotNull(entities, "entities");
190 checkNotNull(sortBy, "sortBy");
191
192 DataBeans.validate(entities);
193
194 assertAreSorted(sortBy, entities.getResults());
195 }
196
197 @SafeVarargs
198 public static <T extends Entity> void assertAreSorted(final String sortBy, final T... results) {
199
200 checkNotNull(results, "results");
201 checkNotNull(sortBy, "sortBy");
202
203 if (results.length == 0 || results.length == 1) {
204 return;
205 }
206
207 final boolean isDesc;
208 final String propertyName;
209
210 final String trim = normalizeSpace(sortBy);
211
212 if (trim.endsWith(" DESC")) {
213
214 isDesc = true;
215 propertyName = substringBefore(trim, " ");
216
217 } else if (trim.endsWith(" ASC")) {
218
219 isDesc = false;
220 propertyName = substringBefore(trim, " ");
221
222 } else {
223
224 isDesc = false;
225 propertyName = trim;
226 }
227
228 final FieldExtractor extractor = extractFieldExtractor(results.getClass().getComponentType(), propertyName);
229
230 final Comparator<Object> comparator = getComparator(extractor.getReturnType(), isDesc);
231
232 Object current = extractor.invoke(results[0]);
233
234 for (int i = 1; i < results.length; ++i) {
235
236 final Object next = extractor.invoke(results[i]);
237
238 if (!isDesc) {
239
240 if (next == null) {
241
242 current = null;
243
244 continue;
245
246 } else if (current == null) {
247
248 fail("Values should be sorted: [" + (i - 1) + "]." + propertyName + ": " + current
249 + ", [" + i + "]." + propertyName + ": " + next);
250
251 }
252 }
253
254 if (isDesc) {
255
256 if (current == null) {
257
258 current = next;
259
260 continue;
261
262 } else if (next == null) {
263
264 fail("Values should be sorted: [" + (i - 1) + "]." + propertyName + ": " + current
265 + ", [" + i + "]." + propertyName + ": " + next);
266
267 }
268 }
269
270 if (comparator.compare(next, current) < 0) {
271
272 fail("Values should be sorted: [" + (i - 1) + "]." + propertyName + ": " + current
273 + ", [" + i + "]." + propertyName + ": " + next);
274 }
275
276 current = next;
277 }
278 }
279
280 private static Comparator<Object> getComparator(final Class<?> type, final boolean isDesc) {
281
282 checkNotNull(type, "type");
283
284 final int sign = isDesc ? -1 : 1;
285
286 if (String.class.equals(type)) {
287
288 return new Comparator<Object>() {
289
290 @Override
291 public int compare(final Object o1, final Object o2) {
292
293 return sign * ((String) o1).compareToIgnoreCase((String) o2);
294
295
296 }
297 };
298
299 } else if (int.class.equals(type)) {
300
301 return new Comparator<Object>() {
302
303 @Override
304 public int compare(final Object o1, final Object o2) {
305
306 return sign * ((Integer) o1).compareTo((Integer) o2);
307 }
308 };
309
310 } else if (boolean.class.equals(type)) {
311
312 return new Comparator<Object>() {
313
314 @Override
315 public int compare(final Object o1, final Object o2) {
316
317 return sign * ((Boolean) o1).compareTo((Boolean) o2);
318 }
319 };
320
321 } else if (DateTimeHolder.class.equals(type)) {
322
323 return new Comparator<Object>() {
324
325 @Override
326 public int compare(final Object o1, final Object o2) {
327
328 return sign * ((DateTimeHolder) o1).compareTo((DateTimeHolder) o2);
329 }
330 };
331
332 } else if (type.isEnum()) {
333
334 return new Comparator<Object>() {
335
336 @Override
337 public int compare(final Object o1, final Object o2) {
338
339 return sign * o1.toString().compareTo(o2.toString());
340 }
341 };
342
343 } else {
344
345 throw new NotImplementedException("type: " + type.getName());
346 }
347 }
348
349 private interface FieldExtractor {
350
351 Class<?> getReturnType();
352
353 @Nullable
354 Object invoke(Object object);
355 }
356
357 private static final class FieldExtractorFromGetter implements FieldExtractor {
358
359 private final Method getter;
360
361 @Nullable
362 private final FieldExtractor next;
363
364 public FieldExtractorFromGetter(final Method getter) {
365
366 this.getter = checkNotNull(getter, "getter");
367 this.next = null;
368 }
369
370 public FieldExtractorFromGetter(final Method getter, final FieldExtractor next) {
371
372 this.getter = checkNotNull(getter, "getter");
373 this.next = checkNotNull(next, "next");
374 }
375
376 @Override
377 public Class<?> getReturnType() {
378
379 return next == null
380
381 ? getter.getReturnType()
382
383 : next.getReturnType();
384 }
385
386 @Override
387 public Object invoke(final Object object) {
388
389 checkNotNull(object, "object");
390
391 final Object value;
392
393 try {
394
395 value = getter.invoke(object);
396
397 } catch (final IllegalAccessException e) {
398
399 throw new RuntimeException(e);
400
401 } catch (final InvocationTargetException e) {
402
403 throw new RuntimeException(e.getTargetException());
404 }
405
406 if (value == null) {
407
408 return null;
409 }
410
411 return next == null
412
413 ? value
414
415 : next.invoke(value);
416 }
417 }
418
419 @Nullable
420 private static Method extractGetter(final Class<?> clazz, final String propertyName) {
421
422 for (final Method method : clazz.getMethods()) {
423
424 if (method.getParameterTypes().length != 0) {
425 continue;
426 }
427
428 final String methodName = method.getName();
429
430 final String suffix;
431
432 if (methodName.startsWith("get")) {
433
434 suffix = methodName.substring(3);
435
436 } else if (methodName.startsWith("is")) {
437
438 suffix = methodName.substring(2);
439
440 } else {
441
442 continue;
443 }
444
445 if (uncapitalize(suffix).contentEquals(propertyName)) {
446
447 return method;
448 }
449 }
450
451 return null;
452 }
453
454 private static FieldExtractor extractFieldExtractor(final Class<?> clazz, final String propertyName) {
455
456 checkNotNull(clazz, "clazz");
457 checkNotNull(propertyName, "propertyName");
458
459 final Method getter = extractGetter(clazz, propertyName);
460
461 if (getter != null) {
462
463 return new FieldExtractorFromGetter(getter);
464 }
465
466 final String subName = extractSubName(propertyName);
467
468 if (subName != null) {
469
470 final Method subGetter = extractGetter(clazz, subName);
471
472 if (subGetter != null) {
473
474 return new FieldExtractorFromGetter(subGetter,
475 extractFieldExtractor(subGetter.getReturnType(),
476 uncapitalize(propertyName.substring(subName.length()))));
477 }
478 }
479
480 throw new IllegalArgumentException("Cannot find getter for: " + propertyName + ", in: " + clazz.getName());
481 }
482
483 private static String extractSubName(final String propertyName) {
484
485 final StringBuilder sb = new StringBuilder();
486
487 for (final char c : propertyName.toCharArray()) {
488
489 if (!Character.isLowerCase(c)) {
490
491 break;
492 }
493
494 sb.append(c);
495 }
496
497 return sb.toString();
498 }
499
500 @FunctionalInterface
501 public interface Action<T> {
502
503 T action() throws ServiceException;
504 }
505
506 @FunctionalInterface
507 public interface Predicate {
508
509 boolean test() throws ServiceException;
510 }
511
512 public static void retryUntil(final int timeoutMs, final Predicate predicate) throws ServiceException {
513
514 checkNotNull(predicate, "predicate");
515
516 retryUntil(timeoutMs, 1_000, () -> predicate.test() ? "OK" : null);
517 }
518
519 public static <T> T retryUntil(final int timeoutMs, final Action<T> action) throws ServiceException {
520
521 return retryUntil(timeoutMs, 1_000, action);
522 }
523
524 public static <T> T retryUntil(final int timeoutMs, final int delayMs, final Action<T> action)
525 throws ServiceException {
526
527 checkNotNull(action, "action");
528
529 for (final long startMs = System.currentTimeMillis(); System.currentTimeMillis() < startMs + timeoutMs;) {
530
531 final T result = action.action();
532
533 if (result != null) {
534
535 return result;
536 }
537
538 try {
539
540 Thread.sleep(delayMs);
541
542 } catch (final InterruptedException e) {
543
544 e.printStackTrace();
545
546
547 }
548 }
549
550 System.err.println("A timeout occurs (" + timeoutMs + " ms)");
551
552 throw new RuntimeException("Timeout after: " + timeoutMs + " ms");
553 }
554 }