2008/10/06
6 Oct, 2008

Extending WSO2 Registry with Handlers

  • Chathura Ekanayake
  • PhD student - WSO2

Table of Content

Introduction

In simple terms, the WSO2 Registry is a resource store. However, once resources are stored in the Registry, they inherit lots of additional capabilities that makes your resources suitable for enterprise deployments. Your resources can be server configuration files, PDF documents, spreadsheets that contain account data, Java archive (jar) files or anything else you or your organization wants to store and manage. Everything stored in the Registry will be secured with a comprehensive authorization mechanism, based on users and roles. All resources are versioned, such that it allows you to access and rollback previous versions. Basic metadata for resources are tracked and updated automatically, while you get provision to associate additional metadata as properties. All activities related to each and every resource in the Registry, is tracked in an activity log. Really, there are many more to items to add to this list of features. As you see, the built in feature set is more than adequate for a typical enterprise resource management scenario.

Still, there are occasions in which we would like to customize the standard behavior of resources, to suit very specific needs of a particular organization or a deployment. For example, we may want make sure all design documents have all their required sections, before publishing into the Registry. Alternatively, we may want to validate whether certain archived file types contain all required files. This is where handler-based extension mechanism in the WSO2 Registry comes in to play. Handlers allow you to insert custom behaviors for resources that match predefined criteria. Such custom behaviors can be anything from simple resource specific logging, to complicated automatic dependency imports. There is very little limitation on what you can do with handlers, specially with a combination of inter-related handler set. Actually, you can convert the generic WSO2 Registry product to your organization specific, smart enterprise storage, with the right combination of handlers.

Normal Processing of Resources in the Registry





Before going in to the details of handlers, it is useful to have a basic understanding on how resources and related operations are handled in the Registry. The diagram below illustrates a simplified form on the WSO2 Registry architecture. The embedded Registry section is our main interest here. Web and APP interfaces are mechanisms used to expose the Registry functionality, in a servlet deployment. Embedded Registry exposes all its functionalities via two main interfaces. They are: the Registry API and the User Manager API. The Registry API provides all resource related operations, while the User Manager API exposes user/role and authorization features. The User Manager component is not directly related to handlers, and we will ignore its details in our discussions. The Registry API has methods for all basic resource operations such as get(...), put(...), delete(...), as well as methods for other resource related operations such as accessing associations, versioning, etc. Repository component manages the actual storage of resources and the VersionRepository component handles all versioning related functionalities. Data access layer executes SQL statements to interact with the database for the purpose of performing various Registry operations.

 

 

Normal processing of resources



Having a general understanding of key Registry components, let's see what takes place when a Registry API method is invoked. If a method that directly relates to a resource is invoked, the Registry delegates it to the Repository after performing certain housekeeping tasks related to transactions, session management and logging. The Repository performs its operations interacting with the data access layer to perform low-level database operations. Once the Repository completes its operations, the Registry invokes the VersionRepository if the resource requires versioning. Similar to the Repository, all versioning operations will be taken over by the VersionRepository. Now, let's go through the process of handling operations that do not directly relate to resources. Such operations include commenting, tagging, associations, etc. As these operations are not related to resources, there is no need to call the Repository or VersionRepository components. The Registry directly accesses its data access layer, after performing typical housekeeping tasks that relates to such operations.

Now, we have an understanding of how resources are processed under normal conditions. It is time to look at how handlers will alter this behavior and custom behaviors fit in, in the picture.

 

Handlers



Handlers are pluggable components, that contain custom processing logic for handling resources. All handlers extend an abstract class named Handler, which provides default implementations for resource handling methods as well as a few utilities useful for concrete handler implementations. Handler implementations can provide alternative behaviors for basic resource related operations, by overwriting one or more methods in the Handler class. At this point, is useful to look at the Handler class listed below (please note that some details are omitted for clarity).

 

public abstract class Handler {
    public Resource get(RequestContext requestContext) throws RegistryException { return null; }
    public void put(RequestContext requestContext) throws RegistryException {}
    public void importResource(RequestContext requestContext) throws RegistryException {}
    public void delete(RequestContext requestContext) throws RegistryException {}
    public void putChild(RequestContext requestContext) throws RegistryException {}
    public void importChild(RequestContext requestContext) throws RegistryException {}
}



If you compare the Registry API with methods provided in the Handler class, you will notice that it provides quite similar method signatures to the resource handling methods of the Registry API. In fact, most methods provide alternative behaviors for their corresponding Registry API method. As events handled by handler implementations take place, each of the above methods are invoked by the Registry. Below is the list of method names and their corresponding events:

 

  • get - Reading (getting) a resource.
  • put - Adding a new resource or updating an existing resource.
  • importResource - Adding or updating a resource by importing its content from a URL.
  • delete - Deleting a resource.
  • putChild - Adding a child resource to a collection.
  • importChild - Importing a child resource to a collection.



