Monday, December 26, 2011

Annotated callback methods in custom class

We're implementing a simple message service framework to demonstrate how we can implement a framework that uses annotations, like JAX-WS. Using annotations minimizes boilerplate code, and promotes high cohesion of classes. It also promotes decoupling, because it prevents forcing the use of framework classes on clients as much as possible.

The messaging framework has one interface with a register method, which is used to register custom objects that handle incoming messages. The framework does not know what the type is of this object. It uses the reflection API to check for annotated methods, which tell the messaging framework to call these methods when a message has to be handled.

The message service framework supports two types of messages, which we define in as an enum type in MessageType.java:

package com.javaeenotes;


public enum MessageType {
    NORMAL,
    URGENT
}

The interface of the message service framework has a method to register custom client objects that will handle incoming messages. The second method of the interface is only included, so we can send messages through this framework. The interface of the message service framework is defined in MessageService.java:

package com.javaeenotes;


public interface MessageService {
    void registerMessageHandler(Object object);

    void sendMessage(String message, MessageType messageType)
        throws Exception;
}

Now, we define our annotation. This annotation can be used by the client application to mark methods that will handle incoming messages generated by the message service framework. It also has a message type enum parameter, so the framework will only call this method if the type of the message matches the specified type parameter. The annotation is defined in MessageMethod.java:

package com.javaeenotes;


import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;


// Only usable on methods.
@Target(ElementType.METHOD)
// Annotation must be available during runtime.
@Retention(RetentionPolicy.RUNTIME)
public @interface MessageMethod {
    MessageType messageType() default MessageType.NORMAL;
}

A client object will use the annotation to mark callback methods for specified message types. The example client handler has one method for both message types. An example client message handler class for this demo is defined as ExampleMessageHandler.java:

package com.javaeenotes;


public class ExampleMessageHandler {
    @MessageMethod(messageType = MessageType.NORMAL)
    public void handleNormalMessage(String message) {
        System.out.print("NORMAL MESSAGE: " + message + "\n");
    }


    @MessageMethod(messageType = MessageType.URGENT)
    public void handleUrgentMessage(String message) {
        System.out.println("URGENT MESSAGE: " + message + "\n");
    }
}

The most important class of this tutorial is: MessageServiceImpl.java, which you can find below. This is the implementation of our message service framework interface. This class is responsible for determining callback methods provided by the client, and call them when messages arrive.

package com.javaeenotes;


import java.lang.reflect.Method;


public class MessageServiceImpl implements MessageService {
    private Object messageHandler;


    @Override
    public void registerMessageHandler(Object messageHandlerObject) {
        messageHandler = messageHandlerObject;
    }


    @Override
    public void sendMessage(String message, MessageType messageType)
            throws Exception {
        
        for (Method method : messageHandler.getClass().getMethods()) {
            if (method.isAnnotationPresent(MessageMethod.class)) {
                MessageMethod messageMethod
                    = method.getAnnotation(MessageMethod.class);

                if (messageMethod.messageType() == MessageType.NORMAL
                        && messageType == MessageType.NORMAL) {

                    method.invoke(messageHandler, message);
                } else if (messageMethod.messageType()
                        == MessageType.URGENT
                        && messageType == MessageType.URGENT) {

                    method.invoke(messageHandler, message);
                }
            }
        }
    }
}

Now, we only need a Main class to get this demonstration working. The class will create the example client object, and register it in the framework. Then, it will create two example messages, and pass them to the framework, which then will call the annotated callback methods of the example client object. The Main class is defined in: Main.java.

package com.javaeenotes;


