Skip to content
HariKrishnan edited this page Sep 26, 2013 · 10 revisions

Installation

Include jar dependcy in your pom.xml.


  <repositories>
    <repository>
      <id>rapa</id>
      <url>https://github.com/harikrishnan83/maven-repository/raw/master/</url>
    </repository>
  </repositories>
  
  <dependencies>
    <dependency>
      <groupId>org.rest.rapa</groupId>
      <artifactId>rapa</artifactId>
      <version>0.9.1</version>
      <scope>compile</scope>
    </dependency>
  </dependencies>

Gradle


  repositories {
      mavenRepo urls: 'https://github.com/harikrishnan83/maven-repository/raw/master/'
  }

  dependencies {
      compile 'org.rest.rapa:rapa:0.9.1'
  }

Note: Gradle builds seem to have a problem getting rapa’s dependecies.

Getting Started

To get started with all you need is to define a RestClient.
So lets start with that.


        RestClient restClient = new RestClientBuilder()
                            .withUrl("http://localhost:3000/users")
                            .build();

As you can see, there is not much we have done here. The only input is the URL. It is pointing to “/users”, which will be base url based on which urls will be constructed for CRUD operations. Rapa has intelligent defaults for the various options.

In the above case we have assumed the format to be xml, blank username, blank password and so on. Take a look at the RestClientBuilder class in the code base to get an idea of the defaults.

Since our url was pointing to “/users” lets go ahead and create a resource. I am going to create an ultra simple resource called “User”. User has one field “name”.


import org.rest.rapa.resource.Resource;

public class User implements Resource {

    private int id;
    private String name;

    public User() {
    }

    public User(String name) {
        this.name = name;
    }

    @Override
    public int getId() {
        return id;
    }

    @Override
    public void setId(int id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

}

As you can see, the only methods that the interface Resource has are related to the id. Also note that some libraries like JAXB need a default constructor for your resource. Now we are all set to consume the service

</code>
        User user = new User("Roy Fielding");
        restClient.save(user);

        System.out.println(user.getId());

Lets talk little bit more about the RestClient we jump into the CRUD operations.

Formats

Threoritically rapa does not mandate on using specific formats like xml and json. However we have already create the implementation for xml and json. In fact for xml we have two implementations. One based on JAXB and one based on XStream. By default rapa uses JAXB based xml implementation.

Lets try to switch the format to json


        RestClient restClient = restClientBuilder
                            .withUrl("http://localhost:3000/users")
                            .withFormatHandler(new org.rest.rapa.formatter.json.JSonHandler())
                            .useFormatAsExtension()
                            .build();

There are two new lines here. One instantiates a JSON handler class and passes it to the builder. The second tells rapa to use format as extention. This means a .json will be appended to the main url (More on this in the next sections)

How does this work?

Rapa believes in plugin based design. If you are not happy with our implementations for xml or json and have your own parsers you can plug them in. You can even use a format other than both json and xml if you have your own parser. To plugin your own format and/or parser implement the below interface.


public interface FormatHandler {
    String serialize(Resource resource) throws Exception;
    Resource deserialize(String content, Class<?> resourceType) throws Exception;
    String getExtension();
    String getContentType();
}

For reference implementation you can take a look the below classes.

  1. org.rest.rapa.formatter.xstream.XMLHandler
  2. org.rest.rapa.formatter.JAXB.XMLHandler
  3. org.rest.rapa.formatter.json.JSonHandler

Why is this advantageous?

When we were working with XStream, we ran into a strange issue. XStream provides very good configurability, when it comes to alias for class names etc. But it has a strange limitation where it needs a field defined in the class for all the elements in the XML. This can be configured. But how do we pass in the configuration into rapa. Below is the implementation we followed. Thanks to Peter Voss’s blog


        //skip non existing fields
        XStream xStream = new XStream(new DomDriver()) {
              @Override
              protected MapperWrapper wrapMapper(MapperWrapper next) {
                return new MapperWrapper(next) {
                  @Override
                  public boolean shouldSerializeMember(Class definedIn,
                          String fieldName) {
                    if (definedIn == Object.class) {
                      return false;
                    }
                    return super.shouldSerializeMember(definedIn, fieldName);
                  }
                };
              }
            };

        //translate user xml element to User.class rather than using fully qualified names
        xStream.alias("user", User.class);

        XMLHandler formatHandler = new XMLHandler(xStream);

        RestClient restClient = new RestClientBuilder()
                            .withUrl("http://localhost:3000/users")
                            .withFormatHandler(formatHandler)
                            .useFormatAsExtension()
                            .build();

Security:

Username and password can be passed into Rapa as shown below.

 
       RestClient restClient = new RestClientBuilder()
                            .withUrl("http://localhost:3000/users")
                            .withFormatHandler(new JSonHandler())
                            .useFormatAsExtension()
                            .withUserName("username")
                            .withPassword("password")
                            .withBasicAuthentication()
                            .build();

I have also told rapa to use Basic authentication. Rapa supports the below authentication modes.

  1. Basic
  2. Digest
  3. NTLM

If your service uses multiple authentication modes, you can mention all of them if you wish.


RestClient restClient = new RestClientBuilder()
                            .withUrl("http://localhost:3000/users")
                            .withFormatHandler(new JSonHandler())
                            .useFormatAsExtension()
                            .withUserName("username")
                            .withPassword("password")
                            .withBasicAuthentication()
                            .withDigestAuthentication()
                            .withNTLMAuthentication()
                            .build();

Also we can also specify parameters like Realm and scheme as well.

URL

URLs are very important to a REST webservice. Rapa can understand a well structured RESTful URL. It uses the URL that is given to it and generates resource specific urls. If you use “useFormatAsExtension” with XML format then urls which rapa uses will look like below.

http://localhost:3000/users.xml
http://localhost:3000/users/1.xml

If it is not used URLs will look as shown below.

http://localhost:3000/users
http://localhost:3000/users/1

Complete API

The api to rapa is a simple builder. It makes easy to implement intelligent defaults. You can use what you want and leave the rest to rapa.


        RestClient restClient = new RestClientBuilder()
                            .withUrl("http://localhost:3000/users")
                            .withFormatHandler(new JSonHandler())
                            .useFormatAsExtension()
                            .withUserName("username")
                            .withPassword("password")
                            .withBasicAuthentication()
                            .withDigestAuthentication()
                            .withNTLMAuthentication()
                            .withScheme("scheme")
                            .withRealm("realm")
                            .build();

The best place to look for latest updates is the code.

Dependencies

commons-http-client-3.1.jar
commons-logging-1.1.1.jar
commons-codec-1.2.jar

Thanks for reading a rather long documentation (Its one of the longest I have written :)). We understand there is much to be improved. We would be happy to accept any suggestions to improve the same.

If you have a bright idea for a logo, we would delighted to hear form you. We did the naming bit, we want to leave the logo design to you :).

Cheers,
rapa team