Receive notification in the system tray on server startup (Tomcat, Hybris)

On many large e-commerce projects server startup can take up to 6-10 minutes. Hybris projects I worked at are not exception. So usually when I start the tomcat server I can do lots of other things. e.g. reading the spec, browsing the net or even continue development in my IDE. So after 5-6 minutes I can forget about the fact that my server is starting up. So I thought that it would be great to have a tool that can notify me as soon as tomcat starts up and I decided to develop  it. So basically what the tool has to do is to understand when tomcat server finishes startup and call OS utility to notify me about it.

OOTB hybris uses  Growl to notify developer about init/update actions. To activate it you need to install Growl for windows or Grow for OS X and add it your PATH.

Unfortunately Hybris does not support “server is ready” notification. But there is very simply way to do that. First of all tomcat has The LifeCycle Listener Component that can let us know about different events in tomcat server.

public interface Lifecycle {
	String BEFORE_INIT_EVENT = "before_init";
	String AFTER_INIT_EVENT = "after_init";
	String START_EVENT = "start";
	String BEFORE_START_EVENT = "before_start";
	String AFTER_START_EVENT = "after_start";
	String STOP_EVENT = "stop";
	String BEFORE_STOP_EVENT = "before_stop";
	String AFTER_STOP_EVENT = "after_stop";
	String AFTER_DESTROY_EVENT = "after_destroy";
	String BEFORE_DESTROY_EVENT = "before_destroy";
	String PERIODIC_EVENT = "periodic";
	String CONFIGURE_START_EVENT = "configure_start";
	String CONFIGURE_STOP_EVENT = "configure_stop";
}

From the list above AFTER_START_EVENT is the most appropriate event we can listen to. OK, it clear about event, but how should we put notification in the system tray? Basically there are two options:

  1. Use Growl via command line tool as Hybris does
  2. Use Java’s SystemTray class

As a good developer I think we should support both versions. So when Growl exists – use it, if does not exist – use default system tray. Lets write our implementation.

Step 1 – Create a project that can be packed as jar

As maven is still the most popular build tool lets create project from maven archetype.

mvn archetype:generate -DgroupId=com.wordpress.nikitapavlenko
-DartifactId=tomcat-notification
-DarchetypeArtifactId=maven-archetype-quickstart
-DinteractiveMode=false

My maven pom.xml is following:

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"          xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <groupId>com.wordpress.nikitapavlenko</groupId>
    <artifactId>tomcat-notification</artifactId>
    <packaging>jar</packaging>
    <version>1.0-SNAPSHOT</version>
    <name>tomcat-notification</name>
    <url>http://maven.apache.org</url>
    <properties>
        <maven.compiler.source>1.8</maven.compiler.source>
        <maven.compiler.target>1.8</maven.compiler.target>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.apache.tomcat</groupId>
            <artifactId>tomcat-catalina</artifactId>
            <version>7.0.12</version>
        </dependency>
    </dependencies>
</project>

Step 2 – Implement org.apache.catalina.LifecycleListener

package com.wordpress.nikitapavlenko;

import org.apache.catalina.Lifecycle;
import org.apache.catalina.LifecycleEvent;
import org.apache.catalina.LifecycleListener;

import java.awt.*;
import java.awt.TrayIcon.MessageType;
import java.io.IOException;
import java.net.URL;
import java.util.Calendar;
import java.util.Map;

public class TomcatListener implements LifecycleListener {

    @Override
    public void lifecycleEvent(LifecycleEvent event) {
        if (Lifecycle.AFTER_START_EVENT.equals(event.getType())) {
            if (growlExist()) {
                notifyViaGrowl();
            }
            else {
                notifyViaDefaultSystemTray();
            }
        }
    }