public class Main {
    public static void main(String[] args) {
        // Create message service and register message handler.
        MessageService messageService = new MessageServiceImpl();
        messageService.registerMessageHandler(
                new ExampleMessageHandler());

        try {
            // Sending test messages through the message service.
            messageService.sendMessage("This is a normal message.",
                    MessageType.NORMAL);

            messageService.sendMessage("This is an urgent message!",
                    MessageType.URGENT);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

If we run the Main class, we should get the following output:

NORMAL MESSAGE: This is a normal message.
URGENT MESSAGE: This is an urgent message!

Tuesday, December 20, 2011

Custom SLF4J Logger adapter

The Simple Logging Facade for Java or (SLF4J) is a simple facade for various logging frameworks. It decouples the logging framework from the application. The deployer can choose which logging framework to use. For every popular logging framework, an adapter Jar file is available. Deploy this file together with the core SLF4J Jar file, and the logging automagically works.

In my current work situation, a financial application I developed has to be integrated into an existing server application. This server application uses its own logging framework. Luckily, I've been using SLF4J, so logging integration is just a matter of writing a custom SLF4J adapter and replace the current adapter (SLF4J-Log4J) with it.

Writing an adapter is very easy. You only need to create three files:

  1. Create an implementation of the logger interface: org.slf4j.Logger
  2. Create a factory for the new logger using this interface: org.slf4j.ILoggerFactory
  3. Create your own binder class, so SLF4J knows which factory to use to get loggers: org.slf4j.spi.LoggerFactoryBinder
It is important to create these files in the package: org.slf4j.impl, because SLF4J is using this to find the correct adapter.

Firstly, we create the class that implements the SLF4J Logger interface. The implementation of this interface is used by SLF4J to delegate the logging. It's an interface with a large number of methods, so let the IDE generate the empty methods for you. Next is to implement the bodies of the methods to do the actual logging. In my case, I call the custom logging framework.

package org.slf4j.impl;

import org.slf4j.Logger;
import org.slf4j.Marker;
import org.slf4j.helpers.MessageFormatter;

public class MyLoggerAdapter implements Logger {
    // Bunch of inherited methods here. Let your IDE generate this.
    // Implement these methods to do your own logging.
}

Now, we create a factory implementation for the Logger class. This class is used to retrieve Logger objects used by the application. Make sure it implements the ILoggerFactory interface.

package org.slf4j.impl;

import org.slf4j.ILoggerFactory;
import org.slf4j.Logger;

import java.util.HashMap;
import java.util.Map;


public class MyLoggerFactory implements ILoggerFactory {
    private Map<String, MyLoggerAdapter> loggerMap;

    public MyLoggerFactory() {
        loggerMap = new HashMap<String, MyLoggerAdapter>();
    }

    @Override
    public Logger getLogger(String name) {
        synchronized (loggerMap) {
            if (!loggerMap.containsKey(name)) {
                loggerMap.put(name, new MyLoggerAdapter(name));
            }

            return loggerMap.get(name);
        }
    }
}

Finally, we need a way to register or bind the logger factory to SLF4J. To do that, we have to customize the StaticLoggerBinder class. You can almost use the same code provided by other adapters. I shamelessly ripped my code from the Log4J adapter.

package org.slf4j.impl;


import org.slf4j.ILoggerFactory;
import org.slf4j.spi.LoggerFactoryBinder;


public class StaticLoggerBinder implements LoggerFactoryBinder {

    /**
     * The unique instance of this class.
     */
    private static final StaticLoggerBinder SINGLETON
        = new StaticLoggerBinder();

    /**
     * Return the singleton of this class.
     *
     * @return the StaticLoggerBinder singleton
     */
    public static final StaticLoggerBinder getSingleton() {
        return SINGLETON;
    }


    /**
     * Declare the version of the SLF4J API this implementation is
     * compiled against. The value of this field is usually modified
     * with each release.
     */
    // To avoid constant folding by the compiler,
    // this field must *not* be final
    public static String REQUESTED_API_VERSION = "1.6";  // !final

    private static final String loggerFactoryClassStr
        = MyLoggerFactory.class.getName();

    /**
     * The ILoggerFactory instance returned by the
     * {@link #getLoggerFactory} method should always be the same
     * object.
     */
    private final ILoggerFactory loggerFactory;

    private StaticLoggerBinder() {
        loggerFactory = new MyLoggerFactory();
    }

    public ILoggerFactory getLoggerFactory() {
        return loggerFactory;
    }

    public String getLoggerFactoryClassStr() {
        return loggerFactoryClassStr;
    }
}

Compile and deploy this code together with the SLF4J jar, and logging will be handled by the our new adapter.

Links:

Friday, December 16, 2011

Running Wicket on Jetty

If you're only using the web functionality provided by an application server, or you want to optimize HTTP performance, it would be a good idea to consider embedding the Jetty HTTP server inside your application. This way, you don't even have to generate a WAR-file and a web.xml file. The application is just a basic Java application, which you can run on any server with a Java VM.

Maven setup

If you're using a Maven project, add the following dependencies to the pom-file in the "dependencies"-element in order to start working right away:

<dependency>
  <groupId>org.mortbay.jetty</groupId>
  <artifactId>jetty-hightide</artifactId>
  <version>8.0.4.v20111024</version>
</dependency>

<dependency>
  <groupId>org.apache.wicket</groupId>
  <artifactId>wicket-core</artifactId>
  <version>1.5.3</version>
</dependency>

Minimal Wicket application

For our example, we use a minimal Wicket application that consists of one page.

The ExampleWicketApplication.java file:
package com.javaeenotes;


import org.apache.wicket.Page;
import org.apache.wicket.protocol.http.WebApplication;


public class ExampleWicketApplication extends WebApplication {

    @Override
    public Class<? extends Page> getHomePage() {
        return HomePage.class;
    }
}
The HomePage.java file:
package com.javaeenotes;


import org.apache.wicket.markup.html.WebPage;
import org.apache.wicket.markup.html.basic.Label;


public class HomePage extends WebPage {
    public HomePage() {
        add(new Label("message", "Hello World!"));
    }
}
The HomePage.html file:
<html>
<body>
  <span wicket:id="message"></span>
</body>
</html>

The final class we need is a main class with the static main-method, in which we tie the Wicket servlet to the Jetty server.

The Main.java file:
package com.javaeenotes;


import org.apache.wicket.protocol.http.WicketFilter;
import org.apache.wicket.protocol.http.WicketServlet;
import org.eclipse.jetty.server.Connector;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.bio.SocketConnector;
import org.eclipse.jetty.servlet.ServletHolder;
import org.eclipse.jetty.webapp.WebAppContext;


public class Main {
    public static void main(String[] args) {
        // Object that holds, and configures the WicketServlet.
        ServletHolder servletHolder =
                new ServletHolder(new WicketServlet());
        servletHolder.setInitParameter(
                "applicationClassName",
                "com.javaeenotes.ExampleWicketApplication");
        servletHolder.setInitParameter(
                WicketFilter.FILTER_MAPPING_PARAM, "/*");
        servletHolder.setInitOrder(1);

        // Web context configuration.
        WebAppContext context = new WebAppContext();
        context.addServlet(servletHolder, "/*");
        context.setResourceBase("."); // Web root directory.

        // The HTTP-server on port 8080.
        Server server = new Server();
        SocketConnector connector = new SocketConnector();
        connector.setPort(8080);
        server.setConnectors(new Connector[]{connector});
        server.setHandler(context);

        try {
            // Start HTTP-server.
            server.start();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

Run the main method in a VM, and connect to port 8080 with a browser to check that the application is working.