001/*
002 * The contents of this file are subject to the terms of the Common Development and
003 * Distribution License (the License). You may not use this file except in compliance with the
004 * License.
005 *
006 * You can obtain a copy of the License at legal/CDDLv1.0.txt. See the License for the
007 * specific language governing permission and limitations under the License.
008 *
009 * When distributing Covered Software, include this CDDL Header Notice in each file and include
010 * the License file at legal/CDDLv1.0.txt. If applicable, add the following below the CDDL
011 * Header, with the fields enclosed by brackets [] replaced by your own identifying
012 * information: "Portions Copyright [year] [name of copyright owner]".
013 *
014 * Copyright 2009-2010 Sun Microsystems, Inc.
015 * Portions copyright 2012-2015 ForgeRock AS.
016 */
017package org.forgerock.opendj.ldap;
018
019import java.io.Closeable;
020import java.io.IOException;
021import java.net.InetAddress;
022import java.net.InetSocketAddress;
023
024import org.forgerock.opendj.ldap.spi.LDAPListenerImpl;
025import org.forgerock.opendj.ldap.spi.TransportProvider;
026import org.forgerock.util.Option;
027import org.forgerock.util.Options;
028import org.forgerock.util.Reject;
029
030/**
031 * An LDAP server connection listener which waits for LDAP connection requests
032 * to come in over the network and binds them to a {@link ServerConnection}
033 * created using the provided {@link ServerConnectionFactory}.
034 * <p>
035 * When processing requests, {@code ServerConnection} implementations are passed
036 * an integer as the first parameter. This integer represents the
037 * {@code requestID} associated with the client request and corresponds to the
038 * {@code requestID} passed as a parameter to abandon and cancel extended
039 * requests. The request ID may also be useful for logging purposes.
040 * <p>
041 * An {@code LDAPListener} does not require {@code ServerConnection}
042 * implementations to return a result when processing requests. More
043 * specifically, an {@code LDAPListener} does not maintain any internal state
044 * information associated with each request which must be released. This is
045 * useful when implementing LDAP abandon operations which may prevent results
046 * being sent for abandoned operations.
047 * <p>
048 * The following code illustrates how to create a simple LDAP server:
049 *
050 * <pre>
051 * class MyClientConnection implements ServerConnection&lt;Integer&gt; {
052 *     private final LDAPClientContext clientContext;
053 *
054 *     private MyClientConnection(LDAPClientContext clientContext) {
055 *         this.clientContext = clientContext;
056 *     }
057 *
058 *     public void add(Integer requestID, AddRequest request, ResultHandler&lt;Result&gt; handler,
059 *             IntermediateResponseHandler intermediateResponseHandler)
060 *             throws UnsupportedOperationException {
061 *         // ...
062 *     }
063 *
064 *     // ...
065 *
066 * }
067 *
068 * class MyServer implements ServerConnectionFactory&lt;LDAPClientContext, RequestContext&gt; {
069 *     public ServerConnection&lt;RequestContext&gt; accept(LDAPClientContext context) {
070 *         System.out.println(&quot;Connection from: &quot; + context.getPeerAddress());
071 *         return new MyClientConnection(context);
072 *     }
073 * }
074 *
075 * public static void main(String[] args) throws Exception {
076 *     LDAPListener listener = new LDAPListener(1389, new MyServer());
077 *
078 *     // ...
079 *
080 *     listener.close();
081 * }
082 * </pre>
083 */
084public final class LDAPListener extends CommonLDAPOptions implements Closeable {
085
086    /**
087     * Specifies the maximum queue length for incoming connections requests. If a
088     * connection request arrives when the queue is full, the connection is refused.
089     */
090    public static final Option<Integer> CONNECT_MAX_BACKLOG = Option.withDefault(50);
091
092    /**
093     * Specifies the maximum request size in bytes for incoming LDAP requests.
094     * If an incoming request exceeds the limit then the connection will be aborted by the listener.
095     * Default value is 5MiB.
096     */
097    public static final Option<Integer> REQUEST_MAX_SIZE_IN_BYTES = Option.withDefault(5 * 1024 * 1024);
098
099    /**
100     * We implement the factory using the pimpl idiom in order have
101     * cleaner Javadoc which does not expose implementation methods.
102     */
103    private final LDAPListenerImpl impl;
104
105    /** Transport provider that provides the implementation of this listener. */
106    private TransportProvider provider;
107
108    /**
109     * Creates a new LDAP listener implementation which will listen for LDAP
110     * client connections at the provided address.
111     *
112     * @param port
113     *            The port to listen on.
114     * @param factory
115     *            The server connection factory which will be used to create
116     *            server connections.
117     * @throws IOException
118     *             If an error occurred while trying to listen on the provided
119     *             address.
120     * @throws NullPointerException
121     *             If {code factory} was {@code null}.
122     */
123    public LDAPListener(final int port,
124            final ServerConnectionFactory<LDAPClientContext, Integer> factory) throws IOException {
125        this(port, factory, Options.defaultOptions());
126    }
127
128    /**
129     * Creates a new LDAP listener implementation which will listen for LDAP
130     * client connections at the provided address.
131     *
132     * @param port
133     *            The port to listen on.
134     * @param factory
135     *            The server connection factory which will be used to create
136     *            server connections.
137     * @param options
138     *            The LDAP listener options.
139     * @throws IOException
140     *             If an error occurred while trying to listen on the provided
141     *             address.
142     * @throws NullPointerException
143     *             If {code factory} or {@code options} was {@code null}.
144     */
145    public LDAPListener(final int port,
146            final ServerConnectionFactory<LDAPClientContext, Integer> factory,
147            final Options options) throws IOException {
148        Reject.ifNull(factory, options);
149        this.provider = getTransportProvider(options);
150        this.impl = provider.getLDAPListener(new InetSocketAddress(port), factory, options);
151    }
152
153    /**
154     * Creates a new LDAP listener implementation which will listen for LDAP
155     * client connections at the provided address.
156     *
157     * @param address
158     *            The address to listen on.
159     * @param factory
160     *            The server connection factory which will be used to create
161     *            server connections.
162     * @throws IOException
163     *             If an error occurred while trying to listen on the provided
164     *             address.
165     * @throws NullPointerException
166     *             If {@code address} or {code factory} was {@code null}.
167     */
168    public LDAPListener(final InetSocketAddress address,
169            final ServerConnectionFactory<LDAPClientContext, Integer> factory) throws IOException {
170        this(address, factory, Options.defaultOptions());
171    }
172
173    /**
174     * Creates a new LDAP listener implementation which will listen for LDAP
175     * client connections at the provided address.
176     *
177     * @param address
178     *            The address to listen on.
179     * @param factory
180     *            The server connection factory which will be used to create
181     *            server connections.
182     * @param options
183     *            The LDAP listener options.
184     * @throws IOException
185     *             If an error occurred while trying to listen on the provided
186     *             address.
187     * @throws NullPointerException
188     *             If {@code address}, {code factory}, or {@code options} was
189     *             {@code null}.
190     */
191    public LDAPListener(final InetSocketAddress address,
192            final ServerConnectionFactory<LDAPClientContext, Integer> factory,
193            final Options options) throws IOException {
194        Reject.ifNull(address, factory, options);
195        this.provider = getTransportProvider(options);
196        this.impl = provider.getLDAPListener(address, factory, options);
197    }
198
199    /**
200     * Creates a new LDAP listener implementation which will listen for LDAP
201     * client connections at the provided address.
202     *
203     * @param host
204     *            The address to listen on.
205     * @param port
206     *            The port to listen on.
207     * @param factory
208     *            The server connection factory which will be used to create
209     *            server connections.
210     * @throws IOException
211     *             If an error occurred while trying to listen on the provided
212     *             address.
213     * @throws NullPointerException
214     *             If {@code host} or {code factory} was {@code null}.
215     */
216    public LDAPListener(final String host, final int port,
217            final ServerConnectionFactory<LDAPClientContext, Integer> factory) throws IOException {
218        this(host, port, factory, Options.defaultOptions());
219    }
220
221    /**
222     * Creates a new LDAP listener implementation which will listen for LDAP
223     * client connections at the provided address.
224     *
225     * @param host
226     *            The address to listen on.
227     * @param port
228     *            The port to listen on.
229     * @param factory
230     *            The server connection factory which will be used to create
231     *            server connections.
232     * @param options
233     *            The LDAP listener options.
234     * @throws IOException
235     *             If an error occurred while trying to listen on the provided
236     *             address.
237     * @throws NullPointerException
238     *             If {@code host}, {code factory}, or {@code options} was
239     *             {@code null}.
240     */
241    public LDAPListener(final String host, final int port,
242            final ServerConnectionFactory<LDAPClientContext, Integer> factory,
243            final Options options) throws IOException {
244        Reject.ifNull(host, factory, options);
245        final InetSocketAddress address = new InetSocketAddress(host, port);
246        this.provider = getTransportProvider(options);
247        this.impl = provider.getLDAPListener(address, factory, options);
248    }
249
250    /** Closes this LDAP connection listener. */
251    @Override
252    public void close() {
253        impl.close();
254    }
255
256    /**
257     * Returns the {@code InetAddress} that this LDAP listener is listening on.
258     *
259     * @return The {@code InetAddress} that this LDAP listener is listening on.
260     */
261    public InetAddress getAddress() {
262        return getSocketAddress().getAddress();
263    }
264
265    /**
266     * Returns the host name that this LDAP listener is listening on. The
267     * returned host name is the same host name that was provided during
268     * construction and may be an IP address. More specifically, this method
269     * will not perform a reverse DNS lookup.
270     *
271     * @return The host name that this LDAP listener is listening on.
272     */
273    public String getHostName() {
274        return Connections.getHostString(getSocketAddress());
275    }
276
277    /**
278     * Returns the port that this LDAP listener is listening on.
279     *
280     * @return The port that this LDAP listener is listening on.
281     */
282    public int getPort() {
283        return getSocketAddress().getPort();
284    }
285
286    /**
287     * Returns the address that this LDAP listener is listening on.
288     *
289     * @return The address that this LDAP listener is listening on.
290     */
291    public InetSocketAddress getSocketAddress() {
292        return impl.getSocketAddress();
293    }
294
295    /**
296     * Returns the name of the transport provider, which provides the implementation
297     * of this factory.
298     *
299     * @return The name of actual transport provider.
300     */
301    public String getProviderName() {
302        return provider.getName();
303    }
304
305    @Override
306    public String toString() {
307        return impl.toString();
308    }
309}