Featured post

Docker setup for Liferay 7 with MySQL

Showing posts with label osgi. Show all posts
Showing posts with label osgi. Show all posts

Thursday, 3 May 2018

Background Task Liferay 7 or DXP Part 1


Executing or scheduling background task is always an important activity for developers.

There are so many scenarios where background tasks are used like import/export, sending bulk notification or email, processing orders etc.

You can find this interesting link here which talks in length for background tasks.
What it does not offer is implementation example and changes for background task in Liferay 7/DXP

By following few Liferay conventions, we can easily use this weapon.
In this example I will explain creation of background task, pass parameters and handle errors if any.

In next example I will demonstrate check status, progress and display it over UI.


We will be creating two osgi components for this activity.
1. Create osgi module for background task creation and register it to handler
2. Use above module to create background tasks


Render method to create background task every time portlet loads.

@Overridepublic void doView(RenderRequest renderRequest, RenderResponse renderResponse) throws IOException, PortletException {
    Random random = new Random(12);

    HttpServletRequest request = PortalUtil
            .getHttpServletRequest(renderRequest);

    ThemeDisplay themeDisplay = (ThemeDisplay) renderRequest
            .getAttribute(WebKeys.THEME_DISPLAY);

    ServiceContext serviceContext = null;
    try {
        serviceContext = ServiceContextFactory.getInstance(renderRequest);
    } catch (PortalException e) {
        logger.error("Eror in getting service context", e.getCause());
    }

    // This taskContextMap can be used as transporter to background job
    Map taskContextMap = new HashMap<>();
    taskContextMap.put("processName", "testing " + random.nextInt());
    taskContextMap.put("totalNodes", String.valueOf(random.nextInt()));
    //taskContextMap.put("serviceContext", serviceContext);    try {
        // Adding the job to liferay background manager
        com.liferay.portal.kernel.backgroundtask.BackgroundTask backgroundTask = backgroundTaskmanager.addBackgroundTask(themeDisplay.getUserId(),
                themeDisplay.getScopeGroupId(), SampleBackgroundTaskExecutor.class.getName(),SampleBackgroundTaskExecutor.class.getName(),taskContextMap, serviceContext);
        // With returned background object you can check status, id etc.
        renderRequest.setAttribute("backgroundTaskId",
                backgroundTask.getBackgroundTaskId());

    } catch (PortalException e) {
        logger.error(e.getCause());
    } catch (SystemException e) {
        logger.error(e.getCause());
    }




Create Background Task Executor

@Component(
        immediate = true,
        property = {"background.task.executor.class.name=com.netcracker.cabinet.background.executor.MigrationBackgroundTaskExecutor"}, 
        // Without this property osgi will not register this as background executor/handler
        service = BackgroundTaskExecutor.class)
public class SampleBackgroundTaskExecutor extends BaseBackgroundTaskExecutor {


isSerial - True

// if it's not serial then multiple instances of this executor can run parallel, to run it in queue mode, we use isSerial true
@Overridepublic boolean isSerial() {
    return true;
}



Main execute method for the Job

public BackgroundTaskResult execute(BackgroundTask backgroundTask)
      throws Exception {
      // taskContextMap which is sent by the caller
      Map taskContextMap = backgroundTask.getTaskContextMap();

      String taskName = (String)taskContextMap.get("processName") ;
      String totalNodes = (String)taskContextMap.get("totalNodes");

      //ServiceContext serviceContext  = (ServiceContext) taskContextMap.get("serviceContext");
   if(LOGGER.isDebugEnabled()){
      LOGGER.debug("Task Name : "+ taskName);
   }

   BackgroundTaskVO messageContent = new BackgroundTaskVO();
   messageContent.setTotalNodes(totalNodes);

   // Sending the data to util for MessageBus
   SampleDataHandlerStatusMessageSenderUtil.sendStatusMessage(messageContent);

  // Telling the system if, background task is successful or not
   BackgroundTaskResult backgroundTaskResult = new BackgroundTaskResult(
      BackgroundTaskConstants.STATUS_SUCCESSFUL);
   backgroundTaskResult.setStatusMessage("Wonder full");
   return backgroundTaskResult;
}



Message Bus update

public static void sendStatusMessage(BackgroundTaskVO messageContent) {
   // Leave if no background task
   if (!BackgroundTaskThreadLocal.hasBackgroundTask()) {
      return;
   }

   // Message Creation
   Message message = createMessage(messageContent);

   // Send message to message bus
   MessageBusUtil.sendMessage(DestinationNames.BACKGROUND_TASK_STATUS,
         message);
}


You can download the source code from here

You are just done, Try & Enjoy the function.............:)

