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.nio;
018
019import java.io.DataInputStream;
020import java.io.DataOutputStream;
021import java.io.EOFException;
022import java.io.IOException;
023import java.net.Socket;
024import java.net.URI;
025import java.net.UnknownHostException;
026import java.nio.ByteBuffer;
027import java.nio.channels.SelectionKey;
028import java.nio.channels.SocketChannel;
029
030import javax.net.SocketFactory;
031
032import org.apache.activemq.openwire.OpenWireFormat;
033import org.apache.activemq.transport.Transport;
034import org.apache.activemq.transport.tcp.TcpTransport;
035import org.apache.activemq.util.IOExceptionSupport;
036import org.apache.activemq.util.ServiceStopper;
037import org.apache.activemq.wireformat.WireFormat;
038
039/**
040 * An implementation of the {@link Transport} interface using raw tcp/ip
041 *
042 *
043 */
044public class NIOTransport extends TcpTransport {
045
046    // private static final Logger log = LoggerFactory.getLogger(NIOTransport.class);
047    protected SocketChannel channel;
048    protected SelectorSelection selection;
049    protected ByteBuffer inputBuffer;
050    protected ByteBuffer currentBuffer;
051    protected int nextFrameSize;
052
053    public NIOTransport(WireFormat wireFormat, SocketFactory socketFactory, URI remoteLocation, URI localLocation) throws UnknownHostException, IOException {
054        super(wireFormat, socketFactory, remoteLocation, localLocation);
055    }
056
057    public NIOTransport(WireFormat wireFormat, Socket socket) throws IOException {
058        super(wireFormat, socket);
059    }
060
061    /**
062     * @param format
063     * @param socket
064     * @param initBuffer
065     * @throws IOException
066     */
067    public NIOTransport(WireFormat format, Socket socket, InitBuffer initBuffer) throws IOException {
068        super(format, socket, initBuffer);
069    }
070
071    @Override
072    protected void initializeStreams() throws IOException {
073        channel = socket.getChannel();
074        channel.configureBlocking(false);
075
076        // listen for events telling us when the socket is readable.
077        selection = SelectorManager.getInstance().register(channel, new SelectorManager.Listener() {
078            @Override
079            public void onSelect(SelectorSelection selection) {
080                serviceRead();
081            }
082
083            @Override
084            public void onError(SelectorSelection selection, Throwable error) {
085                if (error instanceof IOException) {
086                    onException((IOException)error);
087                } else {
088                    onException(IOExceptionSupport.create(error));
089                }
090            }
091        });
092
093        // Send the data via the channel
094        // inputBuffer = ByteBuffer.allocateDirect(8*1024);
095        inputBuffer = ByteBuffer.allocateDirect(getIoBufferSize());
096        currentBuffer = inputBuffer;
097        nextFrameSize = -1;
098        currentBuffer.limit(4);
099        NIOOutputStream outPutStream = new NIOOutputStream(channel, getIoBufferSize());
100        this.dataOut = new DataOutputStream(outPutStream);
101        this.buffOut = outPutStream;
102    }
103
104    protected int readFromBuffer() throws IOException {
105        return channel.read(currentBuffer);
106    }
107
108    protected void serviceRead() {
109        try {
110            while (true) {
111
112                int readSize = readFromBuffer();
113                if (readSize == -1) {
114                    onException(new EOFException());
115                    selection.close();
116                    break;
117                }
118                if (readSize == 0) {
119                    break;
120                }
121
122                this.receiveCounter += readSize;
123                if (currentBuffer.hasRemaining()) {
124                    continue;
125                }
126
127                // Are we trying to figure out the size of the next frame?
128                if (nextFrameSize == -1) {
129                    assert inputBuffer == currentBuffer;
130
131                    // If the frame is too big to fit in our direct byte buffer,
132                    // Then allocate a non direct byte buffer of the right size
133                    // for it.
134                    inputBuffer.flip();
135                    nextFrameSize = inputBuffer.getInt() + 4;
136
137                    if (wireFormat instanceof OpenWireFormat) {
138                        long maxFrameSize = ((OpenWireFormat)wireFormat).getMaxFrameSize();
139                        if (nextFrameSize > maxFrameSize) {
140                            throw new IOException("Frame size of " + (nextFrameSize / (1024 * 1024)) + " MB larger than max allowed " + (maxFrameSize / (1024 * 1024)) + " MB");
141                        }
142                    }
143
144                    if (nextFrameSize > inputBuffer.capacity()) {
145                        currentBuffer = ByteBuffer.allocateDirect(nextFrameSize);
146                        currentBuffer.putInt(nextFrameSize);
147                    } else {
148                        inputBuffer.limit(nextFrameSize);
149                    }
150
151                } else {
152                    currentBuffer.flip();
153
154                    Object command = wireFormat.unmarshal(new DataInputStream(new NIOInputStream(currentBuffer)));
155                    doConsume(command);
156
157                    nextFrameSize = -1;
158                    inputBuffer.clear();
159                    inputBuffer.limit(4);
160                    currentBuffer = inputBuffer;
161                }
162
163            }
164
165        } catch (IOException e) {
166            onException(e);
167        } catch (Throwable e) {
168            onException(IOExceptionSupport.create(e));
169        }
170    }
171
172    @Override
173    protected void doStart() throws Exception {
174        connect();
175        selection.setInterestOps(SelectionKey.OP_READ);
176        selection.enable();
177    }
178
179    @Override
180    protected void doStop(ServiceStopper stopper) throws Exception {
181        if (selection != null) {
182            selection.close();
183            selection = null;
184        }
185        super.doStop(stopper);
186    }
187}