Hibernate Books

All times are UTC - 5 hours [ DST ]



Post new topic Reply to topic  [ 3 posts ] 
Author Message
 Post subject: Review gnarly bits implementing HV in Karaf/PaxExam
PostPosted: Fri Jun 09, 2017 1:58 pm 
Regular
Regular

Joined: Mon Aug 07, 2006 6:22 pm
Posts: 66
With help from the docs and the HV maintainers, I've been able to get my HV implementation working in Karaf/PaxExam. However, I'm not really happy about the odd "gnarly bits" that were required to get this working. I'd like to review here all the things I had to do to get this working, in the hopes of clarifying or simplifying part of it.

This is the block that creates the factory:
Code:
        factory =
                Validation.
                byProvider(HibernateValidator.class).
                providerResolver(new HibernateValidatorProviderResolver()).
                configure().
                externalClassLoader(getClass().getClassLoader()).
                messageInterpolator(new ResourceBundleMessageInterpolator(
                        new PlatformResourceBundleLocator(ResourceBundleMessageInterpolator.USER_VALIDATION_MESSAGES),
                        true, buildExpressionFactory())).
                buildValidatorFactory();


Here's the "buildExpressionFactory" method:
Code:
    private ExpressionFactory buildExpressionFactory() {
        ClassLoader oldTccl = Thread.currentThread().getContextClassLoader();
        CompositeClassLoader    ccl = new CompositeClassLoader();
        ccl.add(ExpressionFactoryImpl.class.getClassLoader());
        Thread.currentThread().setContextClassLoader(ccl);
        try {
            return ExpressionFactory.newInstance();
        }
        finally {
            Thread.currentThread().setContextClassLoader( oldTccl );
        }
    }


Here's my simple provider resolver:
Code:
public class HibernateValidatorProviderResolver implements ValidationProviderResolver {
    @Override
    public List<ValidationProvider<?>> getValidationProviders() { return Collections.singletonList(new HibernateValidator()); }
}

And here's the "CompositeClassLoader":
Code:
public class CompositeClassLoader extends ClassLoader {
    private final List<ClassLoader> classLoaders = Collections.synchronizedList(new ArrayList<>());

    public CompositeClassLoader() {
        add(Object.class.getClassLoader()); // bootstrap loader.
        add(getClass().getClassLoader()); // whichever classloader loaded this jar.
    }

    public void add(ClassLoader classLoader) {
        if (classLoader != null) {
            classLoaders.add(0, classLoader);
        }
    }

    @Override
    public Class<?> loadClass(String name) throws ClassNotFoundException {
        for (Iterator<?> iterator = classLoaders.iterator(); iterator.hasNext();) {
            ClassLoader classLoader = (ClassLoader) iterator.next();
            try {
                return classLoader.loadClass(name);
            } catch (ClassNotFoundException notFound) {
                // ok.. try another one
            }
        }
        // One last try - the context class loader associated with the current thread. Often used in j2ee servers.
        // Note: The contextClassLoader cannot be added to the classLoaders list up front as the thread that constructs
        // XStream is potentially different to thread that uses it.
        ClassLoader contextClassLoader = Thread.currentThread().getContextClassLoader();
        if (contextClassLoader != null) {
            return contextClassLoader.loadClass(name);
        }
        throw new ClassNotFoundException(name);
    }
}


When I first implemented this and verified the functionality with unit tests, the required setup was much simpler. All of the odd stuff became necessary for running in Karaf/PaxExam.

I really need to understand more about these required pieces, and whether any of this could be simplified. It all works, but I'm concerned that there's too much magic here that I don't understand. I'm particularly concerned about the classloader manipulations, and somewhat less so about the "messageInterpolator" setting.


Top
 Profile  
 
 Post subject: Re: Review gnarly bits implementing HV in Karaf/PaxExam
PostPosted: Wed Jun 14, 2017 3:16 am 
Hibernate Team
Hibernate Team

Joined: Sat Jan 24, 2009 12:46 pm
Posts: 385
Hi,

It looks largely alright, only the set-up relating to the expression factory seems overly complex.

The composite class loader shouldn't be needed really, instead the expression factory should be retrievable by just setting the TCCL to your module's class loader (provided you import the javax.el package and the JAR with the coordinates org.glassfish:javax.el:3.0.1-b08 is present as a bundle). In fact it shouldn't even be needed to configure the expression factory yourself at all, i.e. you should be able to omit the entire specification of the message interpolator. HV will then obtain the expression factory itself (again, provided that this EL JAR is present).

