mapjfx problems

Since some time now users have reported that mapjfx does not properly display the maps. I wrote a small JavaFX application which is just a WebView and a textfield to edit an URL. This application as well shows buggy behaviour in not loading the map tiles properly. This is the plain JavaFX WebView, with no adddition, custom Javascript code or things that mapjfx does:

Other browsers have no problem with these pages. I do not know if this is some bug in WebView – or more probably in WebKit that’s used internally, but there is nothing I have found up to now that I could do to work around.

This map-tile loading problem only seems to happen with OpenStreetMap servers, I have up to now found no problems when using Bing Maps.

processing meta annotations with Java

Everybody who is using Spring these days knows that Spring has many annotations that are just used to group together a couple of other annotations and so to keep the number of annotations small. For example the @RestController annotation combines basically the @Controller and the @ResponseBody annotation so that the user only needs to annotate his class with one annotation instead of two annotations.

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Controller
@ResponseBody
public @interface RestController {
    String value() default "";
}

The JEE @Stereotype annotation works basically the same way.

In this post I show how to define and evaluate such meta annotations in plain Java without using Spring or JEE.

The annotations

I define the annotations for the example within one class I called WorkingDays. I define one annotation for each day of the week and two annotations that group working days and weekend days by referencing the other annotations:

public class WorkingDays {

    @Retention(RUNTIME)
    @interface OnMonday { }

    @Retention(RUNTIME)
    @interface OnTuesday { }

    @Retention(RUNTIME)
    @interface OnWednesday { }

    @Retention(RUNTIME)
    @interface OnThursday { }

    @Retention(RUNTIME)
    @interface OnFriday { }

    @Retention(RUNTIME)
    @interface OnSaturday { }

    @Retention(RUNTIME)
    @interface OnSunday { }

    @Retention(RUNTIME)
    @OnMonday @OnTuesday @OnWednesday @OnThursday @OnFriday
    @interface OnWorkday { }

    @Retention(RUNTIME)
    @OnSaturday @OnSunday
    @interface OnWeekend { }

    public static List<Class<? extends Annotation>> dayOfWeekAnnotations() {
        return Arrays.asList(WorkingDays.OnMonday.class, WorkingDays.OnTuesday.class, WorkingDays.OnWednesday.class,
                WorkingDays.OnThursday.class, WorkingDays.OnFriday.class, WorkingDays.OnSaturday.class,
                WorkingDays.OnSunday.class);
    }
}

In addition to that there is a method dayOfWeekAnnotations() which returns the annotations for the single days, but not the grouping ones.

The class using the annotations

I define a class Employee with three nested subclasses, one is using the @OnWorkday annotation, another one the @OnWeekend annotation and the third one both of them. Please notice, that the annotations for the separate days are not used here:

public class Employee {

    @OnWorkday
    public static class OnlyOnWorkdayEmployee extends Employee {
    }

    @OnWeekend
    public static class OnlyOnWeekendEmployee extends Employee {
    }

    @OnWorkday @OnWeekend
    public static class EveryDayEmployee extends Employee {
    }

    public String simpleClassName() {
        return getClass().getSimpleName();
    }
}

The class evaluating the annotations

To evaluate this, I create a class named Office in which I first create three different employees, each of a different type.

Then I iterate over all the annotations for the single days – remember, the employees use the meta annotations, not the ones for the days –  and print out which of the employees works on which day. For this I use a utility class MetaAnnotationUtil which I will show after the output.

public class Office {
    private static final Logger LOGGER = LoggerFactory.getLogger(Office.class);

    public static void main(String[] args) {
        List<Employee> employees = getEmployees();

        WorkingDays.dayOfWeekAnnotations()
                .forEach(dayAnnotation -> {
                    LOGGER.info("\n{}", dayAnnotation.getSimpleName());
                    employees.forEach(employee ->
                            LOGGER.info(" {} is working: {}", employee.simpleClassName(),
                                    employeeWorksOn(employee, dayAnnotation)));
                });
    }