    private void notifyViaGrowl() {
        try {
            String message = "\"Server successfully started. [" + Calendar.getInstance().getTime()+"]\"";
            Runtime.getRuntime().exec("growlnotify /t:Tomcat " + message);
            System.out.println("Growl notification sent");
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    private boolean growlExist() {
        Map<String, String> env = System.getenv();
        String path = env.get("Path");
        return path != null && path.toLowerCase().contains("growl");
    }

    private void notifyViaDefaultSystemTray() {
        SystemTray tray = SystemTray.getSystemTray();
        TrayIcon trayIcon = createTrayIcon();
        try {
            tray.add(trayIcon);
            trayIcon.displayMessage("Tomcat", "Server successfully started.", MessageType.INFO);
            System.out.println("System tray notification sent");
        } catch (AWTException e) {
            e.printStackTrace();
        }
    }

    private TrayIcon createTrayIcon() {
        URL resource = getClass().getClassLoader().getResource("icon.png");
        Image image = Toolkit.getDefaultToolkit().createImage(resource);
        TrayIcon trayIcon = new TrayIcon(image, "Tomcat");
        trayIcon.setImageAutoSize(true);
        trayIcon.setToolTip("Hybris server");
        return trayIcon;
    }

}

Step 3 – Package sources as jar file

mvn clean package

At this moment you will have an artifact “tomcat-notification-1.0-SNAPSHOT.jar”

Step 4 – Register listener in the server.xml

For the based hybris app open config/tomcat/conf/server.xml and add a new listener there:

...
<Listener className="com.wordpress.nikitapavlenko.TomcatListener" context="GenericJavaBeanResource"/>
...

Step 5 – Add our jar to tomcat lib folder

For the hybris based project add this jar to config/customize/platform/tomcat/lib and don’t forget to run customize target before ant all.

Step 6 – Results

When growl is in system’s Path:
growl.png

When growl is not in the system’s Path:

default.png

The source code is available my Github. Thanks for reading!

Advertisements

Creating configurable Hybris converters using annotations

The most of developers are aware about converter pattern. The main goal of converter is to convert a source object of type S to a target of type T. We usually need it to convert a model object to the DTO. Despite the fact that idea is very simple there are even many frameworks which help you to do that e.g. Dozer or Mapstruct. Hybris also has it own way to convert data. Basically it provides Converter and Populator interfaces. Converter is used to instantiate an instance of target type and then converter delegates work to a populator which is used to fill the target instance.

interface Converter<S,T> {
   T convert(S source);
}
interface Populator<S,T> {
   void populate(S source, T target);
}

The idea of hybris way is to have a single implementation of converter and reuse it all over the codebase. This implementation should accept target type and populators as parameters and any developer should only write code for populator and create converters only using spring configuration. Sometimes it might be annoying…

Once when I finished writing a populator and I thougt “Why should I declare 10 more lines in spring.xml just to create converter? Why not to do this declaratively?”. At that point I decided to make my own declarative implementation.

My idea was following:

  1. create @Converter annotation
  2. implement BeanDefinitionRegistryPostProcessor to dynamically add converter bean definitions in the context
  3. all added bean definitions for converters are instantiated by spring automatically.
  4. profit

Step 1 – Create @Converter annotation

We need to have a Runtime available annotation with value field for converter name

@Retention(RetentionPolicy.RUNTIME)
public @interface Converter {
 String value();
}

Step 2 – Implement BeanDefinitionRegistryPostProcessor

Here the most interesting part goes. Things we need to do in this class are:

  1. Scan all bean definitions that implement Populator interface
  2. Filter only those classes that have @Converter annotation
  3. Group filtered bean definitions by converter name
  4. Create a converter for a group of populators using converter name as bean id, type of target parameter in the populator as a target class, and grouped populator as populators for converter.

Lets have an example:

@Component
@Converter("productConverter")
public class BasicProductPopulator implements Populator<Product, ProductData> {

    public void populate(Product product, ProductData productData) {
        productData.setName(product.getName());
        productData.setDescription(product.getDescription());
    }

}
@Component
@Converter("productConverter")
public class MoneyProductPopulator implements Populator<Product, ProductData> {

    public void populate(Product product, ProductData productData) {
        productData.setPrice(formatPrice(product.getPrice()));
        productData.setTax(formatTax(product.getTax()));
    }

    private String formatPrice(Price price) {
        return price.getValue() + price.getCurrencySymbol();
    }

    private String formatTax(Tax tax) {
        return tax.getVatRate() + " " + tax.getCode();
    }
}

So we have 2 populators MoneyProductPopulator and BasicProductPopulator. Both of them implement Populator<Product, ProductData>. After our custom BeanDefinitionRegistryPostProcessor scanned and filtered all beans definitions it would group MoneyProductPopulator and BasicProductPopulator into list, it will take
ProductData as a target class for converter and create a bean definition for them in spring context. So that you could inject the converter in your code.

@Resource
Converter<Product, ProductData> productConverter;

It is the source code of ConverterResolverBeanDefinitionRegistryPostProcessor:

package example;

import example.Populator;
import org.springframework.beans.BeansException;
import org.springframework.beans.MutablePropertyValues;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.BeanDefinitionRegistryPostProcessor;
import org.springframework.beans.factory.support.GenericBeanDefinition;
import org.springframework.beans.factory.support.ManagedList;
import org.springframework.stereotype.Component;

import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import java.util.stream.Stream;

@Component
public class ConverterResolverBeanDefinitionRegistryPostProcessor implements BeanDefinitionRegistryPostProcessor {

    public static final String CONVERTER_CLASS = "com.copmany.DEFAUL_IMPLEMENTATION_OF_CONVERTER";
    public static final String TARGET_CLASS = "targetClass";
    public static final String POPULATORS = "populators";
    public static final String POPULATE_METHOD = "populate";
    public static final int TARGET_PARAM = 1;

    @Override
    public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry beanDefinitionRegistry) throws BeansException {
        Map<String, List<BeanDefinition>> converterNameToPopulators =
                Stream.of(beanDefinitionRegistry.getBeanDefinitionNames())
                        .map(beanDefinitionRegistry::getBeanDefinition)
                        .filter(this::doesBeanImplementPopulatorInterface)
                        .filter(this::doesBeanClassHaveConverterAnnotation)
                        .collect(Collectors.groupingBy(this::getConverterName));

        converterNameToPopulators.forEach((converterName, populators) ->
                createAndRegisterConverter(converterName, populators, beanDefinitionRegistry));
    }

    private boolean doesBeanImplementPopulatorInterface(BeanDefinition beanDefinition) {
        Class<?> beanClass = getBeanClass(beanDefinition);
        return Stream.of(beanClass.getInterfaces()).anyMatch(Populator.class::equals);
    }

    private boolean doesBeanClassHaveConverterAnnotation(BeanDefinition beanDefinition) {
        Class<?> beanClass = getBeanClass(beanDefinition);
        return Stream.of(beanClass.getAnnotations()).map(Annotation::annotationType).anyMatch(Converter.class::equals);
    }

    private String getConverterName(BeanDefinition populatorBeanDefinition) {
        Converter converterAnnotation = getBeanClass(populatorBeanDefinition).getAnnotation(Converter.class);
        return converterAnnotation.value();
    }

    private void createAndRegisterConverter(String converterName, List<BeanDefinition> populators, BeanDefinitionRegistry beanDefinitionRegistry) {
        BeanDefinition converter = createConverterBeanDefinition(populators);
        beanDefinitionRegistry.registerBeanDefinition(converterName, converter);
    }

    private BeanDefinition createConverterBeanDefinition(List<BeanDefinition> populators) {
        GenericBeanDefinition converterBeanDefinition = new GenericBeanDefinition();
        converterBeanDefinition.setBeanClassName(CONVERTER_CLASS);
        converterBeanDefinition.setPropertyValues(createBeanProperties(populators));
        return converterBeanDefinition;
    }

    private MutablePropertyValues createBeanProperties(List<BeanDefinition> populators) {
        MutablePropertyValues properties = new MutablePropertyValues();
        properties.addPropertyValue(POPULATORS, createPopulatorsList(populators));
        properties.addPropertyValue(TARGET_CLASS, getPopulatorTargetClass(populators.get(0)));
        return properties;
    }

    private ManagedList<BeanDefinition> createPopulatorsList(List<BeanDefinition> populators) {
        ManagedList<BeanDefinition> populatorReferences = new ManagedList<>();
        populatorReferences.setElementTypeName(Populator.class.getName());
        populatorReferences.addAll(populators);
        return populatorReferences;
    }

    private Class<?> getPopulatorTargetClass(BeanDefinition populatorBeanDefinition) {
        Method[] declaredMethods = getBeanClass(populatorBeanDefinition).getDeclaredMethods();
        return Stream.of(declaredMethods)
                .filter(method -> POPULATE_METHOD.equals(method.getName()))
                .findFirst()
                .map(Method::getParameters)
                .map(parameters -> parameters[TARGET_PARAM])
                .map(Parameter::getType)
                .get();
    }

    private Class<?> getBeanClass(BeanDefinition beanDefinition) {
        String className = beanDefinition.getBeanClassName();
        return toClass(className);
    }

    private  Class<?> toClass(String className) {
        try {
            return Class.forName(className);
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
            return null;
        }
    }

    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
        // no operation
    }

}

So basically that is all for the article. Thanks for reading.