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.