Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Artemis JMS documentation #4186

Merged
merged 2 commits into from
Sep 25, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
287 changes: 287 additions & 0 deletions docs/src/main/asciidoc/artemis-jms-guide.adoc
Original file line number Diff line number Diff line change
@@ -0,0 +1,287 @@
////
This guide is maintained in the main Quarkus repository
and pull requests should be submitted there:
https://github.com/quarkusio/quarkus/tree/master/docs/src/main/asciidoc
////
= Quarkus - Using Artemis JMS extension
include::./attributes.adoc[]

This guide demonstrates how your Quarkus application can use Artemis JMS messaging.

== Prerequisites

To complete this guide, you need:

* less than 15 minutes
* an IDE
* JDK 1.8+ installed with `JAVA_HOME` configured appropriately
* Apache Maven 3.5.3+
* A running Artemis server, or Docker Compose to start one
* GraalVM installed if you want to run in native mode.

== Architecture

In this guide, we are going to generate (random) prices in one component.
These prices are written in an JMS queue (`prices`).
Another component reads from the `prices` queue and stores the last price.
The data can be fetch from a browser using a fetch button from a JAX-RS resource.

== Solution

We recommend that you follow the instructions in the next sections and create the application step by step.
However, you can go right to the completed example.

Clone the Git repository: `git clone {quickstarts-clone-url}`, or download an {quickstarts-archive-url}[archive].

The solution is located in the `artemis-jms` {quickstarts-tree-url}/artemis-jms[directory].
middagj marked this conversation as resolved.
Show resolved Hide resolved

== Creating the Maven Project

First, we need a new project. Create a new project with the following command:

[source, subs=attributes+]
----
mvn io.quarkus:quarkus-maven-plugin:{quarkus-version}:create \
-DprojectGroupId=org.acme \
-DprojectArtifactId=artemis-jms \
-Dextensions="artemis-jms"
----

This command generates a Maven project, importing the Artemis JMS extension.

== Starting an Artemis server

Then, we need an Artemis server.
You can follow the instructions from the https://activemq.apache.org/components/artemis/[Apache Artemis web site] or via docker:

[source]
----
docker run -it --rm -p 8161:8161 -p 61616:61616 -e ARTEMIS_USERNAME=quarkus -e ARTEMIS_PASSWORD=quarkus vromero/activemq-artemis:2.9.0-alpine
----

== The price producer

Create the `src/main/java/org/acme/quarkus/sample/PriceProducer.java` file, with the following content:

[source, java]
----
package org.acme.artemis;

import java.util.Random;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;

import javax.enterprise.context.ApplicationScoped;
import javax.enterprise.event.Observes;
import javax.inject.Inject;
import javax.jms.ConnectionFactory;
import javax.jms.JMSContext;
import javax.jms.Session;

import io.quarkus.runtime.ShutdownEvent;
import io.quarkus.runtime.StartupEvent;

/**
* A bean producing random prices every 5 seconds and sending them to the prices JMS queue.
*/
@ApplicationScoped
public class PriceProducer implements Runnable {

@Inject
ConnectionFactory connectionFactory;

private final Random random = new Random();
private final ScheduledExecutorService scheduler = Executors.newSingleThreadScheduledExecutor();

void onStart(@Observes StartupEvent ev) {
scheduler.scheduleWithFixedDelay(this, 0L, 5L, TimeUnit.SECONDS);
}

void onStop(@Observes ShutdownEvent ev) {
scheduler.shutdown();
}

@Override
public void run() {
try (JMSContext context = connectionFactory.createContext(Session.AUTO_ACKNOWLEDGE)) {
context.createProducer().send(context.createQueue("prices"), Integer.toString(random.nextInt(100)));
}
}
}
----

== The price consumer

The price consumer reads the prices from JMS, and stores the last one.
Create the `src/main/java/org/acme/quarkus/sample/PriceConsumer.java` file with the following content:

[source, java]
----
package org.acme.artemis;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

import javax.enterprise.context.ApplicationScoped;
import javax.enterprise.event.Observes;
import javax.inject.Inject;
import javax.jms.ConnectionFactory;
import javax.jms.JMSConsumer;
import javax.jms.JMSContext;
import javax.jms.JMSException;
import javax.jms.Message;
import javax.jms.Session;

import io.quarkus.runtime.ShutdownEvent;
import io.quarkus.runtime.StartupEvent;

/**
* A bean consuming prices from the JMS queue.
*/
@ApplicationScoped
public class PriceConsumer implements Runnable {

@Inject
ConnectionFactory connectionFactory;

private final ExecutorService scheduler = Executors.newSingleThreadExecutor();

private volatile String lastPrice;

public String getLastPrice() {
return lastPrice;
}

void onStart(@Observes StartupEvent ev) {
scheduler.submit(this);
}

void onStop(@Observes ShutdownEvent ev) {
scheduler.shutdown();
}

@Override
public void run() {
try (JMSContext context = connectionFactory.createContext(Session.AUTO_ACKNOWLEDGE)) {
JMSConsumer consumer = context.createConsumer(context.createQueue("prices"));
while (true) {
Message message = consumer.receive();
if (message == null) return;
lastPrice = message.getBody(String.class);
}
} catch (JMSException e) {
throw new RuntimeException(e);
}
}
}
----

== The price resource

Finally, let's create a simple JAX-RS resource to show the last price.
Creates the `src/main/java/org/acme/quarkus/sample/PriceResource.java` file with the following content:

[source, java]
----
package org.acme.artemis;

import javax.inject.Inject;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;

