HiveBrain v1.2.0
Get Started
← Back to all entries
patternjavaMinor

Automagic (or not quite so) Domain Model Class Validation

Submitted by: @import:stackexchange-codereview··
0
Viewed 0 times
domainclassautomagicvalidationquitenotmodel

Problem

The Task

I was assigned a small task, concerning validation of Domain-Model classes.

The Validation for a String property of one of our Models was required to be unique across the whole Table. And as this already was halfway implemented after 20 minutes research, using a database constraint, it seemed fairly easy.

Obviously it wasn't, because what use does a validation have, when you're not able to show Error-Messages to your user in a feasible manner. The plan thusly changed slightly.

The only feasible approach to this seems to be a defensive select statement at persist time. This required me to use some additional features of JBoss, namely Seam and it's InjectingConstraintValidatorFactory. This allows me to access the current persistence context using an Injected EntityManager and run my defensive Select-Statement.

So here's what I ended up with:

```
@ManagedBean
public class UniqueLocationValidator implements
ConstraintValidator {

@Inject
EntityManager entityManager;

private String message;

@Override
public void initialize(final UniqueLocation annotation) {
message = annotation.message();
}

@Override
public boolean isValid(final Location instance,
final ConstraintValidatorContext context) {
if (instance == null) {
// Recommended, instead use explicit @NotNull Annotation for
// validating non-nullable instances
return true;
}

final String checkedValue = instance.getLocationName();
final long id = instance.getId();

// must not return a result for name-equality on the same Id
String queryString = "SELECT * FROM Location WHERE locationName = :value AND id <> :id";

Query defensiveSelect = entityManager.createNativeQuery(queryString)
.setParameter("value", checkedValue).setParameter("id", id);

@SuppressWarnings("unchecked")
List results = defensiveSelect.getResultList();

Solution

-
Querying "SELECT * FROM Location is a little over the head. You don't need to get all columns.

-
As the value of queryString doesn't change, you should make it final

-
Renaming message to constraintValidationMessage for understandability regarding new staff

-
Single Responsible Principle is violated as you are querying a database and constructing a ConstraintViolation for the context.

-
Introduce a duplicateLocationExists() method which takes a Location as parameter and just returns a Boolean. The results object is only needed to check if it is empty, which should be returned by the duplicateLocationExists() method.

private boolean duplicateLocationExists(final Location location){

    if (location == null) {
         // Recommended, instead use explicit @NotNull Annotation for
         // validating non-nullable instances
         return false;
    }

    final String checkedValue = location.getLocationName();
    final long id = location.getId();
    final String queryString = "SELECT id FROM Location WHERE locationName = :value AND id <> :id";

    Query defensiveSelect = entityManager.createNativeQuery(queryString)
        .setParameter("value", checkedValue).setParameter("id", id);

    return !defensiveSelect.getResultList().IsEmpty();
}


-
Inroduce a method for constructing the ConstraintViolation for the context

private void createConstraintViolations(final ConstraintValidatorContext context){
    context.disableDefaultConstraintViolation();
    context.buildConstraintViolationWithTemplate(message)
           .addNode("locationName").addConstraintViolation();
}


So the isValid() method will become

@Override
public boolean isValid(final Location instance,
        final ConstraintValidatorContext context) {

    if (duplicateLocationExists(instance)) {
        createConstraintViolations(context);
        return false;
    } else {
        return true;
    }
}

Code Snippets

private boolean duplicateLocationExists(final Location location){

    if (location == null) {
         // Recommended, instead use explicit @NotNull Annotation for
         // validating non-nullable instances
         return false;
    }

    final String checkedValue = location.getLocationName();
    final long id = location.getId();
    final String queryString = "SELECT id FROM Location WHERE locationName = :value AND id <> :id";

    Query defensiveSelect = entityManager.createNativeQuery(queryString)
        .setParameter("value", checkedValue).setParameter("id", id);

    return !defensiveSelect.getResultList().IsEmpty();
}
private void createConstraintViolations(final ConstraintValidatorContext context){
    context.disableDefaultConstraintViolation();
    context.buildConstraintViolationWithTemplate(message)
           .addNode("locationName").addConstraintViolation();
}
@Override
public boolean isValid(final Location instance,
        final ConstraintValidatorContext context) {

    if (duplicateLocationExists(instance)) {
        createConstraintViolations(context);
        return false;
    } else {
        return true;
    }
}

Context

StackExchange Code Review Q#64852, answer score: 3

Revisions (0)

No revisions yet.