April 10, 2020
Estimated Post Reading Time ~

Developing first OSGi bundle

In this post, we will be creating and deploying our custom bundle into AEM.
While doing so, we will be learning concepts of OSGi Components and OSGi Services.

Software Modularity

In modern times, complex software can be thought of as a collection of various modules or components. These modules are normally independent of each other and modification in one module does not affect the other modules.

These modules interact with each other via an API. The API is defined as a set of classes and methods which can be used from other components.

If a module uses an API from another module, it has a dependency on the other module, i.e., it requires the other module exists and works correctly.

A module that is used by other components should try to keep its API stable. This avoids that a change affects other modules. But it should be free to change its internal implementation.

What is OSGi?

OSGi stands for Open Service Gateway initiative. It is a Java framework for developing and deploying modular software programs and libraries.

OSGi has two parts. 
  • The first part is a specification for modular components called bundles, which are commonly referred to as plug-ins. The specification defines an infrastructure for a bundle's life cycle and determines how bundles will interact.  
  • The second part of OSGi is a Java Virtual Machine (JVM)-level service registry that bundles can use to publish, discover and bind to services in a service-oriented architecture (SOA). 
The components and services can be dynamically installed, activated, de-activated, updated and uninstalled.

The OSGi specification has several implementations, for example, EquinoxKnopflerfish, and Apache Felix. AEM uses Apache Felix implementation. A bundle is the smallest unit of modularization which means in OSGi, a software component is a bundle. 

In AEM, along with out of the box bundles, we can also install our custom bundles very easily. So without further ado, let us start creating an OSGi bundle.

Creating a Bundle

We will now create an OSGi bundle that has OSGi service which reads JSON data using HTTP GET request from the URL - https://jsonplaceholder.typicode.com/todos/. Following are the steps to create an OSGi bundle - 

package org.redquark.demo.core.utils;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.MalformedURLException;
import java.net.URL;

import javax.net.ssl.HttpsURLConnection;

/**
 * @author Anirudh Sharma
 * 
 * This class has all the Network related calls
 */
public final class Network {

 private static final String USER_AGENT = "Mozilla/5.0";

 public static String readJson(String url) {

  try {
   /**
    * Get the URL object from the passed url string
    */
   URL requestURL = new URL(url);

   /**
    * Creating an object of HttpURLConnection
    */
   HttpsURLConnection connection = (HttpsURLConnection) requestURL.openConnection();

   /**
    * Setting the request method
    */
   connection.setRequestMethod("GET");

   /**
    * Setting the request property
    */
   connection.setRequestProperty("User-Agent", USER_AGENT);

   /**
    * Get the response code
    */
   int responseCode = connection.getResponseCode();

   if (responseCode == HttpsURLConnection.HTTP_OK) {

    /**
     * Getting an instance of BufferedReader to read the response returned
     */
    BufferedReader in = new BufferedReader(new InputStreamReader(connection.getInputStream()));

    /**
     * String which will read the response line by line
     */
    String inputLine;

    /**
     * StringBuffer object to append the string as a whole
     */
    StringBuffer response = new StringBuffer();

    /**
     * Read until empty line is encountered
     */
    while ((inputLine = in .readLine()) != null) {

     /**
      * Append each line to make the response as a whole
      */
     response.append(inputLine);
    }

    /**
     * Closing the BufferedReader to avoid memory leaks
     */
    in .close();

    /**
     * Return the response
     */
    return response.toString();
   }

  } catch (MalformedURLException e) {
   e.printStackTrace();
  } catch (IOException e) {
   e.printStackTrace();
  }

  return "";
 }
}
  • Create an AEM Multimodule Project in Eclipse.
  • Create an interface ReadJsonService in the package org.redquark.demo.core.services. This will be exposed to our service.
package org.redquark.demo.core.services;

/**
 * @author Anirudh Sharma
 * 
 * Service which will be exposed
 */
public interface ReadJsonService {

 /**
  * @return JSON String
  */
 public String getData();
}
  • Now we need to create an implementation class for this service which will have the business logic. Hence, create a class ReadJsonDataImpl in the package org.redquark.demo.core.services.impl.
