8

I'm a bit of a Java EE/EJB noob, but from the docs and other posts I've gathered you cannot query the database using the same entitymanager/session during entity validation.

In general, the lifecycle method of a portable application should not invoke EntityManager or Query operations, access other entity instances, or modify relationships within the same persistence context.[43] A lifecycle callback method may modify the non-relationship state of the entity on which it is invoked.

Translation please?

This is pretty abstract...can it be explained in more concrete terms? It leads to more questions than it answers. For example, if my entity has a lazy-loaded collection am I allowed to access it during validation? The collection is 'another entity' and will require a DB query which seems to be in violation of the docs.

This 'lifecycle requirement' seems odd because it's just a fact of life that certain validations do indeed require querying the database.

From other posts I've also seen people get around this querying issue by creating a new entitymanager/session using the entitymanagerfactory.

This leads me to two questions about using EntityManagers and Hibernate Validation:

  1. Is it possible I have some sort of design flaw or am misusing Hibernate Validation because I need to query the database during validation?
  2. Given that I'm using Java EE with JBoss, how do I inject my validator with an EntityManagerFactory?

I've tried something like this:

@Stateless
public class UserValidator implements ConstraintValidator<ValidUser, User> {
    @PersistenceUnit(unitName="blahblah")
    EntityManagerFactory emf;

    ...
}

But the EMF never gets injected. I'm guessing the @Stateless tag becomes irrelevant because I'm implementing a ConstraintValidator interface which is needed for the Hibernate Validator stuff to work.

So what's the general pattern for getting at an EntityManagerFactory from a Validator?

Thanks!

Arjan Tijms
  • 37,782
  • 12
  • 108
  • 140
lostdorje
  • 6,150
  • 9
  • 44
  • 86
  • Sounds like you're trying to validate that the user doesn't already exist, why not let the database do that? – rdcrng Aug 16 '13 at 09:22
  • Really, I'm asking the question in a very general sense. Needing database access as a part of validation does not seem unusual to me. In this particular case though I have a business requirement that a user changing their password is not reusing an old password. So I have to join with a passwords table. I'm lazy loading the list, but it's ugly to have a method User.getOldPasswords(), so I'd rather not map List pwds on the user at all and just query the db for it during validation. – lostdorje Aug 16 '13 at 10:12

3 Answers3

6

Through some of the comments and enough scrounging around, I finally figured out a somewhat 'canonical' way to answer my question.

But to clear things up, my question really was asking two things which have 2 distinct answers:

  1. How do you inject things into Validators that are used in the Hibernate Validation framework?
  2. Assuming we can inject things is it safe to inject an EntityManagerFactory or EntityManager and use them for querying during a JPA lifecycle event?

Answering the second question first I'll simply say, it is strongly encouraged to use a second EntityManager to do queries during validation. That means you should be injecting an EntityManagerFactory and creating a new EntityManager for queries (rather than injecting an EntityManager which will be the same one that created the lifecycle event to begin with).

Generally speaking, for validation purposes, you'll only be querying the database anyway and not inserting/updating so this should be fairly safe to do.

I asked a very related SO question here.

Now to answer question 1.

Yes it is completely possible to inject things into Validators used in the Hibernate Validation framework. To accomplish this you need to do 3 things:

  1. Create a custom ConstraintValidatorFactory that will create the validators used in the framework (overriding Hibernate's default factory). (My example uses Java EE, not Spring so I use BeanManager, but in Spring you'd probably use ApplicationContext for this).
  2. Create a validation.xml file which tells the Hibernate Validation framework which class to use for the ConstraintValidatorFactory. Make sure this file ends up on your classpath.
  3. Write a Validator that injects something.

Here is an example custom ConstraintValidatorFactory that uses 'managed' (injectable) validators:

package com.myvalidator;

public class ConstraintInjectableValidatorFactory implements ConstraintValidatorFactory {

    private static BeanManager beanManager;

    @SuppressWarnings(value="unchecked")
    @Override
    public <T extends ConstraintValidator<?, ?>> T getInstance(Class<T> clazz) {
        // lazily initialize the beanManager
        if (beanManager == null) {
            try {
                beanManager = (BeanManager) InitialContext.doLookup("java:comp/BeanManager");
            } catch (NamingException e) {
                // TODO what's the best way to handle this?
                throw new RuntimeException(e);
            }
        }

        T result = null;

        Bean<T> bean = (Bean<T>) beanManager.resolve(beanManager.getBeans(clazz));
        // if the bean/validator specified by clazz is not null that means it has
        // injection points so get it from the beanManager and return it. The validator
        // that comes from the beanManager will already be injected.
        if (bean != null) {
            CreationalContext<T> context = beanManager.createCreationalContext(bean);
            if (context != null) {
                result = (T) beanManager.getReference(bean, clazz, context);
            }
        // the bean/validator was not in the beanManager meaning it has no injection
        // points so go ahead and just instantiate a new instance and return it
        } else {
            try {
                result = clazz.newInstance();
            } catch (Throwable t) {
                throw new RuntimeException(t);
            }
        }

        return result;
    }
}

