CQ Development - OSGi bundles and Components

Recently one of my blog follower 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 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, 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 details.

Steps:
1)      Create an OSGi bundle.
2)      Create a OSGi service (using Felix/OSGi annotations).
3)      Write an 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) screen shot:




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 which we want to access from a component or other java files (with in same bundle or from other bundles).

2.       Create a 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 a 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 “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 an utility class with a static method so that the code to access service is wrapped inside it and we don’t need to write same code again and again. This how the utility class should look like:
In order to access the “FormattingService” we’ll write an utility class with a static method so that the code to access service is wrapped inside it and we don’t need to write 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 those 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 CQ component. For simple things we should avoid creating services. We should create services only for those functionalities that fits in to the OSGi definition.

You can read more about OSGi annotations at:

Comments

Kike Chapin said…
Hi Suryakand,

In the ComponentUtil class, you used the FrameworkUtil.getBundle() method, I tried it, but my CQ instance didn't recognize this method. Is it necessary update the org.osgi.framework.FrameworkUtil package?
Suryakand said…
FrameworkUtil class is part of org.osgi.core.jar and the version of JAR that I have in my CQ is 4.1.0. Are you sure that you have same version of org.osgi.core.jar in your CQ instance? If not then try upgrading your CQ to 5.4 version.
Also, even if you see a red mark/error on that method the code will compile. When I use this method with my CQ I also get red mark/error in my editor but when I build bundle it gets compiled and works perfectly fine.

Try compiling your code and see if you can proceed or not? Let me know you feedback and if there is any issue I’ll look in to it…

Thanks & Regards
-- Surya
Kike Chapin said…
Surya

It worked fine. You are right about red mark/error. This a a bug of CQ 5.4.

Thanks a lot!!
Anonymous said…
Hi Suryakand,
Thank you - I'm starting to learn CQ development as well and this is very helpful.

I do have one (basic) question: If I need to add a jar file that my new class requires (say for example, I need a JSON parser) - what are the steps I need to do?

Thanks!
Danny.
Suryakand said…
As you might already know that CQ works based on OSGi enabled Bundles/JARs. An OSGi enabled JAR file is almost similar to regular JAR file, the only difference is that it has some META information about the classes (components, services etc.) that is exposed via META-INF file. To add a new JAR file as a dependency the easiest way is to add that JAR file in “lib” directory (of your project, refer the snapshot from step 1.C above) and rebuild the bundle.

OR,

You can create a separate project/bundle (by following the steps that I have mentioned in this post, skip step 2 & 3) and add the required JAR file in generated lib folder and rebuild the project/bundle. By adding JAR file using this second method we are keeping our JAR file isolated from your application code and, let’s say if we want to upgrade to a newer version of JAR file then we don’t need to build our project code (because we have our JAR in a separate project, we just need to build the bundle that we have created for JAR file).

Hope this will help you, if you have any question please feel free to post your question.
Anonymous said…
When I build the bundle the bundle does not get deployed. I have to manually deploy it- is this the case?
Thanks for your blog very helpful
Suryakand said…
This comment has been removed by the author.
Suryakand said…
Can you make sure that when you are building the bundle a new version of jar is getting generated in install folder? If you see a new JAR each time you build a bundle and still it is not getting deployed automatically then check following things:

1) You current bundle should not have cyclic dependency on other bundles.
2) Try adding an activator class and add some comments in it and make sure that you see those comments in your log file.

Check your log file for any error, if you see any error then please post it here and might be I'll be able to suggest something specific...
dips said…
Hi Surya,

Instead from a jsp I am calling the ComponentUtil.getFormattingService().getDateByInterval("M", "MM-dd-YYY", -1); from a java class.While I run this java class as a java application I get an error that says "The archive: /WebContent/apps/geometrixx/install/cq-wcm-geometrixx-5.4.0.jar which is referenced by the classpath, does not exist."

But this jar do exists in the same path.Please help me to resolve this error.
Dan said…
The Src Javadoc taags used in this guide are depreciated in CQ 5.5, read more about it and what you need to do here:
http://www.6dlabs.com/blog/dklco/2012-05-16/scr-javadoc-tags-deprecated-cq-55#.T7U1vvdZ5sw
Suryakand said…
Thanks Dan for posting an update.
Unknown said…
Hi,

Thanks for the post , I am creating my first bundle in CQ 5.5 using it.

I could see the OSGI bundle active in there. I am actually stumbled on one point, I dont see FormattingService in my components ? Did you / any one faced the same issue ?
Anonymous said…
Hi Surya,

I can see "return (FormattingService)bundleContext.getService(
bundleContext.getServiceReference(FormattingService.class.getName()))" statement returning the FormattingServiceImpl object.What if there are two services implementing the same interface, how will I create one specific object in this case?
Unknown said…
Is it possible to give a input for service using console?
Unknown said…
Hi Suryakant,

I need your help. I am using FrameworkUtil class to get the bundle context so that I can get my required workflow to execute on the deactivate event. The problem is that we use 2 jars dependency namely -
org.osgi
org.osgi.core


Both jars have FrameworkUtil class and R4 jar has only createFilter() method where as the osgi.core has proper method. I included both and it worked fine in my eclipse but its goiving compilation error when I build using hudson build server. Mentioned below is the error I get. Please help me to resolve it.

[ERROR] COMPILATION ERROR :
[INFO] -------------------------------------------------------------
[ERROR] /home/bsbuild/data/hudson_home/jobs/CQ5 WCM(solr)/workspace/solrsearch-core/src/main/java/com/solr/cq/listener/DeactivateEventHandler.java:[48,45] cannot find symbol
symbol : method getBundle(java.lang.Class)
location: class org.osgi.framework.FrameworkUtil
[INFO] 1 error

Popular posts from this blog

AEM, FORM Submission & Handling POST requests

AEM - Query list of components and templates

AEM 6.3 - Bundle Whitelisting - Deprecation of administrative authentication

AEM as a Cloud Service (AEMaaCS) – Architecture Overview