001/*
002 *  Licensed to the Apache Software Foundation (ASF) under one
003 *  or more contributor license agreements.  See the NOTICE file
004 *  distributed with this work for additional information
005 *  regarding copyright ownership.  The ASF licenses this file
006 *  to you under the Apache License, Version 2.0 (the
007 *  "License"); you may not use this file except in compliance
008 *  with the License.  You may obtain a copy of the License at
009 *
010 *    http://www.apache.org/licenses/LICENSE-2.0
011 *
012 *  Unless required by applicable law or agreed to in writing,
013 *  software distributed under the License is distributed on an
014 *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
015 *  KIND, either express or implied.  See the License for the
016 *  specific language governing permissions and limitations
017 *  under the License.
018 *
019 */
020package org.apache.mina.core.service;
021
022import java.net.SocketAddress;
023import java.util.concurrent.Executor;
024import java.util.concurrent.Executors;
025
026import org.apache.mina.core.future.ConnectFuture;
027import org.apache.mina.core.future.IoFuture;
028import org.apache.mina.core.future.IoFutureListener;
029import org.apache.mina.core.session.IdleStatus;
030import org.apache.mina.core.session.IoSession;
031import org.apache.mina.core.session.IoSessionConfig;
032import org.apache.mina.core.session.IoSessionInitializer;
033
034/**
035 * A base implementation of {@link IoConnector}.
036 *
037 * @author <a href="http://mina.apache.org">Apache MINA Project</a>
038 */
039public abstract class AbstractIoConnector extends AbstractIoService implements IoConnector {
040    /**
041     * The minimum timeout value that is supported (in milliseconds).
042     */
043    private long connectTimeoutCheckInterval = 50L;
044
045    private long connectTimeoutInMillis = 60 * 1000L; // 1 minute by default
046
047    /** The remote address we are connected to */
048    private SocketAddress defaultRemoteAddress;
049
050    /** The local address */
051    private SocketAddress defaultLocalAddress;
052
053    /**
054     * Constructor for {@link AbstractIoConnector}. You need to provide a
055     * default session configuration and an {@link Executor} for handling I/O
056     * events. If null {@link Executor} is provided, a default one will be
057     * created using {@link Executors#newCachedThreadPool()}.
058     * 
059     * @see AbstractIoService#AbstractIoService(IoSessionConfig, Executor)
060     * 
061     * @param sessionConfig
062     *            the default configuration for the managed {@link IoSession}
063     * @param executor
064     *            the {@link Executor} used for handling execution of I/O
065     *            events. Can be <code>null</code>.
066     */
067    protected AbstractIoConnector(IoSessionConfig sessionConfig, Executor executor) {
068        super(sessionConfig, executor);
069    }
070
071    /**
072    * @return
073     *  The minimum time that this connector can have for a connection
074     *  timeout in milliseconds.
075     */
076    public long getConnectTimeoutCheckInterval() {
077        return connectTimeoutCheckInterval;
078    }
079
080    public void setConnectTimeoutCheckInterval(long minimumConnectTimeout) {
081        if (getConnectTimeoutMillis() < minimumConnectTimeout) {
082            this.connectTimeoutInMillis = minimumConnectTimeout;
083        }
084
085        this.connectTimeoutCheckInterval = minimumConnectTimeout;
086    }
087
088    /**
089     * @deprecated
090     *  Take a look at <tt>getConnectTimeoutMillis()</tt>
091     */
092    public final int getConnectTimeout() {
093        return (int) connectTimeoutInMillis / 1000;
094    }
095
096    /**
097     * {@inheritDoc}
098     */
099    public final long getConnectTimeoutMillis() {
100        return connectTimeoutInMillis;
101    }
102
103    /**
104     * @deprecated
105     *  Take a look at <tt>setConnectTimeoutMillis(long)</tt>
106     */
107    public final void setConnectTimeout(int connectTimeout) {
108
109        setConnectTimeoutMillis(connectTimeout * 1000L);
110    }
111
112    /**
113     * Sets the connect timeout value in milliseconds.
114     * 
115     */
116    public final void setConnectTimeoutMillis(long connectTimeoutInMillis) {
117        if (connectTimeoutInMillis <= connectTimeoutCheckInterval) {
118            this.connectTimeoutCheckInterval = connectTimeoutInMillis;
119        }
120        this.connectTimeoutInMillis = connectTimeoutInMillis;
121    }
122
123    /**
124     * {@inheritDoc}
125     */
126    public SocketAddress getDefaultRemoteAddress() {
127        return defaultRemoteAddress;
128    }
129
130    /**
131     * {@inheritDoc}
132     */
133    public final void setDefaultLocalAddress(SocketAddress localAddress) {
134        defaultLocalAddress = localAddress;
135    }
136
137    /**
138     * {@inheritDoc}
139     */
140    public final SocketAddress getDefaultLocalAddress() {
141        return defaultLocalAddress;
142    }
143
144    /**
145     * {@inheritDoc}
146     */
147    public final void setDefaultRemoteAddress(SocketAddress defaultRemoteAddress) {
148        if (defaultRemoteAddress == null) {
149            throw new IllegalArgumentException("defaultRemoteAddress");
150        }
151
152        if (!getTransportMetadata().getAddressType().isAssignableFrom(defaultRemoteAddress.getClass())) {
153            throw new IllegalArgumentException("defaultRemoteAddress type: " + defaultRemoteAddress.getClass()
154                    + " (expected: " + getTransportMetadata().getAddressType() + ")");
155        }
156        this.defaultRemoteAddress = defaultRemoteAddress;
157    }
158
159    /**
160     * {@inheritDoc}
161     */
162    public final ConnectFuture connect() {
163        SocketAddress defaultRemoteAddress = getDefaultRemoteAddress();
164        if (defaultRemoteAddress == null) {
165            throw new IllegalStateException("defaultRemoteAddress is not set.");
166        }
167
168        return connect(defaultRemoteAddress, null, null);
169    }
170
171    /**
172     * {@inheritDoc}
173     */
174    public ConnectFuture connect(IoSessionInitializer<? extends ConnectFuture> sessionInitializer) {
175        SocketAddress defaultRemoteAddress = getDefaultRemoteAddress();
176        if (defaultRemoteAddress == null) {
177            throw new IllegalStateException("defaultRemoteAddress is not set.");
178        }
179
180        return connect(defaultRemoteAddress, null, sessionInitializer);
181    }
182
183    /**
184     * {@inheritDoc}
185     */
186    public final ConnectFuture connect(SocketAddress remoteAddress) {
187        return connect(remoteAddress, null, null);
188    }
189
190    /**
191     * {@inheritDoc}
192     */
193    public ConnectFuture connect(SocketAddress remoteAddress,
194            IoSessionInitializer<? extends ConnectFuture> sessionInitializer) {
195        return connect(remoteAddress, null, sessionInitializer);
196    }
197
198    /**
199     * {@inheritDoc}
200     */
201    public ConnectFuture connect(SocketAddress remoteAddress, SocketAddress localAddress) {
202        return connect(remoteAddress, localAddress, null);
203    }
204
205    /**
206     * {@inheritDoc}
207     */
208    public final ConnectFuture connect(SocketAddress remoteAddress, SocketAddress localAddress,
209            IoSessionInitializer<? extends ConnectFuture> sessionInitializer) {
210        if (isDisposing()) {
211            throw new IllegalStateException("The connector is being disposed.");
212        }
213
214        if (remoteAddress == null) {
215            throw new IllegalArgumentException("remoteAddress");
216        }
217
218        if (!getTransportMetadata().getAddressType().isAssignableFrom(remoteAddress.getClass())) {
219            throw new IllegalArgumentException("remoteAddress type: " + remoteAddress.getClass() + " (expected: "
220                    + getTransportMetadata().getAddressType() + ")");
221        }
222
223        if (localAddress != null && !getTransportMetadata().getAddressType().isAssignableFrom(localAddress.getClass())) {
224            throw new IllegalArgumentException("localAddress type: " + localAddress.getClass() + " (expected: "
225                    + getTransportMetadata().getAddressType() + ")");
226        }
227
228        if (getHandler() == null) {
229            if (getSessionConfig().isUseReadOperation()) {
230                setHandler(new IoHandler() {
231                    public void exceptionCaught(IoSession session, Throwable cause) throws Exception {
232                        // Empty handler
233                    }
234
235                    public void messageReceived(IoSession session, Object message) throws Exception {
236                        // Empty handler
237                    }
238
239                    public void messageSent(IoSession session, Object message) throws Exception {
240                        // Empty handler
241                    }
242
243                    public void sessionClosed(IoSession session) throws Exception {
244                        // Empty handler
245                    }
246
247                    public void sessionCreated(IoSession session) throws Exception {
248                        // Empty handler
249                    }
250
251                    public void sessionIdle(IoSession session, IdleStatus status) throws Exception {
252                        // Empty handler
253                    }
254
255                    public void sessionOpened(IoSession session) throws Exception {
256                        // Empty handler
257                    }
258
259                    public void inputClosed(IoSession session) throws Exception {
260                        // Empty handler
261                    }
262                });
263            } else {
264                throw new IllegalStateException("handler is not set.");
265            }
266        }
267
268        return connect0(remoteAddress, localAddress, sessionInitializer);
269    }
270
271    /**
272     * Implement this method to perform the actual connect operation.
273     *
274     * @param remoteAddress The remote address to connect from
275     * @param localAddress <tt>null</tt> if no local address is specified
276     * @param sessionInitializer The IoSessionInitializer to use when the connection s successful
277     * @return The ConnectFuture associated with this asynchronous operation
278     * 
279     */
280    protected abstract ConnectFuture connect0(SocketAddress remoteAddress, SocketAddress localAddress,
281            IoSessionInitializer<? extends ConnectFuture> sessionInitializer);
282
283    /**
284     * Adds required internal attributes and {@link IoFutureListener}s
285     * related with event notifications to the specified {@code session}
286     * and {@code future}.  Do not call this method directly;
287     */
288    @Override
289    protected final void finishSessionInitialization0(final IoSession session, IoFuture future) {
290        // In case that ConnectFuture.cancel() is invoked before
291        // setSession() is invoked, add a listener that closes the
292        // connection immediately on cancellation.
293        future.addListener(new IoFutureListener<ConnectFuture>() {
294            public void operationComplete(ConnectFuture future) {
295                if (future.isCanceled()) {
296                    session.close(true);
297                }
298            }
299        });
300    }
301
302    /**
303     * {@inheritDoc}
304     */
305    @Override
306    public String toString() {
307        TransportMetadata m = getTransportMetadata();
308        return '(' + m.getProviderName() + ' ' + m.getName() + " connector: " + "managedSessionCount: "
309        + getManagedSessionCount() + ')';
310    }
311}