1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22 package org.nuiton.validator.xwork2.field;
23
24 import com.google.common.base.Objects;
25 import com.opensymphony.xwork2.util.ValueStack;
26 import com.opensymphony.xwork2.validator.ValidationException;
27 import com.opensymphony.xwork2.validator.validators.FieldExpressionValidator;
28 import org.apache.commons.lang3.builder.HashCodeBuilder;
29 import org.apache.commons.logging.Log;
30 import org.apache.commons.logging.LogFactory;
31
32 import java.util.Collection;
33 import java.util.Collections;
34 import java.util.Comparator;
35 import java.util.Set;
36 import java.util.TreeSet;
37
38
39
40
41
42
43
44 public class CollectionFieldExpressionValidator extends NuitonFieldExpressionValidator {
45
46 private static final Log log = LogFactory.getLog(CollectionFieldExpressionValidator.class);
47
48 public enum Mode {
49
50
51 AT_LEAST_ONE,
52
53 EXACTLY_ONE,
54
55 ALL,
56
57 NONE,
58
59 UNIQUE_KEY
60 }
61
62
63 protected Mode mode;
64
65
66
67
68
69
70
71
72
73
74
75
76 protected String collectionFieldName;
77
78
79
80
81
82
83
84
85
86 protected boolean useSensitiveContext;
87
88
89
90
91
92
93 protected String expressionForFirst;
94
95
96
97
98
99
100 protected String expressionForLast;
101
102
103
104
105
106 protected String[] keys;
107
108
109 protected WalkerContext c;
110
111 private boolean useFirst, useLast;
112
113 public Mode getMode() {
114 return mode;
115 }
116
117 public void setMode(Mode mode) {
118 this.mode = mode;
119 }
120
121 public String getCollectionFieldName() {
122 return collectionFieldName;
123 }
124
125 public void setCollectionFieldName(String collectionFieldName) {
126 this.collectionFieldName = collectionFieldName;
127 }
128
129 public boolean isUseSensitiveContext() {
130 return useSensitiveContext;
131 }
132
133 public void setUseSensitiveContext(boolean useSensitiveContext) {
134 this.useSensitiveContext = useSensitiveContext;
135 }
136
137 public String getExpressionForFirst() {
138 return expressionForFirst;
139 }
140
141 public void setExpressionForFirst(String expressionForFirst) {
142 this.expressionForFirst = expressionForFirst;
143 }
144
145 public String getExpressionForLast() {
146 return expressionForLast;
147 }
148
149 public void setExpressionForLast(String expressionForLast) {
150 this.expressionForLast = expressionForLast;
151 }
152
153 public String[] getKeys() {
154 return keys;
155 }
156
157 public void setKeys(String[] keys) {
158 if (keys != null && keys.length == 1 && keys[0].contains(",")) {
159 this.keys = keys[0].split(",");
160 } else {
161 this.keys = keys;
162 }
163 }
164
165 @Override
166 public void validateWhenNotSkip(Object object) throws ValidationException {
167 if (mode == null) {
168 throw new ValidationException("no mode defined!");
169 }
170 useFirst = expressionForFirst != null && !expressionForFirst.trim().isEmpty();
171 useLast = expressionForLast != null && !expressionForLast.trim().isEmpty();
172
173 if (useFirst && mode != Mode.ALL) {
174 throw new ValidationException("can only use expressionForFirst in " +
175 "mode ALL but was " + mode);
176 }
177 if (useLast && mode != Mode.ALL) {
178 throw new ValidationException("can only use expressionForLast in " +
179 "mode ALL but was " + mode);
180 }
181
182 String fieldName = getFieldName();
183
184 Collection<?> col = getCollection(object);
185
186 if (useSensitiveContext) {
187 c = new WalkerContext(col.size());
188 }
189
190 boolean answer;
191
192 boolean pop = false;
193
194 if (!stack.getRoot().contains(object)) {
195 stack.push(object);
196 pop = true;
197 }
198
199 switch (mode) {
200 case ALL:
201 answer = validateAllEntries(col);
202 break;
203 case AT_LEAST_ONE:
204 answer = validateAtLeastOneEntry(col);
205 break;
206 case EXACTLY_ONE:
207 answer = validateExtacltyOneEntry(col);
208 break;
209 case NONE:
210 answer = validateNoneEntry(col);
211 break;
212 case UNIQUE_KEY:
213 if (keys == null || keys.length == 0) {
214 throw new ValidationException("no unique keys defined");
215 }
216 answer = validateUniqueKey(col);
217 break;
218
219 default:
220
221 answer = false;
222 }
223
224 if (!answer) {
225 addFieldError(fieldName, object);
226 }
227 if (pop) {
228 stack.pop();
229 }
230 }
231
232 protected ValueStack stack;
233
234 @Override
235 public void setValueStack(ValueStack stack) {
236 super.setValueStack(stack);
237 this.stack = stack;
238 }
239
240 @Override
241 public String getMessage(Object object) {
242 boolean pop = false;
243
244 if (useSensitiveContext && !stack.getRoot().contains(c)) {
245 stack.push(c);
246 pop = true;
247 }
248 String message = super.getMessage(object);
249
250 if (pop) {
251 stack.pop();
252 }
253 return message;
254 }
255
256 protected Boolean validateAllEntries(Collection<?> col) throws ValidationException {
257 boolean answer = true;
258 for (Object entry : col) {
259 answer = validateOneEntry(entry);
260 if (!answer) {
261
262
263 break;
264 }
265 }
266 return answer;
267 }
268
269 protected Boolean validateNoneEntry(Collection<?> col) throws ValidationException {
270 boolean answer = true;
271 for (Object entry : col) {
272 boolean b = validateOneEntry(entry);
273 if (b) {
274
275
276 answer = false;
277 break;
278 }
279 }
280 return answer;
281 }
282
283 protected Boolean validateAtLeastOneEntry(Collection<?> col) throws ValidationException {
284 boolean answer = false;
285 for (Object entry : col) {
286 answer = validateOneEntry(entry);
287 if (answer) {
288
289
290 break;
291 }
292 }
293 return answer;
294 }
295
296 protected Boolean validateExtacltyOneEntry(Collection<?> col) throws ValidationException {
297 int count = 0;
298 for (Object entry : col) {
299 boolean answer = validateOneEntry(entry);
300 if (answer) {
301
302 count++;
303 if (count > 1) {
304
305
306 break;
307 }
308
309 }
310 }
311 return count == 1;
312 }
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338 protected Boolean validateUniqueKey(Collection<?> col) throws ValidationException {
339 boolean answer = true;
340
341 Comparator<? super Object> comparator1 = getComparator();
342 Set<? super Object> hashCodes = new TreeSet<Object>(comparator1);
343 int index = -1;
344 for (Object entry : col) {
345 index++;
346 boolean wasAdded = hashCodes.add(entry);
347 if (!wasAdded) {
348 answer = false;
349 if (log.isDebugEnabled()) {
350 log.debug("duplicated unique entry at position: " + index);
351 }
352 break;
353 }
354 }
355 return answer;
356 }
357
358 protected boolean validateOneEntry(Object object) throws ValidationException {
359
360 Boolean answer = Boolean.FALSE;
361
362 boolean extraExpression = false;
363
364 if (useSensitiveContext) {
365 c.addCurrent(object);
366 object = c;
367
368 if (c.isFirst() && useFirst) {
369
370 answer = evaluateExpression(expressionForFirst, object);
371 extraExpression = true;
372 }
373 if (c.isLast() && useLast) {
374
375 answer = (!extraExpression || answer) && evaluateExpression(expressionForLast, object);
376 extraExpression = true;
377 }
378 }
379
380 answer = (!extraExpression || answer) && evaluateExpression(getExpression(), object);
381
382 return answer;
383 }
384
385 protected boolean evaluateExpression(String expression, Object object) throws ValidationException {
386 Object obj = null;
387 try {
388 obj = getFieldValue(expression, object);
389 } catch (ValidationException e) {
390 throw e;
391 } catch (Exception e) {
392 log.error(e.getMessage(), e);
393
394 }
395
396 Boolean answer = Boolean.FALSE;
397
398 if (obj != null && obj instanceof Boolean) {
399 answer = (Boolean) obj;
400 } else {
401 log.warn("Got result of " + obj + " when trying to get Boolean for expression " + expression);
402 }
403 return answer;
404 }
405
406
407
408
409
410
411 protected Collection<?> getCollection(Object object) throws ValidationException {
412 String fieldName = getCollectionFieldName();
413 if (fieldName == null || fieldName.trim().isEmpty()) {
414
415 fieldName = getFieldName();
416 }
417
418 Object obj = null;
419
420
421 try {
422 obj = getFieldValue(fieldName, object);
423 } catch (ValidationException e) {
424 throw e;
425 } catch (Exception e) {
426
427 }
428
429 if (obj == null) {
430
431 return Collections.emptyList();
432 }
433
434 if (!Collection.class.isInstance(obj)) {
435 throw new ValidationException("field " + fieldName + " is not a collection type! (" + obj.getClass() + ")");
436 }
437 return (Collection<?>) obj;
438 }
439
440
441
442
443
444
445
446
447 protected Integer getUniqueKeyHashCode(Object o) throws ValidationException {
448
449 HashCodeBuilder builder = new HashCodeBuilder();
450 for (String key : keys) {
451 Object property = getFieldValue(key, o);
452 if (log.isDebugEnabled()) {
453 log.debug("key " + key + " : " + property);
454 }
455 builder.append(property);
456 }
457 return builder.toHashCode();
458 }
459
460 @Override
461 public String getValidatorType() {
462 return "collectionFieldExpression";
463 }
464
465 public class WalkerContext {
466
467 protected final int size;
468
469 public WalkerContext(int size) {
470 this.size = size;
471 }
472
473 protected int index = -1;
474
475 protected Object current;
476
477 protected Object previous;
478
479 public void addCurrent(Object current) {
480 index++;
481 previous = this.current;
482 this.current = current;
483 }
484
485 public Object getCurrent() {
486 return current;
487 }
488
489 public int getIndex() {
490 return index;
491 }
492
493 public Object getPrevious() {
494 return previous;
495 }
496
497 public int getSize() {
498 return size;
499 }
500
501 public boolean isEmpty() {
502 return size == 0;
503 }
504
505 public boolean isFirst() {
506 return index == 0;
507 }
508
509 public boolean isLast() {
510 return index == size - 1;
511 }
512 }
513
514 Comparator<? super Object> comparator;
515
516 private Comparator<? super Object> getComparator() {
517 if (comparator == null) {
518 comparator = new MyComparator<Object>(keys);
519 }
520 return comparator;
521 }
522
523 private class MyComparator<O> implements Comparator<O> {
524
525 private final String[] keys;
526
527 public MyComparator(String... keys) {
528 this.keys = keys;
529 }
530
531 @Override
532 public int compare(O o1, O o2) {
533
534 boolean equals = true;
535
536 for (String key : keys) {
537
538 Object property1 = getPropertyValue(key, o1);
539 Object property2 = getPropertyValue(key, o2);
540
541 equals = Objects.equal(property1, property2);
542
543 if (!equals) {
544 break;
545 }
546 }
547
548 return equals ? 0 : -1;
549 }
550 }
551
552 protected Object getPropertyValue(String key, Object o) {
553
554 try {
555 return getFieldValue(key, o);
556
557 } catch (ValidationException e) {
558 if (log.isErrorEnabled()) {
559 log.error("Can't get property '" + key + "'value on oject: " + o);
560 }
561 return null;
562 }
563 }
564 }