You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@tomee.apache.org by rm...@apache.org on 2016/04/21 09:58:59 UTC

tomee git commit: TOMEE-1785 TOMEE-1786 bean validation for jaxrs + ensuring bval cdi filters actually works

Repository: tomee
Updated Branches:
  refs/heads/master ad8b1f92e -> ba36564ba


TOMEE-1785 TOMEE-1786 bean validation for jaxrs + ensuring bval cdi filters actually works


Project: http://git-wip-us.apache.org/repos/asf/tomee/repo
Commit: http://git-wip-us.apache.org/repos/asf/tomee/commit/ba36564b
Tree: http://git-wip-us.apache.org/repos/asf/tomee/tree/ba36564b
Diff: http://git-wip-us.apache.org/repos/asf/tomee/diff/ba36564b

Branch: refs/heads/master
Commit: ba36564badd07fbce0c2ca51864774de28717be5
Parents: ad8b1f9
Author: Romain manni-Bucau <rm...@gmail.com>
Authored: Thu Apr 21 09:58:29 2016 +0200
Committer: Romain manni-Bucau <rm...@gmail.com>
Committed: Thu Apr 21 09:58:29 2016 +0200

----------------------------------------------------------------------
 .../openejb/assembler/classic/Assembler.java    |   6 +-
 .../org/apache/openejb/bval/BValCdiFilter.java  |   7 +-
 .../apache/openejb/log/logger/Log4j2Logger.java |  13 ++
 .../openejb/util/ContainerClassesFilter.java    |   1 +
 .../server/cxf/rs/CxfRsHttpListener.java        | 133 ++++++++++++++-----
 .../openejb/server/cxf/rs/AutoBValTest.java     | 104 +++++++++++++++
 6 files changed, 227 insertions(+), 37 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/tomee/blob/ba36564b/container/openejb-core/src/main/java/org/apache/openejb/assembler/classic/Assembler.java
----------------------------------------------------------------------
diff --git a/container/openejb-core/src/main/java/org/apache/openejb/assembler/classic/Assembler.java b/container/openejb-core/src/main/java/org/apache/openejb/assembler/classic/Assembler.java
index 619dca4..d576fe6 100644
--- a/container/openejb-core/src/main/java/org/apache/openejb/assembler/classic/Assembler.java
+++ b/container/openejb-core/src/main/java/org/apache/openejb/assembler/classic/Assembler.java
@@ -371,7 +371,11 @@ public class Assembler extends AssemblerTool implements org.apache.openejb.spi.A
             try { // bval jars are optional so do it by reflection
                 final ClassLoader loader = ParentClassLoaderFinder.Helper.get();
                 final Object filter = loader.loadClass("org.apache.openejb.bval.BValCdiFilter").newInstance();
-                loader.loadClass("org.apache.bval.cdi.BValExtension").getMethod("setAnnotatedTypeFilter").invoke(null, filter);
+                loader.loadClass("org.apache.bval.cdi.BValExtension")
+                        .getMethod(
+                            "setAnnotatedTypeFilter",
+                            loader.loadClass("org.apache.bval.cdi.BValExtension$AnnotatedTypeFilter"))
+                        .invoke(null, filter);
             } catch (final Throwable th) {
                 // ignore, bval not compatible or not present
             }

http://git-wip-us.apache.org/repos/asf/tomee/blob/ba36564b/container/openejb-core/src/main/java/org/apache/openejb/bval/BValCdiFilter.java
----------------------------------------------------------------------
diff --git a/container/openejb-core/src/main/java/org/apache/openejb/bval/BValCdiFilter.java b/container/openejb-core/src/main/java/org/apache/openejb/bval/BValCdiFilter.java
index 12ca171..a72c73c 100644
--- a/container/openejb-core/src/main/java/org/apache/openejb/bval/BValCdiFilter.java
+++ b/container/openejb-core/src/main/java/org/apache/openejb/bval/BValCdiFilter.java
@@ -26,6 +26,11 @@ public class BValCdiFilter implements BValExtension.AnnotatedTypeFilter {
 
     @Override
     public boolean accept(final AnnotatedType<?> annotatedType) {
-        return delegate.accept(annotatedType.getJavaClass().getName());
+        final String name = annotatedType.getJavaClass().getName();
+        if (name.startsWith("org.apache.openejb.")) {
+            final String sub = name.substring("org.apache.openejb.".length());
+            return !sub.startsWith("cdi.transactional") && !sub.startsWith("resource.activemq.jms2");
+        }
+        return delegate.accept(name);
     }
 }

