/* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You 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 * * http://www.apache.org/licenses/LICENSE-2.0 * * 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 org.messaginghub.pooled.jms.mock; import java.util.HashSet; import java.util.Map; import java.util.Set; import java.util.UUID; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.ThreadFactory; import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicLong; import java.util.concurrent.atomic.AtomicReference; import javax.jms.Connection; import javax.jms.ConnectionConsumer; import javax.jms.ConnectionMetaData; import javax.jms.Destination; import javax.jms.ExceptionListener; import javax.jms.IllegalStateException; import javax.jms.InvalidClientIDException; import javax.jms.JMSException; import javax.jms.JMSSecurityException; import javax.jms.Message; import javax.jms.Queue; import javax.jms.QueueConnection; import javax.jms.QueueSession; import javax.jms.ServerSessionPool; import javax.jms.Session; import javax.jms.TemporaryQueue; import javax.jms.TemporaryTopic; import javax.jms.Topic; import javax.jms.TopicConnection; import javax.jms.TopicSession; import org.messaginghub.pooled.jms.util.JMSExceptionSupport; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * Mock JMS Connection object used for test the JMS Pool */ public class MockJMSConnection implements Connection, TopicConnection, QueueConnection, AutoCloseable { private static final Logger LOG = LoggerFactory.getLogger(MockJMSConnection.class); private final MockJMSConnectionStats stats = new MockJMSConnectionStats(); private final Map<String, MockJMSSession> sessions = new ConcurrentHashMap<>(); private final Map<MockJMSTemporaryDestination, MockJMSTemporaryDestination> tempDestinations = new ConcurrentHashMap<>(); private final Set<MockJMSConnectionListener> connectionListeners = new HashSet<>(); private final AtomicBoolean closed = new AtomicBoolean(); private final AtomicBoolean connected = new AtomicBoolean(); private final AtomicBoolean started = new AtomicBoolean(); private final AtomicLong sessionIdGenerator = new AtomicLong(); private final AtomicLong tempDestIdGenerator = new AtomicLong(); private final AtomicReference<Exception> failureCause = new AtomicReference<>(); private final ThreadPoolExecutor executor; private final UUID connectionId = UUID.randomUUID(); private final MockJMSUser user; private ExceptionListener exceptionListener; private String clientID; private boolean explicitClientID; public MockJMSConnection(MockJMSUser user) { this.user = user; executor = new ThreadPoolExecutor(1, 1, 5, TimeUnit.SECONDS, new LinkedBlockingQueue<Runnable>(), new ThreadFactory() { @Override public Thread newThread(Runnable r) { Thread newThread = new Thread(r, "MockJMSConnection Thread: " + connectionId); newThread.setDaemon(true); return newThread; } }); executor.setRejectedExecutionHandler(new ThreadPoolExecutor.DiscardOldestPolicy()); } @Override public void start() throws JMSException { checkClosedOrFailed(); ensureConnected(); started.set(true); } @Override public void stop() throws JMSException { checkClosedOrFailed(); ensureConnected(); started.set(false); } @Override public void close() throws JMSException { if (closed.compareAndSet(false, true)) { connected.set(false); started.set(false); // Refuse any new work, and let any existing work complete. executor.shutdown(); } } protected void shutdown(Exception error) throws JMSException { // TODO Shutdown of connection resources with flag for failed cause } //----- Create Session ---------------------------------------------------// @Override public QueueSession createQueueSession(boolean transacted, int acknowledgeMode) throws JMSException { checkClosedOrFailed(); ensureConnected(); int ackMode = getSessionAcknowledgeMode(transacted, acknowledgeMode); MockJMSQueueSession result = new MockJMSQueueSession(getNextSessionId(), ackMode, this); addSession(result); if (started.get()) { result.start(); } return result; } @Override public TopicSession createTopicSession(boolean transacted, int acknowledgeMode) throws JMSException { checkClosedOrFailed(); ensureConnected(); int ackMode = getSessionAcknowledgeMode(transacted, acknowledgeMode); MockJMSTopicSession result = new MockJMSTopicSession(getNextSessionId(), ackMode, this); addSession(result); if (started.get()) { result.start(); } return result; } @Override public Session createSession(boolean transacted, int acknowledgeMode) throws JMSException { checkClosedOrFailed(); ensureConnected(); int ackMode = getSessionAcknowledgeMode(transacted, acknowledgeMode); MockJMSSession result = new MockJMSSession(getNextSessionId(), ackMode, this); signalCreateSession(result); addSession(result); if (started.get()) { result.start(); } return result; } @Override public Session createSession(int acknowledgeMode) throws JMSException { return createSession(acknowledgeMode == Session.SESSION_TRANSACTED ? true : false, acknowledgeMode); } @Override public Session createSession() throws JMSException { return createSession(false, Session.AUTO_ACKNOWLEDGE); } //----- Connection Consumer ----------------------------------------------// @Override public ConnectionConsumer createConnectionConsumer(Topic topic, String messageSelector, ServerSessionPool sessionPool, int maxMessages) throws JMSException { checkClosedOrFailed(); ensureConnected(); throw new JMSException("Not Supported"); } @Override public ConnectionConsumer createConnectionConsumer(Destination destination, String messageSelector, ServerSessionPool sessionPool, int maxMessages) throws JMSException { checkClosedOrFailed(); ensureConnected(); throw new JMSException("Not Supported"); } @Override public ConnectionConsumer createDurableConnectionConsumer(Topic topic, String subscriptionName, String messageSelector, ServerSessionPool sessionPool, int maxMessages) throws JMSException { checkClosedOrFailed(); ensureConnected(); throw new JMSException("Not Supported"); } @Override public ConnectionConsumer createSharedDurableConnectionConsumer(Topic topic, String subscriptionName, String messageSelector, ServerSessionPool sessionPool, int maxMessages) throws JMSException { checkClosedOrFailed(); ensureConnected(); throw new JMSException("Not Supported"); } @Override public ConnectionConsumer createSharedConnectionConsumer(Topic topic, String subscriptionName, String messageSelector, ServerSessionPool sessionPool, int maxMessages) throws JMSException { checkClosedOrFailed(); ensureConnected(); throw new JMSException("Not Supported"); } @Override public ConnectionConsumer createConnectionConsumer(Queue queue, String messageSelector, ServerSessionPool sessionPool, int maxMessages) throws JMSException { checkClosedOrFailed(); ensureConnected(); throw new JMSException("Not Supported"); } //----- Get and Set methods for Connection -------------------------------// @Override public String getClientID() throws JMSException { checkClosedOrFailed(); return clientID; } @Override public void setClientID(String clientID) throws JMSException { checkClosedOrFailed(); if (explicitClientID) { throw new IllegalStateException("The clientID has already been set"); } if (clientID == null || clientID.isEmpty()) { throw new InvalidClientIDException("Cannot have a null or empty clientID"); } if (connected.get()) { throw new IllegalStateException("Cannot set the client id once connected."); } setClientID(clientID, true); // We weren't connected if we got this far, we should now connect to ensure the // configured clientID is valid. initialize(); } void setClientID(String clientID, boolean explicitClientID) { this.explicitClientID = explicitClientID; this.clientID = clientID; } @Override public ConnectionMetaData getMetaData() throws JMSException { return MockJMSConnectionMetaData.INSTANCE; } @Override public ExceptionListener getExceptionListener() throws JMSException { checkClosedOrFailed(); return exceptionListener; } @Override public void setExceptionListener(ExceptionListener listener) throws JMSException { checkClosedOrFailed(); this.exceptionListener = listener; } public String getUsername() throws JMSException { checkClosedOrFailed(); return user.getUsername(); } public String getPassword() throws JMSException { checkClosedOrFailed(); return user.getPassword(); } public UUID getConnectionId() throws JMSException { checkClosedOrFailed(); return connectionId; } public boolean isStarted() throws JMSException { checkClosedOrFailed(); return started.get(); } public MockJMSUser getUser() throws JMSException { checkClosedOrFailed(); return user; } public MockJMSConnectionStats getConnectionStats() { return stats; } public void addConnectionListener(MockJMSConnectionListener listener) throws JMSException { checkClosedOrFailed(); if (listener != null) { connectionListeners.add(listener); } } public void removeConnectionListener(MockJMSConnectionListener listener) throws JMSException { checkClosedOrFailed(); connectionListeners.remove(listener); } public boolean isClosed() { return closed.get(); } //----- Mock Connection behavioral control -------------------------------// public void injectConnectionFailure(final Exception error) throws JMSException { connectionFailed(error); injectConnectionError(error); if (!closed.get()) { executor.execute(new Runnable() { @Override public void run() { try { shutdown(error); } catch (JMSException e) { LOG.trace("Exception during connection cleanup, " + e, e); } // Don't accept any more connection work but allow all pending work // to complete in order to ensure notifications are sent to any blocked // resources. executor.shutdown(); } }); } } public void injectConnectionError(Exception error) throws JMSException { if (!closed.get() ) { if (this.exceptionListener != null) { final JMSException jmsError = JMSExceptionSupport.create(error); executor.execute(new Runnable() { @Override public void run() { MockJMSConnection.this.exceptionListener.onException(jmsError); } }); } else { LOG.debug("Async exception with no exception listener: {}", error, error); } } } //----- Internal Utility Methods -----------------------------------------// protected String getNextSessionId() { return connectionId.toString() + sessionIdGenerator.incrementAndGet(); } protected TemporaryQueue createTemporaryQueue() throws JMSException { String destinationName = connectionId.toString() + ":" + tempDestIdGenerator.incrementAndGet(); MockJMSTemporaryQueue queue = new MockJMSTemporaryQueue(destinationName); signalCreateTemporaryDestination(queue); tempDestinations.put(queue, queue); queue.setConnection(this); stats.temporaryDestinationCreated(queue); return queue; } protected TemporaryTopic createTemporaryTopic() throws JMSException { String destinationName = connectionId.toString() + ":" + tempDestIdGenerator.incrementAndGet(); MockJMSTemporaryTopic topic = new MockJMSTemporaryTopic(destinationName); signalCreateTemporaryDestination(topic); tempDestinations.put(topic, topic); topic.setConnection(this); stats.temporaryDestinationCreated(topic); return topic; } protected int getSessionAcknowledgeMode(boolean transacted, int acknowledgeMode) throws JMSException { int result = acknowledgeMode; if (!transacted && acknowledgeMode == Session.SESSION_TRANSACTED) { throw new JMSException("acknowledgeMode SESSION_TRANSACTED cannot be used for an non-transacted Session"); } if (transacted) { result = Session.SESSION_TRANSACTED; } else if (acknowledgeMode < Session.SESSION_TRANSACTED || acknowledgeMode > Session.DUPS_OK_ACKNOWLEDGE){ throw new JMSException("acknowledgeMode " + acknowledgeMode + " cannot be used for an non-transacted Session"); } return result; } private boolean isConnected() throws JMSException { return connected.get(); } protected void checkClosedOrFailed() throws JMSException { checkClosed(); if (failureCause.get() != null) { throw JMSExceptionSupport.create("Connection has failed", failureCause.get()); } } protected void checkClosed() throws IllegalStateException { if (closed.get()) { throw new IllegalStateException("The Connection is closed"); } } protected void connectionFailed(Exception cause) { failureCause.compareAndSet(null, cause); } MockJMSConnection initialize() throws JMSException { if (explicitClientID) { ensureConnected(); } return this; } private void ensureConnected() throws JMSException { if (isConnected() || closed.get()) { return; } synchronized(this.connectionId) { if (isConnected() || closed.get()) { return; } if (clientID == null || clientID.trim().isEmpty()) { throw new IllegalArgumentException("Client ID cannot be null or empty string"); } if (!user.isValid()) { executor.shutdown(); throw new JMSSecurityException(user.getFailureCause()); } connected.set(true); } } void addSession(MockJMSSession session) { sessions.put(session.getSessionId(), session); } void removeSession(MockJMSSession session) throws JMSException { sessions.remove(session.getSessionId()); signalCloseSession(session); } void deleteTemporaryDestination(MockJMSTemporaryDestination destination) throws JMSException { checkClosedOrFailed(); try { for (MockJMSSession session : sessions.values()) { if (session.isDestinationInUse(destination)) { throw new IllegalStateException("A consumer is consuming from the temporary destination"); } } signalDeleteTemporaryDestination(destination); stats.temporaryDestinationDestroyed(destination); tempDestinations.remove(destination); } catch (Exception e) { throw JMSExceptionSupport.create(e); } } boolean isTemporaryDestinationDeleted(MockJMSTemporaryDestination destination) { return !tempDestinations.containsKey(destination); } private void signalCreateSession(MockJMSSession session) throws JMSException { for (MockJMSConnectionListener listener : connectionListeners) { listener.onCreateSession(session); } } private void signalCloseSession(MockJMSSession session) throws JMSException { for (MockJMSConnectionListener listener : connectionListeners) { listener.onCloseSession(session); } } private void signalMessageSend(MockJMSSession session, Message message) throws JMSException { for (MockJMSConnectionListener listener : connectionListeners) { listener.onMessageSend(session, message); } } private void signalCreateTemporaryDestination(MockJMSTemporaryDestination destination) throws JMSException { for (MockJMSConnectionListener listener : connectionListeners) { if (destination.isQueue()) { listener.onCreateTemporaryQueue((MockJMSTemporaryQueue) destination); } else { listener.onCreateTemporaryTopic((MockJMSTemporaryTopic) destination); } } } private void signalDeleteTemporaryDestination(MockJMSTemporaryDestination destination) throws JMSException { for (MockJMSConnectionListener listener : connectionListeners) { if (destination.isQueue()) { listener.onDeleteTemporaryQueue((MockJMSTemporaryQueue) destination); } else { listener.onDeleteTemporaryTopic((MockJMSTemporaryTopic) destination); } } } private void signalCreateMessageConsumer(MockJMSSession session, MockJMSMessageConsumer consumer) throws JMSException { for (MockJMSConnectionListener listener : connectionListeners) { listener.onCreateMessageConsumer(session, consumer); } } private void signalCloseMessageConsumer(MockJMSSession session, MockJMSMessageConsumer consumer) throws JMSException { for (MockJMSConnectionListener listener : connectionListeners) { listener.onCloseMessageConsumer(session, consumer); } } private void signalCreateMessageProducer(MockJMSSession session, MockJMSMessageProducer producer) throws JMSException { for (MockJMSConnectionListener listener : connectionListeners) { listener.onCreateMessageProducer(session, producer); } } private void signalCloseMessageProducer(MockJMSSession session, MockJMSMessageProducer producer) throws JMSException { for (MockJMSConnectionListener listener : connectionListeners) { listener.onCloseMessageProducer(session, producer); } } //----- Event points for MockJMS resources -------------------------------// void onMessageSend(MockJMSSession session, Message message) throws JMSException { checkClosedOrFailed(); signalMessageSend(session, message); } void onMessageConsumerCreate(MockJMSSession session, MockJMSMessageConsumer consumer) throws JMSException { checkClosedOrFailed(); signalCreateMessageConsumer(session, consumer); } void onMessageConsumerClose(MockJMSSession session, MockJMSMessageConsumer consumer) throws JMSException { checkClosedOrFailed(); signalCloseMessageConsumer(session, consumer); } void onMessageProducerCreate(MockJMSSession session, MockJMSMessageProducer producer) throws JMSException { checkClosedOrFailed(); signalCreateMessageProducer(session, producer); } void onMessageProducerClose(MockJMSSession session, MockJMSMessageProducer producer) throws JMSException { checkClosedOrFailed(); signalCloseMessageProducer(session, producer); } }