segunda-feira, 25 de setembro de 2017

Java Message Service 2.0, WildFly 11 and Maven

In this message I'm going to run a basic example of Java Message Service 2.0 using Maven to manage the project and WildFly 11 as the server.

First, you should create a Maven project in your favorite IDE (Eclipse or IntelliJ, for example) using the maven-archetype-quickstart. Later, I will give a complete pom.xml that supersedes the default one. In this project, let us write a bare-bones synchronous message sender:

package blog.example_JMS;

import javax.jms.ConnectionFactory;
import javax.jms.Destination;
import javax.jms.JMSContext;
import javax.jms.JMSProducer;
import javax.naming.InitialContext;
import javax.naming.NamingException;

public class Sender {
private ConnectionFactory cf;
private Destination d;
public Sender() throws NamingException {
this.cf = InitialContext.doLookup("jms/RemoteConnectionFactory");
this.d = InitialContext.doLookup("jms/queue/QueueExample");
}
public void send(String msg) {
try (JMSContext cntx = this.cf.createContext("joao", "br1o+sa*")) {
JMSProducer prod = cntx.createProducer();
prod.send(d, msg);
}
}

public static void main(String[] args) throws NamingException {
new Sender().send("Hello World, JMS!");
}


}


and a receiver:

package blog.example_JMS;

import javax.jms.ConnectionFactory;
import javax.jms.Destination;
import javax.jms.JMSConsumer;
import javax.jms.JMSContext;
import javax.naming.InitialContext;
import javax.naming.NamingException;

public class Receiver {
private ConnectionFactory cf;
private Destination d;
public Receiver() throws NamingException {
this.cf = InitialContext.doLookup("jms/RemoteConnectionFactory");
this.d = InitialContext.doLookup("jms/queue/QueueExample");
}

public String receive() {
try (JMSContext cntxtthis.cf.createContext("joao", "br1o+sa*")) {
JMSConsumer cons = cntxt.createConsumer(d);
return cons.receiveBody(String.class);
}
}
public static void main(String[] args) throws NamingException {
System.out.println(new Receiver().receive());
}


}


Please be aware of the package: these files should go to the directory src/main/java/blog/example_JMS/.

An issue now is that you should see your IDE pointing out lots of errors in this code, because we haven't changed the pom.xml yet. This a tricky step, as we must find out the right JMS APIs, the ActiveMQ client implementations, and the Java Naming and Directory Interface (JNDI) client. Besides the previous dependencies, we need to correct the default Java version to 1.8 using a plugin (or whatever more recent version you might want to use; Java 9 was just released just four days ago, as I write this message). Overall, this new pom.xml should do the it:

<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>blog</groupId>
<artifactId>example-JMS</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>jar</packaging>

<name>example-JMS</name>
<url>http://maven.apache.org</url>

<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>

<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>3.8.1</version>
<scope>test</scope>
</dependency>

<!-- BOM with wildfly JEE7 implementations -->
<dependency>
<groupId>org.wildfly</groupId>
<artifactId>wildfly-messaging-activemq</artifactId>
<version>11.0.0.CR1</version>
</dependency>

<!-- JMS dependency we need from the BOM -->
<dependency>
<groupId>javax</groupId>
<artifactId>javaee-api</artifactId>
<version>7.0</version>
</dependency>

<dependency>
<groupId>org.wildfly</groupId>
<artifactId>wildfly-jms-client-bom</artifactId>
<version>11.0.0.CR1</version>
<type>pom</type>
</dependency>

<!-- JNDI dependency we need from the BOM -->
<!-- https://mvnrepository.com/artifact/org.wildfly/wildfly-ejb-client-bom -->
<dependency>
<groupId>org.wildfly</groupId>
<artifactId>wildfly-ejb-client-bom</artifactId>
<version>11.0.0.CR1</version>
<type>pom</type>
</dependency>
</dependencies>

<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.7.0</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
</configuration>
</plugin>
</plugins>
</build>


</project>


If you do a Maven-->update or something similar in your IDE the compiling errors should disappear, as the IDE updates the libraries of the project according to the indications of the pom.xml. To compile your project, you need to define the compile goal. For Eclipse, the following figure should help:


You may press the 'Run' button to compile. Please check my previous message for more details on Maven. We are only a few steps away from running the basic JMS example. First, we need to configure the 'doLookup' operation. For this, we use a jndi.properties file that goes to a new folder that you should create, src/main/resources. This is important, as the programs may not run without the resources  folder:

java.naming.factory.initial=org.wildfly.naming.client.WildFlyInitialContextFactory
java.naming.provider.url=http-remoting://localhost:8080

jboss.naming.client.ejb.context=false


This sets the class and port that are used to do the lookup for the ConnectionFactory and Destination that both, sender and receiver, must use.

Finally, we need to create the queue that we are using in WildFly, more precisely, 'QueueExample'. Before we do it, we need to create a management user, to create the 'QueueExample' queue in the browser. We also need to set up an application user called 'joao' with password 'br1o+sa*'. For this, you should run add-user.sh command on the bin directory of WildFly:

filipius-portatil:bin filipius$ ./add-user.sh 

What type of user do you wish to add? 
 a) Management User (mgmt-users.properties) 
 b) Application User (application-users.properties)

The options are really straightforward, but I detailed them in a previous message, here and won't repeat them here.

We are now in conditions to start with WildFly. Just go to the bin directory of WildFly (if you are not there yet), and do the following on the command line:

./standalone.sh -c standalone-full.xml

the standalone-full.xml configuration launches the ActiveMQ provider, which is not active by default (i.e, when the -c option is not provided).

If everything goes well you should see a large number of lines, including some like these, referring to ActiveMQ Artemis:

12:53:38,900 INFO  [org.apache.activemq.artemis.core.server] (ServerService Thread Pool -- 68) AMQ221007: Server is now live
12:53:38,900 INFO  [org.apache.activemq.artemis.core.server] (ServerService Thread Pool -- 68) AMQ221001: Apache ActiveMQ Artemis Message Broker version 1.5.5.jbossorg-007 [default, nodeID=38071513-93d3-11e7-b595-58b03576e95b] 

It is now time to configure the queue 'ExampleQueue' via a web interface. Just hit 'localhost:9990' in the browser. After a few clicks you should arrive here (look at the 'Queues/Topics' button on the right-top):


Now, we must add the previously mentioned queue and give it a public name (compare this to the lookup in the code):


Once this queue is set, we can now run our sender and receiver to get the 'Hello World, JMS!' output.

Let's do another (typical) exercise and create an asynchronous receiver. The System.in.read() in this code illustrates the possibility of doing something else, like reading from another queue, while waiting for an asynchronous event from 'QueueExample':

package blog.example_JMS;

import java.io.IOException;

import javax.jms.ConnectionFactory;
import javax.jms.Destination;
import javax.jms.JMSConsumer;
import javax.jms.JMSContext;
import javax.jms.JMSException;
import javax.jms.Message;
import javax.jms.MessageListener;
import javax.jms.TextMessage;
import javax.naming.InitialContext;
import javax.naming.NamingException;

public class AsyncReceiver implements MessageListener {
private ConnectionFactory cf;
private Destination d;
public AsyncReceiver() throws NamingException {
this.cf = InitialContext.doLookup("jms/RemoteConnectionFactory");
this.d = InitialContext.doLookup("jms/queue/QueueExample");
}

@Override
public void onMessage(Message msg) {
try {
System.out.println(((TextMessage) msg).getText());
} catch (JMSException e) {
e.printStackTrace();
}
}

public void receive() throws IOException {
try (JMSContext cntxt = this.cf.createContext("joao", "br1o+sa*")) {
JMSConsumer cons = cntxt.createConsumer(d);
cons.setMessageListener(this);
System.in.read();
}
}
public static void main(String[] args) throws NamingException, IOException {
new AsyncReceiver().receive();
}


}

quinta-feira, 21 de setembro de 2017

Hello World, Maven!

This year, I am demanding students to manage their projects using Maven. Around 2015 or 2016, I started to see an increasing usage of IntelliJ among students to the detriment of Eclipse. This raised a problem, as I am much more experienced with Eclipse than with IntelliJ, and I'm not willing to master both (as some students kept loyal to Eclipse). Hence, I pretty much saw Maven as an opportunity to settle the differences. Working with Maven is also a great opportunity for students to gain and practice a competence they might well need in their future jobs.

