Creating and installing a custom WSO2 Carbon feature - I

This is the first of the two articles I am going to write about creating and installing WSO2 Carbon features to an existing WSO2 product. Most of the content here is based on the excellent tutorial written by Amal Gunatilake on the same topic. You can find it here.

WSO2 Carbon is an award-winning, component-based, service oriented platform for the enterprise-grade WSO2 middleware products. It is 100% open source and delivered under Apache License 2.0 by WSO2 Inc 1. WSO2 provides comprehensive “data-to-screen” open source products based on carbon which include WSO2 ESB, WSO2 API Manager, WSO2 Complex event processor, WSO2 Application Server, etc 2. Learning to write a carbon feature will give you the ability to customize these products very easily to meet your specific requirements.

Introduction to WSO2 Carbon

Carbon (omitting the “WSO2” prefix from here on) can be thought of as “Eclipse for Server”. Similar to Eclipse, Carbon is built as a collection of well defined OSGi bundles (or components). This makes carbon a modular middleware platform which supports easy module installation, uninstallation, update, etc. WSO2 has built a very rich enterprise middleware products stack by exploiting modular nature of the Carbon platform 2.

Initially, WSO2 developed their products separately by allocating different teams for different products. These teams worked on developing their respective products by adding new features. Some of the customers who were using multiple products from WSO2 began requesting features which they found useful in one product (say, WSO2 Application Server) but not found in others (WSO2 ESB, etc.).

WSO2 came up with Carbon to increase reuse of code between their products. They packed a set of reusable components among the products in Carbon. Currently, Carbon core provides middleware components for enterprise security, clustering, logging, statistics, caching, tracing, throttling among other facilities and a management UI framework 4. A WSO2 product is essentially a combination of a subset of the core Carbon components and product specific features developed using Carbon framework (See the diagram below). This has made products more flexible to deploy, easy to extend and manage 3.

WSO2 carbon uses well established products like Equinox P2 (for provisioning Carbon components/features), Apache Axis2 (to create web services/clients as Carbon components) and Tomcat server (to deploy webapps, JSP support) to provide a rich framework for middleware application development.


WSO2 Carbon platform – Source: WSO2 Library

Carbon components, features and feature repositories

A Carbon component is a set of OSGi bundles that is developed within the carbon framework. A Carbon component can use services provided by the Carbon framework like registry service, user manager service, etc. There are three main parts in a Carbon component: Server component (back end), UI component (front end) and a Service stub which facilitates the communication between the front end and the back end.

A Carbon feature is an installable form of one or more logically related Carbon components. Features can be installed into any carbon based product using its feature manager. Once a carbon component is built, it is released as a feature to enable easy install using a feature manager. Feature manager in WSO2 products use Equinox P2 (Provisioning Platform). Equinox P2 can be used to provision any OSGi bundle (remember that Carbon is based on OSGi, hence all the Carbon features / components are essentially OSGi bundles).

Equinox P2 looks for features inside local/remote repositories. Hence, the feature manager in WSO2 products also looks for features inside repositories. A feature repository is a collection of features from which we can select the feature(s) we want to install in our product.

Carbon feature development process

There are three main tasks we need to do if we want to create and install a custom carbon feature.

  1. Component development

    We need to develop the Carbon component including back end component, front end component and common bundles (if there is any). Note that neither back end nor front end components are compulsory. All of these components are developed as OSGi bundles.

  2. Feature development

    Next we need to generate the corresponding feature for the component. Back end feature and front end feature are generated separately and then a composite feature is developed by combining them.

  3. Installing the feature

    We can build the feature along with the product (this is how the WSO2 develops the products) or install the feature using the feature manager after generating a feature repository. We are going to follow the second approach in this tutorials.

Creating the project structure

Let’s create a carbon feature that will mimic an order processing system and add it to a WSO2 product using the product’s feature manager. The feature will allow the user to add new orders and display the current orders in the front end UI. We’ll create a single project called order-processor. We need our project to do the following tasks in the given order.

  1. Create a Carbon component (with Server/UI components)
  2. Create a Carbon feature for the carbon component
  3. Develop the feature repository

order-processor

Let’s create three sub-projects inside order-processor to do each of the above tasks. Also, We are going use Maven to build the project. If you are not familiar with Maven, please go through the Maven getting started guide. We’ll place the main pom.xml (required by Maven) file inside the main project. pom.xml and all the three subdirectories are empty for the time being. Below is the current directory structure. Let’s denote the project root by . (i.e., absolute path of order-processor).

    order-processor
    ├── order-processor-components
    ├── order-processor-features
    ├── order-processor-repository
    └── pom.xml

    4 directories, 1 files

