001/**
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements.  See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License.  You may obtain a copy of the License at
008 *
009 *      http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017package org.apache.activemq.transport.vm;
018
019import java.io.IOException;
020import java.io.InterruptedIOException;
021import java.net.URI;
022import java.util.concurrent.BlockingQueue;
023import java.util.concurrent.LinkedBlockingQueue;
024import java.util.concurrent.TimeUnit;
025import java.util.concurrent.atomic.AtomicBoolean;
026import java.util.concurrent.atomic.AtomicLong;
027
028import org.apache.activemq.command.ShutdownInfo;
029import org.apache.activemq.thread.Task;
030import org.apache.activemq.thread.TaskRunner;
031import org.apache.activemq.thread.TaskRunnerFactory;
032import org.apache.activemq.transport.FutureResponse;
033import org.apache.activemq.transport.ResponseCallback;
034import org.apache.activemq.transport.Transport;
035import org.apache.activemq.transport.TransportDisposedIOException;
036import org.apache.activemq.transport.TransportListener;
037import org.slf4j.Logger;
038import org.slf4j.LoggerFactory;
039
040/**
041 * A Transport implementation that uses direct method invocations.
042 */
043public class VMTransport implements Transport, Task {
044    protected static final Logger LOG = LoggerFactory.getLogger(VMTransport.class);
045
046    private static final AtomicLong NEXT_ID = new AtomicLong(0);
047
048    // Transport Configuration
049    protected VMTransport peer;
050    protected TransportListener transportListener;
051    protected boolean marshal;
052    protected boolean network;
053    protected boolean async = true;
054    protected int asyncQueueDepth = 2000;
055    protected final URI location;
056    protected final long id;
057
058    // Implementation
059    private LinkedBlockingQueue<Object> messageQueue;
060    private TaskRunnerFactory taskRunnerFactory;
061    private TaskRunner taskRunner;
062
063    // Transport State
064    protected final AtomicBoolean started = new AtomicBoolean();
065    protected final AtomicBoolean disposed = new AtomicBoolean();
066
067    private volatile int receiveCounter;
068
069    public VMTransport(URI location) {
070        this.location = location;
071        this.id = NEXT_ID.getAndIncrement();
072    }
073
074    public void setPeer(VMTransport peer) {
075        this.peer = peer;
076    }
077
078    @Override
079    public void oneway(Object command) throws IOException {
080
081        if (disposed.get()) {
082            throw new TransportDisposedIOException("Transport disposed.");
083        }
084
085        if (peer == null) {
086            throw new IOException("Peer not connected.");
087        }
088
089        try {
090
091            if (peer.disposed.get()) {
092                throw new TransportDisposedIOException("Peer (" + peer.toString() + ") disposed.");
093            }
094
095            if (peer.async || !peer.started.get()) {
096                peer.getMessageQueue().put(command);
097                peer.wakeup();
098                return;
099            }
100
101        } catch (InterruptedException e) {
102            InterruptedIOException iioe = new InterruptedIOException(e.getMessage());
103            iioe.initCause(e);
104            throw iioe;
105        }
106
107        dispatch(peer, peer.messageQueue, command);
108    }
109
110    public void dispatch(VMTransport transport, BlockingQueue<Object> pending, Object command) {
111        TransportListener transportListener = transport.getTransportListener();
112        if (transportListener != null) {
113            // Lock here on the target transport's started since we want to wait for its start()
114            // method to finish dispatching out of the queue before we do our own.
115            synchronized (transport.started) {
116
117                // Ensure that no additional commands entered the queue in the small time window
118                // before the start method locks the dispatch lock and the oneway method was in
119                // an put operation.
120                while(pending != null && !pending.isEmpty() && !transport.isDisposed()) {
121                    doDispatch(transport, transportListener, pending.poll());
122                }
123
124                // We are now in sync mode and won't enqueue any more commands to the target
125                // transport so lets clean up its resources.
126                transport.messageQueue = null;
127
128                // Don't dispatch if either end was disposed already.
129                if (command != null && !this.disposed.get() && !transport.isDisposed()) {
130                    doDispatch(transport, transportListener, command);
131                }
132            }
133        }
134    }
135
136    public void doDispatch(VMTransport transport, TransportListener transportListener, Object command) {
137        transport.receiveCounter++;
138        transportListener.onCommand(command);
139    }
140
141    @Override
142    public void start() throws Exception {
143
144        if (transportListener == null) {
145            throw new IOException("TransportListener not set.");
146        }
147
148        // If we are not in async mode we lock the dispatch lock here and then start to
149        // prevent any sync dispatches from occurring until we dispatch the pending messages
150        // to maintain delivery order.  When async this happens automatically so just set
151        // started and wakeup the task runner.
152        if (!async) {
153            synchronized (started) {
154                if (started.compareAndSet(false, true)) {
155                    LinkedBlockingQueue<Object> mq = getMessageQueue();
156                    Object command;
157                    while ((command = mq.poll()) != null && !disposed.get() ) {
158                        receiveCounter++;
159                        doDispatch(this, transportListener, command);
160                    }
161                }
162            }
163        } else {
164            if (started.compareAndSet(false, true)) {
165                wakeup();
166            }
167        }
168    }
169
170    @Override
171    public void stop() throws Exception {
172        // Only need to do this once, all future oneway calls will now
173        // fail as will any asnyc jobs in the task runner.
174        if (disposed.compareAndSet(false, true)) {
175
176            TaskRunner tr = taskRunner;
177            LinkedBlockingQueue<Object> mq = this.messageQueue;
178
179            taskRunner = null;
180            messageQueue = null;
181
182            if (mq != null) {
183                mq.clear();
184            }
185
186            // Allow pending deliveries to finish up, but don't wait
187            // forever in case of an stalled onCommand.
188            if (tr != null) {
189                try {
190                    tr.shutdown(TimeUnit.SECONDS.toMillis(1));
191                } catch(Exception e) {
192                }
193                tr = null;
194            }
195
196            if (peer.transportListener != null) {
197                // let the peer know that we are disconnecting after attempting
198                // to cleanly shutdown the async tasks so that this is the last
199                // command it see's.
200                try {
201                    peer.transportListener.onCommand(new ShutdownInfo());
202                } catch (Exception ignore) {
203                }
204
205                // let any requests pending a response see an exception
206                try {
207                    peer.transportListener.onException(new TransportDisposedIOException("peer (" + this + ") stopped."));
208                } catch (Exception ignore) {
209                }
210            }
211
212            // shutdown task runner factory
213            if (taskRunnerFactory != null) {
214                taskRunnerFactory.shutdownNow();
215                taskRunnerFactory = null;
216            }
217        }
218    }
219
220    protected void wakeup() {
221        if (async && started.get()) {
222            try {
223                getTaskRunner().wakeup();
224            } catch (InterruptedException e) {
225                Thread.currentThread().interrupt();
226            } catch (TransportDisposedIOException e) {
227            }
228        }
229    }
230
231    /**
232     * @see org.apache.activemq.thread.Task#iterate()
233     */
234    @Override
235    public boolean iterate() {
236
237        final TransportListener tl = transportListener;
238
239        LinkedBlockingQueue<Object> mq;
240        try {
241            mq = getMessageQueue();
242        } catch (TransportDisposedIOException e) {
243            return false;
244        }
245
246        Object command = mq.poll();
247        if (command != null && !disposed.get()) {
248            tl.onCommand(command);
249            return !mq.isEmpty() && !disposed.get();
250        } else {
251            if(disposed.get()) {
252                mq.clear();
253            }
254            return false;
255        }
256    }
257
258    @Override
259    public void setTransportListener(TransportListener commandListener) {
260        this.transportListener = commandListener;
261    }
262
263    public void setMessageQueue(LinkedBlockingQueue<Object> asyncQueue) {
264        synchronized (this) {
265            if (messageQueue == null) {
266                messageQueue = asyncQueue;
267            }
268        }
269    }
270
271    public LinkedBlockingQueue<Object> getMessageQueue() throws TransportDisposedIOException {
272        LinkedBlockingQueue<Object> result = messageQueue;
273        if (result == null) {
274            synchronized (this) {
275                result = messageQueue;
276                if (result == null) {
277                    if (disposed.get()) {
278                        throw new TransportDisposedIOException("The Transport has been disposed");
279                    }
280
281                    messageQueue = result = new LinkedBlockingQueue<Object>(this.asyncQueueDepth);
282                }
283            }
284        }
285        return result;
286    }
287
288    protected TaskRunner getTaskRunner() throws TransportDisposedIOException {
289        TaskRunner result = taskRunner;
290        if (result == null) {
291            synchronized (this) {
292                result = taskRunner;
293                if (result == null) {
294                    if (disposed.get()) {
295                        throw new TransportDisposedIOException("The Transport has been disposed");
296                    }
297
298                    String name = "ActiveMQ VMTransport: " + toString();
299                    if (taskRunnerFactory == null) {
300                        taskRunnerFactory = new TaskRunnerFactory(name);
301                        taskRunnerFactory.init();
302                    }
303                    taskRunner = result = taskRunnerFactory.createTaskRunner(this, name);
304                }
305            }
306        }
307        return result;
308    }
309
310    @Override
311    public FutureResponse asyncRequest(Object command, ResponseCallback responseCallback) throws IOException {
312        throw new AssertionError("Unsupported Method");
313    }
314
315    @Override
316    public Object request(Object command) throws IOException {
317        throw new AssertionError("Unsupported Method");
318    }
319
320    @Override
321    public Object request(Object command, int timeout) throws IOException {
322        throw new AssertionError("Unsupported Method");
323    }
324
325    @Override
326    public TransportListener getTransportListener() {
327        return transportListener;
328    }
329
330    @Override
331    public <T> T narrow(Class<T> target) {
332        if (target.isAssignableFrom(getClass())) {
333            return target.cast(this);
334        }
335        return null;
336    }
337
338    public boolean isMarshal() {
339        return marshal;
340    }
341
342    public void setMarshal(boolean marshal) {
343        this.marshal = marshal;
344    }
345
346    public boolean isNetwork() {
347        return network;
348    }
349
350    public void setNetwork(boolean network) {
351        this.network = network;
352    }
353
354    @Override
355    public String toString() {
356        return location + "#" + id;
357    }
358
359    @Override
360    public String getRemoteAddress() {
361        if (peer != null) {
362            return peer.toString();
363        }
364        return null;
365    }
366
367    /**
368     * @return the async
369     */
370    public boolean isAsync() {
371        return async;
372    }
373
374    /**
375     * @param async the async to set
376     */
377    public void setAsync(boolean async) {
378        this.async = async;
379    }
380
381    /**
382     * @return the asyncQueueDepth
383     */
384    public int getAsyncQueueDepth() {
385        return asyncQueueDepth;
386    }
387
388    /**
389     * @param asyncQueueDepth the asyncQueueDepth to set
390     */
391    public void setAsyncQueueDepth(int asyncQueueDepth) {
392        this.asyncQueueDepth = asyncQueueDepth;
393    }
394
395    @Override
396    public boolean isFaultTolerant() {
397        return false;
398    }
399
400    @Override
401    public boolean isDisposed() {
402        return disposed.get();
403    }
404
405    @Override
406    public boolean isConnected() {
407        return !disposed.get();
408    }
409
410    @Override
411    public void reconnect(URI uri) throws IOException {
412        throw new IOException("Transport reconnect is not supported");
413    }
414
415    @Override
416    public boolean isReconnectSupported() {
417        return false;
418    }
419
420    @Override
421    public boolean isUpdateURIsSupported() {
422        return false;
423    }
424
425    @Override
426    public void updateURIs(boolean reblance,URI[] uris) throws IOException {
427        throw new IOException("URI update feature not supported");
428    }
429
430    @Override
431    public int getReceiveCounter() {
432        return receiveCounter;
433    }
434}