The disadvantage is that Maven might be truly painful to configure, use and debug and might well stand in the way of inexperienced users, easily consuming more time than its fair share.

The first thing to understand about Maven is that, by default, it always tries to do something like compiling, testing, etc. To see this, let's first create a Maven project based on the "Maven Quickstart Archetype". This archetype is available both on Eclipse and IntelliJ. This archetype creates the following structure (see here):

project
|-- pom.xml
`-- src
    |-- main
    |   `-- java
    |       `-- App.java
    `-- test
        `-- java
            `-- AppTest.java

and, if I specify the groupId to be 'blog' and the artifact id to be 'example', I get the following pom.xml:

<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>blog</groupId>
  <artifactId>example</artifactId>
  <version>0.0.1-SNAPSHOT</version>
  <packaging>jar</packaging>

  <name>example</name>
  <url>http://maven.apache.org</url>

  <properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
  </properties>

  <dependencies>
    <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <version>3.8.1</version>
      <scope>test</scope>
    </dependency>
  </dependencies>

</project>


This Archetype brings an App.java file in the blog.example package in the src/main/java folder that will run the classic "Hello World!" program.

package blog.example;

/**
 * Hello world!
 *
 */
public class App 
{
    public static void main( String[] args )
    {
        System.out.println( "Hello World!" );
    }

}


Now, in my Eclipse installation, I immediately get a warning, because my project sets up the J2SE-1.5 JRE system library, and I don't have any JRE compatible with that version. This must be fixed, and this fix should help us to exactly understand how Maven works.

The first thing to notice is that we haven't specified anything about compilation in the pom.xml. Hence, Maven will assume a default compilation phase, which as of September 2017 uses Java version 5 as input and output. That's why we see this issue in the project (with the 1.5 JRE). To make the problem more evident let's include a feature in our program that was not available in Java 5: streams.

package blog.example;

import java.util.stream.IntStream;

public class App 
{
public static void main( String[] args )
{
System.out.println( "Hello World!" );
IntStream.range(1, 4)
.forEach(System.out::println);
}

}


Now, we have errors, instead of a simple warning. We need to change our compiler from version 5 to version 8 (or whatever last version is available). In Eclipse (and IntelliJ) projects, we would use the mouse, navigate menus and so on, to change the installed libraries. We will do it in Maven now, by specifying a plugin that changes the default behavior of the compilation phase.

<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>blog</groupId>
<artifactId>example</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>jar</packaging>

<name>example</name>
<url>http://maven.apache.org</url>

<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>

<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>3.8.1</version>
<scope>test</scope>
</dependency>
</dependencies>

<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.7.0</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
</configuration>
</plugin>
</plugins>
</build>

</project>


We can see the different options of the maven-compiler-plugin in the appropriate web site (click here). I just changed the source and the target, but I've seen cases with some students, where it was necessary to specify the location of the Java compiler in a slightly more complicated configuration, also available in the page I mentioned. This plugin will change the way Maven behaves, when going through the compilation of our project. But, in the case of Eclipse, the errors in the project persist, because Eclipse still doesn't know about the changes we did to the pom.xml. We need to update the project, to put the pom.xml and the Eclipse project in sync or 'updated' (this is available in the context menu over the name of the project, under the title 'Maven'). In IntelliJ, a step like this is also necessary and may be configured to run automatically.

Note that we don't need to do a Maven update or, in other words, to put the project in sync, to compile the project with Maven or run any other phase of the Maven lifecycle, like packaging. We do this for the sake of working in a clean IDE environment, and for being able to get the benefits of using the IDE. If we don't update, and the project is kept with errors, Eclipse (or IntelliJ) won't be able to compile your source code on the fly. If everything is in sync, both Eclipse and yourself (manually via Maven) will be able to compile the source code and generate the classes. So, an update is clearly useful.

