org.zeromq.jms.protocol.ZmqGatewayFactory Maven / Gradle / Ivy
package org.zeromq.jms.protocol;
/*
* Copyright (c) 2015 Jeremy Miller
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
import java.io.IOException;
import java.lang.management.ManagementFactory;
import java.lang.reflect.Constructor;
import java.nio.file.Path;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.jms.Queue;
import org.zeromq.jms.AbstractZmqDestination;
import org.zeromq.jms.ZmqException;
import org.zeromq.jms.ZmqSession;
import org.zeromq.jms.ZmqURI;
import org.zeromq.jms.annotation.ZmqComponent;
import org.zeromq.jms.protocol.event.ZmqEventHandler;
import org.zeromq.jms.protocol.event.ZmqStompEventHandler;
import org.zeromq.jms.protocol.filter.ZmqFilterPolicy;
import org.zeromq.jms.protocol.filter.ZmqFixedFilterPolicy;
import org.zeromq.jms.protocol.redelivery.ZmqRedeliveryPolicy;
import org.zeromq.jms.protocol.redelivery.ZmqRetryRedeliveryPolicy;
import org.zeromq.jms.protocol.store.ZmqJournalStore;
import org.zeromq.jms.selector.ZmqMessageSelector;
import org.zeromq.jms.selector.ZmqMessageSelectorFactory;
import org.zeromq.jms.util.ClassUtils;
/**
* Factory class for returning a protocol instance.
*/
public class ZmqGatewayFactory {
private static final Logger LOGGER = Logger.getLogger(ZmqSession.class.getCanonicalName());
private final Class> defaultGateway = ZmqFireAndForgetGateway.class;
private final ZmqMessageSelectorFactory defaultSelectorFactory = new ZmqMessageSelectorFactory();
private final ZmqEventHandler defaultEventHandler = new ZmqStompEventHandler();
private final ZmqFilterPolicy defaultFilterPolicy = new ZmqFixedFilterPolicy();
private final ZmqRedeliveryPolicy defaultRedeliveryPolicy = new ZmqRetryRedeliveryPolicy(3);
private final Map destinationSchema;
private final List> gatewayClasses;
private final List> eventHandlerClasses;
private final List> filterPolicyClasses;
private final List> redeliveryPolicyClasses;
private final List> journalStoreClasses;
/**
* Construct the protocol factory around the destination schema.
* @param extensionPackageNames the array of package names to find extensions
* @param destinationSchema the schema of known destinations
*/
public ZmqGatewayFactory(final String[] extensionPackageNames, final Map destinationSchema) {
this.destinationSchema = destinationSchema;
this.gatewayClasses = getClasses(extensionPackageNames, ZmqGateway.class);
this.eventHandlerClasses = getClasses(extensionPackageNames, ZmqEventHandler.class);
this.filterPolicyClasses = getClasses(extensionPackageNames, ZmqFilterPolicy.class);
this.redeliveryPolicyClasses = getClasses(extensionPackageNames, ZmqRedeliveryPolicy.class);
this.journalStoreClasses = getClasses(extensionPackageNames, ZmqJournalStore.class);
}
/**
* Return all classes below the package roots that have the specified component class.
* @param packageNames the starting packages to search.
* @param componentInterface the component interface to find
* @return return a list of classes found (an empty list is possible)
*/
public static List> getClasses(final String[] packageNames, final Class> componentInterface) {
final List> annotatedClasses = new LinkedList>();
try {
final List> mainClasses = ClassUtils.getClasses("org.zeromq.jms");
for (Class> clazz : mainClasses) {
if (clazz.isAnnotationPresent(ZmqComponent.class) && componentInterface.isAssignableFrom(clazz)) {
annotatedClasses.add(clazz);
}
}
if (packageNames != null) {
for (String packageName : packageNames) {
if (!packageName.startsWith("org.zeromq.jms")) {
final List> extensionClasses = ClassUtils.getClasses(packageName);
for (Class> clazz : extensionClasses) {
if (clazz.isAnnotationPresent(ZmqComponent.class) && componentInterface.isAssignableFrom(clazz)) {
annotatedClasses.add(clazz);
}
}
}
}
}
} catch (IOException | ClassNotFoundException ex) {
LOGGER.log(Level.SEVERE, "Unable to scan classes for ZMQ annotations.", ex);
}
return annotatedClasses;
}
/**
* Construct a socket session based on defaults and the URI properties.
* @param uri the URI representing the ZMQ
* @param defaultType the default type, i.e. PULL
* @param defaultBindFlag the default bind connection flag
* @return return the socket context
* @throws ZmqException throws validation exceptions
*/
protected ZmqSocketContext getSocketContext(final ZmqURI uri, final ZmqSocketType defaultType, final boolean defaultBindFlag)
throws ZmqException {
final ZmqSocketContext context = new ZmqSocketContext();
context.setRecieveMsgFlag(0);
if (uri.isOption("gateway.addr")) {
context.setBindFlag(uri.getOptionValue("gateway.bind", defaultBindFlag));
context.setType(ZmqSocketType.valueOf(uri.getOptionValue("gateway.type", defaultType.toString())));
context.setAddr(uri.getOptionValue("gateway.addr", ','));
} else if (uri.isOption("socket.addr")) {
context.setBindFlag(uri.getOptionValue("socket.bind", defaultBindFlag));
context.setType(ZmqSocketType.valueOf(uri.getOptionValue("socket.type", defaultType.toString())));
context.setAddr(uri.getOptionValue("socket.addr", ','));
final Map> socketOptions = uri.getOptions("socket");
final Map> proxyOptions = uri.getOptions("proxy");
try {
ClassUtils.setMethods(socketOptions, context);
ClassUtils.setMethods(proxyOptions, context);
} catch (ReflectiveOperationException ex) {
throw new ZmqException("Unable to set 'socket' properties from URI: " + uri, ex);
}
}
// Validate the details
if (context.getAddr() == null) {
throw new ZmqException("Missing URI '{socket|gateway}.addr' construct gateway consumer: " + uri);
}
return context;
}
/**
* Return the Zero MQ JMS consumer protocol based on the attributes and any meta data.
* @param namePrefix the prefix name of the producer gateway
* @param destination the destination
* @param type the ZMQ socket type as enum, i.e. PUB, SUB, etc...
* @param isBound the connect or bind to the socket
* @param messageSelector the JMS select expression
* @param transacted the transaction indicator
* @return return the constructed gateway
* @throws ZmqException throw JMS exception when filters cannot be resolved
*/
public ZmqGateway newConsumerGateway(final String namePrefix, final AbstractZmqDestination destination,
final ZmqSocketType type, final boolean isBound, final String messageSelector, final boolean transacted) throws ZmqException {
final String destinationName = destination.getName();
final ZmqURI destinationUri = destination.getURI();
final ZmqMessageSelector selector = getZmqMessageSelector(destination, messageSelector);
final ZmqRedeliveryPolicy redelivery = null;
final ZmqEventHandler eventHandler = getZmqEventHandler(destination);
final ZmqFilterPolicy filter = getZmqFilterPolicy(destination);
final ZmqJournalStore store = getZmqJournalStore(destination, ZmqGateway.Direction.INCOMING);
try {
final ZmqURI uri = (destinationUri == null) ? destinationSchema.get(destinationName) : destinationUri;
if (uri == null) {
throw new ZmqException("Missing URI to construct gateway consumer: " + destination);
}
final String value = uri.getOptionValue("gateway", null);
Class> gatewayClass = defaultGateway;
if (value != null) {
gatewayClass = ClassUtils.getClass(gatewayClasses, ZmqComponent.class, "value", value);
if (gatewayClass == null) {
throw new ZmqException("Unable to find specified gateway: " + value);
}
}
if (gatewayClass != null) {
LOGGER.info("Using gateway consumer (" + gatewayClass.getCanonicalName() + ") for destination: " + destination);
}
final Constructor> consumerConstructor = gatewayClass.getConstructor(String.class, ZmqSocketContext.class,
ZmqFilterPolicy.class, ZmqEventHandler.class, ZmqGatewayListener.class,
ZmqJournalStore.class, ZmqMessageSelector.class, ZmqRedeliveryPolicy.class,
boolean.class, ZmqGateway.Direction.class);
ZmqSocketContext socketContext = getSocketContext(uri, type, isBound);
final String name = namePrefix + ":" + destinationName;
final ZmqGateway protocol =
(ZmqGateway) consumerConstructor.newInstance(name, socketContext,
filter, eventHandler, null, store, selector, redelivery,
transacted, ZmqGateway.Direction.INCOMING);
if (uri != null) {
final Map> parameters = uri.getOptions();
ClassUtils.setMethods(parameters, protocol);
}
return protocol;
} catch (IllegalArgumentException | ReflectiveOperationException ex) {
LOGGER.log(Level.SEVERE, "Unable to construct consumer based on URI: " + destinationUri, ex);
throw new ZmqException("Unable to construct consumer based on URI: " + destinationUri, ex);
}
}
/**
* Return the Zero MQ JMS producer protocol based on the attributes and any meta data.
* @param namePrefix the prefix name of the producer gateway
* @param destination the destination
* @param type the ZMQ socket type as enum, i.e. PUB, SUB, etc...
* @param isBound the connect or bind to the socket
* @param transacted the transaction indicator
* @return return the constructed gateway
* @throws ZmqException throw JMS exception when filters cannot be resolved
*/
public ZmqGateway newProducerGateway(final String namePrefix, final AbstractZmqDestination destination,
final ZmqSocketType type, final boolean isBound, final boolean transacted) throws ZmqException {
final String destinationName = destination.getName();
final ZmqURI destinationUri = destination.getURI();
final ZmqMessageSelector selector = null;
final ZmqRedeliveryPolicy redelivery = null;
final ZmqEventHandler handler = getZmqEventHandler(destination);
final ZmqFilterPolicy filter = getZmqFilterPolicy(destination);
final ZmqGatewayListener listener = null;
final ZmqJournalStore store = getZmqJournalStore(destination, ZmqGateway.Direction.OUTGOING);
try {
final ZmqURI uri = (destinationUri == null) ? destinationSchema.get(destinationName) : destinationUri;
if (uri == null) {
throw new ZmqException("Missing URI to construct gateway consumer: " + destination);
}
final String value = uri.getOptionValue("gateway");
Class> gatewayClass = defaultGateway;
if (value != null) {
gatewayClass = ClassUtils.getClass(gatewayClasses, ZmqComponent.class, "value", value);
}
if (gatewayClass != null) {
LOGGER.info("Using gateway produce (" + gatewayClass.getClass().getCanonicalName() + ") for destination: " + destination);
}
final Constructor> producerConstructor = gatewayClass.getConstructor(String.class, ZmqSocketContext.class,
ZmqFilterPolicy.class, ZmqEventHandler.class, ZmqGatewayListener.class,
ZmqJournalStore.class, ZmqMessageSelector.class, ZmqRedeliveryPolicy.class,
boolean.class, ZmqGateway.Direction.class);
ZmqSocketContext socketContext = getSocketContext(uri, type, isBound);
final String name = namePrefix + ":" + destinationName;
final ZmqGateway protocol = (ZmqGateway) producerConstructor.newInstance(name, socketContext,
filter, handler, listener, store, selector, redelivery,
transacted, ZmqGateway.Direction.OUTGOING);
if (uri != null) {
final Map> parameters = uri.getOptions();
ClassUtils.setMethods(parameters, protocol);
}
return protocol;
} catch (IllegalArgumentException | ReflectiveOperationException ex) {
LOGGER.log(Level.SEVERE, "Unable to construct producer based on URI: " + destinationUri, ex);
throw new ZmqException("Unable to construct produce based on URI: " + destinationUri, ex);
}
}
/**
* Return the message selector based on the specified condition statement.
* @param destination the destination.
* @param expression the SQL style condition expression
* @return return the selector
* @throws ZmqException throw JMS exception when message selector cannot be resolved
*/
protected ZmqMessageSelector getZmqMessageSelector(final AbstractZmqDestination destination, final String expression) throws ZmqException {
if (expression == null || expression.trim().length() == 0) {
return null;
}
final String name = destination.getName();
try {
ZmqMessageSelector selector = null;
if (destinationSchema.containsKey(name)) {
final ZmqURI uri = destinationSchema.get(name);
final String value = uri.getOptionValue("selector", null);
if (value != null) {
final Class> selectorFactoryClass = ClassUtils.getClass(eventHandlerClasses, ZmqComponent.class, "value", value);
if (selectorFactoryClass != null) {
final ZmqMessageSelectorFactory selectorFactory = (ZmqMessageSelectorFactory) selectorFactoryClass.newInstance();
LOGGER.info("Using selector factory (" + selectorFactory.getClass().getCanonicalName() + ") for destination: " + destination);
selector = selectorFactory.parse(expression);
}
}
}
if (selector == null) {
selector = defaultSelectorFactory.parse(expression);
LOGGER.info("Using default selector factory (" + defaultSelectorFactory.getClass().getCanonicalName() + ") for destination: "
+ destination);
}
if (destinationSchema.containsKey(name) && (selector != null)) {
final ZmqURI uri = destinationSchema.get(name);
final Map> parameters = uri.getOptions();
ClassUtils.setMethods(parameters, selector);
}
return selector;
} catch (Exception ex) {
throw new ZmqException("Unable resolve the message selector for destination: " + name, ex);
}
}
/**
* Return the event handler for this destination. When no specific event handler is found
* then the default handler is used.
* @param destination the destination.
* @return return the event handler
* @throws ZmqException throw JMS exception when event handler cannot be resolved
*/
protected ZmqEventHandler getZmqEventHandler(final AbstractZmqDestination destination) throws ZmqException {
final String name = destination.getName();
try {
ZmqEventHandler eventHandler = null;
if (destinationSchema.containsKey(name)) {
final ZmqURI uri = destinationSchema.get(name);
final String value = uri.getOptionValue("event");
if (value != null) {
final Class> eventHandlerClass = ClassUtils.getClass(eventHandlerClasses, ZmqComponent.class, "value", value);
if (eventHandlerClass == null) {
throw new ZmqException("Unable to find specified event handler: " + value);
} else {
eventHandler = (ZmqEventHandler) eventHandlerClass.newInstance();
LOGGER.info("Using event handler (" + eventHandler.getClass().getCanonicalName() + ") for destination: " + destination);
}
}
}
if (eventHandler == null) {
eventHandler = defaultEventHandler;
LOGGER.info("Using default event handler (" + eventHandler.getClass().getCanonicalName() + ") for destination: " + destination);
}
if (destinationSchema.containsKey(name) && (eventHandler != null)) {
final ZmqURI uri = destinationSchema.get(name);
final Map> parameters = uri.getOptions();
ClassUtils.setMethods(parameters, eventHandler);
}
return eventHandler;
} catch (Exception ex) {
LOGGER.log(Level.SEVERE, "Unable resolve the event handler for destination " + name, ex);
throw new ZmqException("Unable resolve the event handler for destination: " + name, ex);
}
}
/**
* Return the subscribe filter policy. The are 2 parts, the String filter based on the underlying the
* JMS messages when publishing, and subscription when consuming which is a list string filters.
* NOTE: ZMQ Pub/Sub requires a valid string value to be used ("null" is not allowed), so the Fixed Filter
* policy always uses the none" string when no otherwise initialised.
* @param destination the destination.
* @return return the subscribe resolver
* @throws ZmqException throw JMS exception when subscribe resolver cannot be resolved
*/
protected ZmqFilterPolicy getZmqFilterPolicy(final AbstractZmqDestination destination) throws ZmqException {
if (destination instanceof Queue) {
return null;
}
final String name = destination.getName();
try {
ZmqFilterPolicy filter = null;
if (destinationSchema.containsKey(name)) {
final ZmqURI uri = destinationSchema.get(name);
final String value = uri.getOptionValue("filter");
if (value != null) {
final Class> filterPolicyClass = ClassUtils.getClass(filterPolicyClasses, ZmqComponent.class, "value", value);
if (filterPolicyClass == null) {
throw new ZmqException("Unable to find specified filter policy: " + value);
} else {
filter = (ZmqFilterPolicy) filterPolicyClass.newInstance();
LOGGER.info("Using filter policy (" + filter.getClass().getCanonicalName() + ") for destination: " + destination);
}
}
}
if (filter == null) {
filter = defaultFilterPolicy;
if (filter == null) {
LOGGER.info("NULL default filter policy for destination: " + destination);
} else {
LOGGER.info("Using default filter policy (" + defaultFilterPolicy.getClass().getCanonicalName() + ") for destination: "
+ destination);
}
}
if (destinationSchema.containsKey(name) && (filter != null)) {
final ZmqURI uri = destinationSchema.get(name);
final Map> parameters = uri.getOptions();
ClassUtils.setMethods(parameters, filter);
}
return filter;
} catch (Exception ex) {
LOGGER.log(Level.SEVERE, "Unable resolve the message filter policy for destination " + name, ex);
throw new ZmqException("Unable resolve the message filter policy for destination: " + name, ex);
}
}
/**
* Return the defaulter message journal store for this destination.
* @param destination the destination.
* @param direction the direction of the queue
* @return return the re-delivery policy
* @throws ZmqException throw JMS exception when journal store cannot be resolved
*/
protected ZmqJournalStore getZmqJournalStore(final AbstractZmqDestination destination, final ZmqGateway.Direction direction) throws ZmqException {
final String name = destination.getName();
try {
ZmqJournalStore store = null;
if (destinationSchema.containsKey(name)) {
final ZmqURI uri = destinationSchema.get(name);
final String value = uri.getOptionValue("journal");
if (value != null) {
final Class> journalStoreClass = ClassUtils.getClass(journalStoreClasses, ZmqComponent.class, "value", value);
if (journalStoreClass == null) {
throw new ZmqException("Unable to find specified journal store: " + value);
} else {
final Constructor> storeConstructor =
journalStoreClass.getConstructor(Path.class, String.class, String.class);
final String groupId = name + "-" + direction.name().toLowerCase();
final String uniqueId = ManagementFactory.getRuntimeMXBean().getName().replaceAll("\\W+", "-").toLowerCase();
store = (ZmqJournalStore) storeConstructor.newInstance(null, groupId, uniqueId);
LOGGER.info("Using journal store (" + store.getClass().getCanonicalName() + ") for destination: "
+ destination);
}
}
}
if (store == null) {
LOGGER.info("Using NO jounral store for destination: " + destination);
}
if (destinationSchema.containsKey(name) && (store != null)) {
final ZmqURI uri = destinationSchema.get(name);
final Map> parameters = uri.getOptions();
ClassUtils.setMethods(parameters, store);
}
return store;
} catch (Exception ex) {
LOGGER.log(Level.SEVERE, "Unable resolve the message journal store for destination " + name, ex);
throw new ZmqException("Unable resolve the message journal store for destination: " + name, ex);
}
}
/**
* Return the defaulter re-delivery policy for this destination.
* @param destination the destination.
* @return return the re-delivery policy
* @throws ZmqException throw JMS exception when redelivery policy cannot be resolved
*/
protected ZmqRedeliveryPolicy getRedeliveryPolicy(final AbstractZmqDestination destination) throws ZmqException {
final String name = destination.getName();
try {
ZmqRedeliveryPolicy redeliveryPolicy = null;
if (destinationSchema.containsKey(name)) {
final ZmqURI uri = destinationSchema.get(name);
final String value = uri.getOptionValue("redelivery");
if (value != null) {
final Class> redeliveryPolicyClass = ClassUtils.getClass(redeliveryPolicyClasses, ZmqComponent.class, "value", value);
if (redeliveryPolicyClass == null) {
throw new ZmqException("Unable to find specified re-delivery policy: " + value);
} else {
redeliveryPolicy = (ZmqRedeliveryPolicy) redeliveryPolicyClass.newInstance();
LOGGER.info("Using re-delivery policy (" + redeliveryPolicy.getClass().getCanonicalName() + ") for destination: "
+ destination);
}
}
}
if (redeliveryPolicy == null) {
redeliveryPolicy = defaultRedeliveryPolicy;
LOGGER.info("Using default re-delivery policy (" + redeliveryPolicy.getClass().getCanonicalName() + ") for destination: "
+ destination);
}
if (destinationSchema.containsKey(name) && (redeliveryPolicy != null)) {
final ZmqURI uri = destinationSchema.get(name);
final Map> parameters = uri.getOptions();
ClassUtils.setMethods(parameters, redeliveryPolicy);
}
return redeliveryPolicy;
} catch (Exception ex) {
LOGGER.log(Level.SEVERE, "Unable resolve the re-delivery policy for destination " + name, ex);
throw new ZmqException("Unable resolve the re-delivery policy for destination: " + name, ex);
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy