What is carbon?
Most probably when you search to write a carbon component I am pretty sure you are coming here with a background knowledge about the carbon platform. Thus I will not go into details of it.
In a nutshell carbon is the core of any WSO2 middleware product. WSO2 middleware products are built on top of Carbon core. It is based on Java OSGi technology, which allows components to be dynamically installed, started, stopped, updated and uninstalled, and it eliminates component version conflicts. [1]
What we are going to build?
We will write a simple service, which returns a collection of orders, a front end client to consume the service and display the orders. This will be done using 3 maven modules.
- Back end service
- Service stubs
- Frontend
We will be using Maven as our build tool. So if you do not have maven installed in your computer install maven before continue.
In order to deploy the components we create, we will be needing a WSO2 carbon product. In my case I use WSO2 AS. For the ease of development I am using intelliJ. You can use any IDE you are accustom to.
Let’s get started!!!
To begin with, let’s create the project structure. I am going to create a one maven project which will be the parent maven project for aforementioned 3 maven modules.
- Parent maven project
- Order-Manager
- groupId: org.wso2.carbon.sample.ordermanager
- artifactId: order-manager
- packaging: bundle
- Sub modules
- Back end server
- artifactId: order-manager-service
- packaging: bundle
- Service stub
- artifactId: order-manager-stub
- packaging: bundle
- Frontend
- artifacatId: order-manager-client
- packaging: bundle
Note: The sole purpose of parent maven project is to aggregate the 3 sub modules. If you are creating this from an IDE, you will get src folder for this project which provide no purpose. So you can delete it and continue. In addition, these names are arbitrary names I came up with. You have the liberty of choosing your own naming for these projects. If so make sure you do the necessary code changes accordingly.
If you are creating these projects/modules from an IDE, the pom files of each module will point the parent as the
Order-Manager project and pom.xml of Order-Manager will contain all 3 sub modules. You might need to update all 4 pom files otherwise.
If you reach up to here you will see your intelliJ project view as follows;
 |
| figure 1 - project structure |
Now its time to give attention to each project one at a time.
Order-Manager
So lets start from the Order-Manager project. There is nothing left for you to do if you use an IDE. I am just illustrating my pom file. Cross check against your parent project's pom file.
<?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">
<modelVersion>4.0.0</modelVersion>
<groupId>org.wso2.carbon.sample.ordermanager</groupId>
<artifactId>order-manager</artifactId>
<packaging>pom</packaging>
<version>1.0-SNAPSHOT</version>
<modules>
<module>order-manager-service</module>
<module>order-manager-stub</module>
<module>order-manager-client</module>
</modules>
</project>
snippet 1 - pom.xml for order-manager project
order-manager-service
Next lets move to
order-manager-server project. Here we create a service and bundle it as an OSGi component and deploy it in WSO2 Application server.
 |
