quarta-feira, 8 de janeiro de 2014

Using BRMS/Drools 5 REST API - Part 1

As you might have noticed already, everything comes with a RESTful API. REST is just awesome. A friend of mine works everyday with Business Rule Management System, BRMS, and it has a simple,but useful, client API. In this series of articles, we will create a Java wrapper for the REST API and then a simple JavaFX 2.2 application to use it.
In this article I'll describe the reusable wrapper I created. On next article we will talk about the JavaFX application.

* I call a programming language wrapper  an API that allow us to access a REST API without caring about the HTTP operations, but only with the programming language itself. Our wrapper is aimed to help us access the REST API provided by BRMS without having to handle HTTP requests.

About BRMS/Drools

Drools is a community project  which is part of the product created by Red Hat called BRMS. The BRMS 5.x documentation defines it as follow:

"JBoss Enterprise BRMS Platform is a business rules management system for the management, storage, creation, modification, and deployment of business rules and business processes. Web-based user interfaces and plug-ins for JBoss Developer Studio provide users with different roles the environment suited to their needs. JBoss Enterprise BRMS provides specialized environments for business analysts, rules experts, developers, and rule administrators."

It organize its stuff using the concept of Package, Assets and Rules. According to the BRMS documentation:

Rules
Rules provide the logic for the rule engine to execute against. A rule includes a name, attributes, a 'when' statement on the left hand side of the rule, and a 'then' statement on the right hand side of the rule.

Packages
Packages are deployable collections of assets. Rules and other assets must be collected into a package before they can be deployed. When a package is built, the assets contained in the package are validated and compiled into a deployable package.

Assets
Anything that can be stored as a version in the asset repository is an asset. This includes rules, packages, business processes, decision tables, fact models, and DSLs.

Our java wrapper will cover the manipulation of packages and assets!

The BRMS 5 REST API

To access and modify assets, packages, rules, BRMS has a REST API. The API is easy to use, it uses the representation of resources in  JSON, XML, ATOM and Octo-Stream(for binary content). All main HTTP methods are used, so we can access and modify the target resource. The URL base for the API: {host}/jboss-brms/rest/. 

The API was greatly explored by my co-worker Fernando Ribeiro, see his presentation and the blog post about it. There's also the Drools REST API documentation, so I see no reason on why I should talk more about it here :)


A Java Wrapper for the REST API

Let's make a Java wrapper for this REST API. The first thing we must to do is with we are dealing with to model into objects. We can, of course, use other approach, but I always prefer to model to objects.
I created three object: DroolsPackage, Asset and Category. A summarized version of each class is below (excluding getters and setters):

@XmlRootElement(name="package")
public class DroolsPackage {
        private List assets;
        private String checkInComment;
        private String description;
        private PackageMetadata metadata;
        private String sourceLink;
        private String title;
        private int version;

        // getters and setters
        
        @Override
        public String toString() {                
                return ToStringBuilder.reflectionToString(this);
        }
        
        @XmlRootElement(name="metadata")
        public static class PackageMetadata {

                private boolean achived;
                private Date created;
                private Date lastModified;
                private String lastContributor;        
                private String uuid;
                private String state;
  
  // getters and setters
        }
}


@XmlRootElement
public class Asset {
        private String binaryLink;
        private String checkInComment;
        private String description;
        private String refLink;
        private String sourceLink;
        private String type;
        private int version;
        private AssetMetadata metadata;

        // getters and setters

        @XmlRootElement(name = "metadata")
        public static class AssetMetadata {
                private String binaryContentAttachmentFileName;
                private Date created;
                private boolean disabled;
                private String createdBy;
                // enum?
                private String format;
                private Date lastModified;
                private String note;
                private String title;
                private String uuid;

                // getters and setters

                @Override
                public String toString() {
                        return ToStringBuilder.reflectionToString(this);
                }
        }

        @Override
        public String toString() {
                return ToStringBuilder.reflectionToString(this);
        }
}

@XmlRootElement
public class Category {
        private String path;
        private String refLink;

        // getters and setters
        
        @Override
        public String toString() {                
                return "["+ path+","+ refLink+"]";
        }
}

After finishing the model, it was created an interface to define what operations we want to implement:

public interface DroolsAPIService {
        public List getPackages();
        public DroolsPackage getPackage(String title);        
        public List getCategories();
        public Category getCategory(String name);
        public List getAssetsByPackage(DroolsPackage droolsPackage);
        public List getAssetsByCategory(Category category);
        public Asset getAsset(String packageName, String assetName);        
        public DroolsPackage createOrUpdatePackage(DroolsPackage droolsPackage);        
        public Asset createAsset(String packageTitle, byte[] content, String assetName);
        public Asset createAsset(String packageTitle, String title, String summary);
        public Asset updateAsset(String packageTitle, Asset asset);        
        public String updateAssetSource(String pkgName, String assetName, String newSourceCode);
        public String getSourceCode(String pkgTitle, String assetName);        
        public void removePackage(String title);
        public void removeAsset(String pkgTitle, String assetName);
}