    private static boolean employeeWorksOn(Employee employee, Class<? extends Annotation> dayAnnotation) {
        return MetaAnnotationUtil.hasMetaAnnotation(employee.getClass(), dayAnnotation);
    }

    @NotNull
    private static List<Employee> getEmployees() {
        List<Employee> employees = new ArrayList<>();
        employees.add(new Employee.EveryDayEmployee());
        employees.add(new Employee.OnlyOnWorkdayEmployee());
        employees.add(new Employee.OnlyOnWeekendEmployee());
        return employees;
    }
}

This is the output when running the program:

13:04:18.302 [main] INFO  com.sothawo.annotations.Office - 
OnMonday
13:04:18.342 [main] INFO  com.sothawo.annotations.Office -  EveryDayEmployee is working: true
13:04:18.342 [main] INFO  com.sothawo.annotations.Office -  OnlyOnWorkdayEmployee is working: true
13:04:18.343 [main] INFO  com.sothawo.annotations.Office -  OnlyOnWeekendEmployee is working: false
13:04:18.343 [main] INFO  com.sothawo.annotations.Office - 
OnTuesday
13:04:18.343 [main] INFO  com.sothawo.annotations.Office -  EveryDayEmployee is working: true
13:04:18.343 [main] INFO  com.sothawo.annotations.Office -  OnlyOnWorkdayEmployee is working: true
13:04:18.343 [main] INFO  com.sothawo.annotations.Office -  OnlyOnWeekendEmployee is working: false
13:04:18.343 [main] INFO  com.sothawo.annotations.Office - 
OnWednesday
13:04:18.344 [main] INFO  com.sothawo.annotations.Office -  EveryDayEmployee is working: true
13:04:18.344 [main] INFO  com.sothawo.annotations.Office -  OnlyOnWorkdayEmployee is working: true
13:04:18.344 [main] INFO  com.sothawo.annotations.Office -  OnlyOnWeekendEmployee is working: false
13:04:18.344 [main] INFO  com.sothawo.annotations.Office - 
OnThursday
13:04:18.344 [main] INFO  com.sothawo.annotations.Office -  EveryDayEmployee is working: true
13:04:18.344 [main] INFO  com.sothawo.annotations.Office -  OnlyOnWorkdayEmployee is working: true
13:04:18.345 [main] INFO  com.sothawo.annotations.Office -  OnlyOnWeekendEmployee is working: false
13:04:18.345 [main] INFO  com.sothawo.annotations.Office - 
OnFriday
13:04:18.346 [main] INFO  com.sothawo.annotations.Office -  EveryDayEmployee is working: true
13:04:18.346 [main] INFO  com.sothawo.annotations.Office -  OnlyOnWorkdayEmployee is working: true
13:04:18.346 [main] INFO  com.sothawo.annotations.Office -  OnlyOnWeekendEmployee is working: false
13:04:18.347 [main] INFO  com.sothawo.annotations.Office - 
OnSaturday
13:04:18.347 [main] INFO  com.sothawo.annotations.Office -  EveryDayEmployee is working: true
13:04:18.347 [main] INFO  com.sothawo.annotations.Office -  OnlyOnWorkdayEmployee is working: false
13:04:18.347 [main] INFO  com.sothawo.annotations.Office -  OnlyOnWeekendEmployee is working: true
13:04:18.348 [main] INFO  com.sothawo.annotations.Office - 
OnSunday
13:04:18.349 [main] INFO  com.sothawo.annotations.Office -  EveryDayEmployee is working: true
13:04:18.349 [main] INFO  com.sothawo.annotations.Office -  OnlyOnWorkdayEmployee is working: false
13:04:18.349 [main] INFO  com.sothawo.annotations.Office -  OnlyOnWeekendEmployee is working: true

The class doing the magic

In the MetaAnnotationUtil class I setup a Set to collect the annotations and then add the found annotations if I have them not in the set already and recursively process the annotations of the current annotation:

public abstract class MetaAnnotationUtil {