Let’s add content to the ./pom.xml. We need to tell Maven about the sub-projects and give a project name, version, etc.

    <?xml version="1.0" encoding="UTF-8"?>
    <project xmlns="http://maven.apache.org/POM/4.0.0"
             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
        
        <!-- POM version, required -->
        <modelVersion>4.0.0</modelVersion>
        
        <!-- Naming the project, required-->
        <!-- groupId is unique among an organization --> 
        <groupId>org.wso2.carbon</groupId>
        <!-- artifactId is the name of the project -->
        <artifactId>order-processor</artifactId>
        <!-- version of the project -->
        <version>1.0.0</version>
        
        <!-- Inform maven about the sub-projects -->
        <modules>
            <module>order-processor-components</module>
            <module>order-processor-features</module>
            <module>order-processor-repository</module>
        </modules>
     
        <!-- We use packaging type `pom` since this project is basically a
        container for sub-projects -->
        <packaging>pom</packaging>
        
        <!-- A human friendly name -->
        <name>WSO2 Carbon feature - Order Processor</name>
    </project>
file: ./pom.xml

order-processor-components

Now let’s concentrate on the sub-project order-processor-components. We are going to create our server component, UI and the service stub inside this project. Let’s create the project folders for those sub-projects and the pom.xml (all empty for the time being) inside the order-processor-components project. Below is the updated directory structure.

    order-processor
    ├── order-processor-components
    │   ├── org.wso2.carbon.orderprocessor.server
    │   ├── org.wso2.carbon.orderprocessor.stub
    │   ├── org.wso2.carbon.orderprocessor.ui
    │   └── pom.xml
    ├── order-processor-features
    ├── order-processor-repository
    └── pom.xml

    7 directories, 2 files

Now let’s write the pom.xml for order-process-components sub-project. Since this sub-project is also a container for a set of sub-projects, we can reuse most of the structure from order-processor’s pom file. Additionally, we will have to add details of the parent project in it. This is done to make sure that the sub-projects properly inherit from the parent project. Our pom.xml for order-processor-components should look similar to something like below.

    <?xml version="1.0" encoding="UTF-8"?>
    <project xmlns="http://maven.apache.org/POM/4.0.0"
             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
             
        <parent>
            <groupId>org.wso2.carbon</groupId>
            <artifactId>order-processor</artifactId>
            <version>1.0.0</version>
        </parent>
        
        <modelVersion>4.0.0</modelVersion>
     
        <artifactId>order-processor-components</artifactId>
     
        <packaging>pom</packaging>
     
        <modules>
            <module>org.wso2.carbon.orderprocessor.server</module>
            <module>org.wso2.carbon.orderprocessor.stub</module>
            <module>org.wso2.carbon.orderprocessor.ui</module>
        </modules>
        
    </project>
file: ./order-processor-components/pom.xml

Note that the above pom inherits groupId and version from the parent pom.

order-processor-features

Now let’s add a pom file to the order-processor-features sub-project as well. We will use a very simple pom file for the time being. We will be updating this file later.

    <?xml version="1.0" encoding="UTF-8"?>
    <project xmlns="http://maven.apache.org/POM/4.0.0"
             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
        
        <parent>
            <artifactId>order-processor</artifactId>
            <groupId>org.wso2.carbon</groupId>
            <version>4.2.0</version>
        </parent>
        
        <modelVersion>4.0.0</modelVersion>
     
        <artifactId>order-processor-features</artifactId>
     
        <packaging>pom</packaging>
        
        <!-- We will update this pom later -->
        
    </project>
file: ./order-processor-features/pom.xml

order-processor-repository

Add a pom file to the sub-project order-processor-repository as well. This will also be updated later.

    <?xml version="1.0" encoding="UTF-8"?>
    <project xmlns="http://maven.apache.org/POM/4.0.0"
             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
        
        <parent>
            <artifactId>order-processor</artifactId>
            <groupId>org.wso2.carbon</groupId>
            <version>4.2.0</version>
        </parent>
        
        <modelVersion>4.0.0</modelVersion>
     
        <artifactId>order-processor-repository</artifactId>
     
        <packaging>pom</packaging>
        
        <!-- We will update the pom later -->
        
    </project>
