Play is a web application framework for Java and Scala. SOAP, originally defined as Simple Object Access Protocol, is a specification for exchanging structured information (i.e. sending messages) over computer networks. SOAP is widely supported, a notable implementation for Java (and Scala) is the Apache CXF library.
In this tutorial, we’re going to create a Play application which also acts as a SOAP service and serves requests using
Update: the following tutorial uses Play 2.2.x code and samples. Ported versions exist for Play 2.4.x, 2.3.x and 2.1.x in the play-2.4.x, play-2.3.x and play-2.1.x branches of the repository.
Update: for Play 2.4, use the library version 1.2.1 instead of 1.2.0. For other versions, 1.2.0 is the most up-to-date version available.
Play Framework makes it easy to build web applications with Java & Scala. It has a lightweight, stateless, web-friendly architecture and enables some really neat features, like high scalability or the “Hit refresh workflow”.
SOAP, originally defined as Simple Object Access Protocol, is a specification for exchanging structured information (i.e. sending messages) over computer networks. It relies on other transport protocols, like HTTP (Hypertext Transfer Protocol) or SMTP (Simple Mail Transfer Protocol) for message transmission. SOAP is widely supported, its two notable implementations for Java (and Scala) are the Apache Axis2 and Apache CXF libraries.
In this tutorial, we’re going to create a Play application which also acts as a SOAP service and serves requests using Apache CXF so that our JAX-WS annotated Hello World service “just works”:
// HelloWorld.java package services.hello; import javax.jws.WebService; @WebService public interface HelloWorld { String sayHi(String text); }
// HelloWorldImpl.java package services.hello; import javax.jws.WebService; @WebService(endpointInterface = "services.hello.HelloWorld") public class HelloWorldImpl implements HelloWorld { @Override public String sayHi(String text) { return "Hello " + text; } }
Getting it done
For those in a hurry, here’s what you have to do:
- Create a Hello World application, named play-cxf-hello, using the following command:
play new play-cxf-hello
Alternatively, you can use another, already existing Play application. Note that the play-cxf repository contains working sample applications in the
samples
directory.
Theplay
command is part of Play Framework. If you didn’t set it up yet, you can do so by following its installation guide.
Update: as of Play 2.3, activator replaces play as the command line tool of the framework. It’s backward compatible, so you can just useactivator
instead ofplay
everywhere in the tutorial. - Add the following dependencies to your Hello World application:
libraryDependencies += "org.springframework" % "spring-context" % "4.2.0.RELEASE" libraryDependencies += "eu.imind.play" %% "play-cxf_play22" % "1.2.0" libraryDependencies += "org.apache.cxf" % "cxf-rt-bindings-soap" % "3.1.2" libraryDependencies += "org.apache.cxf" % "cxf-rt-frontend-jaxws" % "3.1.2"
Note that the second line references a Play Framework module which is dependent on the framework version you use. For Play 2.2, the dependency name is
play-cxf_play22
. For Play 2.4, 2.3 and 2.1, useplay-cxf_play24
,play-cxf_play23
andplay-cxf_play21
respectively.
Note: for Play 2.4, you should useplay-cxf_play24
version1.2.1
instead of1.2.0
.
The above snippet uses thebuild.sbt
syntax. The wholebuild.sbt
file should look similar to the following:name := "play-cxf-hello" version := "1.0-SNAPSHOT" libraryDependencies += "org.springframework" % "spring-context" % "4.2.0.RELEASE" libraryDependencies += "eu.imind.play" %% "play-cxf_play22" % "1.2.0" libraryDependencies += "org.apache.cxf" % "cxf-rt-bindings-soap" % "3.1.2" libraryDependencies += "org.apache.cxf" % "cxf-rt-frontend-jaxws" % "3.1.2" play.Project.playScalaSettings
There is a detailed documentation about managing Play Framework dependencies.
- Apache CXF uses Spring for its configuration. First, create a Spring configuration in our Hello World application’s
conf/applicationContext.xml
file:<!-- applicationContext.xml --> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:jaxws="http://cxf.apache.org/jaxws" xsi:schemaLocation=" http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.0.xsd http://cxf.apache.org/jaxws http://cxf.apache.org/schemas/jaxws.xsd "> <!-- Import Apache CXF configuration and Play! transport plugin. --> <import resource="classpath:cxf.xml"/> </beans>
Then create a
Global.scala
file in theapp
directory, with the following contents:// Global.scala import org.springframework.context.support.ClassPathXmlApplicationContext import play.api.{Application, GlobalSettings} object Global extends GlobalSettings { val ctx = new ClassPathXmlApplicationContext("applicationContext.xml") override def onStart(app: Application) { super.onStart(app) ctx.start() } override def onStop(app: Application) { ctx.stop() super.onStop(app) } }
This will initialize the Spring framework when our application starts up. More about the Global object in the Play Framework documentation.
- Add the following lines to the Hello World project’s
conf/routes
file:# Apache CXF controller GET /service/*path org.apache.cxf.transport.play.CxfController.handle(path) POST /service/*path org.apache.cxf.transport.play.CxfController.handle(path)
More about the routes file in its documentation.
- At this point, the application is ready to serve SOAP-based services using JAX-WS. In order to create the example HelloWorld service, place the already listed
HelloWorld.java
andHelloWorldImpl.java
in theapp/services/hello/
directory. After that, configure the service by modifying theconf/applicationContext.xml
file so that it contains the following:<!-- applicationContext.xml --> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:jaxws="http://cxf.apache.org/jaxws" xsi:schemaLocation=" http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.0.xsd http://cxf.apache.org/jaxws http://cxf.apache.org/schemas/jaxws.xsd "> <!-- Import Apache CXF configuration and Play! transport plugin. --> <import resource="classpath:cxf.xml"/> <!-- Define Hello World endpoint. It will be available at http://localhost:9000/service/hello --> <jaxws:endpoint name="helloWorld" transportId="http://schemas.xmlsoap.org/soap/http" address="/service/hello" implementor="services.hello.HelloWorldImpl"/> </beans>
If you want to use another URL prefix instead of
/service/
, take care to modify theroutes
file as well so that the path given there matches the one in the Spring configuration.
More about the JAX-WS Spring configuration in the Apache CXF docs. - That is all! Run the application by issuing the following commands from the console:
cd play-cxf-hello play run
You should be able to access the SOAP service’s WSDL descriptor at
http://localhost:9000/service/hello?wsdl
and use the service with thehttp://localhost:9000/service/hello
endpoint. You can test it using SoapUI.
Try to add another method to the service! You won’t need to recompile or redeploy anything, just download the WSDL descriptor again. The new method should show up instantly.
Frequently Asked Questions
Why so complicated?
The Play Framework has built-in support for creating RESTful web services, but no support for SOAP at all. In order to use SOAP services (and in particular, Apache CXF) with Play Framework, we needed a small Play module which acts as a compatibility layer between the two systems. You may wonder why it’s such a difficult task to set up SOAP with Play when it works effortlessly with just about any other Java or Scala web framework. The answer is in Play Framework’s decision to abandon the Java Servlet API, which serves to be the connecting point between the HTTP transport plugin of Apache CXF and the application container. The solution is to create a custom transport plugin to connect Apache CXF to Play’s Controller API. This transport plugin is what’s in our Play module.
Furthermore, Apache CXF supports a number of configuration methods, a custom CXFServlet
servlet being one of them, using Spring another. The former isn’t supported in Play due to the lack of the Servlet API, so we used the latter.
How to enable WSDL-based validation?
Add the following to your endpoint configuration:
<jaxws:endpoint ... > <jaxws:properties> <entry key="schema-validation-enabled" value="true"/> </jaxws:properties> </jaxws:endpoint>
Note that validation is enabled for your service’s generated responses, too, not just for incoming requests. Currently, if the response doesn’t pass validation, then the service will return nothing and the validation exception gets swallowed, so it can be tricky to find the cause. If you encounter a similar issue, remember to disable validation as a first step to solve it.
I have … issue with the generated WSDL!
Don’t use the Java-first approach for building a service. It’s fine for Hello World examples, but for anything more, create your own WSDL file, then generate the service code from that.
You can then configure the JAX-WS endpoint to use an external WSDL file for the ?wsdl
URL and for validation with the wsdlLocation property.
How to have host-specific endpoint addresses?
Have a look at the MultiEnv sample.
Can I use play-cxf as a SOAP client?
No. This plugin is for creating SOAP services. It’s not needed for calling other SOAP services. All Java or Scala SOAP clients should work out-of-the-box with Play Framework.
Error: type CxfController is not a member of package org.apache.cxf.transport.play
You’re probably using Play 2.4 with its new dependency injection. Please switch the play-cxf_play24
version to 1.2.1
in your project to fix the issue.
What’s next?
For more information about developing a JAX-WS web service (that is: how to develop a slightly more complex HelloWorldImpl class?), refer to the Apache CXF docs about the topic.
In order to learn more about the Play Framework, you can visit its online documentation.
If you have any comments, you can find me at laszlo.kustra@imind.eu You can also follow us for updates on twitter, or facebook.