Daniel Mitterdorfer

Using Perlock with Spring

Perlock - a simple Java path watching library I have written - is available in Maven Central since a few weeks now. I thought it would be nice to provide another demo application to show how it can be used with Spring. It is provided along with Perlock on Github in examples/perlock-spring-demo.

Demo Scenario

The demo application implements a XML file processing application. Clients put XML files in an input directory (in the example, it's just the /tmp directory), a MessageDispatcher listens for new XML files in the directory with Perlock, and dispatches the file processing to a MessageProcessor which simulates some work. Below is an overview of the involved classes:

Key Classes of the Spring Perlock demo application

The most interesting part of the demo application is MessageDispatcher (slightly simplified below):

public class MessageDispatcher extends AbstractPathChangeListener {
    private final Executor executor;

    public MessageDispatcher(Executor executor) {
        this.executor = executor;
    }

    @Override
    public void onPathCreated(Path path) {
        if (path.toString().endsWith("xml")) {
            executor.execute(new MessageProcessor(path));
        }
    }
}

It is notified by Perlock whenever a path is created (callback #onPathCreated(Path)) and decides whether a corresponding message needs to be processed. As message processing might by very time-consuming, the MessageDispatcher executes each MessageProcessor in an Executor. Thus, the MessageDispatcher returns very quickly allowing the application to stay responsive.

Bootstraping Perlock with Spring

The Perlock API provides three key API elements: We have seen the first one already: PathChangeListener, which listens for changes on a file system. It is always associated to one PathWatcher, which allows applications to control the path watching life cycle. A PathWatcher is created by a PathWatcherFactory. For details refer to the README on Github or my introductory post on Perlock. The Perlock-Spring integration consists of two FactoryBean implementations; one to create the PathWatcherFactory and another one to create PathWatcher instances:

Key classes of the Spring-Perlock Integration

This might look verbose at first glance, but keep in mind that an application should only create one instance of PathWatcherFactory but may create multiple instances of PathWatcher to watch different paths, so the separation of the FactoryBeans makes sense.

Trying the Spring Demo

To try the demo yourself, follow these steps:

  1. Clone the repo: git clone https://github.com/danielmitterdorfer/perlock.git
  2. Create a JAR with all dependencies: gradle fatJar
  3. Issue: java -jar examples/perlock-spring-demo/build/libs/perlock-spring-demo-0.2.0.jar

You'll now see this output:

TRACE 2014-02-19 21:04:15,298 [main] (PathWatcherFactory.java:114) - Submitting 'PathWatcher for '/tmp'' to executor service.
 INFO 2014-02-19 21:04:15,302 [main] (SpringPathWatcherDemo.java:29) - Press any key to stop the demo
TRACE 2014-02-19 21:04:15,302 [pathWatchExecutor-1] (PathWatcherFactory.java:140) - About to run 'PathWatcher for '/tmp''.
TRACE 2014-02-19 21:04:15,308 [pathWatchExecutor-1] (AbstractRegistrationStrategy.java:33) - Registering Path: '/tmp'
TRACE 2014-02-19 21:04:15,309 [pathWatchExecutor-1] (WatchServicePathWatcher.java:91) - Waiting for file system events

cd /tmp in a new terminal window and issue touch hello{1,2}.xml && sleep 2 && touch hello{3,4}.xml:

You'll now see output like:

TRACE 2014-02-19 21:07:01,425 [pathWatchExecutor-1] (WatchServicePathWatcher.java:124) - Received watch event with kind 'ENTRY_CREATE'
 INFO 2014-02-19 21:07:01,426 [pathWatchExecutor-1] (MessageDispatcher.java:31) - Starting handling path '/tmp/hello1.xml'
TRACE 2014-02-19 21:07:01,428 [pathWatchExecutor-1] (WatchServicePathWatcher.java:124) - Received watch event with kind 'ENTRY_CREATE'
TRACE 2014-02-19 21:07:01,428 [messageHandlingExecutor-1] (MessageProcessor.java:24) - Simulating work on '/tmp/hello1.xml'
 INFO 2014-02-19 21:07:01,428 [pathWatchExecutor-1] (MessageDispatcher.java:31) - Starting handling path '/tmp/hello2.xml'
TRACE 2014-02-19 21:07:01,428 [messageHandlingExecutor-1] (MessageProcessor.java:27) - Processing '/tmp/hello1.xml'. 5 seconds left...
TRACE 2014-02-19 21:07:01,429 [pathWatchExecutor-1] (WatchServicePathWatcher.java:124) - Received watch event with kind 'ENTRY_CREATE'
TRACE 2014-02-19 21:07:01,429 [messageHandlingExecutor-2] (MessageProcessor.java:24) - Simulating work on '/tmp/hello2.xml'
TRACE 2014-02-19 21:07:02,429 [messageHandlingExecutor-1] (MessageProcessor.java:27) - Processing '/tmp/hello1.xml'. 4 seconds left...
# ... more output here ...
TRACE 2014-02-19 21:07:05,438 [messageHandlingExecutor-3] (MessageProcessor.java:27) - Processing '/tmp/hello3.xml'. 1 seconds left...
TRACE 2014-02-19 21:07:06,433 [messageHandlingExecutor-1] (MessageProcessor.java:34) - Done. Cleaning up... '/tmp/hello1.xml'
TRACE 2014-02-19 21:07:06,434 [messageHandlingExecutor-2] (MessageProcessor.java:34) - Done. Cleaning up... '/tmp/hello2.xml'
TRACE 2014-02-19 21:07:06,439 [messageHandlingExecutor-3] (MessageProcessor.java:34) - Done. Cleaning up... '/tmp/hello3.xml'
TRACE 2014-02-19 21:07:06,439 [messageHandlingExecutor-4] (MessageProcessor.java:34) - Done. Cleaning up... '/tmp/hello4.xml'

Note how all MessageProcessors run interleaved in their own thread. This allows the application to receive new events while still processing older messages.

What have we done?

The takeaways of this post are:

  • Perlock provides an easy integration of Perlock into Spring based on two FactoryBean implementations.
  • The Spring demo application shows a very small working example of how the integration of Perlock and Spring can be achieved.
  • Remember to offload heavy-lifting to a dedicated thread and implement only very lightweight PathChangeListeners.

If you need path watching in a Spring-based application go and get the new Perlock-Spring integration! In Gradle it is as easy as:

dependencies {
    compile group: 'name.mitterdorfer.perlock', name: 'perlock-spring', version: '0.2.0'
}

or alternatively via Maven.

<dependency>
    <groupId>name.mitterdorfer.perlock</groupId>
    <artifactId>perlock-spring</artifactId>
    <version>0.2.0</version>
</dependency>

Questions or comments?

Just ping me on Twitter