file: ./order-processor-repository/pom.xml

Now the directory structure should look something similar to the following.

    order-processor
    ├── order-processor-components
    │   ├── org.wso2.carbon.orderprocessor.server
    │   ├── org.wso2.carbon.orderprocessor.stub
    │   ├── org.wso2.carbon.orderprocessor.ui
    │   └── pom.xml
    ├── order-processor-features
    │   └── pom.xml
    ├── order-processor-repository
    │   └── pom.xml
    └── pom.xml

    7 directories, 4 files

Now we are done with the project structure. Time do some coding! Let’s start by creating the Carbon component. We need to update the three empty projects created under order-processor-components.

Creating the Carbon component (1/3): Server component

Let’s write the server component for our order processor (which should be placed under org.wso2.carbon.orderprocessor.server directory). This component should be able to store new orders and return existing orders. We will develop this as an Axis2 service (remember: carbon framework uses Axis2).

We are going to implement the server using two Java files: Order.java and OrderProcessor.java. Order.java is a flimsy model of a real world order and OrderProcessor.java is the service implementation which provides methods for creating new orders and getting the existing order list. Since we are building the project using Maven, we need to put these source files inside /src/main/java respecting their package definitions. Below is an example implementation of the above two files.

    package org.wso2.carbon.orderprocessor.server;

    import org.wso2.carbon.orderprocessor.server.models.Order;
    import java.util.HashMap;

    public class OrderProcessor {
        private int orderId;
        private static HashMap<Integer, Order> orderMap = new HashMap<Integer, Order>();

        public OrderProcessor() {
            orderId = 1;
        }

        public int createOrder(int customerId, int itemId, int quantity) {

            Order newOrder = new Order(customerId, itemId, orderId, quantity);
            orderMap.put(new Integer(orderId), newOrder);
            orderId++;
            return orderId - 1;
        }

        public Order[] getOrders() {
            return (Order[]) orderMap.keySet().toArray();
        }
    }
file: OrderProcessor.java


    package org.wso2.carbon.orderprocessor.server.models;

    import java.util.Arrays;

    public class Order {
        private int customerId;
        private int itemId;
        private int orderId;
        private int quantity;

        public Order(int customerId, int itemId, int orderId, int quantity) {
            this.customerId = customerId;
            this.itemId = itemId;
            this.orderId = orderId;
            this.quantity = quantity;
        }

        public int getCustomerId() {
            return customerId;
        }

        public int getItemId() {
            return itemId;
        }

        public int getQuantity() {
            return quantity;
        }

        public int getOrderId() {
            return orderId;
        }

        public void setCustomerId(int customerId) {
            this.customerId = customerId;
        }

        public void setItemId(int itemId) {
            this.itemId = itemId;
        }

        public void setOrderId(int orderId) {
            this.orderId = orderId;
        }

        public void setQuantity(int quantity) {
            this.quantity = quantity;
        }
    }
file: Order.java

Also, we need to add an service description file, services.xml for the created Axis2 service. services.xml is used to convey information like the name of the service, class that contains the service implementation, transports used to connect with the server to Axis2 runtime. This file should be placed inside the META-INF folder under the maven resources folder (/src/main/resources/). You can read more about the services.xml here.

    <serviceGroup>
        <service name="OrderProcessor" scope="transportsession">
            <transports>
                <transport>https</transport>
            </transports>
            <parameter name="ServiceClass">org.wso2.carbon.orderprocessor.server.OrderProcessor</parameter>
        </service>

        <parameter name="adminService" locked="true">true</parameter>
        <parameter name="hiddenService" locked="true">true</parameter>
        <parameter name="AuthorizationAction" locked="true">/permission/protected/manage</parameter>
    </serviceGroup>
file: services.xml