Tuesday, 10 April 2018

Osgi Target, Property attributes and Multiple Implementations

Osgi Target and Property attributes

In Spring you can use @Qualifier to get the correct class for respective instances.
How can we achieve this in OSGI framework is the question?

With help of Osgi and it's SCR(Service Component Runtime) you can use dynamic instantiation of Declarative Services.
Correct implementation should be selected for each instance to develop and use IS-A relationships.

Developers can instantiate a interface with different implementations by utlizing "target" attribute inside @Reference annotation. All you need is to enable the classes to get it from SCR with "property" attribute inside @Component annotation.

Below is the sample code to achieve the described matter

Problem Statement : We need to convert Numbers from string in different formats like Integer, Double, Float etc. and use them in different osgi components.


We will create interface and two classes which will implement this interface and consume them in another component for demonstration purpose.



public interface OsgiTarget {

 public Number convert(String str);
}

As you can see we defined multiple properties inside @Component annotation.

@Component(property = {
        "osgi.target.example.integer=true",
        "osgi.target.example.integer.again=true" // Dummy entry for multiple target example, you can get this instance with this property as well
})
public class OsgiTargetIntegerImpl implements OsgiTarget {
 @Override
 public Integer convert(String str) {
  return Integer.valueOf(str);
 }
}

Here we have single property for the same.

@Component(property = "osgi.target.example.double=true")
public class OsgiTargetDoubleImpl implements OsgiTarget{

 @Override
 public Double convert(String str) {
  return Double.valueOf(str);
 }
}


Now we have basic converters with different implementations.
We will use them in our component to show how it's going to work.


@Component(immediate = true)
public class OsgiTargetExampleConsumer {
 
 // Osgi target property for OsgiTargetIntegerImpl
 @Reference(target = "(osgi.target.example.integer=true)")
 OsgiTarget osgiTargetInteger;
 
 // Osgi target property for OsgiTargetIntegerImpl with another property but same instance 
 @Reference(target = "(osgi.target.example.integer.again=true)")
 OsgiTarget osgiTargetIntegerAgain;
 
 // Osgi target property for OsgiTargetDoubleImpl
 @Reference(target = "(osgi.target.example.double=true)")
 OsgiTarget osgiTargetDouble;

 
 @Activate
 public void run() {
  System.out.println("__________________" + osgiTargetInteger.convert("21"));
  System.out.println("__________________");
  System.out.println("__________________" + osgiTargetInteger.convert("12"));
  System.out.println("__________________");
  System.out.println("__________________" + osgiTargetDouble.convert("1987"));
 }
}


In the above class we are using the interface with @Reference annotation to get the object from SCR with respective classes just by adding target . If you look closely, you can see one class can be invoked by multiple properties as well.

Output will be similar like this :



You are just done, Try & Enjoy the function.............:)

Friday, 7 April 2017

Spring OSGI portlet or bundle with Liferay 7


When you see the blog title, The question arises, is what's new in this post?


Everyone knows how to create Spring MVC Portlets! 
But do we know how to create a portlet which is OSGI enabled and can run parallel with other osgi bundles inside Liferay.

If you have been through these links and many more -

https://dev.liferay.com/develop/tutorials/-/knowledge_base/7-0/spring-mvc
https://github.com/vernaillen/liferay7-springmvc-portlet