package org.redquark.demo.core.services.impl;

import org.osgi.service.component.annotations.Component;
import static org.redquark.demo.core.constants.AppConstants.URL;
import org.redquark.demo.core.services.ReadJsonService;
import org.redquark.demo.core.utils.Network;

/**
 * @author Anirudh Sharma
 * 
 * Implementation of ReadJsonService
 */
@Component(immediate = true, service = ReadJsonService.class)
public class ReadJsonDataImpl implements ReadJsonService {


 /**
  * Overridden method which will read the JSON data via an HTTP GET call
  */
 @Override
 public String getData() {

  String response = Network.readJson(URL);

  return response;
 }

}
  • Now create constants class for storing project constants and Network class for the HTTP requests
package org.redquark.demo.core.constants;

/**
 * @author Anirudh Sharma
 * 
 * This class has all the project related constants
 */
public final class AppConstants {

 public static final String URL = "https://jsonplaceholder.typicode.com/todos/";

}
  • Let us understand this code. If you see the ReadJsonServiceImpl.java closely, you will see there is a @Component annotation. This annotation signifies that the given class is a component in AEM and the property service = ReadJsonService.class signifies that this is a service that is exposed via the ReadJsonService interface.

OSGi Components and Services

A service is an object that is registered in the OSGi Service Registry and can be looked up using its interface name(s). The only prerequisite is that a service should implement some interface. For example, I could register a runnable object under the java.lang.Runnable interface and clients could look it up using that interface name.

A "component" tends to be an object whose lifecycle is managed, usually by a component framework such as Declarative Services (DS), Blueprint or iPOJO.

A component may have any of the following features, in combination or alone:
  1. A component may be started and stopped; this would be considered an "active" component, though that is also an informal term. A component that doesn't need to be started or stopped is called passive.
  2. A component may publish itself as an OSGi service.
  3. A component may bind to or consume OSGi services.
  4. In general, using a component framework is the easiest way to work with OSGi services because the framework will manage the binding to the services that you want to consume. For example, you could say that your component "depends on" a particular service, in which case the component will only be created and activated when that service is available -- and also it will be destroyed when the service becomes unavailable.
  • Once the Java code is done, we can now deploy the code using eclipse and maven.
  • Now go to the AEM instance and in CRXDE, you should be seeing your project under the /apps folder.
Demo project
  • Right-click on /apps/<your-project>/content/, then Create... and then Create Component... 
  • In the dialog, fill the values as shown in the below screenshot 
Create a Component
  • Click Next, then OK
  • Go to /apps/<your-project>/components/content/sampleComponent and open the sampleComponent.jsp file and add the below code and save. Note: We are using JSP only for simplicity. In the latest versions of AEM, we should use HTL (formerly called Sightly) and the use of JSP is discouraged.
<%@include file="/libs/foundation/global.jsp"%>
<% org.redquark.demo.core.services.ReadJsonService service = sling.getService(org.redquark.demo.core.services.ReadJsonService.class);
String result = service.getData();
%>

<h2>This page invokes the AEM ReadJsonService</h2>
<h3>RESPONSE: <%=result%></h3>
  • Add the sample component on a page and you should be able to see the JSON string returned on that page as follows
    • [{
        "userId": 1,
        "id": 1,
        "title": "delectus aut autem",
        "completed": false
       },
       {
        "userId": 1,
        "id": 2,
        "title": "quis ut nam facilis et officia qui",
        "completed": false
       },
       {
        "userId": 1,
        "id": 3,
        "title": "fugiat veniam minus",
        "completed": false
       },
       {
        "userId": 1,
        "id": 4,
        "title": "et porro tempora",
        "completed": true
       }, ...
      ]

Conclusion

Congratulations!! you have created your first bundle with a component and service and deployed it in the AEM server. We have battled with the custom code in AEM and on the same lines we can create multiple components and services in one bundle. 

You can find the complete code of this project on my GitHub. Feel free to fork or open issues, if any.


By aem4beginner

No comments:

Post a Comment

If you have any doubts or questions, please let us know.