Now the only thing we are left with is writing the pom.xml file for the server project. Since the back end server component needs to be packaged as an OSGi bundle, we need to build the project using the maven-bundle-plugin. Read more about OSGi bundles and maven-bundle-plugin in Creating a simple OSGi bundle and running it with Equinox OSGi container.

    <?xml version="1.0" encoding="UTF-8"?>
    <project xmlns="http://maven.apache.org/POM/4.0.0"
             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
        
        <!-- Inherit from the parent project -->
        <parent>
            <groupId>org.wso2.carbon</groupId>
            <artifactId>order-processor-components</artifactId>
            <version>1.0.0</version>
        </parent>
        
        <modelVersion>4.0.0</modelVersion>

        <artifactId>org.wso2.carbon.orderprocessor.server</artifactId>

        <!-- Backend server component is packaged as an OSGi bundle -->
        <packaging>bundle</packaging>

        <build>
            <plugins>
                <!-- Plugin used for creating an OSGi bundel -->
                <plugin>
                    <groupId>org.apache.felix</groupId>
                    <artifactId>maven-bundle-plugin</artifactId>
                    <version>1.4.0</version>
                    <extensions>true</extensions>
                    <configuration>
                        <instructions>
                            <Bundle-SymbolicName>${pom.artifactId}</Bundle-SymbolicName>
                            <Bundle-Name>${pom.artifactId}</Bundle-Name>
                            <Export-Package>org.wso2.carbon.orderprocessor.server.*</Export-Package>
                        </instructions>
                    </configuration>
                </plugin>
            </plugins>
        </build>

    </project>
file: ./order-processor-components/org.wso2.carbon.orderprocessor.server/pom.xml

After all these additions, the current directory structure should look something similar to the following.

    order-processor
    ├── order-processor-components
    │   ├── org.wso2.carbon.orderprocessor.server
    │   │   ├── pom.xml
    │   │   └── src
    │   │       └── main
    │   │           ├── java
    │   │           │   └── org
    │   │           │       └── wso2
    │   │           │           └── carbon
    │   │           │               └── orderprocessor
    │   │           │                   └── server
    │   │           │                       ├── models
    │   │           │                       │   └── Order.java
    │   │           │                       └── OrderProcessor.java
    │   │           └── resources
    │   │               └── META-INF
    │   │                   └── services.xml
    │   ├── org.wso2.carbon.orderprocessor.stub
    │   ├── org.wso2.carbon.orderprocessor.ui
    │   └── pom.xml
    ├── order-processor-features
    │   └── pom.xml
    ├── order-processor-repository
    │   └── pom.xml
    └── pom.xml

    18 directories, 8 files

Now you can build the server project by typing mvn clean install at the project root directory. You’ll see a new jar file named org.wso2.carbon.orderprocessor.server-1.0.0.jar inside the newly created target folder project root.

Creating the Carbon component (2/3): Service stub

To create the service stub, we need the WSDL of the service we created. To get the WSDL, first copy the generated org.wso2.carbon.orderprocessor.server-1.0.0.jar into <PRODUCT_HOME>/repository/components/dropins in any WSO2 carbon based product. Here <PRODUCT_HOME> denotes the root directory of the product. You can find a list of carbon based products here. After downloading, start the product by running <PRODUCT_HOME>/bin/wso2server.sh if you are in a linux environment or <PRODUCT_HOME>/bin/wso2server.bat if you are using Windows.

After the product has started, you’ll see a link to the product’s management console at the bottom of the command line. Open that link in the browser and login using a valid user-name and a password (You can use the default admin account with the password admin). In Manage --> Services --> List, you’ll be able to see available services. Click the WSDL1.1 table entry of one of these services and you can see the WSDL file. Let’s say you click the WSDL entry for HelloService. In the browser address bar, you will see an address like the following.

http://10.100.7.79:9765/services/HelloService?wsdl

Make sure that you have set HideAdminServiceWSDLs to false in <PRODUCT_HOME>/repository/conf/carbon.xml. Then replace HelloService by OrderProcessor and save the new WSDL file as OrderProcessor.wsdl in some temporary location. Otherwise the WSDL will not be accessible.

Now we are going to build the project structure inside the stub project folder (org.wso2.carbon.orderprocessor.stub). We don’t have any java code in this project. We only need to store OrderProcessor.wsdl inside src/main/resources and write the pom.xml of the project to create the service stub from this WSDL file.

To create the service stub component, first we need to generate Java code from our WSDL file. Apache Axis2 provides a class (WSDL2Java) for this code generation. We can run this Java class as an Ant (another build tool used for building Java projects. Read more about Apache Ant) task inside our pom.xml using maven-ant-run plugin. Then we need to create an OSGi bundle out of the generated code. That can be done using maven-bundle-plugin but this is a bit tricky.