http://git-wip-us.apache.org/repos/asf/tomee/blob/ba36564b/container/openejb-core/src/main/java/org/apache/openejb/log/logger/Log4j2Logger.java
----------------------------------------------------------------------
diff --git a/container/openejb-core/src/main/java/org/apache/openejb/log/logger/Log4j2Logger.java b/container/openejb-core/src/main/java/org/apache/openejb/log/logger/Log4j2Logger.java
index 60ac420..8ec568c 100644
--- a/container/openejb-core/src/main/java/org/apache/openejb/log/logger/Log4j2Logger.java
+++ b/container/openejb-core/src/main/java/org/apache/openejb/log/logger/Log4j2Logger.java
@@ -19,6 +19,7 @@ package org.apache.openejb.log.logger;
 
 import org.apache.logging.log4j.LogManager;
 import org.apache.logging.log4j.Logger;
+import org.apache.openejb.util.reflection.Reflections;
 
 import java.util.HashMap;
 import java.util.Map;
@@ -50,6 +51,18 @@ public class Log4j2Logger extends AbstractDelegatingLogger {
         log = LogManager.getLogger(name);
     }
 
+    @Override
+    public void setLevel(final Level newLevel) throws SecurityException {
+        try {
+            Reflections.invokeByReflection(
+                    log, "setLevel",
+                    new Class<?>[]{org.apache.logging.log4j.Level.class},
+                    new Object[]{TO_LOG4J.get(newLevel)});
+        } catch (final Throwable ignore) {
+            // no-op
+        }
+    }
+
     public Level getLevel() {
         final org.apache.logging.log4j.Level l = log.getLevel();
         if (l != null) {

http://git-wip-us.apache.org/repos/asf/tomee/blob/ba36564b/container/openejb-core/src/main/java/org/apache/openejb/util/ContainerClassesFilter.java
----------------------------------------------------------------------
diff --git a/container/openejb-core/src/main/java/org/apache/openejb/util/ContainerClassesFilter.java b/container/openejb-core/src/main/java/org/apache/openejb/util/ContainerClassesFilter.java
index 371e8d8..1cde71f 100644
--- a/container/openejb-core/src/main/java/org/apache/openejb/util/ContainerClassesFilter.java
+++ b/container/openejb-core/src/main/java/org/apache/openejb/util/ContainerClassesFilter.java
@@ -42,6 +42,7 @@ public class ContainerClassesFilter implements Filter {
         final Set<String> excluded = new HashSet<>();
         excluded.add("javax");
         excluded.add("org.apache.myfaces");
+        excluded.add("org.apache.bval");
         excluded.add("org.apache.cxf");
         excluded.add("org.apache.oro");
         excluded.add("org.apache.ws");

http://git-wip-us.apache.org/repos/asf/tomee/blob/ba36564b/server/openejb-cxf-rs/src/main/java/org/apache/openejb/server/cxf/rs/CxfRsHttpListener.java
----------------------------------------------------------------------
diff --git a/server/openejb-cxf-rs/src/main/java/org/apache/openejb/server/cxf/rs/CxfRsHttpListener.java b/server/openejb-cxf-rs/src/main/java/org/apache/openejb/server/cxf/rs/CxfRsHttpListener.java
index 1a8d95e..bd2de16 100644
--- a/server/openejb-cxf-rs/src/main/java/org/apache/openejb/server/cxf/rs/CxfRsHttpListener.java
+++ b/server/openejb-cxf-rs/src/main/java/org/apache/openejb/server/cxf/rs/CxfRsHttpListener.java
@@ -23,7 +23,9 @@ import org.apache.cxf.endpoint.EndpointException;
 import org.apache.cxf.endpoint.ManagedEndpoint;
 import org.apache.cxf.endpoint.Server;
 import org.apache.cxf.endpoint.ServerImpl;
+import org.apache.cxf.feature.Feature;
 import org.apache.cxf.helpers.IOUtils;
+import org.apache.cxf.interceptor.Interceptor;
 import org.apache.cxf.jaxrs.JAXRSServerFactoryBean;
 import org.apache.cxf.jaxrs.JAXRSServiceImpl;
 import org.apache.cxf.jaxrs.ext.ResourceComparator;
@@ -41,6 +43,10 @@ import org.apache.cxf.message.Message;
 import org.apache.cxf.service.invoker.Invoker;
 import org.apache.cxf.transport.DestinationFactory;
 import org.apache.cxf.transport.servlet.BaseUrlHelper;
+import org.apache.cxf.validation.BeanValidationFeature;
+import org.apache.cxf.validation.BeanValidationInInterceptor;
+import org.apache.cxf.validation.BeanValidationOutInterceptor;
+import org.apache.cxf.validation.BeanValidationProvider;
 import org.apache.johnzon.jaxrs.WadlDocumentMessageBodyWriter;
 import org.apache.openejb.AppContext;
 import org.apache.openejb.BeanContext;
@@ -146,6 +152,10 @@ public class CxfRsHttpListener implements RsHttpListener {
     private static final Map<String, String> STATIC_CONTENT_TYPES;
     private static final String[] DEFAULT_WELCOME_FILES = new String[]{"/index.html", "/index.htm"};
 
+    // we have proxies etc so we can't really give it to cxf properly,
+    // bval impl supports it (message just uses Object instead of the real instance)
+    private static final Object FAKE_SERVICE_OBJECT = new Object();
+
     private final DestinationFactory transportFactory;
     private final String wildcard;
     private HttpDestination destination;
@@ -321,7 +331,7 @@ public class CxfRsHttpListener implements RsHttpListener {
                            final Collection<Object> additionalProviders,
                            final ServiceConfiguration configuration) {
         deploy(contextRoot, loadedClazz, fullContext, new OpenEJBPerRequestPojoResourceProvider(loader, loadedClazz, injections, context, owbCtx),
-            null, app, null, additionalProviders, configuration, owbCtx);
+                null, app, null, additionalProviders, configuration, owbCtx);
     }
 
     @Override
@@ -334,8 +344,8 @@ public class CxfRsHttpListener implements RsHttpListener {
         final Object proxy = ProxyEJB.subclassProxy(beanContext);
 
         deploy(contextRoot, beanContext.getBeanClass(), fullContext, new NoopResourceProvider(beanContext.getBeanClass(), proxy),
-            proxy, null, new OpenEJBEJBInvoker(Collections.singleton(beanContext)), additionalProviders, configuration,
-            beanContext.getWebBeansContext());
+                proxy, null, new OpenEJBEJBInvoker(Collections.singleton(beanContext)), additionalProviders, configuration,
+                beanContext.getWebBeansContext());
     }
 
     private void deploy(final String contextRoot, final Class<?> clazz, final String address, final ResourceProvider rp, final Object serviceBean,
@@ -471,10 +481,10 @@ public class CxfRsHttpListener implements RsHttpListener {
 
     private static boolean shouldSkipProvider(final String name) {
         return "false".equalsIgnoreCase(SystemInstance.get().getProperty(name + ".activated", "true"))
-            || name.startsWith("org.apache.wink.common.internal.");
+                || name.startsWith("org.apache.wink.common.internal.");
     }
 
-    private static void addMandatoryProviders(final Collection<Object> instances) {
+    private static void addMandatoryProviders(final Collection<Object> instances, final ServiceConfiguration serviceConfiguration) {
         if (!shouldSkipProvider(WadlDocumentMessageBodyWriter.class.getName())) {
             instances.add(new WadlDocumentMessageBodyWriter());
         }
@@ -483,6 +493,19 @@ public class CxfRsHttpListener implements RsHttpListener {
         }
         if (!shouldSkipProvider(ValidationExceptionMapper.class.getName())) {
             instances.add(new ValidationExceptionMapper());
+            final String level = SystemInstance.get()
+                    .getProperty(
+                        "openejb.cxf.rs.bval.log.level",
+                        serviceConfiguration.getProperties().getProperty(CXF_JAXRS_PREFIX + "bval.log.level"));
+            if (level != null) {
+                try {
+                    LogUtils.getL7dLogger(ValidationExceptionMapper.class).setLevel(Level.parse(level));
+                } catch (final UnsupportedOperationException uoe) {
+                    LOGGER.warning("Can't set level " + level + " on " +
+                            "org.apache.cxf.jaxrs.validation.ValidationExceptionMapper logger, " +
+                            "please configure it in your logging framework.");
+                }
+            }
         }
     }
 
@@ -554,7 +577,7 @@ public class CxfRsHttpListener implements RsHttpListener {
                         factory.setResourceProvider(clazz, new NoopResourceProvider(restServiceInfo.context.getBeanClass(), proxy));
                     } else {
                         factory.setResourceProvider(clazz, new OpenEJBPerRequestPojoResourceProvider(
-                            classLoader, clazz, injections, context, owbCtx));
+                                classLoader, clazz, injections, context, owbCtx));
                     }
                 }
             }
@@ -656,9 +679,9 @@ public class CxfRsHttpListener implements RsHttpListener {
             for (final ProviderInfo<?> o : values) { // using getName to not suppose any classloader setup
                 final String name = o.getResourceClass().getName();
                 if ("org.apache.johnzon.jaxrs.ConfigurableJohnzonProvider".equals(name)
-                    || "org.apache.johnzon.jaxrs.jsonb.jaxrs.JsonbJaxrsProvider".equals(name)
-                    // contains in case of proxying
-                    || name.contains("com.fasterxml.jackson.jaxrs.json")) {
+                        || "org.apache.johnzon.jaxrs.jsonb.jaxrs.JsonbJaxrsProvider".equals(name)
+                        // contains in case of proxying
+                        || name.contains("com.fasterxml.jackson.jaxrs.json")) {
                     customJsonProvider = true;
                     break; //  cause we only handle json for now
                 }
@@ -669,7 +692,7 @@ public class CxfRsHttpListener implements RsHttpListener {
                 while (it.hasNext()) {
                     final String name = it.next().getResourceClass().getName();
                     if ("org.apache.johnzon.jaxrs.JohnzonProvider".equals(name) ||
-                        "org.apache.openejb.server.cxf.rs.CxfRSService$TomEEJohnzonProvider".equals(name)) {
+                            "org.apache.openejb.server.cxf.rs.CxfRSService$TomEEJohnzonProvider".equals(name)) {
                         it.remove();
                         break;
                     }
@@ -777,8 +800,8 @@ public class CxfRsHttpListener implements RsHttpListener {
         // effective logging
 
         LOGGER.info("REST Application: " + Logs.forceLength(prefix, addressSize, true) + " -> " +
-            (InternalApplication.class.isInstance(application) && InternalApplication.class.cast(application).getOriginal() != null ?
-            InternalApplication.class.cast(application).getOriginal() : application));
+                (InternalApplication.class.isInstance(application) && InternalApplication.class.cast(application).getOriginal() != null ?
+                        InternalApplication.class.cast(application).getOriginal() : application));
 
         Collections.sort(resourcesToLog);
 
@@ -786,29 +809,29 @@ public class CxfRsHttpListener implements RsHttpListener {
 
             // Init and register MBeans
             final ObjectNameBuilder jmxName = new ObjectNameBuilder("openejb.management")
-                .set("j2eeType", "JAX-RS")
-                .set("J2EEServer", "openejb")
-                .set("J2EEApplication", base)
-                .set("EndpointType", resource.type)
-                .set("name", resource.classname);
+                    .set("j2eeType", "JAX-RS")
+                    .set("J2EEServer", "openejb")
+                    .set("J2EEApplication", base)
+                    .set("EndpointType", resource.type)
+                    .set("name", resource.classname);
 
             final ObjectName jmxObjectName = jmxName.build();
             LocalMBeanServer.registerDynamicWrapperSilently(
-                new RestServiceMBean(resource),
-                jmxObjectName);
+                    new RestServiceMBean(resource),
+                    jmxObjectName);
 
             jmxNames.add(jmxObjectName);
 
             LOGGER.info("     Service URI: "
-                + Logs.forceLength(resource.address, addressSize, true) + " -> "
-                + Logs.forceLength(resource.type, 4, false) + " "
-                + Logs.forceLength(resource.classname, classSize, true));
+                    + Logs.forceLength(resource.address, addressSize, true) + " -> "
+                    + Logs.forceLength(resource.type, 4, false) + " "
+                    + Logs.forceLength(resource.classname, classSize, true));
 
             for (final Logs.LogOperationEndpointInfo log : resource.operations) {
                 LOGGER.info("          "
-                    + Logs.forceLength(log.http, resource.methodSize, false) + " "
-                    + Logs.forceLength(log.address, addressSize, true) + " ->      "
-                    + Logs.forceLength(log.method, resource.methodStrSize, true));
+                        + Logs.forceLength(log.http, resource.methodSize, false) + " "
+                        + Logs.forceLength(log.address, addressSize, true) + " ->      "
+                        + Logs.forceLength(log.method, resource.methodStrSize, true));
             }
 
             resource.operations.clear();
@@ -844,6 +867,46 @@ public class CxfRsHttpListener implements RsHttpListener {
         }
         CxfUtil.configureEndpoint(factory, serviceConfiguration, CXF_JAXRS_PREFIX);
 
+        // activate bval
+        boolean bvalActive = Boolean.parseBoolean(
+                SystemInstance.get().getProperty("openejb.cxf.rs.bval.active",
+                        serviceConfiguration.getProperties().getProperty(CXF_JAXRS_PREFIX + "bval.active", "true")));
+        if (factory.getFeatures() == null && bvalActive) {
+            factory.setFeatures(new ArrayList<Feature>());
+        } else if (bvalActive) { // check we should activate it and user didn't configure it
+            for (final Feature f : factory.getFeatures()) {
+                if (BeanValidationFeature.class.isInstance(f)) {
+                    bvalActive = false;
+                    break;
+                }
+            }
+            for (final Interceptor<?> i : factory.getInInterceptors()) {
+                if (BeanValidationInInterceptor.class.isInstance(i)) {
+                    bvalActive = false;
+                    break;
+                }
+            }
+            for (final Interceptor<?> i : factory.getOutInterceptors()) {
+                if (BeanValidationOutInterceptor.class.isInstance(i)) {
+                    bvalActive = false;
+                    break;
+                }
+            }
+        }
+        if (bvalActive) {
+            final BeanValidationProvider provider = new BeanValidationProvider();
+
+            final BeanValidationInInterceptor in = new BeanValidationInInterceptor();
+            in.setProvider(provider);
+            in.setServiceObject(FAKE_SERVICE_OBJECT);
+            factory.getInInterceptors().add(in);
+
+            final BeanValidationOutInterceptor out = new BeanValidationOutInterceptor();
+            out.setProvider(provider);
+            out.setServiceObject(FAKE_SERVICE_OBJECT);
+            factory.getOutInterceptors().add(out);
+        }
+
         final Collection<ServiceInfo> services = serviceConfiguration.getAvailableServices();
 
         final String staticSubresourceResolution = serviceConfiguration.getProperties().getProperty(CXF_JAXRS_PREFIX + STATIC_SUB_RESOURCE_RESOLUTION_KEY);
@@ -858,7 +921,7 @@ public class CxfRsHttpListener implements RsHttpListener {
                 ResourceComparator instance = (ResourceComparator) ServiceInfos.resolve(services, resourceComparator);
                 if (instance == null) {
                     instance = (ResourceComparator) Thread.currentThread().getContextClassLoader()
-                        .loadClass(resourceComparator).newInstance();
+                            .loadClass(resourceComparator).newInstance();
                 }
                 factory.setResourceComparator(instance);
             } catch (final Exception e) {
@@ -920,11 +983,11 @@ public class CxfRsHttpListener implements RsHttpListener {
         }
 
         if (!ignoreAutoProviders) {
-            addMandatoryProviders(providers);
+            addMandatoryProviders(providers, serviceConfiguration);
         }
 
         SystemInstance.get().fireEvent(new ExtensionProviderRegistration(
-            AppFinder.findAppContextOrWeb(Thread.currentThread().getContextClassLoader(), AppFinder.AppContextTransformer.INSTANCE), providers));
+                AppFinder.findAppContextOrWeb(Thread.currentThread().getContextClassLoader(), AppFinder.AppContextTransformer.INSTANCE), providers));
 
         if (!providers.isEmpty()) {
             factory.setProviders(providers);
@@ -934,7 +997,7 @@ public class CxfRsHttpListener implements RsHttpListener {
     private Comparator<?> findProviderComparator(final ServiceConfiguration serviceConfiguration, final WebBeansContext ctx) {
         final String comparatorKey = CXF_JAXRS_PREFIX + "provider-comparator";
         final String comparatorClass = serviceConfiguration.getProperties()
-            .getProperty(comparatorKey, SystemInstance.get().getProperty(comparatorKey));
+                .getProperty(comparatorKey, SystemInstance.get().getProperty(comparatorKey));
 
         Comparator<Object> comparator = null;
         if (comparatorClass == null) {
@@ -1026,8 +1089,8 @@ public class CxfRsHttpListener implements RsHttpListener {
             final boolean loadersNotNull = classLoader1 != null && classLoader2 != null;
 
             if (classLoader1 != classLoader2
-                && loadersNotNull
-                && !classLoader1.equals(classLoader2) && !classLoader2.equals(classLoader1)) {
+                    && loadersNotNull
+                    && !classLoader1.equals(classLoader2) && !classLoader2.equals(classLoader1)) {
                 if (isParent(classLoader1, classLoader2)) {
                     return 1;
                 }
@@ -1042,9 +1105,9 @@ public class CxfRsHttpListener implements RsHttpListener {
 
                 if (MessageBodyWriter.class.isInstance(o1.getProvider())) {
                     final List<MediaType> types1 =
-                        JAXRSUtils.sortMediaTypes(JAXRSUtils.getProviderProduceTypes(MessageBodyWriter.class.cast(o1.getProvider())), JAXRSUtils.MEDIA_TYPE_QS_PARAM);
+                            JAXRSUtils.sortMediaTypes(JAXRSUtils.getProviderProduceTypes(MessageBodyWriter.class.cast(o1.getProvider())), JAXRSUtils.MEDIA_TYPE_QS_PARAM);
                     final List<MediaType> types2 =
-                        JAXRSUtils.sortMediaTypes(JAXRSUtils.getProviderProduceTypes(MessageBodyWriter.class.cast(o2.getProvider())), JAXRSUtils.MEDIA_TYPE_QS_PARAM);
+                            JAXRSUtils.sortMediaTypes(JAXRSUtils.getProviderProduceTypes(MessageBodyWriter.class.cast(o2.getProvider())), JAXRSUtils.MEDIA_TYPE_QS_PARAM);
 
                     if (types1.contains(MediaType.WILDCARD_TYPE) && !types2.contains(MediaType.WILDCARD_TYPE)) {
                         return 1;
@@ -1059,9 +1122,9 @@ public class CxfRsHttpListener implements RsHttpListener {
                     }
                 } else if (MessageBodyReader.class.isInstance(o1.getProvider())) { // else is not super good but using both is not sa well so let it be for now
                     final List<MediaType> types1 =
-                        JAXRSUtils.sortMediaTypes(JAXRSUtils.getProviderConsumeTypes(MessageBodyReader.class.cast(o1.getProvider())), null);
+                            JAXRSUtils.sortMediaTypes(JAXRSUtils.getProviderConsumeTypes(MessageBodyReader.class.cast(o1.getProvider())), null);
                     final List<MediaType> types2 =
-                        JAXRSUtils.sortMediaTypes(JAXRSUtils.getProviderConsumeTypes(MessageBodyReader.class.cast(o2.getProvider())), null);
+                            JAXRSUtils.sortMediaTypes(JAXRSUtils.getProviderConsumeTypes(MessageBodyReader.class.cast(o2.getProvider())), null);
 
                     if (types1.contains(MediaType.WILDCARD_TYPE) && !types2.contains(MediaType.WILDCARD_TYPE)) {
                         return 1;

http://git-wip-us.apache.org/repos/asf/tomee/blob/ba36564b/server/openejb-cxf-rs/src/test/java/org/apache/openejb/server/cxf/rs/AutoBValTest.java
----------------------------------------------------------------------
diff --git a/server/openejb-cxf-rs/src/test/java/org/apache/openejb/server/cxf/rs/AutoBValTest.java b/server/openejb-cxf-rs/src/test/java/org/apache/openejb/server/cxf/rs/AutoBValTest.java
new file mode 100644
index 0000000..d76fbc7
--- /dev/null
+++ b/server/openejb-cxf-rs/src/test/java/org/apache/openejb/server/cxf/rs/AutoBValTest.java
@@ -0,0 +1,104 @@
+/*
+ *     Licensed to the Apache Software Foundation (ASF) under one or more
+ *     contributor license agreements.  See the NOTICE file distributed with
+ *     this work for additional information regarding copyright ownership.
+ *     The ASF licenses this file to You under the Apache License, Version 2.0
+ *     (the "License"); you may not use this file except in compliance with
+ *     the License.  You may obtain a copy of the License at
+ *
+ *        http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *     Unless required by applicable law or agreed to in writing, software
+ *     distributed under the License is distributed on an "AS IS" BASIS,
+ *     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *     See the License for the specific language governing permissions and
+ *     limitations under the License.
+ */
+package org.apache.openejb.server.cxf.rs;
+
+import org.apache.openejb.junit.ApplicationComposer;
+import org.apache.openejb.testing.Classes;
+import org.apache.openejb.testing.ContainerProperties;
+import org.apache.openejb.testing.EnableServices;
+import org.apache.openejb.testing.RandomPort;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import javax.validation.Valid;
+import javax.validation.constraints.NotNull;
+import javax.ws.rs.Consumes;
+import javax.ws.rs.POST;
+import javax.ws.rs.Path;
+import javax.ws.rs.Produces;
+import javax.ws.rs.client.ClientBuilder;
+import javax.ws.rs.client.Entity;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.Response;
+import java.net.URL;
+
+import static org.junit.Assert.assertEquals;
+
+@EnableServices("jaxrs")
+@Classes(cdi = true, innerClassesAsBean = true)
+@RunWith(ApplicationComposer.class)
+@ContainerProperties(@ContainerProperties.Property(name = "openejb.cxf.rs.bval.log.level", value = "INFO"))
+public class AutoBValTest {
+    @RandomPort("http")
+    private URL base;
+
+    @Test
+    public void passing() {
+        final Payload payload = new Payload();
+        payload.setName("ok");
+        assertEquals(
+                "ok",
+                ClientBuilder.newClient().target(base.toExternalForm()).path("openejb/test").request(MediaType.APPLICATION_JSON_TYPE)
+                        .post(Entity.entity(payload, MediaType.APPLICATION_JSON_TYPE), Payload.class)
+                        .getName());
+    }
+
+    @Test
+    public void inFailing() {
+        final Payload payload = new Payload();
+        assertEquals(
+                Response.Status.BAD_REQUEST.getStatusCode(), // thanks to the mapper
+                ClientBuilder.newClient().target(base.toExternalForm()).path("openejb/test").request(MediaType.APPLICATION_JSON_TYPE)
+                        .post(Entity.entity(payload, MediaType.APPLICATION_JSON_TYPE)).getStatus());
+    }
+
+    @Test
+    public void outFailing() {
+        final Payload payload = new Payload();
+        payload.setName("empty");
+        assertEquals(
+                Response.Status.BAD_REQUEST.getStatusCode(), // thanks to the mapper
+                ClientBuilder.newClient().target(base.toExternalForm()).path("openejb/test").request(MediaType.APPLICATION_JSON_TYPE)
+                        .post(Entity.entity(payload, MediaType.APPLICATION_JSON_TYPE)).getStatus());
+    }
+
+    @Path("test")
+    public static class ValidateMe {
+        @POST
+        @Valid
+        @Consumes(MediaType.APPLICATION_JSON)
+        @Produces(MediaType.APPLICATION_JSON)
+        public Payload get(@Valid final Payload in) {
+            final Payload payload = new Payload();
+            payload.setName("empty".equals(in.name) ? null : in.name);
+            return payload;
+        }
+    }
+
+    public static class Payload {
+        @NotNull
+        private String name;
+
+        public String getName() {
+            return name;
+        }
+
+        public void setName(final String name) {
+            this.name = name;
+        }
+    }
+}