Here is an example validation.xml file that tells Hibernate Validator which class to use as the ValidatorFactory:

<?xml version="1.0" encoding="UTF-8"?>
<validation-config
    xmlns="http://jboss.org/xml/ns/javax/validation/configuration"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://jboss.org/xml/ns/javax/validation/configuration validation-configuration-1.0.xsd">
    <constraint-validator-factory>
        com.myvalidator.ConstraintInjectableValidatorFactory
    </constraint-validator-factory>
</validation-config>

And finally a validator class with injection points:

public class UserValidator implements ConstraintValidator<ValidUser, User> {

    @PersistenceUnit(unitName="myvalidator")
    private EntityManagerFactory entityManagerFactory;

    private EntityManager entityManager;

    @Override
    public void initialize(ValidUser annotation) {
    }

    @Override
    public boolean isValid(User user, ConstraintValidatorContext context) {
        // validation takes place during the entityManager.persist() lifecycle, so
        // here we create a new entityManager separate from the original one that
        // invoked this validation
        entityManager = entityManagerFactory.createEntityManager();

        // use entityManager to query database for needed validation

        entityManager.close();
    }
}
Community
  • 1
  • 1
lostdorje
  • 6,150
  • 9
  • 44
  • 86
  • 1
    +1 for perseverence, and because I learned something new about custom validation resolution, and bean management. That said, it all boils down to a call to the initial context `InitialContext.doLookup("java:comp/BeanManager");`, so I don't see a clear benefit over looking up the entity manager itself in the validator (as I suggest in my answer). – ewernli Aug 23 '13 at 07:58
  • The lookup I got from your answer, so thanks for that! :-) I think going this route might be better because going this route lets you inject anything that the bean manager knows about. Things that may or may not have registered in JNDI. Also I don't have facts on this, but I think JNDI look ups can be expensive, so 1 lookup for the bean manager and then getting everything else from the bean manager should be more efficient. – lostdorje Aug 24 '13 at 00:01
0

I think I get your desire to do all the validation using the awesome bean validation API, but remember that is not required.

Furthermore, think about these two requirements:

  1. Password must not be empty.
  2. User must not use a password that is equal to any of the previous passwords.

The first clearly only depends on the password itself and I would classify it as validating data and therefore such validation belongs in the data layer.

The second depends on the relation of a piece of data with a number of other entities, or with the current state of the system. I would classify this as something that belongs in the business layer.

That said, instead of trying to put the validation constraints on the entity class, put them on some business layer class (yes, you can even use bean validation if you so desire now).

For example, say you have a User entity with current password field and a Passwords entity from which you can query for a user's old passwords. Now make your user data access object:

@Stateful // yes stateful, need the same instance across method invocations
@ValidatePassword
public class UserDao {

    @PersistenceContext private EntityManager em;
    private String password;

    public String getPassword() {
        return this.password;
    }

    public void setPassword(String password) {
        this.password = password;
    }

    public boolean isValidPassword() {
        // use the em to find the old passwords
        // check that the submitted password is valid
    }

    public void savePassword() {
        // find the user
        // set the user's now valid password
    }
}

Create your class-level constraint:

@Target( { TYPE })
@Retention(RUNTIME)
@Constraint(validatedBy = MyPasswordValidator.class)
public @interface ValidatePassword {

    String message() default "error message";

    Class<?>[] groups() default {};

    Class<? extends Payload>[] payload() default {};

}

And validator:

public class MyPasswordValidator implements ConstraintValidator<ValidatePassword, UserDao> {

    public void initialize(SelfValidating constraintAnnotation) {
        // get message, etc.
    }

    public boolean isValid(UserDao userDao, ConstraintValidatorContext constraintValidatorContext) {
        return userDao.isValidPassword();
    }
}

Something like this should do it. As a side effect, since the actual validation is done by an EJB now, the validation logic itself will be transacted, if you leave the default transnational attributes that is.