By default, Maven does not recognize generated code as part of the project’s source. To bundle the generated code, we need Maven to see them as a part of the project’s source (then only the bundle plugin can ask for the generated code from Maven). So, to add the generated code directory to the Maven project, we are going to use build-helper-maven-plugin. Our build process consists of running the above three Maven plugins in the correct order. Below is the pom.xml which does this. Comments provide additional clarifications.

    <?xml version="1.0" encoding="UTF-8"?>
    <project xmlns="http://maven.apache.org/POM/4.0.0"
             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
        
        <parent>
            <artifactId>order-processor-components</artifactId>
            <groupId>org.wso2.carbon</groupId>
            <version>1.0.0</version>
        </parent>
        
        <modelVersion>4.0.0</modelVersion>
     
        <artifactId>org.wso2.carbon.orderprocessor.stub</artifactId>
     
        <!-- We want to create an OSGi component -->
        <packaging>bundle</packaging>
     
        <!-- Dependencies for the code generation -->
        <dependencies>
            <dependency>
                <groupId>org.apache.axis2.wso2</groupId>
                <artifactId>axis2</artifactId>
                <version>1.6.1.wso2v10</version>
            </dependency>
            <dependency>
                <groupId>org.apache.axis2.wso2</groupId>
                <artifactId>axis2-client</artifactId>
                <version>1.6.1.wso2v10</version>
            </dependency>
            <dependency>
                <groupId>org.apache.ws.commons.axiom.wso2</groupId>
                <artifactId>axiom</artifactId>
                <version>1.2.11.wso2v4</version>
            </dependency>
            <dependency>
                <groupId>wsdl4j.wso2</groupId>
                <artifactId>wsdl4j</artifactId>
                <version>1.6.2.wso2v4</version>
            </dependency>
        </dependencies>
     
        <!-- Repository to download the dependencies -->
        <repositories>
            <repository>
                <id>wso2-nexus</id>
                <name>WSO2 internal Repository</name>
                <url>http://maven.wso2.org/nexus/content/groups/wso2-public/</url>
                <releases>
                <enabled>true</enabled>
                <updatePolicy>daily</updatePolicy>
                <checksumPolicy>ignore</checksumPolicy>
                </releases>
            </repository>
        </repositories>
     
        <build>
            <plugins>
                
                <!-- Run the WSDL2Java as a Ant task -->
                <plugin>
                    <groupId>org.apache.maven.plugins</groupId>
                    <artifactId>maven-antrun-plugin</artifactId>
                    <version>1.1</version>
                    <executions>
                        <execution>
                            <id>source-code-generation</id>
                            <phase>process-resources</phase>
                            <goals>
                                <goal>run</goal>
                            </goals>
                            <configuration>
                                <tasks>
                                    <!-- Set the classpath appropriately for running the WSDL2Java class
                                    so that the dependencies can be found -->
                                    <path id="wsdl2java.classpath">
                                        <pathelement location="${settings.localRepository}
                                            /org/apache/ws/commons/axiom/wso2/axiom/1.2.11.wso2v4/axiom-1.2.11.wso2v4.jar"/>
                                        <pathelement location="${settings.localRepository}
                                            /org/apache/axis2/wso2/axis2-client/1.6.1.wso2v10/axis2-client-1.6.1.wso2v10.jar"/>
                                        <pathelement location="${settings.localRepository}
                                            /org/apache/axis2/wso2/axis2/1.6.1.wso2v10/axis2-1.6.1.wso2v10.jar"/>
                                    </path>
                                    
                                    <java classname="org.apache.axis2.wsdl.WSDL2Java" fork="true">
                                        <classpath refid="wsdl2java.classpath" />
                                        <!-- Set the commandline argument passed to WSDL2Java class.
                                        Pass the path of the WSDL to be used, path for writing output code
                                        and packaging information. Read more about the commandline options
                                        for WSDL2Java at http://axis.apache.org/axis2/java/core/docs/reference.html -->
                                        <arg line="-uri src/main/resources/OrderProcessor.wsdl
                                            -u -uw -o target/generated-code -p org.wso2.carbon.orderprocessor.stub"/>
                                    </java>
                                </tasks>
                            </configuration>
                        </execution>
                    </executions>
                </plugin>
     
                <!-- Add the generated code to the Maven project -->
                <plugin>
                    <groupId>org.codehaus.mojo</groupId>
                    <artifactId>build-helper-maven-plugin</artifactId>
                    <executions>
                        <execution>
                            <id>add-source</id>
                            <phase>generate-sources</phase>
                            <goals>
                                <goal>add-source</goal>
                            </goals>
                            <configuration>
                                <sources>
                                    <source>target/generated-code/src</source>
                                </sources>
                            </configuration>
                        </execution>
                    </executions>
                </plugin>
     
                <!-- Create the OSGi bundle -->
                <plugin>
                    <groupId>org.apache.felix</groupId>
                    <artifactId>maven-bundle-plugin</artifactId>
                    <version>1.4.0</version>
                    <extensions>true</extensions>
                    <configuration>
                        <instructions>
                            <Bundle-SymbolicName>${project.artifactId}</Bundle-SymbolicName>
                            <Bundle-Name>${project.artifactId}</Bundle-Name>
                            <Private-Package>
                            </Private-Package>
                            <Export-Package>
                                org.wso2.carbon.orderprocessor.*
                            </Export-Package>
                            <Import-Package>
                                !org.wso2.carbon.orderprocessor.*
                            </Import-Package>
                            <DynamicImport-Package>*</DynamicImport-Package>
                        </instructions>
                    </configuration>
                </plugin>
                
            </plugins>        
        </build>    
    </project>