You must have noticed it's the same old way which includes all required jars inside the application's lib because we do not have support of global library now.
Liferay 7 is OSGI way now and by OSGI convention you do not include library into each other, but you share them from same place :)

Basic diagram of OSGI, copied from  -

https://fredhsu.wordpress.com/2013/05/03/opendaylight-and-osgi-basics/


To make it work for spring, we need to do two things

  • We will be using Apache Service Mix Spring Bundles(OSGI bundles for spring)
  • Support of few maven plugins, major one is Maven Bundle Plugin to add require entries to the manifest file and prepare it like a bundle but in a war type.

You can find the source code here - https://github.com/bardiavipin/osgi-spring
Meanwhile, you are checking out source code, I will explain minimal changes in your spring portlets to make them bundles.

Create a basic Spring portlet from your IDE or from command prompt or manually with Maven.

Copy below dependencies to deploy folder

Apache ServiceMix :: Bundles :: spring-beans (4.3.1.RELEASE_1)
Apache ServiceMix :: Bundles :: spring-web (4.3.1.RELEASE_1)
Apache ServiceMix :: Bundles :: spring-core (4.3.1.RELEASE_1)
Apache ServiceMix :: Bundles :: spring-aop (4.3.1.RELEASE_1)
Apache ServiceMix :: Bundles :: spring-expression (4.3.1.RELEASE_1)
com.github.ben-manes.caffeine (2.3.2)
Apache ServiceMix :: Bundles :: spring-context-support (4.3.1.RELEASE_1)
Apache ServiceMix :: Bundles :: spring-context (4.3.1.RELEASE_1)
Apache ServiceMix :: Bundles :: spring-webmvc (4.3.1.RELEASE_1)
Apache ServiceMix :: Bundles :: commons-configuration (1.9.0.2)
Apache ServiceMix :: Bundles :: spring-webmvc-portlet (4.3.1.RELEASE_1)

Note:Caffeine is a required library for one for the spring modules.

You can check the status of bundles from Gogo Shell

Now we will be going through major changes inside Portlet files-


Context file for sample portlet is - spring portlet xml

<!--Not Working--><!--<context:component-scan base-package="com.osgi.spring" />-->

<bean class="com.osgi.spring.PortletViewController"/> <!-- Handler mappings for annotation based controllers --><bean class="org.springframework.web.portlet.mvc.annotation.DefaultAnnotationHandlerMapping"/> <bean class="org.springframework.web.portlet.mvc.annotation.AnnotationMethodHandlerAdapter"/>


As you can see in the context file, component scan is commented as it is not working for this example. I used the workaround to create simple bean and pass the controller class. Spring players can tell me what is the issue here :)

BND Dependency Fix - BNDDependencyFix

@SuppressWarnings("unused")
public class BNDDependencyFix {

    static {
        System.out.println(ModelAndViewResolver.class);


When you deploy the portlet without this class, you get errors like this -
java.lang.ClassNotFoundException: org.springframework.context.config.ContextNamespaceHandler cannot be found.

In this class we are initializing or loading the necessary classes from static method.
If you find more errors with same type, you can add your class name here.

Major Maven Plugins - pom.xml

  • War plugin to exclude all jars from final war
  • Bundle plugin make necessary changes in Manifest file to create it a bundle
  • Shade plugin to make single files from spring.schemas and spring.handlers and transform  them to a single file independently
  • Truezip plugin to move spring.handlers and  spring.schemas  to classes/META-INF

When you are done with building and deploying this portlet, you can see your portlet is interacting to another Liferay bundle - portal-kernel to fetch release information. Each of the library is deployed as OSGI only and shared between applications.

You can include jar as well inside apps lib with little change in configuration, but until and unless it is necessary don't break the architecture :)

You can use Maven Bundle plugin for any of your independent project and convert it to a osgi bundle.


You are just done, Try & Enjoy the function.............:)

Thursday, 25 August 2016

Portal events in Liferay 7


Portal events or sometimes referred as action in Liferay are of different types.
They executes on different time and a developer can choose relevant action to perform.
You can find these entries in portal.properties-

1) Servlet service event
The pre-service events process before Struts processes the request.
The post-service events process after Struts processes the request.