rdcrng
  • 3,415
  • 2
  • 18
  • 36
  • Thanks for the answer. Your answer makes sense and I think it's getting very close. In a slightly different form my main question remains though...if I take your suggestion then in the Validator, I'll need to inject the DAO. But this suffers the same problem that my original question asks, which is how to inject the entitymanagerfactory into the validator? If I can't do that, then I can't inject a DAO either. – lostdorje Aug 19 '13 at 13:26
  • As far as I know, *injecting* anything in a `ConstraintValidator` won't work prior to CDI 1.0. In CDI 1.1 this is possible, look at http://docs.jboss.org/cdi/spec/1.1/cdi-spec.html#_relationship_to_bean_validation. However, it seems to me that the above does what you want, albeit not with injection. Why do you insist on *injecting* something if you can just pass the instance you're validating to the `ConstrintValidator` and achieve the same effect? I mean, I know injection is nice, but limitations are limitations. – rdcrng Aug 19 '13 at 13:57
  • "if you can just pass the instance you're validating"...here the instance being validated is the User and that *does* get passed in. But UserDao does not get passed in. That needs to be injected. If it is not injected it means writing your own code to initialize a new EMF based on the persistence.xml file, creating an EM (or UserDAO) from the EMF and then using the EM or DAO to do the validation query. In your code snippet above, where does the userDao object come from that you use? – lostdorje Aug 20 '13 at 02:10
  • Oh - I forgot...further, even if we could inject a userDao, this won't work per the quote at the beginning of my question. "...the lifecycle method of a portable application should not invoke EntityManager or Query operations, access other entity instances, or modify relationships within the same persistence context". This means it is necessary to grab a new entitymanager/session from the EMF for this to work at all. An injected userDao will have an entitymanager injected into it that is the same entitymanager being used for the persist() call that kicked the validation off to begin with. – lostdorje Aug 20 '13 at 02:22
  • "the instance being validated is the User and that does get passed in. But UserDao does not get passed in." - have you actually looked at the code in the answer? I showed you how to pass in the UserDao. As for grabbing a different EMF you can do it in a similar fashion as I showed you above - just inject two different ones in the UserDao and use one for validation only. If you're asking whether to or not to go against the docs - I do not know. – rdcrng Aug 20 '13 at 09:21
  • Yeah sorry...I wasn't clear and wrote my comment too hastily. Passing the UserDao in like this is sort of an interesting trick. But then you've now lost the actual user you're validating. So now I can call userDao.isValidPassword(), but what password am I now validating? That is now lost isn't it? Or am I missing something obvious? And regarding the EMF, unless I want to write 2 separate UserDAO classes injecting separate EMFs, I'm stuck with the single UserDao I've already written which has the same EMF injected into it (all UserDao instances will have the same EMF injected into it). – lostdorje Aug 20 '13 at 11:43
  • Well, you'd have to make sure that you only work with one entity per UserDao and have the password setter write through to the entity, that's why I marked the UserDao with @Stateful instead of @Stateless so you don't loose state between calls. As for different EMFs, can't you inject two different ones in the same EJB, something like `@PersistenceUnit(unitName="PU1") EntityManagerFactory emf1; @PersistenceUnit(unitName="PU2") EntityManagerFactory emf2;`? – rdcrng Aug 20 '13 at 11:50
  • That would require defining the same things twice in the persistence.xml file. ... and . This seems like it's really going down the wrong road. – lostdorje Aug 20 '13 at 11:52
  • Well, like I mentioned above, limitations are limitations and getting around them usually means tedious workarounds. Might as well look into Java EE 7 / CDI 1.1 if you want injection inside validators, see my comment above. :) – rdcrng Aug 20 '13 at 11:55
  • 1
    Right. :-) This gets me back to my 1st question of: "Is it possible I have some sort of design flaw or am misusing Hibernate Validation because I need to query the database during validation?" Previously (5+ years ago I worked in Java EE) I wrote validation the old school way without Hibernate Validation. I might be trying to overuse/abuse Hibernate Validation. Anyway, thanks for all your thoughtful responses and comments. – lostdorje Aug 20 '13 at 12:05
0

Translation please?

The lifecycle events should not use the entity manager since it could lead to regressions. Imagine that during a pre-update event, you modify another entity. This should generate another pre-update event within the previous pre-update event. To avoid such issues, the use of entity manager is discouraged.

But if all you want to do is read some additional data, conceptually there is not problem. Valation happens implicitly in pre-update and pre-insert events.

If you never use post-load events, then reading data in a lifecycle event shouldn't trigger nested lifecycle events. As far as I understand the spec, querying entities is not stricly forbidden but strongly discouraged. In this case it might be fine. Did you try if this works?

So what's the general pattern for getting at an EntityManagerFactory from a Validator?

Injection works only in managed entities. When injection is not possible, you should be able to do a good old lookup to obtain an entity manager. When using the second entity manager, nested lifecycle events might be generated, though. But if you only do something trivial like reading a list of old password, that should be OK.

Community
  • 1
  • 1
ewernli
  • 38,045
  • 5
  • 92
  • 123