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.opensymphony.xwork2.validator.ValidationException;
25 import com.opensymphony.xwork2.validator.validators.FieldExpressionValidator;
26 import org.apache.commons.lang3.builder.HashCodeBuilder;
27 import org.apache.commons.logging.Log;
28 import org.apache.commons.logging.LogFactory;
29
30 import java.util.ArrayList;
31 import java.util.Collection;
32 import java.util.Collections;
33 import java.util.List;
34
35
36
37
38
39
40
41
42
43
44 public class CollectionUniqueKeyValidator extends NuitonFieldValidatorSupport {
45
46 private static final Log log = LogFactory.getLog(CollectionUniqueKeyValidator.class);
47
48
49
50
51
52
53
54
55
56
57
58
59 protected String collectionFieldName;
60
61
62
63
64
65 protected String[] keys;
66
67
68
69
70
71
72 protected String againstProperty;
73
74
75
76
77
78
79 protected boolean againstMe;
80
81
82
83
84
85
86 protected String againstIndexExpression;
87
88
89
90
91
92
93
94 protected boolean nullValueSkipped;
95
96 public String getCollectionFieldName() {
97 return collectionFieldName;
98 }
99
100 public void setCollectionFieldName(String collectionFieldName) {
101 this.collectionFieldName = collectionFieldName;
102 }
103
104 public String[] getKeys() {
105 return keys;
106 }
107
108 public boolean getAgainstMe() {
109 return againstMe;
110 }
111
112 public void setKeys(String[] keys) {
113 if (keys != null && keys.length == 1 && keys[0].indexOf(',') != -1) {
114 this.keys = keys[0].split(",");
115 } else {
116 this.keys = keys;
117 }
118 }
119
120 public String getAgainstProperty() {
121 return againstProperty;
122 }
123
124 public void setAgainstProperty(String againstProperty) {
125 this.againstProperty = againstProperty;
126 }
127
128 public String getAgainstIndexExpression() {
129 return againstIndexExpression;
130 }
131
132 public void setAgainstIndexExpression(String againstIndexExpression) {
133 this.againstIndexExpression = againstIndexExpression;
134 }
135
136 public void setAgainstMe(boolean againstMe) {
137 this.againstMe = againstMe;
138 }
139
140 public boolean isNullValueSkipped() {
141 return nullValueSkipped;
142 }
143
144 public void setNullValueSkipped(boolean nullValueSkipped) {
145 this.nullValueSkipped = nullValueSkipped;
146 }
147
148 @Override
149 public void validateWhenNotSkip(Object object) throws ValidationException {
150
151 if (keys == null || keys.length == 0) {
152 throw new ValidationException("no unique keys defined");
153 }
154
155 String fieldName = getFieldName();
156
157 Collection<?> col = getCollection(object);
158
159 if (log.isDebugEnabled()) {
160 log.debug("collection found : " + col);
161 }
162 Object againstBean = againstProperty == null ? null :
163 getFieldValue(againstProperty, object);
164
165 if (log.isDebugEnabled()) {
166 log.debug("againtBean = " + againstBean);
167 }
168 Integer againstIndex = (Integer) (againstIndexExpression == null ?
169 -1 :
170 getFieldValue(againstIndexExpression, object));
171 if (againstIndex == null) {
172 againstIndex = -1;
173 }
174 if (!againstMe && againstBean == null && col.size() < 2) {
175
176 return;
177 }
178
179 if (againstMe) {
180
181 againstBean = object;
182 if (log.isDebugEnabled()) {
183 log.debug("againtBean from me = " + againstBean);
184 }
185 }
186
187
188 boolean answer = true;
189
190 Integer againstHashCode = againstBean == null ?
191 null : getUniqueKeyHashCode(againstBean);
192 if (log.isDebugEnabled()) {
193 log.debug("hash for new key " + againstHashCode);
194 }
195
196 if (againstHashCode == null && nullValueSkipped) {
197
198
199 return;
200 }
201 List<Integer> hashCodes = new ArrayList<Integer>();
202
203 int index = 0;
204 for (Object o : col) {
205 Integer hash = getUniqueKeyHashCode(o);
206
207 if (log.isDebugEnabled()) {
208 log.debug("hash for object " + o + " = " + hash);
209 }
210
211 if (hash == null && nullValueSkipped) {
212
213
214 continue;
215 }
216
217 if (againstBean == null) {
218 if (hashCodes.contains(hash)) {
219
220
221
222 answer = false;
223 if (log.isDebugEnabled()) {
224 log.debug("Found same hashcode, not unique!");
225 }
226 break;
227 }
228 } else {
229
230 if (againstIndex != -1) {
231 if (index != againstIndex &&
232 hash.equals(againstHashCode)) {
233
234
235 answer = false;
236 break;
237 }
238 } else {
239 if (!againstBean.equals(o) &&
240 hash.equals(againstHashCode)) {
241
242
243 answer = false;
244 break;
245 }
246 }
247 }
248
249 hashCodes.add(hash);
250
251 index++;
252 }
253
254 if (!answer) {
255 addFieldError(fieldName, object);
256 }
257 }
258
259
260
261
262
263
264
265
266
267 protected Integer getUniqueKeyHashCode(Object o)
268 throws ValidationException {
269
270 HashCodeBuilder builder = new HashCodeBuilder();
271 for (String key : keys) {
272 Object property = getFieldValue(key, o);
273 if (property == null && nullValueSkipped) {
274
275
276 return null;
277 }
278
279 builder.append(property);
280 }
281 return builder.toHashCode();
282 }
283
284
285
286
287
288
289
290 protected Collection<?> getCollection(Object object)
291 throws ValidationException {
292 String fieldName = getCollectionFieldName();
293 if (fieldName == null || fieldName.trim().isEmpty()) {
294
295 fieldName = getFieldName();
296 }
297
298 Object obj = null;
299
300
301 try {
302 obj = getFieldValue(fieldName, object);
303 } catch (ValidationException e) {
304 throw e;
305 } catch (Exception e) {
306
307 }
308
309 if (obj == null) {
310
311 return Collections.emptyList();
312 }
313
314 if (!Collection.class.isInstance(obj)) {
315 throw new ValidationException("field " + fieldName +
316 " is not a collection type! (" + obj.getClass() + ')');
317 }
318 return (Collection<?>) obj;
319 }
320
321 @Override
322 public String getValidatorType() {
323 return "collectionUniqueKey";
324 }
325 }