You may have probably noticed, that all above methods have a single parameter of type RequestContext. RequestContext instance passed in to handler methods contain all the details about the current request. Below is the listing of the RequestContext class (note that some details and getter/setter methods are omitted for clarity).

 

public class RequestContext {
    private boolean processingComplete;
    private ResourcePath resourcePath;
    private String actualPath;
    private Resource resource;
    private String sourceURL;
    private String parentPath;
    private Collection parentCollection;
    private Map<String, Object> properties = new HashMap<String, Object>();
    private Registry registry;
    private Repository repository;
    private VersionRepository versionRepository;
}



Let's go through some of the interesting details provided in the RequestContext. resourcePath is a unique path used to refer to the resource related to the current request. resource attribute contains the actual resource instance targeted by the request. This could be null for certain operations. Therefore, it is recommended to do a null check, prior to accessing the attribute. sourceURL contains valid value only for the import operation. It provides the URL to import resource content. parentPath and parentCollection are path and the collection implementation of the parent resource respectively. It also provides a properties attribute to store handler specific information if necessary. registry, repository and versionRepository attributes contain references to the Registry, Repository and VersionRepository instances of the current Registry. These instances can be used to perform various operations, both related to resources and others, within the handler.

There is one more attribute that did not get out attention yet. That is the processingComplete field. It is possible to chain handlers in the Registry by configuring them to engage for the same criteria. Therefore, Registry will keep on executing other matching handlers, although processing is already done by one handler. This behavior is not recommended and may cause undesirable effects if not used with extreme care. Therefore, it is highly recommended to configure only one handler per request and set the processingComplete field to true to terminate the processing after the handler is invoked.

Given all of the above information, we can now summarize common steps in writing a handler. First you need to extend the Handler class and overwrite necessary methods. Implement whatever custom behaviors in overwritten methods. You may use information provided in the RequestContext instance, in your custom implementation. Once the custom behavior is completed, it is usually required to perform the actual requested operation (e.g. add the resource in put(...) method). Use the Repository instance provided in the RequestContext to perform the actual operation. If it is required to version the resource in put operation, use VersionRepository instance. Note, that it is not recommended to use the Registry instance to perform resource related operations (get, put, import, delete) inside a handler as it may cause infinite loops as the Registry tries to invoke all handlers again. Now, we understand how to write a handler. But there is a missing area. That is how to configure a handler to a given criteria. That is when we need filters.

Filters



Each handler is associated with a filter. Filters provide criteria for engaging handlers. Registry always evaluates the associated filter before invoking a handler. If the filter evaluates to true, it will invoke its associated handler. All filters should extend from a abstract class named Filter. Filter implementations should provide criteria for all handler methods by overwriting corresponding Filter method. Below is the listing of Filter class.

 

public abstract class Filter {
    public abstract boolean handleGet(RequestContext requestContext) throws RegistryException;
    public abstract boolean handlePut(RequestContext requestContext) throws RegistryException;
    public abstract boolean handleImportResource(RequestContext requestContext) throws RegistryException;
    public abstract boolean handleDelete(RequestContext requestContext) throws RegistryException;
    public abstract boolean handlePutChild(RequestContext requestContext) throws RegistryException;
    public abstract boolean handleImportChild(RequestContext requestContext) throws RegistryException;
}



Let's say that there is a handler H1 and filter F1 associated with it. If H1 is configured for GET method, Registry will first invoke the handleGet(...) method of F1. If it returns true, Registry will invoke the get(...) method of H1. If F1.handleGet(...) evaluates to false, Registry will not invoke H1.get(...). The RequestContext instance containing information about the current request is passed in to all methods of Filter. Filter implementations can access these information when deciding whether or not to engage its associated handler. Two built-in Filter implementations for most common filtering scenarios are shipped with the Registry. Those are the MediaTypeMatcher and the URLMatcher. MediaTypeMatcher filters based on the media type of the resource. URLMatcher evaluates to true, if the current resource path matches with a pre-configured regular expression.

 

Handler Architecture



Equipped with knowledge on handlers and filters, now it is time to look at how this fits in to the big picture. Handlers are positioned between the Registry and the rest of the backend we discussed in the section titled "Normal processing of resource in the Registry". If we consider the processing flow, the Registry invokes all the handlers (until a handler indicates that the processing is complete) before delegating resource related requests to the Repository and VersionRepository components. Therefore, handlers can provide completely customized behavior to the normal processing of resources. This is illustrated in the following figure:

 

