Featured post

Docker setup for Liferay 7 with MySQL

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

Monday, 17 April 2017

Spring portlet handler interceptor or spring MVC interceptor Liferay


Today we will be learning about portlet handler interceptor or simply you can say MVC interceptor with spring.


To create an osgi spring portlet please look at the spring osgi portlet.

In this example, we will create an annotation and use that annotation in our portlet class and then by reflection we will call that method in our interceptor.We can do it without all of it, but as we are doing example, let's learn something more out of it.
It has nothing to do with Liferay, but as we are using Liferay environment to run portlet, it's spring osgi liferay portlet.

You can find this source code here - https://github.com/bardiavipin/osgi-spring-interceptor

By Creating this interceptor you create one more layer between View and Controller. You can use filters as well for the same tasks you want to perform with interceptors. Interceptors are more coupled with request/response objects. With Spring interceptor, you can execute before and after the phase executes.

Interceptors are executed in below cases


  • Pre
  • Post
  • After

Methods are available for each of the phase of portlet


  • Render
  • Action
  • Event
  • Resource


I have created two modules for this example, one contains portlet and another contains interceptor and annotation class. Reason behind creating two modules is to demonstrate bundle development where you can interact between these.

Bundle 1

Portlet Interceptor - MyPortletInterceptor

public class MyPortletInterceptor extends HandlerInterceptorAdapter {

    public Gson gson;    

    @Override    public void postHandleRender(RenderRequest request, RenderResponse response, Object portletController, ModelAndView modelAndView) throws Exception {
        Class<?> clazz = portletController.getClass();

        Method[] methods = ReflectionUtils.getUniqueDeclaredMethods(clazz);
        for (Method method : methods) {
            if (method.isAnnotationPresent(LoadJson.class)) {
                Object result = method.invoke(portletController);
                modelAndView.addObject("LoadJson", gson.toJson(result));
                break;
            }
        }
        System.out.println("Your custom spring portlet interceptor called!");
}


I removed Gson setter/getter to make it short.

About MyPortletInterceptor
  • It extends HandlerInterceptorAdapter
  • Override one method postHandleRender
  • Invoke method from annotation
  • Add value to ModelAndView "LoadJson" after invoking the method
  • Even if you don't have annotation in your class, It will print a simple line output

Annotation - LoadJson
You can read about custom annotations from - https://www.javatpoint.com/custom-annotation

We just created a basic annotation with target as method.

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface LoadJson {
}

Bundle 2

Controller - PortletViewController

public class PortletViewController {

   @LoadJson   public Map loadJson(){
      Map map = new HashMap<String, String>();

      map.put("name", "vipin bardia");
      return map;
   }

What happens when we add it to our controller

  • After adding dependency of Bundle 1 to our portlet, we can use this LoadJson annotation
  • We wrote a method loadJson and returned a map
  • This method will be called from our interceptor and output will be added to "LoadJson" attribute as interceptor have access to it

View - view.jsp


Json Data : <c:out escapeXml="true" value="${LoadJson}" />

Let's make the main entry which connects this interceptor to our portlet.

spring-portlet.xml - osgi-spring-portlet.xml

<bean name="gsonbean" class="com.google.gson.Gson" />

<bean name="portletHandlerInterceptor" class="com.osgi.spring.interceptor.MyPortletInterceptor">
    <property name="gson" ref="gsonbean" />
</bean>

<!-- Handler mappings for annotation based controllers --><bean class="org.springframework.web.portlet.mvc.annotation.DefaultAnnotationHandlerMapping">
    <property name="interceptors">
        <list>
            <ref bean="portletHandlerInterceptor"/>
        </list>
    </property>
</bean>

About spring portlet xml

  • We defined gson bean
  • We defined portletHandlerInterceptor and provided property as gson bean
  • In default annotation handler we added the interceptor


I tried to make it work with bean creation inside interceptor module only and use it inside portlet module, but was unable to. If you know this way please share it here.


Advantage out of this interceptor is you can call this interceptor in any of your portlet by just making an entry in your spring portlet xml.
If you don't want it in one of your portlet, simply remove it.


Note : You need to deploy gson bundle as well to access it!


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.............:)

Gadget

This content isn't available over encrypted connections yet.