CXF REST & SOAP Exception Handling Together
CXF REST & SOAP Exception Handling Together explains about how to handle an Exception In CXF REST & SOAP Services together without changing the logic
Apache CXF & is a free and open source project, and a fully featured Webservice framework. It helps you building webservices using different front-end API's, like as JAX-RS (restful based services) and JAX-WS (WSDL based services).
By using following configuration, You can handle exceptions for both restful / JAX-RS (Restful) and JAX-WS (SOAP) services without any code change and helps you to re-use the same business logic
You can see the below example, which is demonstrate about how to handle an Exception in CXF REST & SOAP together
Required Libraries
You need to download
Following jar must be in classpath
- aopalliance-1.0.jar
- commons-logging-1.1.1.jar
- cxf-2.7.3.jar
- httpasyncclient-4.0-beta3.jar
- httpclient-4.2.1.jar
- httpcore-4.2.2.jar
- httpcore-nio-4.2.2.jar
- neethi-3.0.2.jar
- spring-aop-3.0.7.RELEASE.jar
- spring-asm-3.0.7.RELEASE.jar
- spring-beans-3.0.7.RELEASE.jar
- spring-context-3.0.7.RELEASE.jar
- spring-core-3.0.7.RELEASE.jar
- spring-expression-3.0.7.RELEASE.jar
- spring-web-3.0.7.RELEASE.jar
- wsdl4j-1.6.2.jar
- jaxb-impl-2.2.6.jar
- javax.ws.rs-api-2.0-m10.jar
- jettison-1.3.2.jar (JSON library)
- xmlschema-core-2.0.3.jar
CXF REST & SOAP Exception Handling
I am creating a sample restful and JAX-WS service that pass Student object and return with some changes on that object. The service is using simple POJO (Plain Old Java Object) bean.
Firstly create a Dynamic Web Project (File->New->Dynamic Web Project) named "CXFRestAndSoap" according to following screenshot
Create a Student Object
import javax.xml.bind.annotation.XmlRootElement;
@XmlRootElement(name = "Student")
public class Student {
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
Here @XmlRootElement(name = "Student"), is a JAXB convention specifies that Student is XML document.
If you are specifies that @Produces("application/json") then Jettison library converts the JAXB to json text as response
Create a Service Interface
This service interface will defines which methods should be exposed, to be invoked by the client
@WebService annotation specified that this is a JAX-WS operation
import javax.jws.WebService;
@WebService
public interface ChangeStudentDetails {
Student changeName(Student student);
}
Create a ServiceExceptionDetails
This class holds the exception details for a service
import java.io.Serializable;
import javax.xml.bind.annotation.XmlRootElement;
@XmlRootElement(name = "ServiceExceptionDetails")
public class ServiceExceptionDetails implements Serializable {
private String faultCode;
private String faultMessage;
public ServiceExceptionDetails() {
}
public String getFaultCode() {
return faultCode;
}
public void setFaultCode(String faultCode) {
this.faultCode = faultCode;
}
public String getFaultMessage() {
return faultMessage;
}
public void setFaultMessage(String faultMessage) {
this.faultMessage = faultMessage;
}
}
Create a ServiceException
This class contains an array of ServiceExceptionDetails(if more than one exceptions occurred)
import java.io.Serializable;
import javax.xml.bind.annotation.XmlRootElement;
@XmlRootElement(name = "ServiceException")
public class ServiceException extends Exception implements Serializable {
private ServiceExceptionDetails faultDetails[];
public ServiceException(ServiceExceptionDetails faultDetails[]) {
this.faultDetails = faultDetails;
}
public ServiceException(String message, ServiceExceptionDetails faultDetails[]) {
super(message);
this.faultDetails = faultDetails;
}
public ServiceExceptionDetails[] getFaultDetails() {
return faultDetails;
}
}
Implement the Service Interface
Here we implement the service interface created on the previous step
Here we are using one example showing with GET method& another with POST method
GET---> Calling this method will not result any changes to the server
POST---> Calling this method will result changes to the server, This have more secure than GET method
import javax.jws.WebService;
import javax.servlet.http.HttpServletRequest;
import javax.ws.rs.Consumes;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.WebApplicationException;
import javax.ws.rs.core.Response;
import org.apache.cxf.message.Message;
import org.apache.cxf.phase.PhaseInterceptorChain;
@Consumes("application/json")
@Produces("application/json")
@WebService(endpointInterface = "com.student.ChangeStudentDetails")
public class ChangeStudentDetailsImpl implements ChangeStudentDetails {
@POST
@Path("/changeName")
public Student changeName(Student student) throws ServiceException {
if (student.getName().equals("Rockey")) {
student.setName((new StringBuilder("HELLO ")).append(student.getName()).toString());
return student;
} else {
ServiceExceptionDetails serviceFaultDetailArray[] = new ServiceExceptionDetails[1];
ServiceExceptionDetails serviceExceptionDetails = new ServiceExceptionDetails();
serviceExceptionDetails.setFaultCode("100");
serviceExceptionDetails.setFaultMessage("Student Name is not correct");
serviceFaultDetailArray[0] = serviceExceptionDetails;
throwException(new ServiceException(serviceFaultDetailArray));
return null;
}
}
// You can move this method into some utility class so that you can reuse for all the services
public static void throwException(ServiceException serviceException) throws ServiceException {
Message message = PhaseInterceptorChain.getCurrentMessage();
HttpServletRequest servletRequest = (HttpServletRequest) message.get("HTTP.REQUEST");
if (servletRequest.getContentType().equals("application/json")) {
javax.ws.rs.core.Response.ResponseBuilder builder = Response.status
(javax.ws.rs.core.Response.Status.NOT_ACCEPTABLE);
builder.type("application/json");
builder.entity(serviceException.getFaultDetails());
throw new WebApplicationException(builder.build());
} else {
throw new ServiceException("Fault Message", serviceException.getFaultDetails());
}
}
}
Note; On the above ChangeStudentDetailsImpl class, implementing an interface is not necessity, you can create restful services without implementing an interface.
@Consumes annotation specifies, the request is coming from the client
you can specify the Mime type as @Consumes("application/xml"), if the request is in xml format
@Produces annotation specifies, the response is going to the client
you can specify the Mime type as @Produces ("application/xml"), if the response need to be in xml format
cxf.xml
CXF is using Spring internally, Finding classes by spring we need to add service implementation beans are added on "jaxrs:serviceBeans".
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:cxf="http://cxf.apache.org/core" xmlns:jaxrs="http://cxf.apache.org/jaxrs" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:jaxws="http://cxf.apache.org/jaxws" xsi:schemaLocation="http://cxf.apache.org/core http://cxf.apache.org/schemas/core.xsd http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://cxf.apache.org/jaxrs http://cxf.apache.org/schemas/jaxrs.xsd http://cxf.apache.org/jaxws http://cxf.apache.org/schemas/jaxws.xsd"> <import resource="classpath:META-INF/cxf/cxf.xml" /> <bean class="com.student.ChangeStudentDetailsImpl" id="StudentService" /> <jaxrs:server address="/rest" id="base"> <jaxrs:serviceBeans> <ref bean="StudentService" /> </jaxrs:serviceBeans> </jaxrs:server> <jaxws:endpoint address="/ChangeStudent" id="changeStudent" implementor="com.student.ChangeStudentDetailsImpl" /> </beans>
web.xml
Change the web.xml file to find CXF servlet and cxf.xml
<web-app version="2.5" xmlns="http://java.sun.com/xml/ns/javaee" xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemalocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"> <context-param> <param-name>contextConfigLocation</param-name> <param-value>WEB-INF/cxf.xml</param-value> </context-param> <listener> <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class> </listener> <servlet> <servlet-name>CXFServlet</servlet-name> <servlet-class>org.apache.cxf.transport.servlet.CXFServlet</servlet-class> <load-on-startup>1</load-on-startup> </servlet> <servlet-mapping> <servlet-name>CXFServlet</servlet-name> <url-pattern>/*</url-pattern> </servlet-mapping> </web-app>
Publishing CXF Services
Deployed CXF Services
On the below screenshot, you can see that JAX-WS and JAX-RS successfully deployed
You need to change the context path to CXFRestAndSoap before calling the below clients
Run Restful Client
you can see CXF Restful Client in order to run this restful service
When you invoke the above client with student name as Rockey, you will get the correct response
Output
Response From Server {"Student":{"name":"HELLO Rockey"}}
When you invoke the above client with student name other than Rockey, you will get an ServiceExceptionDetails
Output
Error From Server {"ServiceExceptionDetails":[{"faultCode":100,"faultMessage":"Student Name is not correct"}]}
Run SOAP Client
you need to generate proxy for running this service, see Java wsimport Tool Example in order to generate proxy for this service
import java.util.List;
import com.student.ChangeStudentDetails;
import com.student.ChangeStudentDetailsImplService;
import com.student.ServiceExceptionDetails;
import com.student.ServiceException_Exception;
import com.student.Student;
//CXF REST & SOAP Exception Handling
public class Main {
public static void main(String[] args) {
ChangeStudentDetailsImplService service = new ChangeStudentDetailsImplService();
ChangeStudentDetails changeStudentDetailsImplPort = service.getChangeStudentDetailsImplPort();
Student student = new Student();
student.setName("Rockey");
try {
student = changeStudentDetailsImplPort.changeName(student);
System.out.println(student.getName());
} catch (ServiceException_Exception e) {
List faultDetails = e.getFaultInfo().getFaultDetails();
for (ServiceExceptionDetails serviceExceptionDetails : faultDetails) {
System.out.println("Fault code = " + serviceExceptionDetails.getFaultCode() + "\nFault message = "
+ serviceExceptionDetails.getFaultMessage());
}
}
}
}
When you invoke the above client with student name as Rockey, you will get the correct response
Output
HELLO Rockey
When you invoke the above client with student name other than Rockey, you will get a ServiceExceptionDetails
Output
Fault code = 100 Fault message = Student Name is not correct