/* * 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.apache.qpid.jms.provider.mock; import java.io.IOException; import java.net.URI; import java.util.UUID; import java.util.concurrent.ScheduledThreadPoolExecutor; import java.util.concurrent.ThreadFactory; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicInteger; import javax.jms.JMSException; import org.apache.qpid.jms.message.JmsInboundMessageDispatch; import org.apache.qpid.jms.message.JmsMessageFactory; import org.apache.qpid.jms.message.JmsOutboundMessageDispatch; import org.apache.qpid.jms.message.facade.test.JmsTestMessageFactory; import org.apache.qpid.jms.meta.JmsConnectionInfo; import org.apache.qpid.jms.meta.JmsConsumerId; import org.apache.qpid.jms.meta.JmsResource; import org.apache.qpid.jms.meta.JmsSessionId; import org.apache.qpid.jms.meta.JmsTransactionInfo; import org.apache.qpid.jms.provider.AsyncResult; import org.apache.qpid.jms.provider.Provider; import org.apache.qpid.jms.provider.ProviderClosedException; import org.apache.qpid.jms.provider.ProviderConstants.ACK_TYPE; import org.apache.qpid.jms.provider.ProviderFuture; import org.apache.qpid.jms.provider.ProviderListener; import org.apache.qpid.jms.provider.amqp.AmqpProvider; import org.apache.qpid.jms.util.ThreadPoolUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * Mock Provider instance used in testing higher level classes that interact * with a given Provider instance. */ public class MockProvider implements Provider { private static final Logger LOG = LoggerFactory.getLogger(AmqpProvider.class); private static final AtomicInteger PROVIDER_SEQUENCE = new AtomicInteger(); private final JmsMessageFactory messageFactory = new JmsTestMessageFactory(); private final MockProviderStats stats; private final URI remoteURI; private final MockProviderConfiguration configuration; private final ScheduledThreadPoolExecutor serializer; private final AtomicBoolean closed = new AtomicBoolean(); private final MockRemotePeer context; private long connectTimeout = JmsConnectionInfo.DEFAULT_CONNECT_TIMEOUT; private long closeTimeout = JmsConnectionInfo.DEFAULT_CLOSE_TIMEOUT; private String providerId = UUID.randomUUID().toString(); private MockProviderListener eventListener; private ProviderListener listener; public MockProvider(URI remoteURI, MockProviderConfiguration configuration, MockRemotePeer context) { this.remoteURI = remoteURI; this.configuration = configuration; this.context = context; this.stats = new MockProviderStats(context != null ? context.getContextStats() : null); serializer = new ScheduledThreadPoolExecutor(1, new ThreadFactory() { @Override public Thread newThread(Runnable runner) { Thread serial = new Thread(runner); serial.setDaemon(true); serial.setName(MockProvider.this.getClass().getSimpleName() + ":(" + PROVIDER_SEQUENCE.incrementAndGet() + "):[" + getRemoteURI() + "]"); return serial; } }); serializer.setExecuteExistingDelayedTasksAfterShutdownPolicy(false); serializer.setContinueExistingPeriodicTasksAfterShutdownPolicy(false); } @Override public void connect(JmsConnectionInfo connectionInfo) throws IOException { checkClosed(); stats.recordConnectAttempt(); if (configuration.isFailOnConnect()) { throw new IOException("Failed to connect to: " + remoteURI); } if (context != null) { context.connect(this); } } @Override public void start() throws IOException, IllegalStateException { checkClosed(); if (listener == null) { throw new IllegalStateException("Must set a provider listener prior to calling start."); } if (configuration.isFailOnStart()) { throw new IOException(); } } @Override public void close() { if (closed.compareAndSet(false, true)) { stats.recordCloseAttempt(); final ProviderFuture request = new ProviderFuture(); serializer.execute(new Runnable() { @Override public void run() { try { if (context != null) { context.disconnect(MockProvider.this); } if (configuration.isFailOnClose()) { request.onFailure(new RuntimeException()); } else { request.onSuccess(); } } catch (Exception e) { LOG.debug("Caught exception while closing the MockProvider"); request.onFailure(e); } } }); try { if (closeTimeout < 0) { request.sync(); } else { request.sync(closeTimeout, TimeUnit.MILLISECONDS); } } catch (IOException e) { LOG.warn("Error caught while closing Provider: ", e.getMessage()); } finally { ThreadPoolUtils.shutdownGraceful(serializer); } } } @Override public URI getRemoteURI() { return remoteURI; } @Override public void create(final JmsResource resource, final AsyncResult request) throws IOException, JMSException { checkClosed(); serializer.execute(new Runnable() { @Override public void run() { try { checkClosed(); stats.recordCreateResourceCall(resource); if (context != null) { context.createResource(resource); } if (resource instanceof JmsConnectionInfo) { if (listener != null) { listener.onConnectionEstablished(remoteURI); } } request.onSuccess(); } catch (Exception error) { request.onFailure(error); } } }); } @Override public void start(final JmsResource resource, final AsyncResult request) throws IOException, JMSException { checkClosed(); serializer.execute(new Runnable() { @Override public void run() { try { checkClosed(); stats.recordStartResourceCall(resource); if (context != null) { context.startResource(resource); } request.onSuccess(); } catch (Exception error) { request.onFailure(error); } } }); } @Override public void stop(final JmsResource resource, final AsyncResult request) throws IOException, JMSException { checkClosed(); serializer.execute(new Runnable() { @Override public void run() { try { checkClosed(); if (context != null) { context.stopResource(resource); } stats.recordStopResourceCall(resource); request.onSuccess(); } catch (Exception error) { request.onFailure(error); } } }); } @Override public void destroy(final JmsResource resource, final AsyncResult request) throws IOException, JMSException { checkClosed(); serializer.execute(new Runnable() { @Override public void run() { try { checkClosed(); stats.recordDestroyResourceCall(resource); if (context != null) { context.destroyResource(resource); } request.onSuccess(); } catch (Exception error) { request.onFailure(error); } } }); } @Override public void send(final JmsOutboundMessageDispatch envelope, final AsyncResult request) throws IOException, JMSException { checkClosed(); serializer.execute(new Runnable() { @Override public void run() { try { checkClosed(); stats.recordSendCall(); if (context != null) { context.recordSend(MockProvider.this, envelope); } // Put the message back to usable state following send complete envelope.getMessage().onSendComplete(); request.onSuccess(); if (envelope.isCompletionRequired()) { if (context != null && configuration.isDelayCompletionCalls()) { context.recordPendingCompletion(MockProvider.this, envelope); } else { if (listener != null) { listener.onCompletedMessageSend(envelope); } } } } catch (Exception error) { request.onFailure(error); } } }); } @Override public void acknowledge(final JmsSessionId sessionId, final ACK_TYPE ackType, final AsyncResult request) throws IOException, JMSException { checkClosed(); serializer.execute(new Runnable() { @Override public void run() { try { checkClosed(); stats.recoordSessionAcknowledgeCall(); request.onSuccess(); } catch (Exception error) { request.onFailure(error); } } }); } @Override public void acknowledge(final JmsInboundMessageDispatch envelope, final ACK_TYPE ackType, final AsyncResult request) throws IOException, JMSException { checkClosed(); serializer.execute(new Runnable() { @Override public void run() { try { checkClosed(); stats.recoordAcknowledgeCall(); request.onSuccess(); } catch (Exception error) { request.onFailure(error); } } }); } @Override public void commit(final JmsTransactionInfo transactionInfo, final JmsTransactionInfo nextTransactionInfo, final AsyncResult request) throws IOException, JMSException { checkClosed(); serializer.execute(new Runnable() { @Override public void run() { try { checkClosed(); stats.recordCommitCall(); request.onSuccess(); } catch (Exception error) { request.onFailure(error); } } }); } @Override public void rollback(final JmsTransactionInfo transactionInfo, final JmsTransactionInfo nextTransactionInfo, final AsyncResult request) throws IOException, JMSException { checkClosed(); serializer.execute(new Runnable() { @Override public void run() { try { checkClosed(); stats.recordRollbackCall(); request.onSuccess(); } catch (Exception error) { request.onFailure(error); } } }); } @Override public void recover(final JmsSessionId sessionId, final AsyncResult request) throws IOException { checkClosed(); serializer.execute(new Runnable() { @Override public void run() { try { checkClosed(); stats.recordRecoverCall(); request.onSuccess(); } catch (Exception error) { request.onFailure(error); } } }); } @Override public void unsubscribe(final String subscription, final AsyncResult request) throws IOException, JMSException { checkClosed(); serializer.execute(new Runnable() { @Override public void run() { try { checkClosed(); stats.recordUnsubscribeCall(); request.onSuccess(); } catch (Exception error) { request.onFailure(error); } } }); } @Override public void pull(final JmsConsumerId consumerId, final long timeout, final AsyncResult request) throws IOException { checkClosed(); serializer.execute(new Runnable() { @Override public void run() { try { checkClosed(); stats.recordPullCall(); request.onSuccess(); } catch (Exception error) { request.onFailure(error); } } }); } //----- API for generating provider events to a connection ---------------// public void signalConnectionFailed() { serializer.execute(new Runnable() { @Override public void run() { if (!closed.get()) { listener.onConnectionFailure(new IOException("Connection lost")); } } }); } /** * Switch state to closed without sending any notifications */ public void silentlyClose() { close(); } //----- Property getters and setters -------------------------------------// @Override public JmsMessageFactory getMessageFactory() { return messageFactory; } @Override public void setProviderListener(ProviderListener listener) { if (eventListener != null) { eventListener.whenProviderListenerSet(this, listener); } this.listener = listener; } @Override public ProviderListener getProviderListener() { return listener; } public MockProvider setEventListener(MockProviderListener eventListener) { this.eventListener = eventListener; return this; } public MockProviderConfiguration getConfiguration() { return configuration; } public MockProviderStats getStatistics() { return stats; } public void setProviderId(String providerId) { this.providerId = providerId; } public String getProviderId() { return this.providerId; } public long getCloseTimeout() { return this.closeTimeout; } public void setCloseTimeout(long closeTimeout) { this.closeTimeout = closeTimeout; } public long getConnectTimeout() { return connectTimeout; } public void setConnectTimeout(long connectTimeout) { this.connectTimeout = connectTimeout; } //----- Implementation details -------------------------------------------// private void checkClosed() throws ProviderClosedException { if (closed.get()) { throw new ProviderClosedException("This Provider is already closed"); } } }