| figure 2 - order-manager-service project structure |
I have written this simple service to fetch 2 orders which is added programmatically. Feel free to come up with your own architecture of the service.
package org.wso2.carbon.sample.ordermanager.service.beans;
/**
* Created by dinushab on 8/2/15.
*/
public class Item {
private String itemName;
private int itemPrice;
private int quantity;
public Item() {
}
public Item(String name, int price, int quantity) {
this.itemName = name;
this.itemPrice = price;
this.quantity = quantity;
}
public String getItemName() {
return itemName;
}
public void setItemName(String itemName) {
this.itemName = itemName;
}
public int getItemPrice() {
return itemPrice;
}
public void setItemPrice(int itemPrice) {
this.itemPrice = itemPrice;
}
public int getQuantity() {
return quantity;
}
public void setQuantity(int quantity) {
this.quantity = quantity;
}
}
snippet 2 - Item.java
package org.wso2.carbon.sample.ordermanager.service.beans;
/**
* Created by dinushab on 8/2/15.
*/
public class Order {
private int orderId;
private Item[] items;
public int getOrderId() {
return orderId;
}
public void setOrderId(int orderId) {
this.orderId = orderId;
}
public Item[] getItems() {
return items;
}
public void setItems(Item[] items) {
this.items = items;
}
public int getTotalCost() {
int totalCost = 0;
for (Item item : items) {
totalCost += item.getQuantity() * item.getItemPrice();
}
return totalCost;
}
}
snippet 3 - Order.java
package org.wso2.carbon.sample.ordermanager.service;
import org.wso2.carbon.sample.ordermanager.service.beans.Item;
import org.wso2.carbon.sample.ordermanager.service.beans.Order;
import java.util.Collection;
import java.util.HashMap;
/**
* Created by dinushab on 8/2/15.
*/
public class OrderManagerService {
private static HashMap<Integer, Order> orders = new HashMap<Integer, Order>();
private static final String LINE_SEPARATOR = System.getProperty("line.separator");
static {
Order order1 = new Order();
order1.setOrderId(1);
order1.setItems(getSampleItems());
orders.put(1, order1);
Order order2 = new Order();
order2.setOrderId(2);
order2.setItems(getSampleItems2());
orders.put(2, order2);
}
public OrderManagerService() {
}
/**
* Add a new Order
*
* @param newOrder
* Order
* @return String message saying whether add order was successful or not.
*/
public String addOrder(Order newOrder) {
String responseMsg;
if (orders.containsKey(newOrder.getOrderId())) {
responseMsg = "Order already added";
} else {
orders.put(newOrder.getOrderId(), newOrder);
responseMsg = "Order successfully added";
}
return responseMsg;
}
/**
* Return the order for the given order Id. For the moment the scenario of
* not having order for a given order id is not handled.
*
* @param orderId
* int
* @return Order
*/
public Order getOrder(int orderId) {
return orders.get(orderId);
}
/**
* Returns the order summary string.
*
* @param orderId
* int
* @return String
*/
public String getOrderSummary(int orderId) {
if (!orders.containsKey(orderId)) {
return "Order not found";
}
Order order = orders.get(orderId);
StringBuffer buffer = new StringBuffer();
buffer.append("*****************************************************" + LINE_SEPARATOR);
buffer.append("Your order number is : " + orderId + LINE_SEPARATOR);
buffer.append("Total cost for your order is : " + order.getTotalCost() + LINE_SEPARATOR);
buffer.append("*****************************************************");
return buffer.toString();
}
/**
* Return all orders added to the service.
*
* @return Collection<Order>
*/
public Collection<Order> getOrders() {
return orders.values();
}
private static Item[] getSampleItems() {
Item item1 = new Item("Bat", 4500, 3);
Item item2 = new Item("Pads", 5000, 2);
Item[] items = new Item[2];
items[0] = item1;
items[1] = item2;
return items;
}
private static Item[] getSampleItems2() {
Item item1 = new Item("Monitor", 15000, 3 );
Item item2 = new Item("Keyboard", 500, 4);
Item item3 = new Item("Mouse", 350, 4);
Item[] items = new Item[3];
items[0] = item1;
items[1] = item2;
items[2] = item3;
return items;
}
}
snippet 4 - OrderManagerService.java
After completion of writing the service logic, we need to add the configuration file which tells the OSGi container what is the service we are exposing and what is the service implementation class and associated meta-data with it. For that we introduce the services.xml file inside src/main/resources/META-INF folder.
<serviceGroup>
<service name="OrderService" scope="transportsession">
<transports>
<transport>https</transport>
</transports>
<parameter name="ServiceClass">org.wso2.carbon.sample.ordermanager.service.OrderManagerService</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>
snippet 5 -services.xml
Important:
- In services.xml file the service tag defines the service we expose. The name attribute defines the name of the service. We will be using this service name when we develop the client.
- Inside service tag, we specify a special parameter tag having the name attribute as ServiceClass. This is used to point the fully qualified class name of the service implementation.
Now we have completed all the necessary java classes and configuration files to our order manager service. In order to build it as an OSGi bundle, lets look at the pom.xml;
<?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-manager</artifactId>
<groupId>org.wso2.carbon.sample.ordermanager</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>order-manager-service</artifactId>
<packaging>bundle</packaging>
<build>
<plugins>
<plugin>
<groupId>org.apache.felix</groupId>
<artifactId>maven-bundle-plugin</artifactId>
<extensions>true</extensions>
<configuration>
<instructions>
<Bundle-SymbolicName>${pom.artifactId}</Bundle-SymbolicName>
<Bundle-Name>${pom.artifactId}</Bundle-Name>
<Export-Package>org.wso2.carbon.sample.ordermanager.service.*</Export-Package>
</instructions>
</configuration>
</plugin>
</plugins>
</build>
</project>
snippet 6 - pom.xml for order-manager-service project
Did you notice the value for packaging tag? Yes it is marked as bundle. This is because we need to bundle it as an OSGi bundle. To do this we use a plugin called maven-bundle-plugin. Recall your knowledge in OSGi bundles, we need to mark the packages that we expose from the bundle. For that we have specified Export-Package inside the plugin configuration. Otherwise our service classes will not be exposed to OSGi container leaving our service bundle useless.
Now navigate to root level of your order-manager-service project(where your pom.xml resides) in terminal and execute the following command mvn package. This will create the bundle inside the target folder of the project. (You can do the same thing using the maven plugin of your favorite IDE). You can see the order-manager-service-1.0-SNAPSHOT.jar created in the target directory of order-manager-service project. The name of the jar file may vary depending on the naming that you used.
Deploying the service
Now that we have created the service as a bundle, its time to deploy this inside one of WSO2's carbon product. In order to do that, open up the carbon product you downloaded(I will refer to the root directory of the carbon product as <carbon_home>) and copy the service bundle you created in to <carbon_home>/repository/components/dropins directory.
order-manager-stub
Now that we have deployed the service in the WSO2 AS, we need the wsdl of the service to generate the client stub. However, by default the carbon product does not allow you to get the wsdl of a service because you have a secure connection. So you need to do some setting changes to get this. Open up <carbon_home>/repository/carbon.xml and change the value of <HideAdminServiceWSDLs> from true to false.
Save the wsdl in order-manager-stub/src/main/resources/wsdl folder. Revert the change you did in carbon.xml file.
We need to update the pom.xml in order-manager-stub as follows;
<?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-manager</artifactId>
<groupId>org.wso2.carbon.sample.ordermanager</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>order-manager-stub</artifactId>
<packaging>bundle</packaging>
<dependencies>
<dependency>
<groupId>org.apache.axis2.wso2</groupId>
<artifactId>axis2</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>
<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>
<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>
<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" />
<arg line="-uri src/main/resources/wsdl/OrderService.wsdl -u -uw
-o target/generated-code -p org.wso2.carbon.sample.ordermanager.stub"/>
</java>
</tasks>
</configuration>
</execution>
</executions>
</plugin>
<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>
<plugin>
<groupId>org.apache.felix</groupId>
<artifactId>maven-bundle-plugin</artifactId>
<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.sample.ordermanager.*
</Export-Package>
<Import-Package>
!org.wso2.carbon.sample.ordermanager.*
</Import-Package>
<DynamicImport-Package>*</DynamicImport-Package>
</instructions>
</configuration>
</plugin>
</plugins>
</build>
</project>
snippet 7 - pom.xml of order-manager-stub project
Few things to note in this pom file are;
- Dependencies added for code generation
- Remote repository to access those dependencies
- If you use different naming for your project, make sure you point to the correct path for the wsdl file through the -uri switch and give the proper package through -p switch under the arguments for the maven-antrun-plugin.
- maven plugin to generate source code from the wsdl file.
- update the Export-Package and Import-Package tags for maven-bundle-plugin accordingly
Execute
mvn install command. This will generate source code from the wsdl, compile it, package it and install it in the local .m2 repository. That is all you need to do in order-manager-stub project.
order-manager-client
Finally we need to create a client to consume the service we created. Project structure of order-manager-client;
 |