Eclipse (and IntelliJ) will compile the source code on their own and run the program if necessary. To do this ourselves in Eclipse, we need to specify a goal for the Maven build. In this case it is compile, as you can see in the 'Goals' box:



If you press the 'Run' button, what you actually get is a compilation, not an execution. You should get something along the lines:

[INFO] Scanning for projects...
[INFO]                                                                         
[INFO] ------------------------------------------------------------------------
[INFO] Building example 0.0.1-SNAPSHOT
[INFO] ------------------------------------------------------------------------
[INFO] 
[INFO] --- maven-resources-plugin:2.6:resources (default-resources) @ example ---
[INFO] Using 'UTF-8' encoding to copy filtered resources.
[INFO] skip non existing resourceDirectory /Users/filipius/aulas/2017-18/IS/code/example/src/main/resources
[INFO] 
[INFO] --- maven-compiler-plugin:3.7.0:compile (default-compile) @ example ---
[INFO] Nothing to compile - all classes are up to date
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 1.815 s
[INFO] Finished at: 2017-09-21T15:47:45+01:00
[INFO] Final Memory: 8M/155M

[INFO] ------------------------------------------------------------------------

The way to execute the program will depend on the IDE. If you now run the program, you should get:

Hello World!
1
2
3

The funny thing and the true power of Maven comes from the fact that we now may do the same thing in the command line, as long as we install Maven (although a different version of Maven, compared to that used by Eclipse/IntelliJ might cause some glitches). Just change directory to the folder where the pom.xml is and run the following command. Note that this is optional, as Eclipse/IntelliJ should do this for you:

mvn compile

We should get the same thing as before:

[INFO] Scanning for projects...
[INFO]                                                                         
[INFO] ------------------------------------------------------------------------
[INFO] Building example 0.0.1-SNAPSHOT
[INFO] ------------------------------------------------------------------------
[INFO] 
[INFO] --- maven-resources-plugin:2.6:resources (default-resources) @ example ---
[INFO] Using 'UTF-8' encoding to copy filtered resources.
[INFO] skip non existing resourceDirectory /Users/filipius/aulas/2017-18/IS/code/example/src/main/resources
[INFO] 
[INFO] --- maven-compiler-plugin:3.7.0:compile (default-compile) @ example ---
[INFO] Changes detected - recompiling the module!
[INFO] Compiling 1 source file to /Users/filipius/aulas/2017-18/IS/code/example/target/classes
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 2.659 s
[INFO] Finished at: 2017-09-21T15:52:57+01:00
[INFO] Final Memory: 13M/155M

[INFO] ------------------------------------------------------------------------


This action will generate the new App.class, if necessary, under the target directory. No Eclipse and no IntelliJ. Everything is ready for command line automation!

Of course, Maven can get much more complicated than this, especially as projects become larger and we need additional control. For now, there are two important things to learn. 1- there are more lifecycle phases than compile; 2 - we can add up dependencies, aka libraries to our project.

Regarding lifecycles, it's unnecessary to repeat what is available here, but I will list the most important ones for convenience:

  • validate - validate the project is correct and all necessary information is available
  • compile - compile the source code of the project
  • test - test the compiled source code using a suitable unit testing framework. These tests should not require the code be packaged or deployed
  • package - take the compiled code and package it in its distributable format, such as a JAR.
  • verify - run any checks on results of integration tests to ensure quality criteria are met
  • install - install the package into the local repository, for use as a dependency in other projects locally
  • deploy - done in the build environment, copies the final package to the remote repository for sharing with other developers and projects.
For example, you might want to generate a .jar package using Maven. Therefore,  a phase with that same name exists for that purpose. How do you control the packaging process? Through a plugin. Configuring plugins, as we saw in the case of compile, might be relatively complicated, but there should be a lot of documentation around.

To include libraries in our project, we just need to add another <dependency>, under the <dependencies> element, like this, if you need jsoup, for example:

<dependency>
<groupId>org.jsoup</groupId>
<artifactId>jsoup</artifactId>
<version>1.8.3</version>

</dependency>

You can look for the last versions of the dependencies in the Maven Repository. You can look for more details regarding dependencies here, although I'm afraid that this seemingly introductory page is incredibly complicated for beginners.