/* * Copyright 2012-2014 Nikolay A. Viguro * <p/> * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * <p/> * http://www.apache.org/licenses/LICENSE-2.0 * <p/> * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package ru.iris.common.messaging; import com.google.gson.Gson; import com.google.gson.GsonBuilder; import com.rabbitmq.client.*; import javazoom.jl.decoder.JavaLayerException; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.quartz.SchedulerException; import java.io.IOException; import java.util.*; /** * JSON messaging * * @author Nikolay A. Viguro, Tommi S.E. Laukkanen */ public class JsonMessaging { private static final Logger LOGGER = LogManager.getLogger(JsonMessaging.class); /** * The instance ID. */ private UUID instanceId = null; /** * The subjects that has been registered to receive JSON encoded messages. */ private final Set<String> jsonSubjects = Collections.synchronizedSet(new HashSet<>()); private JsonNotification notification = null; private final Gson gson = new GsonBuilder().create(); /** * Boolean flag reflecting whether threads should be close. */ private boolean shutdownThreads = false; /** * The JSON broadcast listen thread. */ private Thread jsonBroadcastListenThread; private Channel channel = JsonConnection.getInstance().getChannel(); public JsonMessaging(final UUID instanceId) { this.instanceId = instanceId; } public JsonMessaging(final UUID instanceId, String queueName) { this.instanceId = instanceId; } public JsonNotification getNotification() { return notification; } public void setNotification(JsonNotification notification) { this.notification = notification; } /** * Starts the messaging to listen for JSON objects. */ public void start() { // Startup listen thread. jsonBroadcastListenThread = new Thread(this::listenBroadcasts, "json-broadcast-listen"); jsonBroadcastListenThread.setName("JSON Messaging Listen Thread"); jsonBroadcastListenThread.start(); // Add close hook to close the listen thread when JVM exits. Runtime.getRuntime().addShutdownHook(new Thread() { public void run() { shutdownThreads = true; jsonBroadcastListenThread.interrupt(); try { jsonBroadcastListenThread.join(); } catch (final InterruptedException e) { LOGGER.debug(e.toString()); } } }); } /** * Closes connection to message broker. */ public void close() { try { shutdownThreads = true; if (jsonBroadcastListenThread != null) { jsonBroadcastListenThread.interrupt(); jsonBroadcastListenThread.join(); } } catch (final Exception e) { LOGGER.error("Error shutting down JsonMessaging.", e); } } /** * Sends object as JSON encoded message with given subject. * * @param subject the subject * @param object the object */ public void broadcast(String subject, Object object) { LOGGER.debug("Broadcast to " + subject); String className = object.getClass().getName(); String jsonString = gson.toJson(object); try { // Create a message headers Map<String, Object> headers = new HashMap<>(); headers.put("sender", instanceId.toString()); headers.put("class", className); // Publish message to topic channel.basicPublish( "iris", subject, new AMQP.BasicProperties.Builder() .correlationId(instanceId.toString()) .headers(headers) .build(), jsonString.getBytes() ); } catch (IOException e) { LOGGER.error("Error sending JSON message: " + object + " to subject: " + subject, e); } } public void response(JsonEnvelope envelope, Object object) { LOGGER.debug("Response to " + envelope.getReceiverInstance() + ", corrId is " + envelope.getCorrelationId()); String className = object.getClass().getName(); String jsonString = gson.toJson(object); try { // Create a message headers Map<String, Object> headers = new HashMap<>(); headers.put("sender", instanceId.toString()); headers.put("class", className); // Publish message to topic channel.basicPublish( "iris", envelope.getReceiverInstance(), new AMQP.BasicProperties.Builder() .correlationId(envelope.getCorrelationId()) .headers(headers) .build(), jsonString.getBytes() ); } catch (IOException e) { LOGGER.error("Error sending reply JSON message: " + object + " to queue: " + envelope.getReceiverInstance(), e); } } /** * Sends object as JSON encoded message with given subject. * * @param subject the subject * @param object the object */ public JsonEnvelope request(String subject, Object object) { String className = object.getClass().getName(); String jsonString = gson.toJson(object); JsonEnvelope envelope = null; try { String replyQueue = channel.queueDeclare().getQueue(); channel.queueBind(replyQueue, "iris", replyQueue); QueueingConsumer consumer = new QueueingConsumer(channel); channel.basicConsume(replyQueue, true, consumer); String corrId = java.util.UUID.randomUUID().toString(); // Create a message headers Map<String, Object> sendheaders = new HashMap<>(); sendheaders.put("sender", instanceId.toString()); sendheaders.put("class", className); LOGGER.debug("Request to " + subject + ", corrId is " + corrId); // Publish message to topic channel.basicPublish( "iris", subject, new AMQP.BasicProperties.Builder() .replyTo(replyQueue) .correlationId(corrId) .headers(sendheaders) .build(), jsonString.getBytes() ); LOGGER.debug("Waiting for answer"); while (true) { QueueingConsumer.Delivery delivery = consumer.nextDelivery(5000); if (delivery.getProperties().getCorrelationId().equals(corrId)) { LOGGER.debug("Got answer for " + corrId); // Get headers Map<String, Object> headers = delivery.getProperties().getHeaders(); String message = new String(delivery.getBody()); String subj = delivery.getEnvelope().getRoutingKey(); String corrID = delivery.getProperties().getCorrelationId(); String replyTo = delivery.getProperties().getReplyTo(); String classNam = new String(((LongString) headers.get("class")).getBytes()); String senderName = new String(((LongString) headers.get("sender")).getBytes()); Class<?> clazz = Class.forName(classNam); Object obj = gson.fromJson(message, clazz); envelope = new JsonEnvelope( UUID.fromString(senderName), replyTo, corrID, subj, obj); break; } LOGGER.debug("Skip envelope with corrId: " + delivery.getProperties().getCorrelationId()); } } catch (IOException | InterruptedException | ClassNotFoundException e) { LOGGER.error("Error sending JSON message: " + object + " to subject: " + subject, e); } return envelope; } /** * Method for receiving subscribing to listen JSON objects on a subject. * * @param subject the subject */ public void subscribe(String subject) { jsonSubjects.add(subject); } public void unsubscribe(String subject) { jsonSubjects.remove(subject); } private void listenBroadcasts() { try { String queueName = channel.queueDeclare().getQueue(); for (String subject : jsonSubjects) { channel.queueBind(queueName, "iris", subject); } QueueingConsumer consumer = new QueueingConsumer(channel); channel.basicConsume(queueName, true, consumer); while (!shutdownThreads) { QueueingConsumer.Delivery delivery = consumer.nextDelivery(); // Get headers Map<String, Object> headers = delivery.getProperties().getHeaders(); String message = new String(delivery.getBody()); String subject = delivery.getEnvelope().getRoutingKey(); String corrID = delivery.getProperties().getCorrelationId(); String replyTo = delivery.getProperties().getReplyTo(); String className = new String(((LongString) headers.get("class")).getBytes()); String senderName = new String(((LongString) headers.get("sender")).getBytes()); Class<?> clazz = Class.forName(className); Object object = gson.fromJson(message, clazz); JsonEnvelope envelope = new JsonEnvelope( UUID.fromString(senderName), replyTo, corrID, subject, object); LOGGER.debug("Received message with ID: " + delivery.getProperties().getMessageId() + " with correlation ID: " + corrID + " sender: " + envelope.getSenderInstance() + " to subject: " + envelope.getSubject() + " (" + envelope.getClass().getSimpleName() + ")"); notification.onNotification(envelope); headers.clear(); } } catch (final ClassNotFoundException e) { LOGGER.error("Error deserializing JSON message.", e); } catch (ConsumerCancelledException e) { LOGGER.error("Consumer cancelled.", e); } catch (IOException | InterruptedException e) { LOGGER.debug("Error JSON message.", e); } catch (SchedulerException | JavaLayerException e) { e.printStackTrace(); } } }