file: ./order-processor-components/org.wso2.carbon.orderprocessor.stub/pom.xml

Now the directory structure should look like the following.

    order-processor
    ├── order-processor-components
    │   ├── org.wso2.carbon.orderprocessor.server
    │   │   ├── pom.xml
    │   │   └── src
    │   │       └── main
    │   │           ├── java
    │   │           │   └── org
    │   │           │       └── wso2
    │   │           │           └── carbon
    │   │           │               └── orderprocessor
    │   │           │                   └── server
    │   │           │                       ├── models
    │   │           │                       │   └── Order.java
    │   │           │                       └── OrderProcessor.java
    │   │           └── resources
    │   │               └── META-INF
    │   │                   └── services.xml
    │   ├── org.wso2.carbon.orderprocessor.stub
    │   │   ├── pom.xml
    │   │   └── src
    │   │       └── main
    │   │           └── resources
    │   │               └── OrderProcessor.wsdl
    │   ├── org.wso2.carbon.orderprocessor.ui
    │   └── pom.xml
    ├── order-processor-features
    │   └── pom.xml
    ├── order-processor-repository
    │   └── pom.xml
    └── pom.xml

    21 directories, 10 files

Creating the Carbon component (3/3): UI component

Now let’s create the final part of our Carbon component: UI component. We are going to create the UI component in the org.wso2.carbon.orderprocessor.ui project. UI component is nothing but a normal Java client (which depends on the generated stub in the previous section) exposed through JSP (JavaServer Pages). We need to create an additional XML file named component.xml which supplies instructions to Carbon on how to load the created JSP file.

Our client programme is initialized according to the configuration information passed from the the Carbon’s Axis2 runtime. It uses the generated stub to call the services we exposed in our Service (OrderProcessor.java). You can find a sample implementation of the client below. This file should be placed under src/main/java after creating the directory structure to match the package definition.

    package org.wso2.carbon.orderprocessor.ui;
     
    import org.apache.axis2.client.Options;
    import org.apache.axis2.client.ServiceClient;
    import org.apache.axis2.context.ConfigurationContext;
    import org.wso2.carbon.orderprocessor.server.models.xsd.Order;
    import org.wso2.carbon.orderprocessor.stub.OrderProcessorStub;

    import java.rmi.RemoteException;
     
    public class OrderProcessorClient {
        private OrderProcessorStub stub;

        /* Constructor takes Axis2 client configurations passed from the Carbon runtime */
        public OrderProcessorClient(ConfigurationContext configCtx, String backendServerURL, String cookie) throws Exception {
            String serviceURL = backendServerURL + "OrderProcessor";
            stub = new OrderProcessorStub(configCtx, serviceURL);
            
            ServiceClient client = stub._getServiceClient();
            Options options = client.getOptions();
            
            options.setManageSession(true);
            options.setProperty(org.apache.axis2.transport.http.HTTPConstants.COOKIE_STRING, cookie);
        }
     
        public Order[] getOrders() throws Exception{
            try {
                return stub.getOrders();
            } catch (RemoteException e) {
                String msg = "Cannot get the list of orders." + 
                    " Backend service may be unavailable.";
                throw new Exception(msg, e);
            }
        }
        
        public int createOrder(int customerId, int itemId, int quantity) throws Exception {
            try {
                return stub.createOrder(customerId, itemId, quantity);
            } catch (RemoteException e) {
                String msg = "Cannot create the order." + 
                    " Backend service may be unavailable.";
                throw new Exception(msg, e);
            }
        }
        
    }
