Library Categories
Popular Content
|
RESTful Web Services with Apache Axis2In this tutorial, Keith Chapman looks at implementing a REST service in Apache Axis2. IntroductionDoes Apache Axis2 support REST? This is a question that's been asked over and over on the Axis2 mailing List. While some think that Axis2 supports REST, others argue that it doesn't. What I refer to here as REST support is support for truly RESTful services, not the Axis2 default of http://localhost:8080/axis2/services/<serviceName>/<operationName>. My stand is that that Axis2 supports truly RESTful services. This tutorial will take you through the steps needed to write a truly RESTful service in Axis2. First, let me give you an introduction to the terms we will be looking at, in this tutorial. What is REST?REST, an acronym for Representational State Transfer is a term coined by Roy Fielding in his Ph.D. dissertation[1] to describe an architecture style of networked systems. REST is built upon the correct use of the HTTP verbs and unique URIs that identify a resource. The most common HTTP verbs used are GET, POST, PUT and DELETE and they are often compared to CRUD (create, Read, Update and Delete) operations. The following is an approximate mapping for the above HTTP verbs to CRUD operations.
WSDL 2.0 and RESTThe WSDL 2.0 HTTP Binding[2] introduces a clean approach to describe REST services in a standard way. It is a fact that some REST fanatics do not like having a contract for RESTful services but here are some of advantages of having a contract:
Whether ot not REST needs a description language is an ongoing debate[3]. The REST support in Axis2 is powered by the WSDL 2.0 HTTP Binding. The WSDL 2.0 HTTP binding allows users to control the following:
RESTful Services in Axis2In order to write truly RESTful services in Axis2, users need to use WSDL 2.0 deployment (as of the 1.4.1 release of Axis2). Plans are under way to provide the ability to control RESTful properties when POJO (Plain Old Java Object) deployment is used as well. Please refer the section on future directions for more details. Writing a WSDL 2.0 from scratch could be a daunting task for users who are not too familiar with WSDL. The rest of the tutorial, will take you down an alternative path where we write our code first and then use Axis2 java2wsdl tool to generate a WSDL for us. We will then tinker the generated WSDL to suit our needs. (Alternatively, if you are brave enough, you could author your WSDL 2.0 by hand and then use the wsdl2java tool of axis2 to generate the code). Simple Example - Student ServiceLet's take a simple scenario, where I am required to expose some student details in a RESTful manner. This service will be able to add new students, update existing students details, delete student details and List details of existing students. So this service will essentially have CRUD operations. Business Logic of The Sample Service - StudentService Classpackage org.apache.axis2; import org.apache.axis2.context.MessageContext; import org.apache.axis2.engine.AxisConfiguration; import org.apache.axis2.description.TransportInDescription; import org.apache.axis2.addressing.EndpointReference; import java.util.HashMap; import java.util.Map; import java.util.Iterator; public class StudentService { private Map<String, Student> map = new HashMap<String, Student>(); private String baseURL = null; public String[] getStudents() { int size = map.size(); String[] students = new String[size]; Iterator<String> iterator = map.keySet().iterator(); int i = 0; while (iterator.hasNext()) { String studentName = iterator.next(); students[i] = getBaseURL() + "student/" + studentName; i++; } return students; } public Student getStudent(String name) throws StudentNotFoundException { Student student = map.get(name); if (student == null) { throw new StudentNotFoundException("Details of student " + name + " cannot be found."); } return student; } public String addStudent(Student student) throws StudentAlreadyExistsException { String name = student.getName(); if (map.get(name) != null) { throw new StudentAlreadyExistsException("Cannot add details of student " + name + ". Details of student " + name + " already exists."); } map.put(name, student); return getBaseURL() + "student/" + name; } public String updateStudent(Student student) throws StudentNotFoundException { String name = student.getName(); if (map.get(name) == null) { throw new StudentNotFoundException("Details of student " + name + " cannot be found."); } map.put(name, student); return getBaseURL() + "student/" + name; } public void deleteStudent(String name) throws StudentNotFoundException { if (map.get(name) == null) { throw new StudentNotFoundException("Details of student " + name + " cannot be found."); } map.remove(name); } // This method attempts to get the Base URI for this service. This will be used to construct // the URIs for the various detsila returned. private String getBaseURL() { if (baseURL == null) { MessageContext messageContext = MessageContext.getCurrentMessageContext(); AxisConfiguration configuration = messageContext .getConfigurationContext().getAxisConfiguration(); TransportInDescription inDescription = configuration.getTransportIn("http"); try { EndpointReference[] eprs = inDescription.getReceiver() .getEPRsForService(messageContext.getAxisService().getName(), null); baseURL = eprs[0].getAddress(); } catch (AxisFault axisFault) { } } return baseURL; } } This simple service keeps student details in a HashMap with the student name as the key. As student details are kept in a HashMap, we have to make sure that this service is deployed in an application scope. Student details are held in the following student bean, which contains three properties including name ( a String), age (an integer) and subjects (an array of Strings). In order to make things interesting, we will add a couple of custom exception classes as well. The StudentNotFoundException class (thrown when details of a student does not exist) and the StudentAlreadyExistsException class (thrown when duplicate details are attempted). Student classpackage org.apache.axis2; public class Student { private String name; private int age; private String[] subjects; public String getName() { return name; } public void setName(String name) { this.name = name; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } public String[] getSubjects() { return subjects; } public void setSubjects(String[] subjects) { this.subjects = subjects; } } StudentNotFoundException class
StudentAlreadyExistsException class
Service Descriptor - Services.xml<serviceGroup> <service name="StudentService" scope="application"> <parameter name="ServiceClass">org.apache.axis2.StudentService</parameter> <messageReceivers> <messageReceiver mep="http://www.w3.org/ns/wsdl/in-only" class="org.apache.axis2.rpc.receivers.RPCInOnlyMessageReceiver"/> <messageReceiver mep="http://www.w3.org/ns/wsdl/in-out" class="org.apache.axis2.rpc.receivers.RPCMessageReceiver"/> </messageReceivers> </service> </serviceGroup> The services.xml (service descriptor for a Axis2 Service) simply states the ServiceClass and and the fact that this application is deployed in application scope (remember we keep all the student information in a HashMap, so we want just one instance of the service class to be created). It also states which message receivers will be used for for the two message exchange patterns we will be using. Our application contains four operations, which use an in-out (request-response) message exchange pattern and a single operation that uses a in-only (request only) message exchange pattern. Try Deploying this ServiceStructure of the Service Archive file If you were to package this service up in a .aar file (the structure of this archive will be as given above) and deploy it, Axis2 would make all operations available at <serviceName>/<operationName> by default. Which means, you will be able to perform a http GET on http://localhost:8080/axis2/services/StudentService/getStudent?name=keith. Now, this is not truly REST as you can clearly see that you have no control over the URL the operation was exposed under. Also, the only way you can pass in parameters to this service is via query parameters. Is there a way that we can change the URl where this operation is exposed at? Even send parameters into this service in the path segment of the URI? You sure can and that's where WSDL 2.0 comes into play. Let's have a look at how this could be achieved using WSDL 2.0. In Order To Be RESTful What Should Be My URLs and HTTP Verbs?The table below lists a brief approximation as to how CRUD operations would map to HTTP verbs. The first step of making the StudentService we've developed RESTful is, to list down HTTP verbs and URLs that these operations will be exposed under. The following table illustrates this:
You would have noticed that the URLs I have selected contains certain patterns. These URI patterns will be later mapped to WSDL 2.0 properties. Let's get a WSDL 2.0 document gererated for our service so that we can tinker with it and make our service RESTful. Using java2wsdl tool to generate a WSDL 2.0 documentYou can use the java2wsdl commandline utility that ships with Axis2 to generate a WSDL 2.0 document for the StudentService we've written. The java2wsdl command line utility can be found in the bin directory of the Axis2 distribution. For our example scenario we won't be using all posible options that java2wsdl offers. Our usage of java2wsdl is,
The java2wsdl utiliy has more options. You can check all of them out by executing "sh java2wsdl.sh". Here is what you see when the above command is executed:
Now, let's get the WSDL 2.0 document generated for our StudentService.
Note : You can safely ignore the error it displays. This will generate a file called StudentService.wsdl in the output directory. The WSDL generated will look similar to this, Making Our Service RESTfulThe WSDL generated will have 3 bindings cause this is the default setting in Axis2. It will have a SOAP 1.1 binding, a SOAP 1.2 binding and a HTTPBinding. We are interested in making our service RESTful, hence, we will concentrate on the HTTPBinding and ignore the rest (we can even take them off the WSDL). We did decide upon the URL patterns and the HTTP verbs we will be using for are sample service. Let's wire them together now. The property that allows us to state the HTTP method that an operation will be exposed under is "whttp:method", which is defined at the binding operation level. If this property is not present, it will look for the "whttp:methodDefault" property which can be defined at the binding level. If this too is not present, WSDL 2.0 defaulting rules will be used. We have two operations that we want to expose over GET, hence lets make whttp:methodDefault="GET" and specify whttp:method for the others at the binding operation level. The property that allows us to state the URL pattern that an operation will be exposed under is "whttp:location", which is defined at the binding operation level. Please refer[4] for more details on what is whttp:location. You may also refer[5] for some tips on deciding what your whttp:location should be. The following is how we want out HTTP binding to look like: <wsdl2:binding name="StudentServiceHttpBinding" whttp:methodDefault="GET" interface="tns:ServiceInterface" type="http://www.w3.org/ns/wsdl/http"> <wsdl2:fault ref="tns:StudentAlreadyExistsException"/> <wsdl2:fault ref="tns:StudentNotFoundException"/> <wsdl2:operation ref="tns:deleteStudent" whttp:location="student/{name}" whttp:method="DELETE"> <wsdl2:outfault ref="tns:StudentNotFoundException"/> </wsdl2:operation> <wsdl2:operation ref="tns:updateStudent" whttp:location="student/{name}" whttp:method="PUT"> <wsdl2:outfault ref="tns:StudentNotFoundException"/> </wsdl2:operation> <wsdl2:operation ref="tns:addStudent" whttp:location="students" whttp:method="POST"> <wsdl2:outfault ref="tns:StudentAlreadyExistsException"/> </wsdl2:operation> <wsdl2:operation ref="tns:getStudent" whttp:location="student/{name}"> <wsdl2:outfault ref="tns:StudentNotFoundException"/> </wsdl2:operation> <wsdl2:operation ref="tns:getStudents" whttp:location="students"/> </wsdl2:binding> A Few More Tweaks to The WSDLIn our example, we are using Axis2's built-in RPCMessageReceivers, and deploying the service using the WSDL. In order for this to work out-of-the-box, there are a few tweaks needed in the WSDL. (This is needed only because we are using a seperate bean class (and a custom Exception classes) in our service. If your service does not use any bean classes this section can be overlooked safely). In the WSDL generated, Axis2 puts all bean classes into a namespace called " http://axis2.apache.org/xsd", while other elements are left in "http://axis2.apache.org". We need to have all data types in the same namespace to use the RPCMessageReceivers out-of-the-box. Here is how you accomplish this task:
Now, your edited WSDL should look similar to this. Deploy your serviceNow we are all set. package the service up in the same manner we did previously and deploy your service. There is a bit of an extra step that you need to do this time though. Make sure you drop in the WSDL we edited into the META-INF directory of the service archieve. Verify that your service is up and running by tryng to get its WSDL 2.0 document at http://localhost:8080/axis2/services/StudentService?wsdl2. You can even try to get the list of students at http://localhost:8080/axis2/services/StudentService/students. Future Directions
ConclusionsQuestion: Does Axis2 support REST? Answer: It sure does. This article has taken you through the process of writing RESTfull services in Axis2. It also illustrates how WSDL 2.0 fits into the picture and demonstrates its ability to describe truly RESTful services. Yes, there are improvements that could be done to make life easy and we do plan to do so in future releases. [1] Dr. Roy Fielding's Ph.D. dissertation - http://roy.gbiv.com/pubs/dissertation/top.htm [2] WSDL 2.0 HTTP Binding - http://www.w3.org/TR/wsdl20-primer/#more-bindings-http [3] Does REST need a description language - http://www.infoq.com/news/2007/06/rest-description-language [4] What is whttp:location? - http://wso2.org/library/3715 [5] Things to note when deciding on values to use for whttp:location - http://wso2.org/library/3725 AuthorKeith Chapman, Senior Software Engineer, WSO2 Inc. keith at wso2 dot com
|
Tag Cloud
|
Providing custom authentication….
This tutorial was very helpful for quickly developing RESTful web services. Any inputs/suggestions on how to provide custom authentication for these REST services.
Thanks
Axis2 in OSGi ServletBridge Container
Hi,
I deployed the same application in Axis2 + OSGi Servlet Bridge container. Though I modified the WSDL, I am not getting the modified one when I am accessing: "http://localhost:8080/bridge/services/StudentService?wsdl2".
Please let me know, how to fix this issue?
- Nagarjuna
Problem with axis2 1.5
Hello Keith
Does your "StudentService" sample still runs with axis2 1.5 ?
a) If I try to put it in the "META-INF/services" folder, the Axis2 v1.5 WebApp seems to be blocked when it tries to deploy the service
b) If I try to upload the service "aar" archive file with the admin page, I get this messages in the tomcat log file :
Woden[Warning],0:0,WSDL504,Could not locate the schema document at URL "http://www.w3.org/2001/XMLSchema.xsd",java.net.SocketException:Connection reset
Woden[Warning],0:0,WSDL504,Could not locate the schema document at URL "http://www.w3.org/2001/XMLSchema",java.net.SocketException:Connection reset
Woden[Warning],0:0,Description-1001,The targetNamespace 'http://axis2.apache.org' is not dereferencable.
[INFO] Deploying Web service: StudentService.aar - file:/D:/_devs_intradon/X3_SERVERWSJULIET/.metadata/.plugins/org.eclipse.wst.server.core/tmp0/wtpwebapps/com.sage.x3.serverws.axis2/WEB-INF/services/StudentService.aar
As the url "http://www.w3.org/2001/XMLSchema.xsd is valid Axis2 needs access to the Internet?
many thanks.
Olivier
Need Client
Hi,
I am using your example and created a Client for the same.
But I am not sure as the StudentServiceStub class contains the soap envelop also. Is ther a way by which i can make my stub SOAP free. here is the client created: Please let me know if this is correct or needs some correction. Also if i change whttp:method to something like PORT instead of POST then it also works fine after generating client using wsdl2java. I am not sure if i am getting result because of soap binding in STUB class though i have removed all the soap related things from wsdl before generating Stub class.
public class Client {
/**
* @param args
* @throws StudentAlreadyExistsExceptionException0
* @throws RemoteException
* @throws StudentNotFoundExceptionException1
*/
public static void main(String[] args) throws RemoteException, StudentAlreadyExistsExceptionException0, StudentNotFoundExceptionException1 {
// TODO Auto-generated method stub
StudentServiceStub service = new StudentServiceStub();
addStudent(service);
//deleteStudent(service);
//updateStudent(service);
getStudent(service);
//updateStudent(service);
//getStudent(service);
}
public static void addStudent(StudentServiceStub stub){
try{
AddStudent add = new AddStudent();
Student student = new Student();
student.setName("Ashu11");
student.setAge(12);
add.setStudent(student);
stub.addStudent(add);
} catch(Exception e){
e.printStackTrace();
System.out.println("nnn");
}
}
public static void deleteStudent(StudentServiceStub stub){
try{
DeleteStudent delete = new DeleteStudent();
delete.setName("name");
stub.deleteStudent(delete);
} catch(Exception e){
e.printStackTrace();
System.out.println("nnnsdasdasd");
}
}
public static void updateStudent(StudentServiceStub stub){
try{
UpdateStudent update = new UpdateStudent();
Student student = new Student();
student.setName("Ashu11");
student.setAge(79);
update.setStudent(student);
stub.updateStudent(update);
} catch(Exception e){
e.printStackTrace();
System.out.println("nnnsdasdasd");
}
}
public static void getStudent(StudentServiceStub stub){
try{
GetStudent get = new GetStudent();
get.setName("Ashu11");
// stub.getStudent(get);
GetStudentResponse res = stub.getStudent(get);
System.out.println(res.get_return().getName()+ "/" + res.get_return().getAge());
} catch(Exception e){
e.printStackTrace();
System.out.println("nnnsdasdasd");
}
}
}
How to create a client.
Hi Keith,
From my understanding of Axis it can support expose the same service both a common web service and a REST service.
My question is how do you use the wsdl2java utility to generate a client that can call a service both as a web service and as a restful service?
When I use the WSDL 2.0 file to generate a client it throws an error.
Exception in thread "main" org.apache.axis2.wsdl.codegen.CodeGenerationException: Error parsing WSDL
at org.apache.axis2.wsdl.codegen.CodeGenerationEngine.(CodeGenerationEngine.java:156)
at org.apache.axis2.wsdl.WSDL2Code.main(WSDL2Code.java:35)
at org.apache.axis2.wsdl.WSDL2Java.main(WSDL2Java.java:24)
Caused by: javax.wsdl.WSDLException: WSDLException (at /wsdl2:description): faultCode=INVALID_WSDL: Expected element '{http://schemas.xmlsoap.org/wsdl/}definitions'.
I know we can set an option to make the client as a REST client, but would this automatically choose the PUT, GET, POST methods for HTTP, if so does it get this information from the WSDL 2.0 format file?
Some of the replies to your blog say that data should be sent as XML .. etc How is this xml mapped to an object? Would the above process work if say I choose JiBX data binding provided by Axis2?
Here are the answers
Hi,
When using a WSDL 2.0 document with wsdl2java you need to pass in the -wv 2 option. This is the reason you got this error message. wsdl2java expects a WSDL 1.1 document otherwise.
Although your service (your business logic) is binding independent (REST or SOAP) your stub is. So if you wanna talk to the service using REST you should generate a stub for the HTTPEndpoint using the -pn option and if you wanna use SOAP you should use one of the SOAP endpoints. If you use the HTTPEndpoint the stub would automatically take care of the HTTP Methods to use etc...
I would assume that JIBX should work as well (Thinking about how Axis2 works) but I haven't tried it out honestly. I would like to know your feedback on this (If you could try it out using JIBX).
In this blog post [1] I explained how Axis2 differentiates REST vs SOAP on the server. I would post in a few days on what happens on the client side for clarity.
Thanks,
Keith.
Blog : http://www.keith-chapman.org
[1] http://www.keith-chapman.org/2009/02/how-does-apache-axis2-differentiate.html
Hi, But what would be the
Hi,
But what would be the client implementation to invoke this service without soap..
A client example would be excelent
Hello,
I am new with webservices and the REST concept, therefore when I've found your tutorial, I have start dancing. Everything runs nice and clean when I am trying to access the webservice via GET.
I understand that should I provide an XML message in case of add/edit/delete in the request body.
I don't understand what is the structure of the XML message I have to provide, I have tried a simple one:
<student>
<name>john</name>
<age>23</age>
</student>
But I get error saying:
"First Element must contain the local name, Envelope , but found student"
It might be a trivial problem,
Thanks
Florin
Hello again, We have managed
Hello again,
We have managed to find the way the xml should look like:
<addStudent xmlns="http://axis2.apache.org">
<student >
<name>marcus florin</name>
<age>12</age>
</student>
</addStudent>
Still, we have problems making delete method to work,
Thanks,
Florin
What is the method you used
What is the method you used to send the request?
Thanks,
Keith.
Hello Keith, Thanks for
Hello Keith,
Thanks for replying.
On Delete problem, initially we got an error such as:
Java.lang.UnsupportedOperationException: An access occurred that is not valid.at org.apache.axis2.description.InOnlyAxisOperation.getMessage(InOnlyAxisOperation.java:109)
This was a problem solved by changing services.xml by updating to "robust-in-only" from "in-only" for RPCInOnlyMessageReceiver configuration.
Right now we are able to delete the Student from the collection using the webservice, the only thing is that we get an error back:
xception in thread "main" org.apache.axis2.AxisFault: The input stream for an incoming message is null.
[java] at org.apache.axis2.transport.TransportUtils.createSOAPMessage(TransportUtils.java:72)
Thanks,
Florin
How did you author your client?
How did you author your client?
From your exception it looks like the client was expecting a response and the server did not send anything.
So got to look at how your client was authored (in-only or in-out) and how this service method is authored (in-only or in-out).
could well be that the client expects it to be in-out whereas for the server its in-only.
Thanks,
Keith.
Regarding your example of RESTful web services using AXIS2
private String getBaseURL() {
if (baseURL == null) {
MessageContext messageContext = MessageContext.getCurrentMessageContext();
AxisConfiguration configuration = messageContext
.getConfigurationContext().getAxisConfiguration();
TransportInDescription inDescription = configuration.getTransportIn("http");
try {
here the statement MessageContext messageContext = MessageContext.getCurrentMessageContext(); is giving NULL.
i am unable to find the solution.
pls help.
Hi, You need to deploy your
Hi,
You need to deploy your class in Axis2 as a service. MessageContext.getCurrentMessageContext() can be used only from within a service, its only then that it will return the current MessageContext.
REST URL - need clarification
Hi Keith,
Regarding the URL : http://localhost:8080/axis2/services/StudentService/getStudent?name=keith, it's mentioned "..not truly REST .. no control over the URL the operation was exposed under." -- i couldn't fully understand this statement. How does the REST-style URL /services/studentService/student/{name} allow control?. Can you please clarify?
Thanks,
J
Here is the Clarification
Hi,
Its all about what would feel more RESTfull (and it certainly depends on the situation). In the situation you've highlighted above http://localhost:8080/axis2/services/StudentService/student/keith is more RESTfull than http://localhost:8080/axis2/services/StudentService/getStudent?name=keith.
This is what I mean by no control over the URL. In the second case (the default is) serviceName/operationName?paramName=paramValue and thats it u cannot change it in anyway. This article explains how you could take control of the URL in that sense. It shows how you could map a URL to an operation so instead of having the operation name in the URL you could have what ever you want.
Thanks,
Keith.
Blog : http://www.keith-chapman.org/
Follow-up : url - operation mapping
Thanks Keith, I see how RESTful URLs don't expose the operation name to the service consumer ; but say, we want to change the operation that an URL maps to, it also means changing the WSDL, which would obviously have an impact on the consumer end as well. In this sense, the mapping is not fully transparent. Can you please clarify?
-J