Providing Injection in Classes built by Java Validation (JSR 303)

1,243 views
Skip to first unread message

Alex Wood

unread,
Apr 1, 2014, 5:56:24 PM4/1/14
to google...@googlegroups.com
Hi,

The web application I work on uses Guice and the JpaPersistModule.  We are also attempting to use JSR 303 Bean Validation with Hibernate Validator.  We would like to provide a custom class (A MessageInterpolator) to look up messages based on the Accept-Language header of the user's request.  This customization appears to be a common one [1].  We have a Guice Provider that reads the locale off the ServletRequest and returns the correct resource bundle.  However, I cannot figure out how to have Guice successfully inject the Provider into the MessageInterpolator.  The class of the MessageInterpolator is defined in META-INF/validation.xml and as far as I can tell, the creation of the ValidatorFactory class (and the included MessageInterpolator) occurs within a Hibernate Event Listener that can be defined in persistence.xml [2].

Does anyone have any experience with getting @Injects to work on items created by JPA as it is getting started?  I'd either like to inject dependencies directly into our own MessageInterpolator or create our own event listener, but I'm not sure how to get injections run on the listener.  The JpaPersistModule is final so I don't have the option of binding our event listener in it.

[1] https://community.jboss.org/wiki/HowToInterpolateMessagesUsingTheClientLocale
[2] http://docs.jboss.org/hibernate/validator/4.3/reference/en-US/html_single/#d0e3096
--------
Regards,
Alex

Moandji Ezana

unread,
Apr 2, 2014, 2:08:42 AM4/2/14
to google...@googlegroups.com

You might be able to use your own Guice-aware interpolator. I haven't tried to code this, so I may be totally wrong, but as you don't control object instantiation, you might need to make the Provider an ugly static field somewhere.

Alternatively, you could configure the Validator and (perhaps) EntityManagerFactory programmatically.

The last thing I can think of is that the JPA 2 spec provides a mechanism for this. Any injection it uses would probably be based on CDI, though.

Also, Guice-Persist is long-unmaintained and riddled with bugs, I wouldn't recommend using it.

--
You received this message because you are subscribed to the Google Groups "google-guice" group.
To unsubscribe from this group and stop receiving emails from it, send an email to google-guice...@googlegroups.com.
To post to this group, send email to google...@googlegroups.com.
Visit this group at http://groups.google.com/group/google-guice.
For more options, visit https://groups.google.com/d/optout.

Stephan Classen

unread,
Apr 2, 2014, 7:53:41 AM4/2/14
to google...@googlegroups.com
We do it like this, but i don't understand fully why it is working :)


class SomeGuiceModule {

    protected void configure() {
        bind(Valdiator.class).to(ValidatorProvider.class);
    }

}



/**
 * This provider returns a validator for Hibernate validation annotations.
 */
@Singleton
public class ValidatorProvider implements Provider<Validator>, Serializable {

    private static final long serialVersionUID = 1L;

    private final Validator validator;

    /**
     * Constructor.
     */
    public ValidatorProvider() {
        final ValidatorFactory validatorFactory = Validation.byDefaultProvider()
                .configure()
                .messageInterpolator(new CustomMessageInterpolator())
                .constraintValidatorFactory(new InjectingConstraintValidatorFactory())
                .buildValidatorFactory();
        validator = validatorFactory.getValidator();
    }

    /**
     * @return a validator instance.
     */
    @Override
    public Validator get() {
        return validator;
    }

    private static class InjectingConstraintValidatorFactory implements ConstraintValidatorFactory {

        @Override
        public final <T extends ConstraintValidator<?, ?>> T getInstance(Class<T> key) {
            final T constraintValidator = ReflectionHelper.newInstance(key, "ConstraintValidator");
            InjectorHolder.getInjector().injectMembers(constraintValidator);
            return constraintValidator;
        }
    }

    private static class CustomMessageInterpolator implements MessageInterpolator {

        /**
         * {@inheritDoc}
         */
        @Override
        public String interpolate(String messageTemplate, Context context) {
            return getLocalizedValue(messageTemplate);
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public String interpolate(String messageTemplate, Context context, Locale locale) {
            return getLocalizedValue(messageTemplate);
        }

        private String getLocalizedValue(String messageTemplate) {
            // Get rid of braces: {javax.validation.constraints.NotNull.message} --> javax.validation.constraints.NotNull.message
            final String messageTemplateWithoutBraces = messageTemplate.substring(1, messageTemplate.length() - 1);
            return Localizer.get().getString(messageTemplateWithoutBraces, null);

Moandji Ezana

unread,
Apr 2, 2014, 8:18:47 AM4/2/14
to google...@googlegroups.com

Yes, thats's programmatic configuration. Does it integrate with JPA2 event listeners?

Alex Wood

unread,
Apr 3, 2014, 9:50:11 AM4/3/14
to google...@googlegroups.com

You might be able to use your own Guice-aware interpolator. I haven't tried to code this, so I may be totally wrong, but as you don't control object instantiation, you might need to make the Provider an ugly static field somewhere.

Alternatively, you could configure the Validator and (perhaps) EntityManagerFactory programmatically.

The last thing I can think of is that the JPA 2 spec provides a mechanism for this. Any injection it uses would probably be based on CDI, though.

Just for posterity's sake, here's the solution I came up with.  In the servlet initialization immediately after we get the Injector, I get the EntityManagerFactory, open it up, and add my listeners.  It's not too pretty, but it does work.

    private void insertValidationEventListeners(Injector injector) {
        javax.inject.Provider<EntityManagerFactory> emfProvider =
            injector.getProvider(EntityManagerFactory.class);
        HibernateEntityManagerFactory hibernateEntityManagerFactory =
            (HibernateEntityManagerFactory) emfProvider.get();
        SessionFactoryImpl sessionFactoryImpl =
            (SessionFactoryImpl) hibernateEntityManagerFactory.getSessionFactory();
        EventListenerRegistry registry =
            sessionFactoryImpl.getServiceRegistry().getService(EventListenerRegistry.class);

        javax.inject.Provider<BeanValidationEventListener> listenerProvider =
            injector.getProvider(BeanValidationEventListener.class);
        registry.getEventListenerGroup(EventType.PRE_INSERT).appendListener(listenerProvider.get());
        registry.getEventListenerGroup(EventType.PRE_UPDATE).appendListener(listenerProvider.get());
        registry.getEventListenerGroup(EventType.PRE_DELETE).appendListener(listenerProvider.get());
    }

and then my listener provider is

public class ValidationListenerProvider implements Provider<BeanValidationEventListener> {
             
    private ValidatorFactory factory;                                                                                  
    private Properties properties; 
             
    @Inject  
    public ValidationListenerProvider(ValidatorFactory factory,                                                        
        @Named("ValidationProperties") Properties properties) {
        this.factory = factory;
        this.properties = properties;  
    }        

    @Override
    public BeanValidationEventListener get() {                                                                         
        return new BeanValidationEventListener(factory, properties);
    }        
}
Reply all
Reply to author
Forward
0 new messages