file: OrderProcessorClient.java

Next we should write the index.jsp file. Main task of the index.jsp is to create an instance of the client programme with the existing Carbon context information and show current orders in a simple table after fetching them from the OrderProcessor service. index.jsp should be placed in a path starting from src/main/resources/web/. In our example, let’s put our file inside a directory called orderprocessor inside the web directory.

    <%@ page import="org.apache.axis2.context.ConfigurationContext" %>
    <%@ page import="org.wso2.carbon.CarbonConstants" %>
    <%@ page import="org.wso2.carbon.ui.CarbonUIUtil" %>
    <%@ page import="org.wso2.carbon.utils.ServerConstants" %>
    <%@ page import="org.wso2.carbon.ui.CarbonUIMessage" %>
    <%@ page import="org.wso2.carbon.orderprocessor.ui.OrderProcessorClient" %>
    <%@ page import="org.wso2.carbon.orderprocessor.models.xsd.Order" %>
    <%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %>
    <%@ taglib uri="http://wso2.org/projects/carbon/taglibs/carbontags.jar" prefix="carbon" %>
    <%
            String serverURL = CarbonUIUtil.getServerURL(config.getServletContext(), session);
            ConfigurationContext configContext =
                    (ConfigurationContext) config.getServletContext().getAttribute(CarbonConstants.CONFIGURATION_CONTEXT);
            String cookie = (String) session.getAttribute(ServerConstants.ADMIN_SERVICE_COOKIE);
     
            OrderProcessorClient client;
            Order[] orders;
     
            try {
                client = new OrderProcessorClient(configContext, serverURL, cookie);
                orders = client.getOrders();
            } catch (Exception e) {
                CarbonUIMessage.sendCarbonUIMessage(e.getMessage(), CarbonUIMessage.ERROR, request, e);
    %>
                <script type="text/javascript">
                       location.href = "../admin/error.jsp";
                </script>
    <%
                return;
        }
    %>
     
    <div id="middle">
        <h2>Order Processor</h2>
     
        <div id="workArea">
            <table class="styledLeft" id="moduleTable">
                    <thead>
                    <tr>
                        <th width="25%">Order ID</th>
                        <th width="25%">Customer ID</th>
                        <th width="25%">Item ID</th>
                        <th width="25%">Quantity</th>
                    </tr>
                    </thead>
                    <tbody>
               <%
                       for(Order order:orders){
               %>
                       <tr>
                           <td><%=order.getOrderId()%></td>
                           <td><%=order.getCustomerId()%></td>
                           <td><%=order.getItemId()%></td>
                           <td><%=order.getQuantity()%></td>
                       </tr>
               <%
                       }
               %>
                    </tbody>
             </table>
        </div>
    </div>
file: index.jsp

Exercise : Update the index.jsp to support addition of new orders.

Next we have to write the component.xml which is used by the Carbon framework (or the product based on the Carbon framework) to link our index.jsp. We are going to link index.jsp to the Manage menu in the navigation panel. component.xml contains a set of configuration information needed by Carbon (or the Carbon based product) to do this. This file should be placed under src/main/resources/META-INF/.

    <component xmlns="http://products.wso2.org/carbon">
        <menus>
            <menu>
                <id>orderprocessor_menu</id>
                <i18n-key>orderprocessor.menu</i18n-key>
                <i18n-bundle>org.wso2.carbon.orderprocessor.ui.i18n.Resources</i18n-bundle>
                <parent-menu>manage_menu</parent-menu>
                <link>../orderprocessor/index.jsp</link>
                <region>region1</region>
                <order>50</order>
                <style-class>manage</style-class>
                <require-permission>/permission/protected/manage</require-permission>
            </menu>
        </menus>
    </component>
file: component.xml

Below note describes what each element in the component.xml does. This note is extracted from an article by Amal Gunatilake 5.

i18n stands for internationalization (i-18 letters-n) of software products. It is a process which enables a software to support multiple languages. Properties of a software that needs to be localized (Documentation, menu names, label text, etc.) are stored in a i18n bundle rather than hard coding them in the software itself. That’s why we are storing the name of our menu item in a separate i18n bundle. Then we can easily change the menu name to a different language if we wanted to do so in future. According to our component.xml, our i18n bundle is the Resources.properties file found under org/wso2/carbon/orderprocessor/ui/i18n/ inside src/main/resources/. Note the extension .properties at the end. This file contain the i18n key orderprocessor.menu and it’s value.

