/**
* 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.activemq.transport.vm;
import java.io.IOException;
import java.io.InterruptedIOException;
import java.net.URI;
import java.security.cert.X509Certificate;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicLong;
import org.apache.activemq.command.ShutdownInfo;
import org.apache.activemq.thread.Task;
import org.apache.activemq.thread.TaskRunner;
import org.apache.activemq.thread.TaskRunnerFactory;
import org.apache.activemq.transport.FutureResponse;
import org.apache.activemq.transport.ResponseCallback;
import org.apache.activemq.transport.Transport;
import org.apache.activemq.transport.TransportDisposedIOException;
import org.apache.activemq.transport.TransportListener;
import org.apache.activemq.util.IOExceptionSupport;
import org.apache.activemq.wireformat.WireFormat;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* A Transport implementation that uses direct method invocations.
*/
public class VMTransport implements Transport, Task {
protected static final Logger LOG = LoggerFactory.getLogger(VMTransport.class);
private static final AtomicLong NEXT_ID = new AtomicLong(0);
// Transport Configuration
protected VMTransport peer;
protected TransportListener transportListener;
protected boolean marshal;
protected boolean async = true;
protected int asyncQueueDepth = 2000;
protected final URI location;
protected final long id;
// Implementation
private volatile LinkedBlockingQueue<Object> messageQueue;
private volatile TaskRunnerFactory taskRunnerFactory;
private volatile TaskRunner taskRunner;
// Transport State
protected final AtomicBoolean started = new AtomicBoolean();
protected final AtomicBoolean disposed = new AtomicBoolean();
private volatile int receiveCounter;
public VMTransport(URI location) {
this.location = location;
this.id = NEXT_ID.getAndIncrement();
}
public void setPeer(VMTransport peer) {
this.peer = peer;
}
@Override
public void oneway(Object command) throws IOException {
if (disposed.get()) {
throw new TransportDisposedIOException("Transport disposed.");
}
if (peer == null) {
throw new IOException("Peer not connected.");
}
try {
if (peer.disposed.get()) {
throw new TransportDisposedIOException("Peer (" + peer.toString() + ") disposed.");
}
if (peer.async) {
peer.getMessageQueue().put(command);
peer.wakeup();
return;
}
if (!peer.started.get()) {
LinkedBlockingQueue<Object> pending = peer.getMessageQueue();
int sleepTimeMillis;
boolean accepted = false;
do {
sleepTimeMillis = 0;
// the pending queue is drained on start so we need to ensure we add before
// the drain commences, otherwise we never get the command dispatched!
synchronized (peer.started) {
if (!peer.started.get()) {
accepted = pending.offer(command);
if (!accepted) {
sleepTimeMillis = 500;
}
}
}
// give start thread a chance if we will loop
TimeUnit.MILLISECONDS.sleep(sleepTimeMillis);
} while (!accepted && !peer.started.get());
if (accepted) {
return;
}
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
InterruptedIOException iioe = new InterruptedIOException(e.getMessage());
iioe.initCause(e);
throw iioe;
}
dispatch(peer, peer.messageQueue, command);
}
public void dispatch(VMTransport transport, BlockingQueue<Object> pending, Object command) {
TransportListener transportListener = transport.getTransportListener();
if (transportListener != null) {
// Lock here on the target transport's started since we want to wait for its start()
// method to finish dispatching out of the queue before we do our own.
synchronized (transport.started) {
// Ensure that no additional commands entered the queue in the small time window
// before the start method locks the dispatch lock and the oneway method was in
// an put operation.
while(pending != null && !pending.isEmpty() && !transport.isDisposed()) {
doDispatch(transport, transportListener, pending.poll());
}
// We are now in sync mode and won't enqueue any more commands to the target
// transport so lets clean up its resources.
transport.messageQueue = null;
// Don't dispatch if either end was disposed already.
if (command != null && !this.disposed.get() && !transport.isDisposed()) {
doDispatch(transport, transportListener, command);
}
}
}
}
public void doDispatch(VMTransport transport, TransportListener transportListener, Object command) {
transport.receiveCounter++;
transportListener.onCommand(command);
}
@Override
public void start() throws Exception {
if (transportListener == null) {
throw new IOException("TransportListener not set.");
}
// If we are not in async mode we lock the dispatch lock here and then start to
// prevent any sync dispatches from occurring until we dispatch the pending messages
// to maintain delivery order. When async this happens automatically so just set
// started and wakeup the task runner.
if (!async) {
synchronized (started) {
if (started.compareAndSet(false, true)) {
LinkedBlockingQueue<Object> mq = getMessageQueue();
Object command;
while ((command = mq.poll()) != null && !disposed.get() ) {
receiveCounter++;
doDispatch(this, transportListener, command);
}
}
}
} else {
if (started.compareAndSet(false, true)) {
wakeup();
}
}
}
@Override
public void stop() throws Exception {
// Only need to do this once, all future oneway calls will now
// fail as will any asnyc jobs in the task runner.
if (disposed.compareAndSet(false, true)) {
TaskRunner tr = taskRunner;
LinkedBlockingQueue<Object> mq = this.messageQueue;
taskRunner = null;
messageQueue = null;
if (mq != null) {
mq.clear();
}
// don't wait for completion
if (tr != null) {
try {
tr.shutdown(1);
} catch(Exception e) {
}
tr = null;
}
if (peer.transportListener != null) {
// let the peer know that we are disconnecting after attempting
// to cleanly shutdown the async tasks so that this is the last
// command it see's.
try {
peer.transportListener.onCommand(new ShutdownInfo());
} catch (Exception ignore) {
}
// let any requests pending a response see an exception
try {
peer.transportListener.onException(new TransportDisposedIOException("peer (" + this + ") stopped."));
} catch (Exception ignore) {
}
}
// shutdown task runner factory
if (taskRunnerFactory != null) {
taskRunnerFactory.shutdownNow();
taskRunnerFactory = null;
}
}
}
protected void wakeup() {
if (async && started.get()) {
try {
getTaskRunner().wakeup();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
} catch (TransportDisposedIOException e) {
}
}
}
/**
* @see org.apache.activemq.thread.Task#iterate()
*/
@Override
public boolean iterate() {
final TransportListener tl = transportListener;
LinkedBlockingQueue<Object> mq;
try {
mq = getMessageQueue();
} catch (TransportDisposedIOException e) {
return false;
}
Object command = mq.poll();
if (command != null && !disposed.get()) {
try {
tl.onCommand(command);
} catch (Exception e) {
try {
peer.transportListener.onException(IOExceptionSupport.create(e));
} catch (Exception ignore) {
}
}
return !mq.isEmpty() && !disposed.get();
} else {
if(disposed.get()) {
mq.clear();
}
return false;
}
}
@Override
public void setTransportListener(TransportListener commandListener) {
this.transportListener = commandListener;
}
public LinkedBlockingQueue<Object> getMessageQueue() throws TransportDisposedIOException {
LinkedBlockingQueue<Object> result = messageQueue;
if (result == null) {
synchronized (this) {
result = messageQueue;
if (result == null) {
if (disposed.get()) {
throw new TransportDisposedIOException("The Transport has been disposed");
}
messageQueue = result = new LinkedBlockingQueue<Object>(this.asyncQueueDepth);
}
}
}
return result;
}
protected TaskRunner getTaskRunner() throws TransportDisposedIOException {
TaskRunner result = taskRunner;
if (result == null) {
synchronized (this) {
result = taskRunner;
if (result == null) {
if (disposed.get()) {
throw new TransportDisposedIOException("The Transport has been disposed");
}
String name = "ActiveMQ VMTransport: " + toString();
if (taskRunnerFactory == null) {
taskRunnerFactory = new TaskRunnerFactory(name);
taskRunnerFactory.init();
}
taskRunner = result = taskRunnerFactory.createTaskRunner(this, name);
}
}
}
return result;
}
@Override
public FutureResponse asyncRequest(Object command, ResponseCallback responseCallback) throws IOException {
throw new AssertionError("Unsupported Method");
}
@Override
public Object request(Object command) throws IOException {
throw new AssertionError("Unsupported Method");
}
@Override
public Object request(Object command, int timeout) throws IOException {
throw new AssertionError("Unsupported Method");
}
@Override
public TransportListener getTransportListener() {
return transportListener;
}
@Override
public <T> T narrow(Class<T> target) {
if (target.isAssignableFrom(getClass())) {
return target.cast(this);
}
return null;
}
public boolean isMarshal() {
return marshal;
}
public void setMarshal(boolean marshal) {
this.marshal = marshal;
}
@Override
public String toString() {
return location + "#" + id;
}
@Override
public String getRemoteAddress() {
if (peer != null) {
return peer.toString();
}
return null;
}
/**
* @return the async
*/
public boolean isAsync() {
return async;
}
/**
* @param async the async to set
*/
public void setAsync(boolean async) {
this.async = async;
}
/**
* @return the asyncQueueDepth
*/
public int getAsyncQueueDepth() {
return asyncQueueDepth;
}
/**
* @param asyncQueueDepth the asyncQueueDepth to set
*/
public void setAsyncQueueDepth(int asyncQueueDepth) {
this.asyncQueueDepth = asyncQueueDepth;
}
@Override
public boolean isFaultTolerant() {
return false;
}
@Override
public boolean isDisposed() {
return disposed.get();
}
@Override
public boolean isConnected() {
return !disposed.get();
}
@Override
public void reconnect(URI uri) throws IOException {
throw new IOException("Transport reconnect is not supported");
}
@Override
public boolean isReconnectSupported() {
return false;
}
@Override
public boolean isUpdateURIsSupported() {
return false;
}
@Override
public void updateURIs(boolean reblance,URI[] uris) throws IOException {
throw new IOException("URI update feature not supported");
}
@Override
public int getReceiveCounter() {
return receiveCounter;
}
@Override
public X509Certificate[] getPeerCertificates() {
return null;
}
@Override
public void setPeerCertificates(X509Certificate[] certificates) {
}
@Override
public WireFormat getWireFormat() {
return null;
}
}