0
# Self-Validation Framework
1
2
A framework enabling objects to define custom validation logic through annotated methods, providing flexibility for complex business rules that cannot be expressed with standard validation annotations. This framework allows objects to validate themselves using custom logic while integrating seamlessly with Bean Validation.
3
4
## Capabilities
5
6
### Self-Validating Annotation
7
8
Marks a class as having self-validation methods that should be executed during validation.
9
10
```java { .api }
11
@Target({ElementType.TYPE, ElementType.ANNOTATION_TYPE})
12
@Retention(RetentionPolicy.RUNTIME)
13
@Constraint(validatedBy = SelfValidatingValidator.class)
14
public @interface SelfValidating {
15
/**
16
* The validation message for this constraint.
17
*
18
* @return the message
19
*/
20
String message() default "";
21
22
/**
23
* The groups the constraint belongs to.
24
*
25
* @return an array of classes representing the groups
26
*/
27
Class<?>[] groups() default {};
28
29
/**
30
* The payloads of this constraint.
31
*
32
* @return the array of payload classes
33
*/
34
Class<? extends Payload>[] payload() default {};
35
}
36
```
37
38
### Self-Validation Method Annotation
39
40
Marks a method as a self-validation method that will be executed to check if the object is valid.
41
42
```java { .api }
43
@Target(ElementType.METHOD)
44
@Retention(RetentionPolicy.RUNTIME)
45
@Inherited
46
public @interface SelfValidation {
47
}
48
```
49
50
**Method Signature Requirements:**
51
- Must be `public`
52
- Must return `void`
53
- Must accept exactly one parameter of type `ViolationCollector`
54
- Method name can be anything
55
56
### Violation Collector
57
58
Collects constraint violations during self-validation, providing methods to add violations at different scopes.
59
60
```java { .api }
61
public class ViolationCollector {
62
/**
63
* Constructs a new ViolationCollector with the given ConstraintValidatorContext.
64
*
65
* @param constraintValidatorContext the wrapped ConstraintValidatorContext
66
*/
67
public ViolationCollector(ConstraintValidatorContext constraintValidatorContext);
68
69
/**
70
* Adds a new violation to this collector. This also sets violationOccurred to true.
71
*
72
* @param message the message of the violation
73
*/
74
public void addViolation(String message);
75
76
/**
77
* Adds a new violation to this collector. This also sets violationOccurred to true.
78
*
79
* @param message the message of the violation
80
* @param messageParameters a map of message parameters which can be interpolated in the violation message
81
*/
82
public void addViolation(String message, Map<String, Object> messageParameters);
83
84
/**
85
* Adds a new violation to this collector. This also sets violationOccurred to true.
86
*
87
* @param propertyName the name of the property
88
* @param message the message of the violation
89
*/
90
public void addViolation(String propertyName, String message);
91
92
/**
93
* Adds a new violation to this collector. This also sets violationOccurred to true.
94
*
95
* @param propertyName the name of the property
96
* @param message the message of the violation
97
* @param messageParameters a map of message parameters which can be interpolated in the violation message
98
*/
99
public void addViolation(String propertyName, String message, Map<String, Object> messageParameters);
100
101
/**
102
* Adds a new violation to this collector. This also sets violationOccurred to true.
103
*
104
* @param propertyName the name of the property with the violation
105
* @param index the index of the element with the violation
106
* @param message the message of the violation
107
*/
108
public void addViolation(String propertyName, Integer index, String message);
109
110
/**
111
* Adds a new violation to this collector. This also sets violationOccurred to true.
112
*
113
* @param propertyName the name of the property with the violation
114
* @param index the index of the element with the violation
115
* @param message the message of the violation
116
* @param messageParameters a map of message parameters which can be interpolated in the violation message
117
*/
118
public void addViolation(String propertyName, Integer index, String message, Map<String, Object> messageParameters);
119
120
/**
121
* Adds a new violation to this collector. This also sets violationOccurred to true.
122
*
123
* @param propertyName the name of the property with the violation
124
* @param key the key of the element with the violation
125
* @param message the message of the violation
126
*/
127
public void addViolation(String propertyName, String key, String message);
128
129
/**
130
* Adds a new violation to this collector. This also sets violationOccurred to true.
131
*
132
* @param propertyName the name of the property with the violation
133
* @param key the key of the element with the violation
134
* @param message the message of the violation
135
* @param messageParameters a map of message parameters which can be interpolated in the violation message
136
*/
137
public void addViolation(String propertyName, String key, String message, Map<String, Object> messageParameters);
138
139
/**
140
* Returns, if a violation has previously occurred.
141
*
142
* @return if any violation was collected
143
*/
144
public boolean hasViolationOccurred();
145
146
/**
147
* Manually sets if a violation occurred. This is automatically set if addViolation is called.
148
*
149
* @param violationOccurred if any violation was collected
150
*/
151
public void setViolationOccurred(boolean violationOccurred);
152
153
/**
154
* This method returns the wrapped context for raw access to the validation framework.
155
* If you use the context to add violations make sure to call setViolationOccurred(true).
156
*
157
* @return the wrapped Hibernate ConstraintValidatorContext
158
*/
159
public ConstraintValidatorContext getContext();
160
}
161
```
162
163
## Usage Examples
164
165
### Basic Self-Validation
166
167
```java
168
import io.dropwizard.validation.selfvalidating.SelfValidating;
169
import io.dropwizard.validation.selfvalidating.SelfValidation;
170
import io.dropwizard.validation.selfvalidating.ViolationCollector;
171
172
@SelfValidating
173
public class UserRegistration {
174
private String username;
175
private String password;
176
private String confirmPassword;
177
private String email;
178
179
@SelfValidation
180
public void validatePasswords(ViolationCollector collector) {
181
if (password != null && !password.equals(confirmPassword)) {
182
collector.addViolation("confirmPassword", "Password confirmation does not match");
183
}
184
}
185
186
@SelfValidation
187
public void validateBusinessRules(ViolationCollector collector) {
188
if (username != null && username.length() < 3) {
189
collector.addViolation("username", "Username must be at least 3 characters long");
190
}
191
192
if (email != null && !email.contains("@")) {
193
collector.addViolation("email", "Email must be a valid email address");
194
}
195
}
196
197
// getters and setters...
198
}
199
```
200
201
### Advanced Self-Validation with Message Parameters
202
203
```java
204
import io.dropwizard.validation.selfvalidating.SelfValidating;
205
import io.dropwizard.validation.selfvalidating.SelfValidation;
206
import io.dropwizard.validation.selfvalidating.ViolationCollector;
207
import java.util.Map;
208
import java.util.HashMap;
209
210
@SelfValidating
211
public class ConfigurationSettings {
212
private int maxConnections;
213
private int maxConnectionsPerHost;
214
private List<String> allowedHosts;
215
216
@SelfValidation
217
public void validateConnectionSettings(ViolationCollector collector) {
218
if (maxConnectionsPerHost > maxConnections) {
219
Map<String, Object> params = new HashMap<>();
220
params.put("maxConnections", maxConnections);
221
params.put("maxConnectionsPerHost", maxConnectionsPerHost);
222
223
collector.addViolation(
224
"maxConnectionsPerHost",
225
"Connections per host ({maxConnectionsPerHost}) cannot exceed total connections ({maxConnections})",
226
params
227
);
228
}
229
}
230
231
@SelfValidation
232
public void validateHostList(ViolationCollector collector) {
233
if (allowedHosts != null) {
234
for (int i = 0; i < allowedHosts.size(); i++) {
235
String host = allowedHosts.get(i);
236
if (host == null || host.trim().isEmpty()) {
237
collector.addViolation("allowedHosts", i, "Host cannot be empty");
238
}
239
}
240
}
241
}
242
}
243
```
244
245
### Complex Business Logic Validation
246
247
```java
248
import io.dropwizard.validation.selfvalidating.SelfValidating;
249
import io.dropwizard.validation.selfvalidating.SelfValidation;
250
import io.dropwizard.validation.selfvalidating.ViolationCollector;
251
252
@SelfValidating
253
public class OrderConfiguration {
254
private BigDecimal minOrderAmount;
255
private BigDecimal maxOrderAmount;
256
private BigDecimal discountThreshold;
257
private BigDecimal discountPercentage;
258
private Map<String, BigDecimal> categoryLimits;
259
260
@SelfValidation
261
public void validateOrderAmounts(ViolationCollector collector) {
262
if (minOrderAmount != null && maxOrderAmount != null) {
263
if (minOrderAmount.compareTo(maxOrderAmount) > 0) {
264
collector.addViolation("minOrderAmount", "Minimum order amount cannot exceed maximum order amount");
265
}
266
}
267
268
if (discountThreshold != null && maxOrderAmount != null) {
269
if (discountThreshold.compareTo(maxOrderAmount) > 0) {
270
collector.addViolation("discountThreshold", "Discount threshold cannot exceed maximum order amount");
271
}
272
}
273
}
274
275
@SelfValidation
276
public void validateDiscountSettings(ViolationCollector collector) {
277
if (discountPercentage != null) {
278
if (discountPercentage.compareTo(BigDecimal.ZERO) < 0 ||
279
discountPercentage.compareTo(new BigDecimal("100")) > 0) {
280
collector.addViolation("discountPercentage", "Discount percentage must be between 0 and 100");
281
}
282
}
283
}
284
285
@SelfValidation
286
public void validateCategoryLimits(ViolationCollector collector) {
287
if (categoryLimits != null) {
288
for (Map.Entry<String, BigDecimal> entry : categoryLimits.entrySet()) {
289
if (entry.getValue().compareTo(BigDecimal.ZERO) <= 0) {
290
collector.addViolation("categoryLimits", entry.getKey(),
291
"Category limit must be positive");
292
}
293
294
if (maxOrderAmount != null && entry.getValue().compareTo(maxOrderAmount) > 0) {
295
collector.addViolation("categoryLimits", entry.getKey(),
296
"Category limit cannot exceed maximum order amount");
297
}
298
}
299
}
300
}
301
}
302
```
303
304
### Integration with Bean Validation
305
306
```java
307
import io.dropwizard.validation.selfvalidating.SelfValidating;
308
import io.dropwizard.validation.selfvalidating.SelfValidation;
309
import io.dropwizard.validation.selfvalidating.ViolationCollector;
310
import javax.validation.constraints.NotNull;
311
import javax.validation.constraints.Email;
312
import javax.validation.constraints.Size;
313
314
@SelfValidating
315
public class UserProfile {
316
@NotNull
317
@Size(min = 3, max = 50)
318
private String username;
319
320
@NotNull
321
322
private String email;
323
324
@NotNull
325
@Size(min = 8)
326
private String password;
327
328
private String confirmPassword;
329
private boolean termsAccepted;
330
331
@SelfValidation
332
public void validateCustomRules(ViolationCollector collector) {
333
// Password confirmation (not covered by standard annotations)
334
if (password != null && !password.equals(confirmPassword)) {
335
collector.addViolation("confirmPassword", "Password confirmation must match password");
336
}
337
338
// Business rule validation
339
if (!termsAccepted) {
340
collector.addViolation("termsAccepted", "Terms and conditions must be accepted");
341
}
342
343
// Complex username validation
344
if (username != null && username.contains("admin")) {
345
collector.addViolation("username", "Username cannot contain 'admin'");
346
}
347
}
348
}
349
350
// Usage
351
Validator validator = BaseValidator.newValidator();
352
UserProfile profile = new UserProfile();
353
// ... set properties
354
Set<ConstraintViolation<UserProfile>> violations = validator.validate(profile);
355
// Both standard Bean Validation and self-validation will be executed
356
```
357
358
## Advanced Features
359
360
### Validation Groups
361
362
```java
363
import io.dropwizard.validation.selfvalidating.SelfValidating;
364
import io.dropwizard.validation.selfvalidating.SelfValidation;
365
366
public interface CreateUser {}
367
public interface UpdateUser {}
368
369
@SelfValidating(groups = {CreateUser.class, UpdateUser.class})
370
public class UserData {
371
private String username;
372
private String password;
373
374
@SelfValidation
375
public void validateForCreation(ViolationCollector collector) {
376
// This validation runs for both CreateUser and UpdateUser groups
377
if (username != null && username.length() < 3) {
378
collector.addViolation("username", "Username too short");
379
}
380
}
381
}
382
```
383
384
### Multiple Self-Validation Methods
385
386
```java
387
@SelfValidating
388
public class ComplexConfiguration {
389
private DatabaseConfig database;
390
private CacheConfig cache;
391
private SecurityConfig security;
392
393
@SelfValidation
394
public void validateDatabaseConfig(ViolationCollector collector) {
395
// Database-specific validation logic
396
}
397
398
@SelfValidation
399
public void validateCacheConfig(ViolationCollector collector) {
400
// Cache-specific validation logic
401
}
402
403
@SelfValidation
404
public void validateSecurityConfig(ViolationCollector collector) {
405
// Security-specific validation logic
406
}
407
408
@SelfValidation
409
public void validateCrossComponentCompatibility(ViolationCollector collector) {
410
// Cross-component validation logic
411
}
412
}
413
```
414
415
## Integration Notes
416
417
- Self-validation methods are executed in addition to standard Bean Validation annotations
418
- All `@SelfValidation` methods on a class are executed when validation occurs
419
- Violations are collected and reported alongside standard constraint violations
420
- Self-validation integrates seamlessly with Dropwizard's configuration validation
421
- Use `ViolationCollector` methods to ensure proper violation reporting and message interpolation
422
- Self-validation is particularly useful for cross-field validation and complex business rules