/**
* A simple resource showing the last price.
*/
@Path("/prices")
public class PriceResource {

@Inject
PriceConsumer consumer;

@GET
@Path("last")
@Produces(MediaType.TEXT_PLAIN)
public String last() {
return consumer.getLastPrice();
}
}
----

== Configuring the Artemis properties

We need to configure the Artemis connection properties.
This is done in the `application.properties` file.

[source]
----
# Configures the Artemis properties.
quarkus.artemis.url=tcp://localhost:61616
quarkus.artemis.username=quarkus
quarkus.artemis.password=quarkus
----

== The HTML page

Final touch, the HTML page reading the converted prices using SSE.

Create the `src/main/resources/META-INF/resources/prices.html` file, with the following content:

[source, html]
----
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Prices</title>

<link rel="stylesheet" type="text/css"
href="https://cdnjs.cloudflare.com/ajax/libs/patternfly/3.24.0/css/patternfly.min.css">
<link rel="stylesheet" type="text/css"
href="https://cdnjs.cloudflare.com/ajax/libs/patternfly/3.24.0/css/patternfly-additions.min.css">
</head>
<body>
<div class="container">

<h2>Last price</h2>
<div class="row">
<p class="col-md-12"><button id="fetch">Fetch</button>The last price is <strong><span id="content">N/A</span>&nbsp;&euro;</strong>.</p>
</div>
</div>
</body>
<script>
document.getElementById("fetch").addEventListener("click", function() {
fetch("/prices/last").then(function (response) {
response.text().then(function (text) {
document.getElementById("content").textContent = text;
})
})
})
</script>
</html>
----

Nothing spectacular here. On each fetch, it updates the page.

== Get it running

If you followed the instructions, you should have the Artemis server running.
Then, you just need to run the application using:

[source, shell]
----
./mvnw compile quarkus:dev
----

Open `http://localhost:8080/prices.html` in your browser.

== Running Native

You can build the native executable with:

[source, shell]
----
./mvnw package -Pnative
----
1 change: 1 addition & 0 deletions docs/src/main/asciidoc/index.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ include::quarkus-intro.adoc[tag=intro]
* link:tika-guide.html[Using Apache Tika]
* link:mongo-guide.html[Using MongoDB]
* link:mongodb-panache-guide.html[Simplified MongoDB with Panache]
* link:artemis-jms-guide.html[Using Artemis JMS Client]

* link:faq.html[FAQs]

Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,11 @@
package io.quarkus.it.artemis;

import javax.annotation.PostConstruct;
import javax.enterprise.context.ApplicationScoped;
import javax.inject.Inject;
import javax.jms.Connection;
import javax.jms.ConnectionFactory;
import javax.jms.JMSConsumer;
import javax.jms.JMSContext;
import javax.jms.JMSException;
import javax.jms.MessageConsumer;
import javax.jms.Session;

@ApplicationScoped
Expand All @@ -15,17 +14,9 @@ public class ArtemisConsumerManager {
@Inject
ConnectionFactory connectionFactory;

private Connection connection;

@PostConstruct
public void init() throws JMSException {
connection = connectionFactory.createConnection();
connection.start();
}

public String receive() {
try (Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE)) {
MessageConsumer consumer = session.createConsumer(session.createQueue("test-jms"));
try (JMSContext context = connectionFactory.createContext(Session.AUTO_ACKNOWLEDGE)) {
JMSConsumer consumer = context.createConsumer(context.createQueue("test-jms"));
return consumer.receive(1000L).getBody(String.class);
} catch (JMSException e) {
throw new RuntimeException("Could not receive message", e);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,10 @@
package io.quarkus.it.artemis;

import javax.annotation.PostConstruct;
import javax.enterprise.context.ApplicationScoped;
import javax.inject.Inject;
import javax.jms.Connection;
import javax.jms.ConnectionFactory;
import javax.jms.JMSException;
import javax.jms.MessageProducer;
import javax.jms.JMSContext;
import javax.jms.JMSProducer;
import javax.jms.Session;

@ApplicationScoped
Expand All @@ -15,19 +13,10 @@ public class ArtemisProducerManager {
@Inject
ConnectionFactory connectionFactory;

private Connection connection;

@PostConstruct
public void init() throws JMSException {
connection = connectionFactory.createConnection();
}

public void send(String body) {
try (Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE)) {
MessageProducer producer = session.createProducer(session.createQueue("test-jms"));
producer.send(session.createTextMessage(body));
} catch (JMSException e) {
throw new RuntimeException("Could not send message", e);
try (JMSContext context = connectionFactory.createContext(Session.AUTO_ACKNOWLEDGE)) {
JMSProducer producer = context.createProducer();
producer.send(context.createQueue("test-jms"), body);
}
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package io.quarkus.it.artemis;

import javax.jms.Session;
import javax.jms.JMSContext;
import javax.ws.rs.core.Response.Status;

import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
Expand All @@ -17,12 +18,12 @@ public class ArtemisConsumerTest implements ArtemisHelper {
@Test
public void test() throws Exception {
String body = createBody();
try (Session session = createSession()) {
session.createProducer(session.createQueue("test-jms")).send(session.createTextMessage(body));
try (JMSContext context = createContext()) {
context.createProducer().send(context.createQueue("test-jms"), body);
}

Response response = RestAssured.with().body(body).get("/artemis");
Assertions.assertEquals(javax.ws.rs.core.Response.Status.OK.getStatusCode(), response.statusCode());
Assertions.assertEquals(Status.OK.getStatusCode(), response.statusCode());
Assertions.assertEquals(body, response.getBody().asString());
}
}
Loading