001    /*
002     * Copyright 2010-2015 UnboundID Corp.
003     * All Rights Reserved.
004     */
005    /*
006     * Copyright (C) 2010-2015 UnboundID Corp.
007     *
008     * This program is free software; you can redistribute it and/or modify
009     * it under the terms of the GNU General Public License (GPLv2 only)
010     * or the terms of the GNU Lesser General Public License (LGPLv2.1 only)
011     * as published by the Free Software Foundation.
012     *
013     * This program is distributed in the hope that it will be useful,
014     * but WITHOUT ANY WARRANTY; without even the implied warranty of
015     * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
016     * GNU General Public License for more details.
017     *
018     * You should have received a copy of the GNU General Public License
019     * along with this program; if not, see <http://www.gnu.org/licenses>.
020     */
021    package com.unboundid.ldap.listener;
022    
023    
024    
025    import java.io.IOException;
026    import java.net.InetAddress;
027    import java.net.ServerSocket;
028    import java.net.Socket;
029    import java.net.SocketException;
030    import java.util.ArrayList;
031    import java.util.concurrent.ConcurrentHashMap;
032    import java.util.concurrent.CountDownLatch;
033    import java.util.concurrent.atomic.AtomicBoolean;
034    import java.util.concurrent.atomic.AtomicLong;
035    import java.util.concurrent.atomic.AtomicReference;
036    import javax.net.ServerSocketFactory;
037    
038    import com.unboundid.ldap.sdk.LDAPException;
039    import com.unboundid.util.Debug;
040    import com.unboundid.util.InternalUseOnly;
041    import com.unboundid.util.ThreadSafety;
042    import com.unboundid.util.ThreadSafetyLevel;
043    
044    
045    
046    /**
047     * This class provides a framework that may be used to accept connections from
048     * LDAP clients and ensure that any requests received on those connections will
049     * be processed appropriately.  It can be used to easily allow applications to
050     * accept LDAP requests, to create a simple proxy that can intercept and
051     * examine LDAP requests and responses passing between a client and server, or
052     * helping to test LDAP clients.
053     * <BR><BR>
054     * <H2>Example</H2>
055     * The following example demonstrates the process that can be used to create an
056     * LDAP listener that will listen for LDAP requests on a randomly-selected port
057     * and immediately respond to them with a "success" result:
058     * <PRE>
059     * // Create a canned response request handler that will always return a
060     * // "SUCCESS" result in response to any request.
061     * CannedResponseRequestHandler requestHandler =
062     *    new CannedResponseRequestHandler(ResultCode.SUCCESS, null, null,
063     *         null);
064     *
065     * // A listen port of zero indicates that the listener should
066     * // automatically pick a free port on the system.
067     * int listenPort = 0;
068     *
069     * // Create and start an LDAP listener to accept requests and blindly
070     * // return success results.
071     * LDAPListenerConfig listenerConfig = new LDAPListenerConfig(listenPort,
072     *      requestHandler);
073     * LDAPListener listener = new LDAPListener(listenerConfig);
074     * listener.startListening();
075     *
076     * // Establish a connection to the listener and verify that a search
077     * // request will get a success result.
078     * LDAPConnection connection = new LDAPConnection("localhost",
079     *      listener.getListenPort());
080     * SearchResult searchResult = connection.search("dc=example,dc=com",
081     *      SearchScope.BASE, Filter.createPresenceFilter("objectClass"));
082     * LDAPTestUtils.assertResultCodeEquals(searchResult,
083     *      ResultCode.SUCCESS);
084     *
085     * // Close the connection and stop the listener.
086     * connection.close();
087     * listener.shutDown(true);
088     * </PRE>
089     */
090    @ThreadSafety(level=ThreadSafetyLevel.NOT_THREADSAFE)
091    public final class LDAPListener
092           extends Thread
093    {
094      // Indicates whether a request has been received to stop running.
095      private final AtomicBoolean stopRequested;
096    
097      // The connection ID value that should be assigned to the next connection that
098      // is established.
099      private final AtomicLong nextConnectionID;
100    
101      // The server socket that is being used to accept connections.
102      private final AtomicReference<ServerSocket> serverSocket;
103    
104      // The thread that is currently listening for new client connections.
105      private final AtomicReference<Thread> thread;
106    
107      // A map of all established connections.
108      private final ConcurrentHashMap<Long,LDAPListenerClientConnection>
109           establishedConnections;
110    
111      // The latch used to wait for the listener to have started.
112      private final CountDownLatch startLatch;
113    
114      // The configuration to use for this listener.
115      private final LDAPListenerConfig config;
116    
117    
118    
119      /**
120       * Creates a new {@code LDAPListener} object with the provided configuration.
121       * The {@link #startListening} method must be called after creating the object
122       * to actually start listening for requests.
123       *
124       * @param  config  The configuration to use for this listener.
125       */
126      public LDAPListener(final LDAPListenerConfig config)
127      {
128        this.config = config.duplicate();
129    
130        stopRequested = new AtomicBoolean(false);
131        nextConnectionID = new AtomicLong(0L);
132        serverSocket = new AtomicReference<ServerSocket>(null);
133        thread = new AtomicReference<Thread>(null);
134        startLatch = new CountDownLatch(1);
135        establishedConnections =
136             new ConcurrentHashMap<Long,LDAPListenerClientConnection>();
137        setName("LDAP Listener Thread (not listening");
138      }
139    
140    
141    
142      /**
143       * Creates the server socket for this listener and starts listening for client
144       * connections.  This method will return after the listener has stated.
145       *
146       * @throws  IOException  If a problem occurs while creating the server socket.
147       */
148      public void startListening()
149             throws IOException
150      {
151        final ServerSocketFactory f = config.getServerSocketFactory();
152        final InetAddress a = config.getListenAddress();
153        final int p = config.getListenPort();
154        if (a == null)
155        {
156          serverSocket.set(f.createServerSocket(config.getListenPort(), 128));
157        }
158        else
159        {
160          serverSocket.set(f.createServerSocket(config.getListenPort(), 128, a));
161        }
162    
163        final int receiveBufferSize = config.getReceiveBufferSize();
164        if (receiveBufferSize > 0)
165        {
166          serverSocket.get().setReceiveBufferSize(receiveBufferSize);
167        }
168    
169        setName("LDAP Listener Thread (listening on port " +
170             serverSocket.get().getLocalPort() + ')');
171    
172        start();
173    
174        try
175        {
176          startLatch.await();
177        }
178        catch (final Exception e)
179        {
180          Debug.debugException(e);
181        }
182      }
183    
184    
185    
186      /**
187       * Operates in a loop, waiting for client connections to arrive and ensuring
188       * that they are handled properly.  This method is for internal use only and
189       * must not be called by third-party code.
190       */
191      @InternalUseOnly()
192      @Override()
193      public void run()
194      {
195        thread.set(Thread.currentThread());
196        final LDAPListenerExceptionHandler exceptionHandler =
197             config.getExceptionHandler();
198    
199        try
200        {
201          startLatch.countDown();
202          while (! stopRequested.get())
203          {
204            final Socket s;
205            try
206            {
207              s = serverSocket.get().accept();
208            }
209            catch (final Exception e)
210            {
211              Debug.debugException(e);
212    
213              if ((e instanceof SocketException) &&
214                  serverSocket.get().isClosed())
215              {
216                return;
217              }
218    
219              if (exceptionHandler != null)
220              {
221                exceptionHandler.connectionCreationFailure(null, e);
222              }
223    
224              continue;
225            }
226    
227    
228            final LDAPListenerClientConnection c;
229            try
230            {
231              c = new LDAPListenerClientConnection(this, s,
232                   config.getRequestHandler(), config.getExceptionHandler());
233            }
234            catch (final LDAPException le)
235            {
236              Debug.debugException(le);
237    
238              if (exceptionHandler != null)
239              {
240                exceptionHandler.connectionCreationFailure(s, le);
241              }
242    
243              continue;
244            }
245    
246            establishedConnections.put(c.getConnectionID(), c);
247            c.start();
248          }
249        }
250        finally
251        {
252          final ServerSocket s = serverSocket.getAndSet(null);
253          if (s != null)
254          {
255            try
256            {
257              s.close();
258            }
259            catch (final Exception e)
260            {
261              Debug.debugException(e);
262            }
263          }
264    
265          serverSocket.set(null);
266          thread.set(null);
267        }
268      }
269    
270    
271    
272      /**
273       * Indicates that this listener should stop accepting connections.  It may
274       * optionally also terminate any existing connections that are already
275       * established.
276       *
277       * @param  closeExisting  Indicates whether to close existing connections that
278       *                        may already be established.
279       */
280      public void shutDown(final boolean closeExisting)
281      {
282        stopRequested.set(true);
283    
284        final ServerSocket s = serverSocket.get();
285        if (s != null)
286        {
287          try
288          {
289            s.close();
290          }
291          catch (final Exception e)
292          {
293            Debug.debugException(e);
294          }
295        }
296    
297        final Thread t = thread.get();
298        if (t != null)
299        {
300          while (t.isAlive())
301          {
302            try
303            {
304              t.join(100L);
305            }
306            catch (final Exception e)
307            {
308              Debug.debugException(e);
309            }
310    
311            if (t.isAlive())
312            {
313    
314              try
315              {
316                t.interrupt();
317              }
318              catch (final Exception e)
319              {
320                Debug.debugException(e);
321              }
322            }
323          }
324        }
325    
326        if (closeExisting)
327        {
328          final ArrayList<LDAPListenerClientConnection> connList =
329               new ArrayList<LDAPListenerClientConnection>(
330                    establishedConnections.values());
331          for (final LDAPListenerClientConnection c : connList)
332          {
333            try
334            {
335              c.close();
336            }
337            catch (final Exception e)
338            {
339              Debug.debugException(e);
340            }
341          }
342        }
343      }
344    
345    
346    
347      /**
348       * Retrieves the address on which this listener is accepting client
349       * connections.  Note that if no explicit listen address was configured, then
350       * the address returned may not be usable by clients.  In the event that the
351       * {@code InetAddress.isAnyLocalAddress} method returns {@code true}, then
352       * clients should generally use {@code localhost} to attempt to establish
353       * connections.
354       *
355       * @return  The address on which this listener is accepting client
356       *          connections, or {@code null} if it is not currently listening for
357       *          client connections.
358       */
359      public InetAddress getListenAddress()
360      {
361        final ServerSocket s = serverSocket.get();
362        if (s == null)
363        {
364          return null;
365        }
366        else
367        {
368          return s.getInetAddress();
369        }
370      }
371    
372    
373    
374      /**
375       * Retrieves the port on which this listener is accepting client connections.
376       *
377       * @return  The port on which this listener is accepting client connections,
378       *          or -1 if it is not currently listening for client connections.
379       */
380      public int getListenPort()
381      {
382        final ServerSocket s = serverSocket.get();
383        if (s == null)
384        {
385          return -1;
386        }
387        else
388        {
389          return s.getLocalPort();
390        }
391      }
392    
393    
394    
395      /**
396       * Retrieves the configuration in use for this listener.  It must not be
397       * altered in any way.
398       *
399       * @return  The configuration in use for this listener.
400       */
401      LDAPListenerConfig getConfig()
402      {
403        return config;
404      }
405    
406    
407    
408      /**
409       * Retrieves the connection ID that should be used for the next connection
410       * accepted by this listener.
411       *
412       * @return  The connection ID that should be used for the next connection
413       *          accepted by this listener.
414       */
415      long nextConnectionID()
416      {
417        return nextConnectionID.getAndIncrement();
418      }
419    
420    
421    
422      /**
423       * Indicates that the provided client connection has been closed and is no
424       * longer listening for client connections.
425       *
426       * @param  connection  The connection that has been closed.
427       */
428      void connectionClosed(final LDAPListenerClientConnection connection)
429      {
430        establishedConnections.remove(connection.getConnectionID());
431      }
432    }