001    /**
002     * Copyright (C) 2012 FuseSource, Inc.
003     * http://fusesource.com
004     *
005     * Licensed under the Apache License, Version 2.0 (the "License");
006     * you may not use this file except in compliance with the License.
007     * 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     */
017    
018    package org.fusesource.hawtdispatch.transport;
019    
020    import org.fusesource.hawtdispatch.*;
021    
022    import java.io.IOException;
023    import java.net.*;
024    import java.nio.channels.SelectionKey;
025    import java.nio.channels.ServerSocketChannel;
026    import java.nio.channels.SocketChannel;
027    
028    /**
029     * A TCP based implementation of {@link TransportServer}
030     *
031     * @author <a href="http://hiramchirino.com">Hiram Chirino</a>
032     */
033    
034    public class TcpTransportServer implements TransportServer {
035    
036        private final String bindScheme;
037        private final InetSocketAddress bindAddress;
038    
039        private int backlog = 100;
040    
041        private ServerSocketChannel channel;
042        private TransportServerListener listener;
043        private DispatchQueue dispatchQueue;
044        private DispatchSource acceptSource;
045        private int receiveBufferSize = 64*1024;
046    
047        public TcpTransportServer(URI location) throws UnknownHostException {
048            bindScheme = location.getScheme();
049            String host = location.getHost();
050            host = (host == null || host.length() == 0) ? "::" : host;
051            bindAddress = new InetSocketAddress(InetAddress.getByName(host), location.getPort());
052        }
053    
054        public void setTransportServerListener(TransportServerListener listener) {
055            this.listener = listener;
056        }
057    
058        public InetSocketAddress getSocketAddress() {
059            return (InetSocketAddress) channel.socket().getLocalSocketAddress();
060        }
061    
062        public DispatchQueue getDispatchQueue() {
063            return dispatchQueue;
064        }
065    
066        public void setDispatchQueue(DispatchQueue dispatchQueue) {
067            this.dispatchQueue = dispatchQueue;
068        }
069    
070        public void suspend() {
071            acceptSource.suspend();
072        }
073    
074        public void resume() {
075            acceptSource.resume();
076        }
077    
078        @Deprecated
079        public void start(Runnable onCompleted) throws Exception {
080            start(new TaskWrapper(onCompleted));
081        }
082        @Deprecated
083        public void stop(Runnable onCompleted) throws Exception {
084            stop(new TaskWrapper(onCompleted));
085        }
086    
087        public void start(Task onCompleted) throws Exception {
088    
089            try {
090                channel = ServerSocketChannel.open();
091                channel.configureBlocking(false);
092                try {
093                    channel.socket().setReceiveBufferSize(receiveBufferSize);
094                } catch (SocketException ignore) {
095                }
096                channel.socket().bind(bindAddress, backlog);
097            } catch (IOException e) {
098                throw new IOException("Failed to bind to server socket: " + bindAddress + " due to: " + e);
099            }
100    
101            acceptSource = Dispatch.createSource(channel, SelectionKey.OP_ACCEPT, dispatchQueue);
102            acceptSource.setEventHandler(new Task() {
103                public void run() {
104                    try {
105                        SocketChannel client = channel.accept();
106                        while( client!=null ) {
107                            handleSocket(client);
108                            client = channel.accept();
109                        }
110                    } catch (Exception e) {
111                        listener.onAcceptError(e);
112                    }
113                }
114            });
115            acceptSource.setCancelHandler(new Task() {
116                public void run() {
117                    try {
118                        channel.close();
119                    } catch (IOException e) {
120                    }
121                }
122            });
123            acceptSource.resume();
124            if( onCompleted!=null ) {
125                dispatchQueue.execute(onCompleted);
126            }
127        }
128    
129        public String getBoundAddress() {
130            try {
131                return new URI(bindScheme, null, bindAddress.getAddress().getHostAddress(), channel.socket().getLocalPort(), null, null, null).toString();
132            } catch (URISyntaxException e) {
133                throw new RuntimeException(e);
134            }
135        }
136    
137        public void stop(final Task onCompleted) throws Exception {
138            if( acceptSource.isCanceled() ) {
139                onCompleted.run();
140            } else {
141                acceptSource.setCancelHandler(new Task() {
142                    public void run() {
143                        try {
144                            channel.close();
145                        } catch (IOException e) {
146                        }
147                        onCompleted.run();
148                    }
149                });
150                acceptSource.cancel();
151            }
152        }
153    
154        public int getBacklog() {
155            return backlog;
156        }
157    
158        public void setBacklog(int backlog) {
159            this.backlog = backlog;
160        }
161    
162        protected final void handleSocket(SocketChannel socket) throws Exception {
163            TcpTransport transport = createTransport();
164            transport.connected(socket);
165            listener.onAccept(transport);
166        }
167    
168        protected TcpTransport createTransport() {
169            return new TcpTransport();
170        }
171    
172        /**
173         * @return pretty print of this
174         */
175        public String toString() {
176            return getBoundAddress();
177        }
178    
179        public int getReceiveBufferSize() {
180            return receiveBufferSize;
181        }
182    
183        public void setReceiveBufferSize(int receiveBufferSize) {
184            this.receiveBufferSize = receiveBufferSize;
185        }
186    
187    }