orderprocessor.menu=Orders
file: Resources.properties

Finally, we will write the pom.xml for the UI component. pom given below is self explanatory for the most part. Important things to remember are that we are going to pack the UI component as an OSGi bundle and that the UI component depends on the server stub. Additionally, when configuring the OSGi bundle, we add a separate instruction to inform the Carbon provisioning platform (Built on Equinox P2, remember?) that this is a Carbon UI component.

    <?xml version="1.0" encoding="UTF-8"?>
    <project xmlns="http://maven.apache.org/POM/4.0.0"
             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
             
        <parent>
            <artifactId>order-processor-components</artifactId>
            <groupId>org.wso2.carbon</groupId>
            <version>1.0.0</version>
        </parent>
        
        <modelVersion>4.0.0</modelVersion>
        
        <artifactId>org.wso2.carbon.orderprocessor.ui</artifactId>
        
        <packaging>bundle</packaging>
     
        <dependencies>
            <dependency>
                <groupId>org.wso2.carbon</groupId>
                <artifactId>org.wso2.carbon.orderprocessor.stub</artifactId>
                <version>1.0.0</version>
            </dependency>
        </dependencies>
     
        <build>
            <plugins>
                <plugin>
                    <groupId>org.apache.felix</groupId>
                    <artifactId>maven-bundle-plugin</artifactId>
                    <version>1.4.0</version>
                    <extensions>true</extensions>
                    <configuration>
                        <instructions>
                            <Bundle-SymbolicName>${pom.artifactId}</Bundle-SymbolicName>
                            <Bundle-Name>${project.artifactId}</Bundle-Name>
                            <Export-Package>
                                org.wso2.carbon.orderprocessor.*
                            </Export-Package>
                            <Import-Package>
                                *;resolution:=optional
                            </Import-Package>
                            <Carbon-Component>UIBundle</Carbon-Component>
                        </instructions>
                    </configuration>
                </plugin>
            </plugins>
        </build>
    </project>
file: ./order-processor-components/org.wso2.carbon.orderprocessor.ui/pom.xml

Here is the final directory structure so that you can verify all the files are placed correctly.

    order-processor
    ├── order-processor-components
    │   ├── org.wso2.carbon.orderprocessor.server
    │   │   ├── pom.xml

    │   │   └── src
    │   │       └── main
    │   │           ├── java
    │   │           │   └── org
    │   │           │       └── wso2
    │   │           │           └── carbon
    │   │           │               └── orderprocessor
    │   │           │                   └── server
    │   │           │                       ├── models
    │   │           │                       │   └── Order.java
    │   │           │                       └── OrderProcessor.java
    │   │           └── resources
    │   │               └── META-INF
    │   │                   └── services.xml
    │   ├── org.wso2.carbon.orderprocessor.stub
    │   │   ├── pom.xml
    │   │   └── src
    │   │       └── main
    │   │           └── resources
    │   │               └── OrderProcessor.wsdl
    │   ├── org.wso2.carbon.orderprocessor.ui
    │   │   ├── pom.xml
    │   │   └── src
    │   │       └── main
    │   │           ├── java
    │   │           │   └── org
    │   │           │       └── wso2
    │   │           │           └── carbon
    │   │           │               └── orderprocessor
    │   │           │                   └── ui
    │   │           │                       └── OrderProcessorClient.java
    │   │           └── resources
    │   │               ├── META-INF
    │   │               │   └── component.xml
    │   │               ├── org
    │   │               │   └── wso2
    │   │               │       └── carbon
    │   │               │           └── orderprocessor
    │   │               │               └── ui
    │   │               │                   └── i18n
    │   │               │                       └── Resources.properties
    │   │               └── web
    │   │                   └── orderprocessor
    │   │                       └── index.jsp
    │   └── pom.xml
    ├── order-processor-features
    │   └── pom.xml
    ├── order-processor-repository
    │   └── pom.xml
    └── pom.xml

    38 directories, 15 files

Now you can build the submodule order-processor-components by typing mvn clean install at the project root. In the next article we will discuss how to create a Carbon feature and install it to an WSO2 product.

References

  1. Introducing Carbon, WSO2 Carbon Documentation
  2. WSO2 Products Overview
  3. Introduction to WSO2 carbon platform, WSO2 Carbon Webinar Series
  4. Carbon Architecture, WSO2 Carbon Documentation
  5. How to write a WSO2 Carbon component
  6. Understanding the Carbon architecture