Listenery


Wstęp

Zadaniem listenerów jPALIO jest zazwyczaj nasłuchiwanie na zdarzenia, które są zależne od danego typu listenera. Może być to np. nasłuchiwanie na danym porcie lub nasłuchiwanie na pojawiające się pliki w katalogu lub np. nasłuchiwanie na pojawianie się nowych linii w pliku. Po odebraniu określonego zdarzenia, listener realizuje operacje/funckjonalności zależne od implementacji danego listenera. Przykładem takiej reakcji może być np. wywołanie wskazanego obiektu jPALIO z zadanymi parametrami.

W jaki sposób stworzyć własny listener jPALIO?

Klasą bazową dla wszystkich listenerów jPALIO jest klasa palio.listeners.PalioListener. Aby stworzyć własny listener należy stworzyć klasę JAVA dziedziczącą po tej klasie. W pierwszym etapie tworzenia listenera klasa listenera powinna wyglądać w następujący sposób:

package sample.listener;

import java.util.Properties;

import palio.Instance;
import palio.listeners.PalioListener;


public class SampleListener extends PalioListener {

public SampleListener(String name, Instance instance, Properties properties) {
super(name, instance, properties);
}

@Override
public void close() {

}

}

Listener najczęściej kojarzony jest z procesem nasłuchującym na jakieś zdarzenia. Startowanie takich procesów (np. otwieranie nasłuchujących soketów, lub tworzenie nowych wątków) powinno odbywać się w konstruktorze. Dobrą praktyką jest wyniesienie kodu startującego do innej metody i wywołanie jej w konstruktorze. Zwiększa to czytelność kodu. W kolejnych wersjach jPALIO listenery będą posiadały swój cykl życia, który uporządkuje proces inicjalizacji, startu, zatrzymywania i zwalniania zasobów.

Metoda close jest wywoływana w momencie zwalniania zasobów instancji jPALIO podczas jej zamykania. W przypadku alokowania zasobów przez dany listener w chwili startu lub w trakcie jego działania metoda ta ma za zadanie zwolnienie wszystkich tych zasobów (np. zamykanie otwartych połączeń, zatrzymywanie procesów nasłuchujących itd).

Stworzoną klasę należy skompilować i umieścić ją serwerze. W przypadku mało skomplikowanych listenerów moża skopiować skompilowaną klasę do katalogu WEB-INF/classes. Klasie listenera mogą towarzyszyć inne powiązane klasy stąd zalecanym sposobem wgrywania listenera na serwer jest zbudowanie archiwum JAR w którym powinny znaleźć się klasa listenera oraz wszystkie klasy powiązane a następnie wgranie go do katalogu WEB-INF/lib.

Szkielet prostego listenera może wyglądać w ten sposób:

package sample.listener;

import java.util.Properties;

import palio.Current;
import palio.Instance;
import palio.listeners.PalioListener;


public class SampleListener extends PalioListener {

private boolean running = false;

public SampleListener(String name, Instance instance, Properties properties) {
super(name, instance, properties);
start();
}

@Override
public void close() {
running = false;
}

public void start() {
running = true;
Thread thread = new Thread(new Runnable() {

public void run() {
// Setting a Current is required to make possible invoking most methods of jPALIO modules.
Current threadCurrent = new Current(getInstance());
Instance.setCurrent(threadCurrent);
while (running) {
// Some listening code
}
}
});

thread.setName("jPALIO - " + instance.getName() + " - " + getClass().getName() + " listener");
thread.setDaemon(true);
thread.start();
}

}

Przykładowa implementacja listenera

Jako przykład zostanie przedstawiony prosty listener nasłuchujący na pojawiające się pliki w danym katalogu, wypisujący do logów podstawowe informacje dotyczące tego pliku i następnie kasujący ten plik ;). Listener będzie nasłuchiwał na zakodowanym na sztywno katalogu (który z założenia powinien istnieć). Okres odpytwania katalogu będzie na sztywno ustawiony na 1000 ms. Nie wszystkie sytuacje wyjątkowe są obsłużone (np. sytuacja gdy zadany katalog nie istnieje). Przykład został maksymalnie uproszczony, aby był jak najbardziej czytelny.

package sample.listener;

import java.io.File;
import java.util.Properties;

import org.apache.log4j.Logger;

import palio.Current;
import palio.Instance;
import palio.listeners.PalioListener;