After defining our methods, it was created an implementation that makes use of JAX-RS 2 client API, using RESTEasy 3.x. The most important point on the implementation besides making the request itself, is how it handles the resources URIs and the use of the Atom provider that comes with RESTEasy.
To implement the API gradually, I created a boilerplate class called DroolsClient to access the implementation and expose only the methods I wanted to implement, so I could, in parallel, work in a client for it. This class also allows me to change the implementation for other ways to access the data. I mean, currently we have a REST API, but we could extend it to support other ways to access BRMS/Drools without too much pain and also allow the users of the API to switch to another implementation.
Here's the DroolsClient source code. Notice how it basically delegates the calls to the actual service class:
public class DroolsClient {

        private String username, password, baseUrl;

        DroolsAPIService service;

        // we will keep them loaded like a cache
        private List packages;
        private List categories;

        // TODO: Execeptions handling for 404 and 401

        public DroolsClient(String baseUrl, String username, String password) {
                this.baseUrl = baseUrl;
                this.username = username;
                this.password = password;
                createDroolsAPIService();
        }

        public List getPackages(boolean refresh) {
                if (packages == null || refresh) {
                        packages = service.getPackages();
                }
                return packages;
        }

        public List getPackages() {
                return getPackages(false);
        }

        public List getCategories(boolean refresh) {
                if (categories == null || refresh) {
                        categories = service.getCategories();
                }
                return categories;
        }

        public List getCategories() {
                return getCategories(false);
        }

        public Category getCategory(String name) {
                return service.getCategory(name);
        }

        // TODO caching of assets
        public List getAssetsByPackage(String droolsPackageTitle) {
                DroolsPackage pkg = service.getPackage(droolsPackageTitle);
                return getAssetsByPackage(pkg);
        }

        public List getAssetsByPackage(DroolsPackage droolsPackage) {
                return service.getAssetsByPackage(droolsPackage);
        }

        public List getAssetsByCategory(Category category) {
                return service.getAssetsByCategory(category);
        }

        public List getAssetsByCategory(String categoryName) {
                Category cat = service.getCategory(categoryName);
                return getAssetsByCategory(cat);
        }

        public Asset getAsset(String pkgName, String assetName) {
                return service.getAsset(pkgName, assetName);
        }

        public DroolsPackage getPackage(String name) {
                return service.getPackage(name);
        }

        public DroolsPackage createOrUpdate(DroolsPackage droolsPackage) {
                return service.createOrUpdatePackage(droolsPackage);
        }

        public Asset createOrUpdate(String pkgTitle, String title, String summary) {
                return service.createAsset(pkgTitle, title, summary);
        }

        public void removePackage(String title) {
                service.removePackage(title);
        }

        public Asset updateAsset(String pkgTitle, Asset asset) {
                return service.updateAsset(pkgTitle, asset);
        }

        public void removeAsset(String pkgTitle, String assetName) {
                service.removeAsset(pkgTitle, assetName);
        }

        public String updateAssetSource(String pkgTitle, String assetName,
                        String newSourceCode) {
                return service.updateAssetSource(pkgTitle, assetName, newSourceCode);
        }

        public String getSourceCode(String pkgTitle, String assetName) {
                return service.getSourceCode(pkgTitle, assetName);
        }

        private void createDroolsAPIService() {
                Credentials credentials = new UsernamePasswordCredentials(username,
                                password);
                DefaultHttpClient httpClient = new DefaultHttpClient();
                // TODO: Check host from AuthScope. Is this right?
                httpClient.getCredentialsProvider().setCredentials(
                                new AuthScope("localhost", 8080), credentials);
                service = new DroolsAPIServiceImpl(baseUrl,
                                new ApacheHttpClient4Engine(httpClient));
        }

}

Other uses for the DroolsClient class is also add caching. I shouldn't make the caching on the service implementation class or I'd have to re-implement it for all the other possible implementations.

Testing

To make sure everything is working as expected, it was created a test class. It will simple use the DroolsClient method and run the tests against a BRMS/Drools installation. To use it, make sure you are running a local BRMS. The test also shows how to use the wrapper itself.

Improvements

The client isn't finished, but it's possible to use it already. Some possible improvements:
  • Add error handling at the service layer
  • Improve logging
  • Add caching in the DroolsClient class

What's next?

In next post I'll be describing the JavaFX client I created for this API. It's a simple client that uses FXML and basically allow us to manipulate the elements from a given BRMS installation.

Nenhum comentário:

Postar um comentário