    /**
     * checks wether an annotated element has an annotaion directly or as a meta annotation (annotation on annotation)
     *
     * @param annotatedElement
     *         the annotated element
     * @param annotationClass
     *         the annotation class to search
     * @return true if the annotaion class is found
     */
    public static boolean hasMetaAnnotation(@NotNull final AnnotatedElement annotatedElement,
                                            @NotNull Class<? extends Annotation> annotationClass) {
        final Set<Class<? extends Annotation>> foundAnnotations = new HashSet<>();
        findAllAnnotations(annotatedElement, foundAnnotations);
        return foundAnnotations.contains(annotationClass);
    }

    /**
     * do a meta (recursive) search on the annotated element and ignore annotations that have already been found
     *
     * @param annotatedElement
     *         the annotated element
     * @param foundAnnotations
     *         the already found annotations
     */
    private static void findAllAnnotations(@NotNull final AnnotatedElement annotatedElement,
                                           @NotNull final Set<Class<? extends Annotation>> foundAnnotations) {
        Arrays.stream(annotatedElement.getDeclaredAnnotations())
                .map(Annotation::annotationType)
                .filter(annotation -> !foundAnnotations.contains(annotation))
                .forEach(aClass -> {
                    foundAnnotations.add(aClass);
                    findAllAnnotations(aClass, foundAnnotations);
                });
    }
}

 

Conclusion

This post has shown that with a little recursive implementation, meta annotations can easily be processed. In a real life scenario I would add caching mechanisms to make sure that the annotations for classes and methods are not repeatedly searched. They can be cached as they are created on compile time and do not change when running.

And, when you are already using Spring and just need to add some custom meta annotations to your project: Use Spring Core’s AnnotationUtil class and do not write something of your own!

mapjfx 1.15.0 using OpenLayers 4.6.4

I just released mapjfx version 1.15.0 it should be found shortly at maven central, the artifact coordinates are:

  <dependency>
    <groupId>com.sothawo</groupId>
    <artifactId>mapjfx</artifactId>
    <version>1.15.0</version>
  </dependency>

The source is available at GitHub.

Now uses OpenLayers 4.6.4.

Comments and contributions welcome.

mapjfx 1.14.0 released

I just released mapjfx version 1.14.0 it should be found shortly at maven central, the artifact coordinates are:

  <dependency>
    <groupId>com.sothawo</groupId>
    <artifactId>mapjfx</artifactId>
    <version>1.14.0</version>
  </dependency>

The source is available at GitHub.

  • There are new events that are triggered on mouse entry and exit on markers and labels. Thanks to skinkie for that.
  • The CSS style property of a Label is now an observed property and changes will be reflected on the map immediately.
  • Now uses OpenLayers 4.4.0

Comments and contributions welcome.

mapjfx 1.13.2 using OpenLayers 4.3.2

I just released mapjfx version 1.13.2 it should be found shortly at maven central, the artifact coordinates are:

  <dependency>
    <groupId>com.sothawo</groupId>
    <artifactId>mapjfx</artifactId>
    <version>1.13.2</version>
  </dependency>

The source is available at GitHub.

Now uses OpenLayers 4.3.2.

Comments and contributions welcome.

a simple web based chat application built with Kotlin, Vaadin, Spring Boot and Apache Kafka

Intro

In this post I show how to combine some language / frameworks and libraries / tools to build a web-based scalable chat application. I chose the following combination of tools:

As I am bad in creating cool names for projects I just put together the first letters of the used tools and named this whole thing kovasbak. The complete source code and project is available on GitHub.

What it will look like

The following screenshot shows four browser windows with four users chatting:

Running the backend

The first thing that I have to do is to get Apache Kafka running. I downloaded the actual version (0.11.0.0) from the Apache Kafka website and unpacked the download in a local directory. According to the Kafka documentation I started first zookeeper and then one Kafka broker:

./bin/zookeeper-server-start.sh config/zookeeper.properties &
./bin/kafka-server-start.sh config/server.properties &

I am just using the default values, that gets Kafka runnning on port 9092.

Setting up the project

I am using Java 1.8.0_131 and IntelliJ IDEA, but the project is totally maven based, so you can use the IDE / editor of your choice. To create the project, I used the Spring Intializr integration in IntelliJ, but of course you can create the project by using the Spring Initializr website.

