Overview
This tutorial will guide you through the following concepts:
- How to write a simple Atom Publishing Protocol server using Abdera
- How to set up an Abdera servlet
- The DefaultProvider and AbstractEntityCollectionAdapter classes
- Modelling an employee directory with an Atom Publishing Protocol Service
This assumes that you have some familiarity with Atom and AtomPub, but you will probably still get value out of the tutorial if you do not.
Concepts
In this tutorial we're going to walk through how to build an Atom Publishing Protocol service using Abdera's concepts of Providers and CollectionAdapters. If you remember your AtomPub basics, you'll recall that AtomPub services are organized in a hierarchical way such that we have:
- Services - A grouping of Workspaces
- Workspaces - A grouping of Collections
- Collections - An Atom Feed which contains Atom entries. Entries can be created, deleted, updated, etc through the HTTP methods and the mapping that AtomPub defines.
Abdera provides some classes which map to these concepts:
AtomPub Concept |
Abdera Classes |
Description |
---|---|---|
Service |
Provider |
Providers provide the implementation of an AtomPub service. In general you should be able to just use the DefaultProvider which we will be using it as we construct our service. |
Workspace |
WorkspaceInfo & WorkspaceManager |
The WorkspaceInfo class provides metadata about your workspace for the services document. The WorkspaceManager provides a way for you to list out the workspaces in a service. |
Collection |
CollectionAdapter |
A CollectionAdapter is where your business logic will lie and allows you to implement the basic GET/DELETE/POST/etc operations for an AtomPub Collection. |
Minimal required Maven dependency
Writing your CollectionAdapter
Abdera comes with a class called the AbstractEntityCollectionAdapter. This class provides a simple "fill in the blanks" approach so that you can easily map your concepts to an AtomPub collection. The idea is that you have an entity class which represents each individual entry. For instance, if you were writing a blog, you would have a BlogEntry class which backs the entry. Or in our case of an employee directory, it would be an Employee class. Abdera will then provide some template methods which you fill in to give Abdera the necessary metadata.
Our Atom Collection is going to be a simple store of employees which can be added to, deleted from, updated, etc. To get started, we must first build our Employee class:
The ID is going to be used as a unique identifier for our Employee so that even if the employee's name changes, we can tie it to a previous atom entry and track the changes. The updated date will be used for our Atom feed, so that consumers know if there were changes and when they occurred.
Now we need to create our CollectionAdapter:
The first thing we'll do is provide a hashmap to store our Employees in. Typically this will be a database or some other type of backend store. To keep things simple, we're going to store employees in a Map according to their employee id.
Next up, we need to implement some methods which provide some basic metadata about our collection such as the feed author, the feed ID, and the feed title:
The ID in getId(RequestContext) must be a unique identifier for your feed. The idea is that even if your feed location changes, or someone is redistributing your feed, the feed reader can use this ID to identify that two feeds are the exact same. For information on how to construct feed IDs and why they matter, its recommend that you use this article on how to create feed ids.
Providing Entry Metadata
The next step is to implement the methods which provide some basic metadata about our entries:
Walking through this we have:
- getId: this ID will uniquely identify your entry. It follows the same rules as the feed ID.
- getTitle: The title of this entry.
- getUpdated: The time this entry was last updated.
- getAuthors: the author of this entry. It is ok to leave this empty provided that there is an author supplied for the feed.
- getContent: The actual content of this entry that will be displayed in the feed. In this case it is just the employee name. You can return a Content or String object from this method. Using the Content object allows you to have control over whether or not the content is rendered as HTML. Alternatively you could provider a summary by overriding getSummary. You must have at least one of the getContent or getSummary methods not return null.
Mapping Entries to Resources
The AbstractEntityCollectionAdapter maps individual entries via a "resource name." You must provide a way to create a resource name for your entry and also provide a way to look up an entry from your resource name. In this case, our entry is an Employee.
When we do this we must ensure that we won't have conflicts. Which means we don't want to use the employee name as the unique resource name as there may be conflicts. However we don't necessarily want to use just the employee ID either as the URL isn't very friendly. So we'll create a hybrid of the form: "EMPLOYEE_ID-EMPLOYEE_NAME".
Note the use of the sanitizer. This will take any invalid characters for a URL and either strip or replace them from the string.
We must also provide a way to turn a resource name into an employee:
Implementing POST/DELETE/PUT
The last step is to implement the POST, DELETE, and PUT operations.
The POST method corresponds to creating an Employee in the database. Here we're:
- Creating a new Employee object
- Creating a new id for it
- Setting the name of the Employee from the Content. In a more advanced case, you woudl want to store the Employee information in an XML structure or an HTML microformat.
- Store the Employee in our HashMap.
The putEntry method is similar. In this case we're just updating the employee name.
Finally, we have the deleteEntry method which gives us the resource name and allows us to remove it from the Map.
Because we've already supplied Abdera with lots of metadata about our entry, you get the HEAD operation for free!
Setting up your Provider and the URL space
You've now written an Abdera CollectionAdapter to expose your employee database to the world via an Atom collection. You must still expose it via a servlet though. Below shows how to extend the AbderaServlet to create your provider.
In this code we're setting up a Provider which contains an Atom workspace which contains our Atom collection.
When we initialize our CollectionAdapter we call setHref. Abdera uses this to determine the URL space. This leaves us with the following URL structure:
URL |
Description |
---|---|
/ |
The services document |
/employee |
The Atom feed which maps to the employee collection. |
/employee/XXX |
An individual atom entry from our collection. |
You could just as well use the Spring Integration or the web.xml configuration capabilities of Abdera to expose your Provider/CollectionAdapter as well.
Deploying your service
Finally, you can deploy your Atom service inside your favorite servlet container witht he following XML.
3 Comments
Jörn Zaefferer
The provider configuration doesn't match that of the web.xml-servlet-configuration. The base for the DefaultProvider provider has to be "/atom/" to match the url-pattern. Please fix the example accordingly, so others don't have to figure this out.
Jorge Ferrer
+1
I've lost quite some time this morning due to this. With the code in this example the web.xml should have the following mapping:
<servlet-mapping id="abdera-mapping">
<servlet-name>Abdera</servlet-name>
<url-pattern>/*</url-pattern>
</servlet-mapping>
Alternatively the provider instance should be created using:
DefaultProvider provider = new DefaultProvider("/atom/");
Reda Abraham
Thanks for the turorial; but it contains some errors,
#1. The EmployeeCollectionAdapter class doesn't override all the methods of the AbstractEntitiyCollectionAdapter<Employee> class
Solution let your IDE, generate a Overriding methods for you. I am using Netbeans and that comes handy
#2. ID_PREFIX is used in EmployeeCollectionAdapter but it is not declared
Solution Add the following instant variable declarations to EmployeeCollectionAdapter
_ private static final String ID_PREFIX = "tag:giantflyingsaucer.com,2011:employee:entry:"; _
_ private AtomicInteger nextId = new AtomicInteger(1000); _
#3. In the EmployeeProviderServlet, it is not clear which Provider class we are referring to.
Answer We are using org.apache.abdera.protocol.server.Provider
#4. public String getName(Employee entry)
is implemented twice,
Solution one of them should be removed.
#5. We cannot have top level public static final class EmployeeProviderServlet class
Solution just remove the keyword static
I hope that helps, but if you still have some trouble, check the the site that helped me by yourself: http://www.giantflyingsaucer.com/blog/?p=2947