public class SampleListener extends PalioListener {

/** Default path for listening */
private final String DEFAULT_PATH = "/tmp/listener/";

/** Default polling period */
private final long DEFAULT_POLLING_PERIOD = 1000L;

private boolean running = false;

public SampleListener(String name, Instance instance, Properties properties) {
super(name, instance, properties);
start();
}

@Override
public void close() {
running = false;
getLogger().info("Listener is going to be stopped ...");
}

public void start() {
running = true;
Thread thread = new Thread(new Runnable() {

public void run() {
// Setting a Current is required to make possible invoking most methods of jPALIO modules.
Current threadCurrent = new Current(getInstance());
Instance.setCurrent(threadCurrent);

File directory = new File(DEFAULT_PATH);

while (running) {
for (File file : directory.listFiles()) {

getLogger().info(String.format("File %s detected, size: %d bytes", file.getPath(), file.length()));

if (file.delete()) {
getLogger().info("File " + file.getPath() + " deleted");
} else {
getLogger().info("Unable to delete a file: " + file.getPath());
}
}

try {
Thread.sleep(DEFAULT_POLLING_PERIOD);
} catch (InterruptedException ex) {
// Ignore
}

}
getLogger().info("Listener stopped");
}
});

thread.setName("jPALIO - " + instance.getName() + " - " + getClass().getName() + " listener");
thread.setDaemon(true);
thread.start();
getLogger().info("Listener started ...");
}

/**
* Returns a logger for this listener
*/
private Logger getLogger() {
return palio.Logger.getLogger(instance, getClass().getName());
}
}

Przykładowe wpisy do logów:

2010-11-22 22:50:36 INFO  workshop.sample.listener.SampleListener - Listener started ...
2010-11-22 22:51:08 INFO workshop.sample.listener.SampleListener - File /tmp/listener/IMG_4180.JPG detected, size: 3916067 bytes
2010-11-22 22:51:08 INFO workshop.sample.listener.SampleListener - File /tmp/listener/IMG_4180.JPG deleted
2010-11-22 22:51:11 INFO workshop.sample.listener.SampleListener - File /tmp/listener/IMG_4181.JPG detected, size: 3768109 bytes
2010-11-22 22:51:11 INFO workshop.sample.listener.SampleListener - File /tmp/listener/IMG_4181.JPG deleted
2010-11-22 22:51:13 INFO workshop.sample.listener.SampleListener - File /tmp/listener/IMG_4182.JPG detected, size: 4333296 bytes
2010-11-22 22:51:13 INFO workshop.sample.listener.SampleListener - File /tmp/listener/IMG_4182.JPG deleted
2010-11-22 22:51:15 INFO workshop.sample.listener.SampleListener - File /tmp/listener/IMG_4652.JPG detected, size: 4988662 bytes
2010-11-22 22:51:15 INFO workshop.sample.listener.SampleListener - File /tmp/listener/IMG_4652.JPG deleted
2010-11-22 22:51:26 INFO workshop.sample.listener.SampleListener - Listener is going to be stopped ...
2010-11-22 22:51:26 INFO workshop.sample.listener.SampleListener - Listener stopped

Konfiguracja i parametryzowanie listenerów

Jeżeli chcemy dodać własny moduł do instancji jPALIO należy dodać konfigurację tego listenera do konfiguracji instancji jPALIO. Najprostsza konfiguracja listenera wygląda w sposób następujący:

<listener name="sample">
    <class>sample.listener.SampleListener</class>
</listener>

Minimalna konfiguracja obejmuje wskazanie nazwy listenera oraz klasy będącej implementującją listenera. Listener może zostać zparametryzowany. Jeżeli listener korzysta z dodatkowych parametrów konfiguracyjnych, powyższy podstawowy wpis należy rozszerzyć do postaci następującej:

<listener name="sample">
<class>sample.listener.SampleListener</class>
<path>/tmp/listener/</path>
<pollingPeriod>1000</pollingPeriod>
 <param1>param1Value</param1>
<param2>param2Value</param2>
...
</listener>

Dostęp do parametrów konfiguracyjnych realizowany jest poprzez pole properties w klasie bazowej. Odnosząc się do przykładowej implementacji listenera z poprzedniego rozdziału dobrym rozwiązaniem będzie sparametryzowanie ścieżki do katalogu, który ma być odpytywany oraz okresu odpytywania. Klasę tę można rozszerzyć o dwa dodatkowe pola:

private final String path;
private final long pollingPeriod;

A ich inicjalizacja, na podstawie parametrów konfiguracji oraz wartości domyślnych może wyglądać w sposób nastepujący:

String paramValue = this.properties.getProperty("path", DEFAULT_PATH);
this.path = paramValue;
paramValue = this.properties.getProperty("pollingPeriod");
this.pollingPeriod = paramValue == null ? DEFAULT_POLLING_PERIOD : Long.valueOf(paramValue);