I just selected Kotlin as language, Java version 1.8, Spring Boot 1.5.4 and additionally selected web/vaadin and io/kafka.

After creating the project you end up with the following pom.xml, I only added the highlighted lines to be able to have server-push (more on that later):

<?xml version="1.0" encoding="UTF-8"?>
<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/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>

  <groupId>com.sothawo</groupId>
  <artifactId>kovasbak</artifactId>
  <version>0.0.1-SNAPSHOT</version>
  <packaging>jar</packaging>

  <name>kovasbak</name>
  <description>a simple chat system built with Kotlin, Vaadin, spring Boot and Apache Kafka</description>

  <parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>1.5.4.RELEASE</version>
    <relativePath/> <!-- lookup parent from repository -->
  </parent>

  <properties>
    <kotlin.compiler.incremental>true</kotlin.compiler.incremental>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
    <java.version>1.8</java.version>

    <kotlin.version>1.1.3</kotlin.version>
    <vaadin.version>8.0.6</vaadin.version>
  </properties>

  <dependencies>
    <dependency>
      <groupId>org.springframework.kafka</groupId>
      <artifactId>spring-kafka</artifactId>
    </dependency>
    <dependency>
      <groupId>com.vaadin</groupId>
      <artifactId>vaadin-spring-boot-starter</artifactId>
    </dependency>
    <dependency>
      <groupId>com.vaadin</groupId>
      <artifactId>vaadin-push</artifactId>
    </dependency>
    <dependency>
      <groupId>org.jetbrains.kotlin</groupId>
      <artifactId>kotlin-stdlib-jre8</artifactId>
      <version>${kotlin.version}</version>
    </dependency>
    <dependency>
      <groupId>org.jetbrains.kotlin</groupId>
      <artifactId>kotlin-reflect</artifactId>
      <version>${kotlin.version}</version>
    </dependency>

    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-test</artifactId>
      <scope>test</scope>
    </dependency>
  </dependencies>

  <dependencyManagement>
    <dependencies>
      <dependency>
        <groupId>com.vaadin</groupId>
        <artifactId>vaadin-bom</artifactId>
        <version>${vaadin.version}</version>
        <type>pom</type>
        <scope>import</scope>
      </dependency>
    </dependencies>
  </dependencyManagement>

  <build>
    <sourceDirectory>${project.basedir}/src/main/kotlin</sourceDirectory>
    <testSourceDirectory>${project.basedir}/src/test/kotlin</testSourceDirectory>
    <plugins>
      <plugin>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-maven-plugin</artifactId>
      </plugin>
      <plugin>
        <artifactId>kotlin-maven-plugin</artifactId>
        <groupId>org.jetbrains.kotlin</groupId>
        <version>${kotlin.version}</version>
        <configuration>
          <compilerPlugins>
            <plugin>spring</plugin>
          </compilerPlugins>
          <jvmTarget>1.8</jvmTarget>
        </configuration>
        <executions>
          <execution>
            <id>compile</id>
            <phase>compile</phase>
            <goals>
              <goal>compile</goal>
            </goals>
          </execution>
          <execution>
            <id>test-compile</id>
            <phase>test-compile</phase>
            <goals>
              <goal>test-compile</goal>
            </goals>
          </execution>
        </executions>
        <dependencies>
          <dependency>
            <groupId>org.jetbrains.kotlin</groupId>
            <artifactId>kotlin-maven-allopen</artifactId>
            <version>${kotlin.version}</version>
          </dependency>
        </dependencies>
      </plugin>
    </plugins>
  </build>


</project>

The code

In this post I will only show the relevant lines from the code, I will skip package and import statements, the full code is available at GitHub.

The application class

The application class created by the initializr just gets one additional line:

@SpringBootApplication
@EnableKafka
class KovasbakApplication

fun main(args: Array<String>) {
    SpringApplication.run(KovasbakApplication::class.java, *args)
}

The @EnableKafka annotation is used to tell Spring Boot to pull in the kafka related classes and libs.

The UI classes

ChatDisplay

