Search code examples
springweb-servicesrestjax-rscxf

Custom exception mapping for JAX-RS Bean validation


I am building a REST web service using Apache CXF, Spring and JAX-RS, where I need to send custom exceptions when input JSON validation fails.

Instead of using business logic, I am trying to use Out-of-the-box bean validation feature of CXF, JAX-RS.

Bean validation works fine, however, it always throws 500 exception, which is less than useful. As per documentation, it is the expected behavior of org.apache.cxf.jaxrs.validation.ValidationExceptionMapper.

I am trying to extend exception mapper so that I can throw different error message, but this custom mapper is not getting called.

Below is my code setup ->

pom.xml

    <!-- Spring Framework -->
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-core</artifactId>
        <version>4.1.4.RELEASE</version>
    </dependency>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-webmvc</artifactId>
        <version>4.1.4.RELEASE</version>
    </dependency>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-context</artifactId>
        <version>4.1.4.RELEASE</version>
    </dependency>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-beans</artifactId>
        <version>4.1.4.RELEASE</version>
    </dependency>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-web</artifactId>
        <version>4.1.4.RELEASE</version>
    </dependency>
    <dependency>
        <groupId>org.springframework.security</groupId>
        <artifactId>spring-security-web</artifactId>
        <version>3.2.7.RELEASE</version>
    </dependency>
    <dependency>
        <groupId>org.springframework.security</groupId>
        <artifactId>spring-security-config</artifactId>
        <version>3.2.7.RELEASE</version>
    </dependency>

    <!-- CXF -->

    <dependency>
        <groupId>org.apache.cxf</groupId>
        <artifactId>cxf-core</artifactId>
        <version>3.0.2</version>
    </dependency>

    <dependency>
        <groupId>org.apache.cxf</groupId>
        <artifactId>cxf-rt-frontend-jaxrs</artifactId>
        <version>3.0.2</version>
    </dependency>

    <dependency>
        <groupId>org.apache.cxf</groupId>
        <artifactId>cxf-bundle-jaxrs</artifactId>
        <version>2.7.5</version>
    </dependency>
    <dependency>
        <groupId>org.codehaus.jackson</groupId>
        <artifactId>jackson-jaxrs</artifactId>
        <version>1.9.3</version>
    </dependency>
    <dependency>
        <groupId>org.codehaus.jackson</groupId>
        <artifactId>jackson-xc</artifactId>
        <version>1.9.3</version>
    </dependency>

    <!-- Hibernate validation -->
    <dependency>
        <groupId>org.hibernate</groupId>
        <artifactId>hibernate-validator</artifactId>
        <version>5.1.3.Final</version>
    </dependency>
    <!-- JSR 303 Validation framework -->
    <dependency>
        <groupId>org.glassfish.jersey.ext</groupId>
        <artifactId>jersey-bean-validation</artifactId>
        <version>2.19</version>
    </dependency>

JAX-RS server

<jaxrs:server address="/" id="iamService">
        <jaxrs:serviceBeans>
            <bean class="com.XXXXXX.iam.service.core.endpoint.impl.IAMIdentityServiceImpl" />
        </jaxrs:serviceBeans>
            <jaxrs:inInterceptors>
        <ref bean="validationInInterceptor" />
    </jaxrs:inInterceptors>
    <jaxrs:outInterceptors>
        <ref bean="validationOutInterceptor" />
        <ref bean="exceptionMapperOutInterceptor" />
    </jaxrs:outInterceptors>
        <jaxrs:extensionMappings>
            <entry key="json" value="application/json" />
        </jaxrs:extensionMappings>
        <jaxrs:providers>
            <ref bean='jsonProvider' />
            <ref bean="ampfExceptionMapper"/>
        </jaxrs:providers>
    </jaxrs:server>

<bean id="ampfExceptionMapper" class="com.xxxxxxx.iam.service.core.interceptor.AmpfValidationExceptionMapper"/>
<bean id="exceptionMapperOutInterceptor" class="org.apache.cxf.jaxrs.interceptor.JAXRSOutExceptionMapperInterceptor"/>

<bean id="validationProvider" class="org.apache.cxf.validation.BeanValidationProvider" />

<bean id="validationInInterceptor" class="org.apache.cxf.jaxrs.validation.JAXRSBeanValidationInInterceptor">
    <property name="provider" ref="validationProvider" />

</bean>


<bean id="validationOutInterceptor" class="org.apache.cxf.jaxrs.validation.JAXRSBeanValidationOutInterceptor">
    <property name="provider" ref="validationProvider" />
</bean> 

Custom Exception mapper

@Provider
@Produces(MediaType.APPLICATION_JSON)
public class AmpfValidationExceptionMapper implements ExceptionMapper<ConstraintViolationException>{



    @Override
    public Response toResponse(ConstraintViolationException exception) {
        System.out.println("Executing Ameriprise Service exception mapping");

        UniqueIDValidationResponse response = new UniqueIDValidationResponse();
        response.setResponse("Invalid request");
        return Response.ok(response).build();
    }


}

Bean class

public class UniqueIDValidationRequest {

/*@Autowired
private Validator validator;*/

@XmlAttribute(name = "ssoId")
@NotBlank
@NotEmpty
@Length(max=20,min=5)
protected String ssoId;

@NotEmpty
@NotBlank
@XmlAttribute(name = "epHashId")
protected String epHashId;

Solution

  • This issue is resolved now. What I had to do is create a custom interceptors. This will catch the fault occurred while bean validation and add actual bean validation error instead. Please check the InInterceptor code below -->

    @Provider
    public class IamServInInterceptor extends
    BeanValidationInInterceptor implements ContainerRequestFilter{
    
    private static Logger logger = LoggerFactory.getLogger(IamServInInterceptor.class);
    
    public IamServInInterceptor() {
        logger.debug("IamServInInterceptor default consturctor called");
    }
    
    public IamServInInterceptor(String phase) {
        super(phase);
        logger.debug("IamServInInterceptor phase : " + phase);
    }
    
    @Override
    public void filter(ContainerRequestContext arg0) throws IOException {
        // TODO Auto-generated method stub
        logger.debug("IamServInInterceptor filter is called");
        InterceptorChain chain = PhaseInterceptorChain.getCurrentMessage()
                .getInterceptorChain();
        chain.add(this);
    }
    
    protected Object getServiceObject(Message message) {
        logger.debug("IamServInInterceptor getServiceObject called");
        return ValidationUtils.getResourceInstance(message);
    }
    
    protected void handleValidation(Message message, Object resourceInstance,
            Method method, List<Object> arguments){
        try {
            logger.debug("IamServInInterceptor handleValidation called");
            super.handleValidation(message, resourceInstance, method, arguments);
        } catch (ConstraintViolationException ex) { 
            logger.debug("IamServInInterceptor handleValidation has some exception");
            UniqueIDValidationResponse response = new UniqueIDValidationResponse();
            response.setResponse("INPUT_VALIDATION_FAILED");
            ConstraintViolationException constraintException = (ConstraintViolationException)ex;
            Set<ConstraintViolation<?>> constraint= 
                    constraintException.getConstraintViolations();
            Iterator<ConstraintViolation<?>> iterator = constraint.iterator();
            while(iterator.hasNext()){
                ConstraintViolation<?> error = iterator.next();
                logger.debug("Error : " + error.toString());
                response.getMessage().add(error.getMessage());
            }
            ex.printStackTrace();
            message.getExchange().put(Response.class, Response.ok(response).build());
        }
    }
    

    }