From cf705dc5e9fc6b0cd364d0d85064d96c85e35d2b Mon Sep 17 00:00:00 2001 From: Jacob Middag Date: Tue, 24 Sep 2019 20:24:18 +0200 Subject: [PATCH 1/2] Simplify Artemis JMS integration test --- .../it/artemis/ArtemisConsumerManager.java | 17 ++++----------- .../it/artemis/ArtemisProducerManager.java | 21 +++++-------------- .../it/artemis/ArtemisConsumerTest.java | 9 ++++---- .../io/quarkus/it/artemis/ArtemisHelper.java | 8 +++---- .../it/artemis/ArtemisProducerTest.java | 11 +++++----- 5 files changed, 23 insertions(+), 43 deletions(-) diff --git a/integration-tests/artemis-jms/src/main/java/io/quarkus/it/artemis/ArtemisConsumerManager.java b/integration-tests/artemis-jms/src/main/java/io/quarkus/it/artemis/ArtemisConsumerManager.java index 5452a493cd0a7..43cd1ed6e19f2 100644 --- a/integration-tests/artemis-jms/src/main/java/io/quarkus/it/artemis/ArtemisConsumerManager.java +++ b/integration-tests/artemis-jms/src/main/java/io/quarkus/it/artemis/ArtemisConsumerManager.java @@ -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 @@ -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); diff --git a/integration-tests/artemis-jms/src/main/java/io/quarkus/it/artemis/ArtemisProducerManager.java b/integration-tests/artemis-jms/src/main/java/io/quarkus/it/artemis/ArtemisProducerManager.java index edd8016e1030c..b7cccd1932ff8 100644 --- a/integration-tests/artemis-jms/src/main/java/io/quarkus/it/artemis/ArtemisProducerManager.java +++ b/integration-tests/artemis-jms/src/main/java/io/quarkus/it/artemis/ArtemisProducerManager.java @@ -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 @@ -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); } } } diff --git a/integration-tests/artemis-jms/src/test/java/io/quarkus/it/artemis/ArtemisConsumerTest.java b/integration-tests/artemis-jms/src/test/java/io/quarkus/it/artemis/ArtemisConsumerTest.java index d8ec77a54f229..8a2f78c0b3b83 100644 --- a/integration-tests/artemis-jms/src/test/java/io/quarkus/it/artemis/ArtemisConsumerTest.java +++ b/integration-tests/artemis-jms/src/test/java/io/quarkus/it/artemis/ArtemisConsumerTest.java @@ -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; @@ -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()); } } diff --git a/integration-tests/artemis-jms/src/test/java/io/quarkus/it/artemis/ArtemisHelper.java b/integration-tests/artemis-jms/src/test/java/io/quarkus/it/artemis/ArtemisHelper.java index 33dd8511cb72f..f18a79cc7a561 100644 --- a/integration-tests/artemis-jms/src/test/java/io/quarkus/it/artemis/ArtemisHelper.java +++ b/integration-tests/artemis-jms/src/test/java/io/quarkus/it/artemis/ArtemisHelper.java @@ -2,7 +2,7 @@ import java.util.Random; -import javax.jms.Connection; +import javax.jms.JMSContext; import javax.jms.JMSException; import javax.jms.Session; @@ -14,9 +14,7 @@ default String createBody() { return Integer.toString(new Random().nextInt(Integer.MAX_VALUE), 16); } - default Session createSession() throws JMSException { - Connection connection = new ActiveMQJMSConnectionFactory("tcp://localhost:61616").createConnection(); - connection.start(); - return connection.createSession(false, Session.AUTO_ACKNOWLEDGE); + default JMSContext createContext() throws JMSException { + return new ActiveMQJMSConnectionFactory("tcp://localhost:61616").createContext(Session.AUTO_ACKNOWLEDGE); } } diff --git a/integration-tests/artemis-jms/src/test/java/io/quarkus/it/artemis/ArtemisProducerTest.java b/integration-tests/artemis-jms/src/test/java/io/quarkus/it/artemis/ArtemisProducerTest.java index 0d2eedbf2327b..f2fda27b2771c 100644 --- a/integration-tests/artemis-jms/src/test/java/io/quarkus/it/artemis/ArtemisProducerTest.java +++ b/integration-tests/artemis-jms/src/test/java/io/quarkus/it/artemis/ArtemisProducerTest.java @@ -1,8 +1,9 @@ package io.quarkus.it.artemis; +import javax.jms.JMSConsumer; +import javax.jms.JMSContext; import javax.jms.Message; -import javax.jms.MessageConsumer; -import javax.jms.Session; +import javax.ws.rs.core.Response.Status; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; @@ -20,10 +21,10 @@ public class ArtemisProducerTest implements ArtemisHelper { public void test() throws Exception { String body = createBody(); Response response = RestAssured.with().body(body).post("/artemis"); - Assertions.assertEquals(javax.ws.rs.core.Response.Status.NO_CONTENT.getStatusCode(), response.statusCode()); + Assertions.assertEquals(Status.NO_CONTENT.getStatusCode(), response.statusCode()); - try (Session session = createSession()) { - MessageConsumer consumer = session.createConsumer(session.createQueue("test-jms")); + try (JMSContext context = createContext()) { + JMSConsumer consumer = context.createConsumer(context.createQueue("test-jms")); Message message = consumer.receive(1000L); Assertions.assertEquals(body, message.getBody(String.class)); } From 0e7a2b49f558bd98f5798dad6dc9e2781995bd44 Mon Sep 17 00:00:00 2001 From: Jacob Middag Date: Tue, 24 Sep 2019 23:36:42 +0200 Subject: [PATCH 2/2] Add Artemis JMS guide --- docs/src/main/asciidoc/artemis-jms-guide.adoc | 287 ++++++++++++++++++ docs/src/main/asciidoc/index.adoc | 1 + 2 files changed, 288 insertions(+) create mode 100644 docs/src/main/asciidoc/artemis-jms-guide.adoc diff --git a/docs/src/main/asciidoc/artemis-jms-guide.adoc b/docs/src/main/asciidoc/artemis-jms-guide.adoc new file mode 100644 index 0000000000000..5873f05e33fe3 --- /dev/null +++ b/docs/src/main/asciidoc/artemis-jms-guide.adoc @@ -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]. + +== 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] +---- + + + + + Prices + + + + + +
+ +

Last price

+
+

The last price is N/A €.

+
+
+ + + +---- + +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 +---- diff --git a/docs/src/main/asciidoc/index.adoc b/docs/src/main/asciidoc/index.adoc index df55b45b7e656..4a4293cbb8a49 100644 --- a/docs/src/main/asciidoc/index.adoc +++ b/docs/src/main/asciidoc/index.adoc @@ -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]