| figure 3 - order-manager-client project structure |
The Client.java class will consume the stub generated in order-manager-stub project.
package org.wso2.carbon.sample.ordermanager.client;
import org.apache.axis2.AxisFault;
import org.apache.axis2.client.Options;
import org.apache.axis2.client.ServiceClient;
import org.apache.axis2.context.ConfigurationContext;
import org.apache.axis2.transport.http.HTTPConstants;
import org.wso2.carbon.sample.ordermanager.service.beans.xsd.Order;
import org.wso2.carbon.sample.ordermanager.stub.OrderServiceStub;
import java.rmi.RemoteException;
/**
* Created by dinushab on 8/2/15.
*/
public class Client {
OrderServiceStub stub;
public Client(ConfigurationContext ctx, String backendServerUrl, String cookie) throws AxisFault {
String serviceUrl = backendServerUrl + "OrderService";
stub = new OrderServiceStub(ctx, serviceUrl);
ServiceClient serviceClient = stub._getServiceClient();
Options options = serviceClient.getOptions();
options.setManageSession(true);
options.setProperty(HTTPConstants.COOKIE_STRING, cookie);
}
public String getOrderSummary(int orderId) throws RemoteException {
return stub.getOrderSummary(orderId);
}
public Order[] getOrders() throws RemoteException {
return stub.getOrders();
}
}
Snippet 8 - Client.java
This class will merely access the stub and call the getOrders method which will return the 2 orders we have added programmatically in our service class code.
Again, if you use different nameing for your project, make sure to change the OrderService text to the correct service name which you created.
The client will be accessible through the management console of your carbon product through a menu item. In order to do that we need give the configuration to the carbon product as to where to show the link and which jsp file to render in the browser so the user can consume the service. We do this from component.xml. The component.xml resides in src/main/resources/META-INF directory of your client project (refer figure 3).
<component xmlns="http://products.wso2.org/carbon">
<menus>
<menu>
<id>order_manager_menu</id>
<i18n-key>order.manager.menu</i18n-key>
<i18n-bundle>org.wso2.carbon.sample.ordermanager.ui.i18n.Resources</i18n-bundle>
<parent-menu>manage_menu</parent-menu>
<link>../ordermanager/index.jsp</link>
<region>region1</region>
<order>50</order>
<style-class>manage</style-class>
<require-permission>/permission/protected/manage</require-permission>
</menu>
</menus>
</component>
Snippet 9 - component.xml
i18n-key is the i18n key for the menu text which will be shown in the front end. The value for this key is picked up from org/wso2.carbon/sample/ordermanager/ui/i18n/Resources.properties file. Note that we specify only the name of the file without the extension.
order.manager.menu = Order Service
Snippet 10 - Resources.properties
link tag in component.xml points to the jsp which will be rendered when the user clicks on the menu item. The jsp file needs to be placed inside order-manager-client/src/main/resources/web/ordermanager/index.jsp. The main point is this file should be in any sub directory under order-manager-client/src/main/resources/web directory and point to it properly inside link tag.
<%@ 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.sample.ordermanager.client.Client" %>
<%@ page import="org.wso2.carbon.sample.ordermanager.service.beans.xsd.Order" %>
<%@ page import="org.wso2.carbon.sample.ordermanager.service.beans.xsd.Item" %>
<%@ 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);
Order[] orders = null;
try {
Client client = new Client(configContext, serverURL, cookie);
orders = client.getOrders();
} catch (Exception e) {
e.printStackTrace();
}
%>
<div id="middle">
<h2>Order Manager</h2>
<div id="workArea">
<%
if(orders != null) {
%>
<%
for(Order order:orders){
%>
<br />
<b><span style="padding-top: 5px;padding-bottom: 2px;">Order Id: <%= order.getOrderId() %> <span></b>
<br />
<table class="styledLeft" id="moduleTable">
<thead>
<tr>
<th width="30%">Name</th>
<th width="20%">Price</th>
<th width="20%">Quantity</th>
<th width="20%">Total</th>
</tr>
</thead>
<tbody>
<%
for(Item item:order.getItems()){
%>
<tr>
<td><%=item.getItemName()%></td>
<td>Rs. <%=item.getItemPrice()%></td>
<td><%=item.getQuantity()%></td>
<td>Rs. <%=(item.getQuantity()*item.getItemPrice())%></td>
</tr>
<%
}
%>
</tbody>
</table>
<%
}
%>
<%
}
%>
</div>
</div>
Snippet 11 - index.jsp
The index.jsp accesses the Client.java and calls the getOrders method. The jsp takes care of rendering 2 orders in a table. This is a simple example to show how the carbon components work. You can add additional operations and extend this service and the client as you wish.
<?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-manager</artifactId>
<groupId>org.wso2.carbon.sample.ordermanager</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>order-manager-client</artifactId>
<packaging>bundle</packaging>
<dependencies>
<dependency>
<groupId>org.wso2.carbon.sample.ordermanager</groupId>
<artifactId>order-manager-stub</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.felix</groupId>
<artifactId>maven-bundle-plugin</artifactId>
<extensions>true</extensions>
<configuration>
<instructions>
<Bundle-SymbolicName>${pom.artifactId}</Bundle-SymbolicName>
<Bundle-Name>${project.artifactId}</Bundle-Name>
<Export-Package>
org.wso2.carbon.sample.ordermanager.*
</Export-Package>
<Import-Package>
*;resolution:=optional
</Import-Package>
<Carbon-Component>UIBundle</Carbon-Component>
</instructions>
</configuration>
</plugin>
</plugins>
</build>
</project>
Snippet 12 - pom.xml of order-manager-client project
Note the dependency in the preceding pom.xml. It references the order-manager-stub project. This will be taken from the .m2 repository. Now run mvn package command to generate the bundle and copy it to the <carbon_home>/repository/components/dropins directory and restart the carbon product.
Notice the Order Service menu item in the left side panel. Clicking it will lead you to figure 4.