servlet.service.events.pre
servlet.service.events.post

2) Login Events process after successful login only.

login.events.pre
login.events.post

3) Logout events called after successful logout.

logout.events.pre
logout.events.post

In this post we will be setting landing page in Liferay 7 OSGI way with post login action.
But at the end you will explore that all of the actions can be implemented by a single change of property.

I will be adding class only in the post, you can choose build type from - Gradle/Maven

To add events you need to -
1) Implement com.liferay.portal.kernel.events.LifecycleAction
2) Add annotation
@Component(
immediate = true, property = {"key=login.events.pre"},
service = LifecycleAction.class
)

that's all, you need to do :)

Below is the code you can refer -


package com.sample;

import javax.servlet.http.HttpSession;

import org.osgi.service.component.annotations.Component;

import com.liferay.portal.kernel.events.ActionException;
import com.liferay.portal.kernel.events.LifecycleAction;
import com.liferay.portal.kernel.events.LifecycleEvent;
import com.liferay.portal.kernel.log.Log;
import com.liferay.portal.kernel.log.LogFactoryUtil;
import com.liferay.portal.kernel.struts.LastPath;
import com.liferay.portal.kernel.util.StringPool;

@Component(
  immediate = true, property = {"key=login.events.post"},
  service = LifecycleAction.class
 )
public class LandingRedirectAction implements LifecycleAction {
 public static final Log LOGGER = LogFactoryUtil.getLog(LandingRedirectAction.class);
 @Override
 public void processLifecycleEvent(LifecycleEvent lifecycleEvent) throws ActionException {
  LOGGER.info("PostLogin called");
        HttpSession session = lifecycleEvent.getRequest().getSession();
        session.setAttribute("LAST_PATH", new LastPath(StringPool.BLANK, "/web/guest/landMeOnMoon"));
 }
}


Now, in here if you want to change this action/event to be called on each page just change the property "key" to "servlet.service.events.pre" and add your code accordingly.

You are just done, Try & Enjoy the function.............:)

Monday, 25 July 2016

Osgi Console and commands

Liferay is now fully supporting osgi, So here are some steps to connect with osgi consoles and some basic commands

OSGI console is the way to deploy and manage state of modules/components.



Connect gogo shell


 Enable telnet in windows
Go to command prompt
 command - telnet localhost 11311

OR

use putty with localhost and port 11311



Install osgi web console

Go to gogo shell

command - install 

http://mirror.switch.ch/mirror/apache/dist/felix/org.apache.felix.webconsole-4.2.16-all.jar

OR

command -  install file:///C:/org.apache.felix.webconsole-4.2.16-all.jar

After installing bundle, use the bundle id to start the bundle. By default bundle is in installed state.

command - start [bundle id]

Access - http://localhost:8080/o/system/console/bundles- (admin/admin)

[bundle id] - is an integer which is identifier for particular bundle.



Gogo shell commands


install file:///C:/liferay-blade-samples/maven/hook/target/hook-1.0.0.jar - Install the bundle
diag [bundle id] - Check for issues
update [bundle id] - Pick up from the last location
lb - List all the bundles
 ss - List all the bundles(Different View)
lb [bundle name or part of the string] - List all the bundles with matching string
uninstall [bundle id] - Uninstall the bundle
start [bundle id] - Start the bundle
stop [bundle id] - Stop the bundle
 b [bundle id] - Provides bundle details
refresh [bundle id] - Refresh bundles
disconnect [bundle id] - Disconnect from Gogo shell
inspect  ('capability' | 'requirement') <namespace>  [bundle id] - Inspect the bundle


Osgi Status

INSTALLED - Bundle is available in container
ACTIVE - Bundle is available to use or can be activated lazily
RESOLVED - All prerequisite are available to activate the bundle
STARTING
STOPPING

You are just done, Try & Enjoy the function.............:)

Gadget

This content isn't available over encrypted connections yet.