April 11, 2020
Estimated Post Reading Time ~

CQ Development - OSGi bundles and Components

Recently one of my blog followers asked about how we can use the OSGi bundle (components and services) in CQ’s JSP/components. In this post, I am going to enlist a few key things about creating and accessing the OSGi bundle in CQ components.

So, a CQ component is nothing but a script file (either a JAVA or JSP) and the primary goal of a component is rending the markup but, a component may need to access OSGi services in order to execute some business logic that is part of OSGi bundle. I am going to use CRXDE (an eclipse flavored IDE for CQ development).

First of all, I’ll enlist the steps to access any component that you have written in a bundle and then I’ll explain it in detail.

Steps:
1) Create an OSGi bundle.
2) Create an OSGi service (using Felix/OSGi annotations).
3) Write a utility class to access the components that we have created in setp#2.

Explanation:
1. Create an OSGi bundle
1.A) To create a bundle open up your CRXDE and right-click on “apps” folder ->Build -> Create Bundle (as shown in below) screenshot:

1.B) You’ll get a “Create bundle” pop-up, fill the details as shown below and hit “Finish” button:

1.C) Once the bundle is created successfully you’ll see following folder/package in your CRXDE IDE:

So, now we have a bundle with the directory structure (package) that we defined in step 1.B. In step#2 we’ll be creating a new class that we want to access from a component or other java files (within the same bundle or from other bundles).

2. Create an OSGi service
For this example, we are going to create a “FormattingService” that will be used for formatting dates.
2.A) Create an interface “FormattingService” in “com.sample.osgi.components” package.
package com.sample.osgi.components;
public interface FormattingService {
      public String getDateByInterval(String WMY, String dateFormat, int interval);
}

2.B) Create and implementation class “FormattingServiceImpl” in same package  “com.sample.osgi.components”. We need to annotate this class (as shown below) in order to expose it as an OSGi service to other bundles and CQ components.

package com.sample.osgi.components;

import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import org.apache.commons.lang.StringUtils;
import org.apache.sling.commons.osgi.OsgiUtil;
import org.osgi.service.component.ComponentContext;

/**
 * A sample OSGi service class that will be used by other OSGi bundles or components
 * to get past or future date.
 *
 * @scr.component immediate="false" label="Date formatting service"
 *                        description="An utility service to get past or future date"
 *                name="com.sample.osgi.components.FormattingServiceImpl"
 * @scr.property  name="date.format" label = "Expected date format"
 *                        description="Configuration to set expected date format"
 * @scr.service
 */
public class FormattingServiceImpl implements FormattingService {
 //this can be configured via OSGi configuration console using "default.date.section" property
 private String dateFormat = null;
 
 /**
  * This method will be invoked only once when the FormattingService is 
  * intialized by OSGi container.
  * @param componentContext
  */
 protected void activate(ComponentContext componentContext) {
  this.dateFormat = OsgiUtil.toString(
    componentContext.getProperties().get("date.format"), "MM/dd/yyyy");
 }

 /**
  * Utility method to choose a future or back date
  * @param WMY W - Week, M - Month, Y - Year
  * @param interval integer value that represents future or past interval
  * @return String date
  */
 public String getDateByInterval(String WMY, String dateFormat, int interval) {
  String formattedDate = "";
  DateFormat expectedDateFormat = null;
  Calendar calendar = Calendar.getInstance();
  
  if(StringUtils.isBlank(dateFormat)) {
   dateFormat = getDateFormat();
  }

  expectedDateFormat = new SimpleDateFormat(dateFormat);
  
  if(StringUtils.equalsIgnoreCase(WMY, "W")){
   calendar.add(Calendar.WEEK_OF_YEAR, interval);
  } else if(StringUtils.equalsIgnoreCase(WMY, "M")){
   calendar.add(Calendar.MONTH, interval);
  } else if(StringUtils.equalsIgnoreCase(WMY, "Y")){
   calendar.add(Calendar.YEAR, interval);
  }

  formattedDate = expectedDateFormat.format(calendar.getTime());

  return formattedDate;
 }

 /**
  * @return the dateFormat
  */
 public String getDateFormat() {
  return dateFormat;
 }

 /**
  * @param dateFormat the dateFormat to set
  */
 public void setDateFormat(String dateFormat) {
  this.dateFormat = dateFormat;
 }

}

2.C) Build the bundle by right clicking on “com.sample.osgi.bundle.bnd” -> Build -> Build Bundle


Once the bundle is created go to (Felix web console) using the URL on your machine: http://localhost:4502/system/console/bundles and the bundle that we have built should be available in Felix console and it should be in ”Active” as shown below:

Now, go to the “components” tab as shown below, and you’ll see that the service “FormattingService” that we have written is registered with Felix OSGi container and is ready to consume:
3. Code to access component/service from other java classes and components.
In order to access the “FormattingService”, we’ll write a utility class with a static method so that the code to access service is wrapped inside it and we don’t need to write the same code again and again. This how the utility class should look like:
In order to access the “FormattingService”, we’ll write a utility class with a static method so that the code to access service is wrapped inside it and we don’t need to write the same code again and again. This how the utility class should look like:

package com.sample.osgi.components;

import org.osgi.framework.BundleContext;
import org.osgi.framework.FrameworkUtil;
import com.newcorp.ccp.resolver.PortalResolver;

public class ComponentUtil {

 /**
  * Utility method that will return an FormattingService instance from OSGi container
  * @return
  */

 public static FormattingService getFormattingService() {
  BundleContext bundleContext = FrameworkUtil.getBundle(FormattingService.class).getBundleContext();
  return (FormattingService)bundleContext.getService(
    bundleContext.getServiceReference(FormattingService.class.getName()));

 }
}

That’s it! Now you can use this class to get an instance of your service from any CQ component or any other class in other OSGi bundles, here is sample code that you can write in your CQ component’s JSP:

<%String formattedDate = ComponentUtil.getFormattingService().getDateByInterval("M", "MM-dd-YYY", -1);%>

I hope this will help the folks who are working on CQ.

I have created this example just to explain the concept of how we can create OSGi services and access it from the CQ component. For simple things we should avoid creating services. We should create services only for those functionalities that fit into the OSGi definition.

You can read more about OSGi annotations at:



By aem4beginner

No comments:

Post a Comment

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