The ChatDisplay is the Panel displaying the chat messages. I first used a TextArea, but had problems with programmatically scrolling to the bottom. So I created this small class that uses a Label to display the data:

class ChatDisplay : Panel() {
    val text: Label

    init {
        setSizeFull()
        text = Label().apply { contentMode = ContentMode.HTML }
        content = VerticalLayout().apply { addComponent(text) }
    }

    fun addMessage(user: String, message: String) {
        text.value = when {
            text.value.isNullOrEmpty() -> "<em>$user:</em> $message"
            else -> text.value + "<br/><em>$user:</em> $message"
        }
        scrollTop = Int.MAX_VALUE
    }
}

ChatUI

This is the main UI class:

@SpringUI
@PreserveOnRefresh
@Push
class ChatUI : UI(), KafkaConnectorListener {

    lateinit var user: String
    val chatDisplay = ChatDisplay()
    val userLabel = Label()

    @Autowired
    lateinit var kafkaConnector: KafkaConnector

    // skipping content here....

    companion object {
        val log: Logger = LoggerFactory.getLogger(ChatUI::class.java)
    }
}

It is marked as a Vaadin UI with @SpringUI, @PreserveOnRefresh keeps the session when the browser is reloaded, and @Push marks this for server-push when new messages arrive from Kafka. The class implements an interface KafkaConnectorListener which is described together with the KafkaConnector class.

The ChatUI has the following fields:

  • user: the name of the user that is chatting
  • chatDisplay: the display panel for the messages
  • userLabel: sits at the bottom left to show the name of the user
  • kafkaConnector: used for sending the own messages and to register for getting the messages from kafka

It further has a companion object containing the Logger. I now show the methods of the class:

override fun init(vaadinRequest: VaadinRequest?) {
    kafkaConnector.addListener(this)
    content = VerticalLayout().apply {
        setSizeFull()
        addComponents(chatDisplay, createInputs())
        setExpandRatio(chatDisplay, 1F)
    }
    askForUserName()
}

private fun createInputs(): Component {
    return HorizontalLayout().apply {
        setWidth(100F, Sizeable.Unit.PERCENTAGE)
        val messageField = TextField().apply { setWidth(100F, Sizeable.Unit.PERCENTAGE) }
        val button = Button("Send").apply {
            setClickShortcut(ShortcutAction.KeyCode.ENTER)
            addClickListener {
                kafkaConnector.send(user, messageField.value)
                messageField.apply { clear(); focus() }
            }
        }
        addComponents(userLabel, messageField, button)
        setComponentAlignment(userLabel, Alignment.MIDDLE_LEFT)
        setExpandRatio(messageField, 1F)
    }
}

This sets up the basic layout with the ChatDisplay and the other UI elements, registers the ChatUI with the KafkaConnector. The click handler for the send button is set up so that the user name and the content of the message TextField are sent to the KafkaConnector (see marked line).

After setting up the layout, the user is asked for her name with the following method:

private fun askForUserName() {
    addWindow(Window("your user:").apply {
        isModal = true
        isClosable = false
        isResizable = false
        content = VerticalLayout().apply {
            val nameField = TextField().apply { focus() }
            addComponent(nameField)
            addComponent(Button("OK").apply {
                setClickShortcut(ShortcutAction.KeyCode.ENTER)
                addClickListener {
                    user = nameField.value
                    if (!user.isNullOrEmpty()) {
                        close()
                        userLabel.value = user
                        log.info("user entered: $user")
                    }
                }
            })
        }
        center()
    })
}

This shows a modal window where the user’s name must be entered.

There is a method that is called when the UI is disposed:

override fun detach() {
    kafkaConnector.removeListener(this)
    super.detach()
    log.info("session ended for user $user")
}

The code used to send the actual message to the kafka connector was already shown, the last thing in this class is the code that is called from the KafkaConnector when new messages arrive:

override fun chatMessage(user: String, message: String) {
    access { chatDisplay.addMessage(user, message) }
}