Handler architecture

Handlers in Action - Writing a Sample Handler



We have discussed concepts and design of the handler mechanism. Now it is time, to put our knowledge in to action. Let's write a handler, that tags all JPG images stored in the Registry as "picture" and writes a handler specific log message saying that "JPG image is added to the path <resource path>". Let's begin by implementing the Handler class. We call this the JPGHandler. Open a Java editor and type in the following code:

 

public class JPGHandler extends Handler {
    private static final Log log = LogFactory.getLog(JPGHandler.class);
    public void put(RequestContext requestContext) throws RegistryException {
        String path = requestContext.getResourcePath().getPath();
        Resource resource = requestContext.getResource();
        requestContext.getRepository().put(path, resource);
        requestContext.getRegistry().applyTag(path, "picture");
        log.info("JPG image is added to the path " + path);
        requestContext.setProcessingComplete(true);
    }
}



Note that we overwrote only the put(...) method. We want to insert custom processing only when storing a JPG resource. We are satisfied with the default behavior for all other operations. Inside the put method, we first store the JPG image in the backend using the Repository object. Then we tag the stored resource with tag "picture". We use Registry API method applyTag(...) for that. Last operation is to add a log message. We use the resource path retrieved from the RequestContext and append it with the rest of the message. We don't want Registry to perform any further processing on our JPG image. So we set the processingComplete flag to true. That's it. We are done with writing the handler.

Next step is to write a Filter implementation to filter out JPG images. But we don't need to write our own Filter for this. MediaTypeMatcher provided with the Registry does exactly the same thing. Just that we need to configure it to filter JPG media type. Now we have everything we need to provide our customized JPG processing. Next step is to bundle our custom implementation as a jar file. Compile the JPGHandler class and create a jar file named jpghandler.jar. Place this jar file in the class path of the Registry. There is one more step to go. That is to register our new handler with the Registry. The Registry configuration is stored in a file named registry.xml. Edit this file and insert the following XML configuration:

<handler class="sample.JPGHandler" methods="PUT">
    <filter class="org.wso2.registry.jdbc.handlers.filters.MediaTypeMatcher">
        <property name="mediaType">image/jpeg</property>
    </filter>
</handler>



There should be a handler element for each handler registered in the Registry. class attribute of the handler element specifies the implementation class of the handler (above configuration assumes that the JPGHandler class is in the package sample). The methods attribute is used to determine methods to which this handler should be engaged. In this example, we only want this to be engaged for the put method. If it is required to engage a handler for multiple methods, method names have to be separated by commas. filter element contains the details of the Filter implementation associated with the handler. class attribute specifies the implementation class of the filter. In this case, we use the built-in MediaTypeMatcher class. Properties for the filter can be specified in the filter element. MediaTypeMatcher requires a single property named mediaType, which contains the media type to be filtered.

Now we are ready to see our handler in action. Easiest way to test this is to use the WSO2 Registry Web application. Deploy the wso2registry.war file in a servlet container. Copy the jpghandler.jar file to the wso2registry/WEB-INF/lib folder of the exploded Web application. Edit the wso2registry/WEB-INF/registry.xml file as mentioned above. Restart the servlet container. Now, you get a WSO2 Registry equipped with JPG handler. Go ahead and see how it works. Log in to the Registry Web console and click "Add Resource" icon. In the add resource section, select "Upload content from file" and give a path of a JPG file in your local file system. Media type field will be automatically filled with "image/jpeg" indicating that it is a JPG file. Click "Add" button to add the resource. Now, click on the name of the new resource and browse its resource page. You will see that a tag "picture" is automatically applied for the resource. Go ahead and add some more JPG files in to various locations of the Registry. Now select "Tag" in the search section at top right hand corner and type "picture" and hit enter. You will get all the JPG files stored in the Registry. This is because all JPG images are automatically tagged by the JPG handler. Now take a look at the log file of your servlet container. You will see that a log messages with text "JPG image is added to the path <resource path>" are added for all JPG images stored in the Registry.

Summary



WSO2 Registry offers enterprise grade resource storage with many added features. The handler framework built into the Registry product, allows it to be further extended by adding pluggable components named handlers. These handlers can provide customized processing for resource related operations and treat different resources in different ways. With this simple yet powerful extension mechanism, it is possible to convert the WSO2 Registry to a customized product that serves the exact requirements of various organizations and specific deployments.

 

Author

Chathura Ekanayake, Senior Software Engineer, WSO2 Inc. chathura at wso2 dot com

 

About Author

  • Chathura Ekanayake
  • PhD student
  • QUT