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.proxy;
021
022import java.net.InetSocketAddress;
023import java.net.SocketAddress;
024import java.util.concurrent.Executor;
025
026import org.apache.mina.core.buffer.IoBuffer;
027import org.apache.mina.core.file.FileRegion;
028import org.apache.mina.core.filterchain.IoFilter;
029import org.apache.mina.core.future.ConnectFuture;
030import org.apache.mina.core.future.DefaultConnectFuture;
031import org.apache.mina.core.service.AbstractIoConnector;
032import org.apache.mina.core.service.DefaultTransportMetadata;
033import org.apache.mina.core.service.IoHandler;
034import org.apache.mina.core.service.TransportMetadata;
035import org.apache.mina.core.session.IoSession;
036import org.apache.mina.core.session.IoSessionConfig;
037import org.apache.mina.core.session.IoSessionInitializer;
038import org.apache.mina.proxy.filter.ProxyFilter;
039import org.apache.mina.proxy.handlers.socks.SocksProxyRequest;
040import org.apache.mina.proxy.session.ProxyIoSession;
041import org.apache.mina.proxy.session.ProxyIoSessionInitializer;
042import org.apache.mina.transport.socket.DefaultSocketSessionConfig;
043import org.apache.mina.transport.socket.SocketConnector;
044import org.apache.mina.transport.socket.SocketSessionConfig;
045
046/**
047 * ProxyConnector.java - Decorator for {@link SocketConnector} to provide proxy
048 * support, as suggested by MINA list discussions.
049 * <p>
050 * Operates by intercepting connect requests and replacing the endpoint address
051 * with the proxy address, then adding a {@link ProxyFilter} as the first
052 * {@link IoFilter} which performs any necessary handshaking with the proxy
053 * before allowing data to flow normally. During the handshake, any outgoing
054 * write requests are buffered.
055 * 
056 * @see <a
057 *      href="http://www.nabble.com/Meta-Transport%3A-an-idea-on-implementing-reconnection-and-proxy-td12969001.html">Proxy
058 *      reconnection</a>
059 * @see <a href="http://issues.apache.org/jira/browse/DIRMINA-415">Proxy
060 *      support</a>
061 * 
062 * @author <a href="http://mina.apache.org">Apache MINA Project</a>
063 * @since MINA 2.0.0-M3
064 */
065public class ProxyConnector extends AbstractIoConnector {
066    private static final TransportMetadata METADATA = new DefaultTransportMetadata("proxy", "proxyconnector", false,
067            true, InetSocketAddress.class, SocketSessionConfig.class, IoBuffer.class, FileRegion.class);
068
069    /**
070     * Wrapped connector to use for outgoing TCP connections.
071     */
072    private SocketConnector connector = null;
073
074    /**
075     * Proxy filter instance.
076     */
077    private final ProxyFilter proxyFilter = new ProxyFilter();
078
079    /**
080     * The {@link ProxyIoSession} in use.
081     */
082    private ProxyIoSession proxyIoSession;
083
084    /**
085     * This future will notify it's listeners when really connected to the target
086     */
087    private DefaultConnectFuture future;
088
089    /**
090     * Creates a new proxy connector.
091     */
092    public ProxyConnector() {
093        super(new DefaultSocketSessionConfig(), null);
094    }
095
096    /**
097     * Creates a new proxy connector.
098     * 
099     * @param connector Connector used to establish proxy connections.
100     */
101    public ProxyConnector(final SocketConnector connector) {
102        this(connector, new DefaultSocketSessionConfig(), null);
103    }
104
105    /**
106     * Creates a new proxy connector.
107     * 
108     * @param connector The Connector used to establish proxy connections.
109     * @param config The session confiugarion to use
110     * @param executor The associated executor
111     */
112    public ProxyConnector(final SocketConnector connector, IoSessionConfig config, Executor executor) {
113        super(config, executor);
114        setConnector(connector);
115    }
116
117    /**
118     * {@inheritDoc}
119     */
120    public IoSessionConfig getSessionConfig() {
121        return connector.getSessionConfig();
122    }
123
124    /**
125     * @return the {@link ProxyIoSession} linked with this connector.
126     */
127    public ProxyIoSession getProxyIoSession() {
128        return proxyIoSession;
129    }
130
131    /**
132     * Sets the proxy session object of this connector.
133     * @param proxyIoSession the configuration of this connector.
134     */
135    public void setProxyIoSession(ProxyIoSession proxyIoSession) {
136        if (proxyIoSession == null) {
137            throw new IllegalArgumentException("proxySession object cannot be null");
138        }
139
140        if (proxyIoSession.getProxyAddress() == null) {
141            throw new IllegalArgumentException("proxySession.proxyAddress cannot be null");
142        }
143
144        proxyIoSession.setConnector(this);
145        setDefaultRemoteAddress(proxyIoSession.getProxyAddress());
146        this.proxyIoSession = proxyIoSession;
147    }
148
149    /**
150     * Connects to the specified <code>address</code>.  If communication starts
151     * successfully, events are fired to the connector's <code>handler</code>.
152     * 
153     * @param remoteAddress the remote address to connect to
154     * @param localAddress the local address
155     * @param sessionInitializer the session initializer
156     * @return {@link ConnectFuture} that will tell the result of the connection attempt
157     */
158    @SuppressWarnings("unchecked")
159    @Override
160    protected ConnectFuture connect0(final SocketAddress remoteAddress, final SocketAddress localAddress,
161            final IoSessionInitializer<? extends ConnectFuture> sessionInitializer) {
162        if (!proxyIoSession.isReconnectionNeeded()) {
163            // First connection
164            IoHandler handler = getHandler();
165            if (!(handler instanceof AbstractProxyIoHandler)) {
166                throw new IllegalArgumentException("IoHandler must be an instance of AbstractProxyIoHandler");
167            }
168
169            connector.setHandler(handler);
170            future = new DefaultConnectFuture();
171        }
172
173        ConnectFuture conFuture = connector.connect(proxyIoSession.getProxyAddress(), new ProxyIoSessionInitializer(
174                sessionInitializer, proxyIoSession));
175
176        // If proxy does not use reconnection like socks the connector's
177        // future is returned. If we're in the middle of a reconnection
178        // then we send back the connector's future which is only used
179        // internally while <code>future</code> will be used to notify
180        // the user of the connection state.
181        if (proxyIoSession.getRequest() instanceof SocksProxyRequest || proxyIoSession.isReconnectionNeeded()) {
182            return conFuture;
183        }
184
185        return future;
186    }
187
188    /**
189     * Cancels the real connection when reconnection is in use.
190     */
191    public void cancelConnectFuture() {
192        future.cancel();
193    }
194
195    /**
196     * Fires the connection event on the real future to notify the client.
197     * 
198     * @param session the current session
199     * @return the future holding the connected session
200     */
201    protected ConnectFuture fireConnected(final IoSession session) {
202        future.setSession(session);
203        return future;
204    }
205
206    /**
207     * @return the {@link SocketConnector} to be used for connections
208     * to the proxy server.
209     */
210    public final SocketConnector getConnector() {
211        return connector;
212    }
213
214    /**
215     * Sets the {@link SocketConnector} to be used for connections
216     * to the proxy server.
217     * 
218     * @param connector the connector to use
219     */
220    private void setConnector(final SocketConnector connector) {
221        if (connector == null) {
222            throw new IllegalArgumentException("connector cannot be null");
223        }
224
225        this.connector = connector;
226        String className = ProxyFilter.class.getName();
227
228        // Removes an old ProxyFilter instance from the chain
229        if (connector.getFilterChain().contains(className)) {
230            connector.getFilterChain().remove(className);
231        }
232
233        // Insert the ProxyFilter as the first filter in the filter chain builder
234        connector.getFilterChain().addFirst(className, proxyFilter);
235    }
236
237    /**
238     * {@inheritDoc}
239     */
240    @Override
241    protected void dispose0() throws Exception {
242        if (connector != null) {
243            connector.dispose();
244        }
245    }
246
247    /**
248     * {@inheritDoc}
249     */
250    public TransportMetadata getTransportMetadata() {
251        return METADATA;
252    }
253}