IntelliJ IDEA kompiliert über Maven

Normalerweise kompiliert die Java-IDE IntelliJ IDEA den Quelltext sobald, der Build-Vorgang gestartet wird. Allerdings kann es vorkommen das stattdessen jedes Mal der Kompiliervorgang über Maven angestoßen wird. Verantwortlich hierfür ist eine Einstellung der IDE.

Nachdem die Option deaktiviert wurde, kompilierte die IDE den Quellcode wieder

Zu finden ist diese Einstellung im Einstellungsdialog unter dem Punkt Build, Execution, Deployment -> Build Tools -> Maven- Runner. Dort existiert der Punkt Delegate IDE build/run actions to Maven. Wird dieser Punkt deaktiviert, ist die IDE wieder für die Kompilierung und den Build-Prozess verantwortlich.

Bibliothek zum Einlesen von MediaWiki-Dump-Dateien

Für ein Projekt war ich auf der Suche nach einer Java-Bibliothek um die Dumps einer MediaWiki-Installation auszuwerten. Fündig geworden bin ich bei der Bibliothek WikiXMLJ, welche allerdings mittlerweile einige Jahre auf dem Buckel hat. Auf Basis dieser Bibliothek habe ich die Bibliothek mediawikixml erstellt. Um die Bibliothek zu nutzen, muss im ersten Schritt eine neue Paketquelle in der pom.xml definiert werden.

<repositories>
 <repository>
 	<id>github</id>
 	<url>https://maven.pkg.github.com/seeseekey/mediawikixml</url>
 </repository>
</repositories>

Anschließend kann die Abhängigkeit eingebunden werden:

<dependency>
    <groupId>net.seeseekey</groupId>
    <artifactId>mediawikixml</artifactId>
    <version>1.0.1</version>
</dependency>

Damit kann die Bibliothek zur Auswertung von MediaWiki-Dumps genutzt werden:

WikiXMLParser wikiXMLParser = WikiXMLParserFactory.getParser("dump-current.xml");

try {

    wikiXMLParser.setPageCallback(new PageCallbackHandler() {
        public void process(WikiPage page) {
            System.out.println(page.getId());
            System.out.println(page.getRevisionId());
            System.out.println(page.getTimestamp());
            System.out.println(page.getTitle());
            System.out.println(page.getWikiText());
        }
    });

    wikiXMLParser.parse();
} catch (Exception e) {
    e.printStackTrace();
}

Für jede MediaWiki-Seite wird unter anderem die ID, die Revision, der Timestamp, der Titel und der eigentliche Inhalt der Seite geparst. Die Bibliothek setzt mindestens Java 8 voraus. Zu finden ist das Repository mit der Bibliothek auf GitHub. Die Bibliothek ist unter der Apache License in Version 2 lizenziert und damit freie Software.

GitHub Package Registry für Maven Packages nutzen

Vor einigen Wochen hat GitHub die GitHub Package Registry vorgestellt. Mit dieser ist es möglich fertige Packages direkt beim Projekt auf GitHub zu hosten. Aktuell unterstützt werden, neben Maven-Packages, unter anderem die Paketsysteme von NPM, Ruby, und NuGet. GitHub selbst definiert den Zweck der Registry wie folgt:

GitHub Package Registry allows you to develop your code and host your packages in one place. You can use packages from GitHub Package Registry as a dependency in your source code on GitHub.

Nach einer Registrierung unter github.com/features/package-registry kann das erste Paket für die Package Registry erstellt werden. Dazu muss im entsprechenden Java-Projekt in der pom.xml folgender Block hinzugefügt werden:

<!-- Definition for uploading artifact to custom repository -->
<distributionManagement>
    <repository>
        <id>github</id>
        <name>GitHub OWNER Apache Maven Packages</name>
        <url>https://maven.pkg.github.com/OWNER/mediawikixml</url>
    </repository>
</distributionManagement>

Das Wort OWNER muss dabei durch den eigenen GitHub-Nutzernamen ausgetauscht werden. Anschließend müssen die Zugangsdaten für GitHub hinterlegt werden. Dazu wird die Maven settings.xml geöffnet:

nano ~/.m2/settings.xml

Zu der Einstellungsdatei wird nun folgender Block hinzugefügt:

<settings xmlns="http://maven.apache.org/SETTINGS/1.0.0"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://maven.apache.org/SETTINGS/1.0.0
                      http://maven.apache.org/xsd/settings-1.0.0.xsd">

  <activeProfiles>
    <activeProfile>github</activeProfile>
  </activeProfiles>

  <profiles>
    <profile>
      <id>github</id>
      <repositories>
        <repository>
          <id>central</id>
          <url>https://repo1.maven.org/maven2</url>
          <releases><enabled>true</enabled></releases>
          <snapshots><enabled>true</enabled></snapshots>
        </repository>
        <repository>
          <id>github</id>
          <name>GitHub OWNER Apache Maven Packages</name>
          <url>https://maven.pkg.github.com/OWNER</url>
        </repository>
      </repositories>
    </profile>
  </profiles>

  <servers>
    <server>
      <id>github</id>
      <username>USERNAME</username>
      <password>TOKEN</password>
    </server>
  </servers>
</settings>

Auch hier muss OWNER wieder durch den eigenen Nutzernamen ersetzt werden. Bei der Konfiguration müssen daneben die Felder USERNAME und TOKEN gefüllt werden. Das Token wird in den Einstellungen von GitHub unter Settings – Developer settings – Personal access tokens erzeugt.

Ein Personal Access Token muss für die Package Registry erzeugt werden

Nachdem das Token erzeugt wurde und die Konfiguration für Maven entsprechend hinterlegt wurde, kann das Package mittels:

mvn deploy

deployed werden und erscheint somit in der Package Registry. Angesehen werden kann das Paket nun mittels der URL:

https://github.com/OWNER/REPOSITORY/packages/

Zur Einbindung des neuen Paketes in einem anderes Projekt muss der pom.xml des anderen Projektes folgender Block hinzugefügt werden:

<!-- External non standard repositories -->
<repositories>
    <repository>
        <id>github</id>
        <url>https://maven.pkg.github.com/OWNER/REPOSITORY</url>
    </repository>
</repositories>

Anschließend kann das neue Paket ganz normal als Dependency hinzugefügt werden:

<dependency>
    <groupId>org.example</groupId>
    <artifactId>libtest</artifactId>
    <version>1.0.1</version>
</dependency>

Damit können schnell Pakete über die GitHub Package Registry bereitgestellt werden.

MQTT-Broker in Java einbinden

Zur Nutzung des MQTT-Protokolls wird ein MQTT-Broker benötigt. Dieser kann separat betrieben oder aber in eine Anwendung eingebettet werden. Ein MQTT-Broker, welcher sich für die Einbettung unter Java eignet, ist Moquette. Zur Einbindung von Moquette muss es den Abhängigkeiten des Projektes hinzugefügt werden:

<dependency>
	<groupId>io.moquette</groupId>
	<artifactId>moquette-broker</artifactId>
	<version>0.12.1</version>
</dependency>

Die Funktionalitäten zum Start und Stop des MQTT-Brokers werden in diesem Beispiel in der Klasse Broker gekapselt. In der Klasse wird eine Instanz vom Typ Server angelegt und über die Methode startServer kann der MQTT-Broker gestartet werden. Beim Start wird in diesem Beispiel das Topic /exit angelegt. Weitere Topics können über die Methode pushTopic angelegt werden. Mittels der Methode stopServer kann der Broker wieder gestoppt werden.

public final class Broker {

    private static Logger log = LoggerFactory.getLogger(new Exception().fillInStackTrace().getStackTrace()[0].getClassName());

    final Server mqttBroker = new Server();

    public void startServer() {

        // Load class path for configuration
        IResourceLoader classpathLoader = new ClasspathResourceLoader();
        final IConfig classPathConfig = new ResourceLoaderConfig(classpathLoader);

        // Start MQTT broker
        log.info("Start MQTT broker...");
        List userHandlers = Collections.singletonList(new PublisherListener());

        try {
            mqttBroker.startServer(classPathConfig, userHandlers);
        } catch (IOException e) {
            log.error("MQTT broker start failed...");
        }

        // Wait before publish topics
        log.info("Wait before topics are pushed...");

        try {
            Thread.sleep(20000);
        } catch (InterruptedException e) {
            log.warn("Pause for publishing topics interupted.");
        }

        // Publishing topics
        log.info("Pushing topics...");

        pushTopic("/exit");

        log.info("Topics pushed...");
    }

    public void stopServer() {
        mqttBroker.stopServer();
    }

    public void pushTopic(String topic) {

        MqttPublishMessage message = MqttMessageBuilders.publish()
                .topicName(topic)
                .retained(true)
                .qos(MqttQoS.EXACTLY_ONCE)
                .payload(Unpooled.copiedBuffer("{}".getBytes(UTF_8)))
                .build();

        mqttBroker.internalPublish(message, "INTRLPUB");
    }
}

Beim Start des Servers wird eine Konfigurationsdatei mit dem Namen moquette.conf aus dem Ordner resources/config geladen. Diese könnte beispielhaft wie folgt aussehen:

##############################################
#  Moquette configuration file. 
#
#  The syntax is equals to mosquitto.conf
# 
##############################################

