-
Notifications
You must be signed in to change notification settings - Fork 200
Getting Started (Version 1)
Note: The following is applicable to Enunciate 1.x. For getting started with Enunciate 2, see Getting Started.
This getting started guide will attempt to walk through a mock example of Web service development, without trying to hide the complexities of deployment descriptors, dependencies, configuration, and packaging. Of course, some attempt to be concise needs to be made, but at least you'll get the idea of the full development effort. And you'll see more than just "Hello, World".
We'll be writing a mock social networking app. For this tutorial, the name of our social networking app is IfYouWannaBeCool.com.
The source code for this application can be obtained in one of two ways:
Enunciate provides a Maven archetype to seed a simple web service project. The project uses the basic modules and includes working sample service and model code. Here's how to invoke it:
mvn archetype:generate \
-DarchetypeGroupId=org.codehaus.enunciate.archetypes \
-DarchetypeArtifactId=enunciate-simple-archetype \
-DarchetypeVersion=1.29 \
-DgroupId=org.codehaus.enunciate \
-DartifactId=wannabecool
The "wannabecool" sample is bundled with the distribution bundle that can be downloaded from the releases page.
We'll start by defining the domain of our app. We'll define a Persona
with an id, an alias, an email address, a name, and a picture. The Name
is a complex structure, made up of a given name and a surname. We'll also define a Link
between two personas and a SocialGroup
consisting of an id, a group leader, the group members, and whether the group is exclusive.
Next, we'll define the services available for our domain data. The PersonaService
will define the operations available on a persona. This includes operations for reading a persona, storing a persona, and deleting a persona. The SocialGroupService
will carry the operations that deal with linking and grouping personas. These operations include creating a link between two personas, creating a social group, adding someone to a social group, and reading the social groups of a given persona.
We also define the possible exceptions that can get thrown, including a PermissionDeniedException
when trying to create a link between two people, and an ExclusiveGroupException
when trying to add a persona to an exclusive group.
After having defined our service interfaces, we create our implementation classes, PersonaServiceImpl
and SocialGroupServiceImpl
.
In order for our code to be identified as a Web service API, we need to apply some metadata in the form of Java annotations. We'll start by defining an XML REST API using JAX-RS annotations. Our objects will be converted to XML using JAXB.
A JAX-RS endpoint is identified by mapping it's implementation to an URI path using the @jakarta.ws.rs.Path
annotation. We'll apply the PersonaServiceImpl
endpoint implementation to the "/persona" path and the SocialGroupServiceImpl
to the "/group" path.
Then, using JAX-RS, we map each method to an HTTP operation and, optionally, to a subpath. We decide to apply method-level metadata on the interface that defines the method. (Note that using interfaces isn't strictly necessary, but it may be convenient to do so if and/or when we apply some AOP or dependency injection.)
So for the PersonaService
, we will use an HTTP GET
to access the readPersona
method and will pass in the persona id as a parameter on the path. We will use an HTTP POST
operation to store a new persona, and an HTTP DELETE
method to delete a person.
Then, for the SocialGroupService
, we will use an HTTP GET
to access the readGroup
method and will pass in the group id as a parameter on the path. To store a new group, we will use the HTTP POST
method and pass in the parameters as HTTP query parameters. To add a persona to a group, we'll use a POST
to the group path and pass in the persona id as a query parameter.
Because we're returning objects from a few of our REST API calls, we need to also apply the @XmlRootElement
annotation so that JAXB will know how to write out the root of our XML tree.
So, we've got our domain defined along with some services that operate on the domain. The source code structure looks like the following (you can see and browse it here):
src
|
|--com
|
|--ifyouwannabecool
|
|--api
| |
| |--ExclusiveGroupException.java
| |--SocialGroupService.java
| |--PermissionDeniedException.java
| |--PersonaService.java
|
|--domain
| |
| |--link
| | |
| | |--Link.java
| | |--SocialGroup.java
| |
| |--persona
| |
| |--Name.java
| |--Persona.java
|
|--impl
|
|--SocialGroupServiceImpl.java
|--PersonaServiceImpl.java
For this example, we're going to invoke Enunciate using the command-line scripts. We do this because it's easy to demonstrate, but you'll probably want to use Ant or Maven. We make sure we have our environment set up, and invoke Enunciate on the source code (multi-line, single command, use the "-v" option to see more output of what Enunciate is doing):
user@localhost>enunciate -Espring.war.file ifyouwannabecool.war\
src/com/ifyouwannabecool/api/ExclusiveGroupException.java\
src/com/ifyouwannabecool/api/PermissionDeniedException.java\
src/com/ifyouwannabecool/api/SocialGroupService.java\
src/com/ifyouwannabecool/api/PersonaService.java\
src/com/ifyouwannabecool/impl/PersonaServiceImpl.java\
src/com/ifyouwannabecool/impl/SocialGroupServiceImpl.java\
src/com/ifyouwannabecool/domain/persona/Name.java\
src/com/ifyouwannabecool/domain/persona/Persona.java\
src/com/ifyouwannabecool/domain/link/Link.java\
src/com/ifyouwannabecool/domain/link/SocialGroup.java
You'll notice that we exported the war artifact that was created by Enunciate to the file "ifyouwannabecool.war" (with the option "-Espring.war.file ifyouwannabecool.war"). Drop that into your favorite J2EE container and hit the app in a browser to see what you've got!
The first thing you'll notice is a nice-looking web page with a generic title divided into two sections. The first section describes the REST API, and it includes the two services we just wrote. The second section is the data section describing our domain. You'll notice the XML-Schema file that was generated that describe our domain.
As you continue to poke around, you'll notice that the documentation is quite sparse (although some information can be gleaned from the names of the methods and arguments).
The "downloads" page links to client-side binaries and source code that can be used to access your REST API. These client-side libraries were generated by Enunciate and packaged up (along with everything else) in the war. The client-side code is clean, intuitive, and powerful, handling all the complexities of the API.
Oh, and of course the endpoints are deployed as described in the documentation.
Even with no extra options or decorations, Enunciate does a pretty good job publishing your API. But there's so much more you can do with only a minimal amount of effort! Let's give our classes some extra love, then we'll talk about what we did.
You can see and browse this source code here.
src
|
|--enunciate.xml
|--LICENSE.txt
|--com
|
|--ifyouwannabecool
|
|--api
| |
| |--ExclusiveGroupException.java
| |--SocialGroupService.java
| |--PermissionDeniedException.java
| |--PersonaService.java
| |--package-info.java
|--domain
| |
| |--link
| | |
| | |--Link.java
| | |--SocialGroup.java
| | |--package-info.java
| |
| |--persona
| |
| |--Name.java
| |--Persona.java
| |--package-info.java
|
|--impl
|
|--SocialGroupServiceImpl.java
|--PersonaServiceImpl.java
|--package-info.java
Adding a SOAP API is as simple as applying the JAX-WS @WebService
annotation to our service interfaces and to our service implementations. (Note that per the JAX-WS specification, the implementation must reference the interface using the endpointInterface
method on the annotation.) We also apply the @WebFault
annotation to our exceptions so they'll be translated to the client correctly.
There is, and always will be, debate around a SOAP API vs. a REST API. Whatever your opinion, there are advantages to defining a SOAP API, including the ability to define your API with a WSDL and provide a well-defined set of operations to your client. And when it's this easy to define a SOAP API, you may want to consider whether the advantages are worth it.
It is considered a "best practice" to namespace-qualify your domain API. We did this with the use of the package-info.java files for the link API and the persona API. Namespace-qualifying your domain API ensures maximum compatability as the use of the default namespace is confusing to implementation vendors. It also provides a tool to help with versioning your API.
You'll notice we added a package-info.java
file to the com.ifyouwannabecool.api
package. This is where we added the introductory (i.e. "splash") documentation for our API. We want this documentation to show up on our index page for our documentation, so we specify this package as the splash packge in our enunciate.xml
configuration file. For more information, check out the user guide.
We've added javadocs to everything, including the endpoints, their methods and parameters, and the domain data. This documentation will show up in our generated documentation. You're free to use HTML tags as you want; they'll be applied in the resulting HTML. Javadoc block tags are recognized, but currently there is no support for javadoc inline tags (they'll show up unparsed in the documentation).
We can specify a title and copyright to the generated documentation in the enunciate.xml
configuration file.
We want to be able to add a downloadable file to the documentation. In this case, we add the license file that governs the use of the API. This extra download is specified in the enunciate.xml
configuration file.
We specify our hostname ("www.ifyouwannabecool.com") and the context at which the app will be deployed ("api") in the enunciate.xml
configuration file. There are two advantages to this. (1) The generated WSDL will have an absolute reference to our SOAP endpoints, making the formal XML contract complete. (2) Consumers of our client code won't have to specify the URL of our endpoints if they don't want to.
Again, done in the enunciate.xml
configuration file. Why would we want to do this? Just because we suffer from OCD and don't like the default "ns0", "ns1", etc. prefixes that are automatically assigned. It also gives a nice name to our wsdl and schema.
Done (where else?) in the enunciate.xml
configuration file. This will distinguish the generated client classes from the original server-side classes. (Makes it easier to test, clearer that you're dealing with client-side classes, etc.)
And there are still a ton of other configuration options available to you as the API developer (which we won't go into here):
- Specify your own CSS for the documentation, or even your own XSLT stylesheet if you don't like the structure.
- Customize the web.xml file used to deploy your app.
- Add AOP interceptors to your services.
- Add your own API rules to be enforced at compile-time.
- Etc.
Well, when we're done polishing things up, we enunciate our API again and deploy the war file as we did in steps 2 and 3. (Note that this time, we specify the config file on the command line.)
user@localhost>enunciate -f enunciate.xml -Espring.war.file ifyouwannabecool.war\
src/com/ifyouwannabecool/api/package-info.java\
src/com/ifyouwannabecool/api/ExclusiveGroupException.java\
src/com/ifyouwannabecool/api/PermissionDeniedException.java\
src/com/ifyouwannabecool/api/SocialGroupService.java\
src/com/ifyouwannabecool/api/PersonaService.java\
src/com/ifyouwannabecool/impl/PersonaServiceImpl.java\
src/com/ifyouwannabecool/impl/SocialGroupServiceImpl.java\
src/com/ifyouwannabecool/domain/persona/Name.java\
src/com/ifyouwannabecool/domain/persona/Persona.java\
src/com/ifyouwannabecool/domain/link/Link.java\
src/com/ifyouwannabecool/domain/link/SocialGroup.java
Take a look now at the generated documentation and you'll notice all the new enhancements!