I.e. the following should be enough:

Code:
Validator validator = Validation.byProvider( HibernateValidator.class )
      .providerResolver( new MyValidationProviderResolver() )
      .configure()
      .externalClassLoader( getClass().getClassLoader() )
      .buildValidatorFactory()
      .getValidator();


If this doesn't work, my guess would be that you have another EL version in use (it used to be two JARs, one for API, one for impl), in which case I'd recommend to try the one above. If you don't need EL at all (because you don't use EL in your validation messages, note though that this is the case for @DecimalMax/@DecimalMin by default), you also could use the org.hibernate.validator.messageinterpolation.ParameterMessageInterpolator which doesn't require the EL dependencies.

Hth,

--Gunnar

_________________
Visit my blog at http://musingsofaprogrammingaddict.blogspot.com/


Top
 Profile  
 
 Post subject: Re: Review gnarly bits implementing HV in Karaf/PaxExam
PostPosted: Mon Jun 26, 2017 12:49 pm 
Regular
Regular

Joined: Mon Aug 07, 2006 6:22 pm
Posts: 66
I don't know what's going on here, but this simplestdoesn't work. I get the following:
Code:
Caused by: java.lang.ClassNotFoundException: com.sun.el.ExpressionFactoryImpl not found by org.hibernate.validator [221]
   at org.apache.felix.framework.BundleWiringImpl.findClassOrResourceByDelegation(BundleWiringImpl.java:1574)
   at org.apache.felix.framework.BundleWiringImpl.access$400(BundleWiringImpl.java:79)
   at org.apache.felix.framework.BundleWiringImpl$BundleClassLoader.loadClass(BundleWiringImpl.java:2018)
   at java.lang.ClassLoader.loadClass(Unknown Source)[:1.8.0_102]
   at javax.el.FactoryFinder.newInstance(FactoryFinder.java:87)


I want to allow for the possibility of using the EL in our messages.

I get the same exception with the "intermediate" solution using a "buildExpressionFactory" method like this:
Code:
    private ExpressionFactory buildExpressionFactory() {
        ClassLoader oldTccl = Thread.currentThread().getContextClassLoader();
//        CompositeClassLoader    ccl = new CompositeClassLoader();
//        ccl.add(ExpressionFactoryImpl.class.getClassLoader());
//        Thread.currentThread().setContextClassLoader(ccl);
        Thread.currentThread().setContextClassLoader(this.getClass().getClassLoader());
        try {
            return ExpressionFactory.newInstance();
        }
        finally {
            Thread.currentThread().setContextClassLoader( oldTccl );
        }
    }


I verified again that my "full" workaround as described in the original post still works.

However, while fiddling with this, I accidentally discovered another combination that also works, although it's a little nonsensical, but it likely provides an important clue.

This is another version of the method that also results in my karaf tests working:
Code:
    private ExpressionFactory buildExpressionFactory() {
        ClassLoader oldTccl = Thread.currentThread().getContextClassLoader();
        Class clz = ExpressionFactoryImpl.class; // this line
//        CompositeClassLoader    ccl = new CompositeClassLoader();
//        ccl.add(ExpressionFactoryImpl.class.getClassLoader());
//        Thread.currentThread().setContextClassLoader(ccl);
        Thread.currentThread().setContextClassLoader(this.getClass().getClassLoader());
        try {
            return ExpressionFactory.newInstance();
        }
        finally {
            Thread.currentThread().setContextClassLoader( oldTccl );
        }
    }

As you can see, the "this line" marker is the odd line here. I'm simply causing the ExpressionFactoryImpl class to load by referencing the class object. Based on this, what would be a more sensible thing for this method to do?


Top
 Profile  
 
Display posts from previous:  Sort by  
Post new topic Reply to topic  [ 3 posts ] 

All times are UTC - 5 hours [ DST ]


You cannot post new topics in this forum
You cannot reply to topics in this forum
You cannot edit your posts in this forum
You cannot delete your posts in this forum

Search for:
cron
© Copyright 2014, Red Hat Inc. All rights reserved. JBoss and Hibernate are registered trademarks and servicemarks of Red Hat, Inc.