port 1883

#websocket_port 8080

host 0.0.0.0

#Password file
#password_file password_file.conf

#ssl_port 8883
#jks_path serverkeystore.jks
#key_store_password passw0rdsrv
#key_manager_password passw0rdsrv

allow_anonymous true

Beim Start des MQTT-Brokers wird ein Handler vom Typ PublisherListener registriert. Diese Handler muss natürlich vorher definiert werden:

public class PublisherListener extends AbstractInterceptHandler {

    private static Logger log = LoggerFactory.getLogger(new Exception().fillInStackTrace().getStackTrace()[0].getClassName());

    @Override
    public String getID() {
        return "PublishListener";
    }

    @Override
    public void onPublish(InterceptPublishMessage msg) {

        // Create array for payload
        int readableBytes = msg.getPayload().readableBytes();
        byte[] payload = new byte[readableBytes];

        // Read bytes from payload
        for (int i = 0; i < readableBytes; i++) {
            payload[i] = msg.getPayload().readByte();
        }

        // Create string from payload
        String decodedPayload = new String(payload, UTF_8);
        log.debug("Received on topic: " + msg.getTopicName() + " content: " + decodedPayload);
    }
}

Der Handler wertet alle Publish-Nachrichten aus und loggt diese mittels des Loggers. Nun kann der Broker gestartet werden:

// Start broker
Broker broker = new Broker();
broker.startServer();

// Bind a shutdown hook
Runtime.getRuntime().addShutdownHook(new Thread(() -> {

	log.info("Stopping MQTT broker...");
	broker.stopServer();
}));

Über den registrierten Shutdown-Hook kann der Server anschließend wieder beendet werden. Damit ist die beispielhafte Einbindung von Moquette in eine Java-Applikation abgeschlossen. Der Quelltext von Moquette kann über GitHub bezogen werden. Lizenziert ist das Projekt unter der Apache License in Version 2 und damit freie Software.

Terminal-Bibliothek für Java

Für ein kleines Projekt wollte ich eine Visualisierung in der Konsole bzw. im Terminal unter Java realisieren. Das Problem ist das bei der Visualisierung, je nach verwendetem Terminal, bestimmte Operationen nicht verfügbar sind. Dies fängt schon beim Löschen des Bildschirmes an. Je nach Art des Terminals bzw. Betriebssystem kann es hier unterschiedliche Ansätze geben, um dieses Problem zu lösen. Abhilfe bei diesen Problemen schafft die Bibliothek Lanterna.

Lanterna in Aktion

Laterna liefert die Möglichkeit Text-Schnittstellen und GUIs zu bauen, unabhängig vom verwendeten Terminal. Damit ähnelt sie der C-Bibliothek ncurses. Die Bibliothek ist in der Lage festzustellen, auf welcher Art Terminal sie läuft und kann somit die Ausgabe entsprechend anpassen. So werden z.B. Unix-Steuercodes genutzt, wenn das Terminal diese unterstützt. Nach der Einbindung per Maven:

<dependency>
    <groupId>com.googlecode.lanterna</groupId>
    <artifactId>lanterna</artifactId>
    <version>3.0.1</version>
</dependency>

kann die Nutzung der Bibliothek beginnen. Im ersten Schritt sollte eine Instanz der Klasse Terminal angelegt werden:

Terminal terminal = new DefaultTerminalFactory().createTerminal();

Dies übernimmt die DefaultTerminalFactory. Sie sorgt dafür, dass das passende Terminal ausgewählt wird. Nun können Zeichen auf das Terminal geschrieben werden:

terminal.clearScreen();
terminal.putCharacter('a');

Hier wird das Terminal erst bereinigt und anschließend das Zeichen a auf das Terminal geschrieben. Neben der direkten Arbeit mit dem Terminal, liefert Lanterna eine Abstraktionsschicht namens Screen mit, mit welcher das Terminal wie ein pixelbasiertes Gerät angesprochen werden kann. Neben der Ausgabe beherrscht die Bibliothek die Eingabe. Mittels:

KeyStroke keyStroke = terminal.pollInput();

wird die aktuelle gedrückte Taste abgefragt. Diese Methode arbeitet nicht-blockierend. Für den Fall das eine blockierende Methode benötigt wird, eignet sich die Methode:

KeyStroke keyStroke = terminal.readInput();

Damit lassen sich mithilfe der Bibliothek komplexe Anwendungen auf dem Terminal umsetzen. Wenn die Anwendung beendet werden soll, muss das Terminal mittels:

terminal.close();

geschlossen werden. Der Quelltext der Bibliothek ist auf GitHub zu finden. Er ist unter der LGPL lizenziert und damit freie Software.