The received data is added to the chatDisplay, but this is wrapped as a Runnable in the UI.access() method for two reasons:

  1. the code is asynchronously from a different thread and must be wrapped to be run on the UI thread.
  2. Executing the code in access() in combination with the @Push annotation on the class results in a server push to the client which is necessary so that the new messages are immediately shown.

The Kafka connector class

All communication with Kafka is wrapped in a Spring Component (thus being a singleton) which just has the following code:

interface KafkaConnectorListener {
    fun chatMessage(user: String, message: String)
}

@Component
class KafkaConnector {

    val listeners = mutableListOf<KafkaConnectorListener>()

    fun addListener(listener: KafkaConnectorListener) {
        listeners += listener
    }

    fun removeListener(listener: KafkaConnectorListener) {
        listeners -= listener
    }

    @Autowired
    lateinit var kafka: KafkaTemplate<String, String>

    fun send(user: String, message: String) {
        log.info("$user sending message \"$message\"")
        kafka.send("kovasbak-chat", user, message)
    }

    @KafkaListener(topics = arrayOf("kovasbak-chat"))
    fun receive(consumerRecord: ConsumerRecord<String?, String?>) {
        val key: String = consumerRecord.key() ?: "???"
        val value: String = consumerRecord.value() ?: "???"
        log.info("got kafka record with key \"$key\" and value \"$value\"")
        listeners.forEach { listener -> listener.chatMessage(key, value) }
    }

    companion object {
        val log: Logger = LoggerFactory.getLogger(KafkaConnector::class.java)
    }
}

First I defined the KafkaConnectorListener interface which the ChatUI class implements so they can be registered for new messages.

The KafkaConnector has a list of listeners and the methods to add and remove listeners. Nothing special here.

For sending a new message to kafka, the send method uses the injected KafkaTemplate (which comes from the spring-kafka library) to send the data to kafka by using the username as key and the message text as payload. The topic name that is used is kovasbak-chat.

By marking the receive method with @KafkaListener the method is called every time when a message in kafka arrives from any client. The data is parsed for the username and message body and the it is sent to all the registered clients. And finally there is a companion object with a Logger.

The configuration

spring.kafka.consumer.group-id=${random.uuid}
spring.kafka.consumer.auto-offset-reset=latest
spring.kafka.bootstrap-servers=localhost:9092

I use a random kafka consumer-group id so that each instance of my webapp gets all messages, I am not interested in old messages and define the host and port of the kafka broker.

Fire it up

You can either run the program from within the IDE or go to the command line and:

mvn package
java -jar target/kovasbak-0.0.1-SNAPSHOT.jar

you can then as well start a second instance on a different port like and access the servers on both localhost:8080 and localhost:8081

java -jar target/kovasbak-0.0.1-SNAPSHOT.jar --server.port=8081

Conclusion

To sum it up: with just a handful of code lines we have a scalable web-based chat-service which uses a scalable backend for message processing.

mapjfx 1.13.1 using OpenLayers 4.2.0

I just released mapjfx version 1.13.1 it should be found shortly at maven central, the artifact coordinates are:

  <dependency>
    <groupId>com.sothawo</groupId>
    <artifactId>mapjfx</artifactId>
    <version>1.13.1</version>
  </dependency>

The source is available at GitHub.

Now uses OpenLayers 4.2.0.

Comments and contributions welcome.

the shortest code to throw a NullPointerException

If I were to throw a NullpointerException from within my code I normally would code something like this:

if(somethingIsNull) {
  throw new NullPointerException();
}

While reading the code examples on https://github.com/reactive-streams/reactive-streams-jvm I just saw this definitely shorter possibility which I did not know up to now:

if(somethingIsNull) {
  throw null;
}

mapjfx 1.13.0 adds the possibility to exclude URLs from being cached

I just released mapjfx version 1.13.0 it should be found shortly at maven central, the artifact coordinates are:

  <dependency>
    <groupId>com.sothawo</groupId>
    <artifactId>mapjfx</artifactId>
    <version>1.13.0</version>
  </dependency>

The source is available at GitHub.

The offline cache now can be configured with a collection of Strings (Java RegExp patterns) so that URLs matching any of these will not be cached.

 

Comments and contributions welcome.