001    /*
002     * CDDL HEADER START
003     *
004     * The contents of this file are subject to the terms of the
005     * Common Development and Distribution License, Version 1.0 only
006     * (the "License").  You may not use this file except in compliance
007     * with the License.
008     *
009     * You can obtain a copy of the license at
010     * trunk/opends/resource/legal-notices/OpenDS.LICENSE
011     * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
012     * See the License for the specific language governing permissions
013     * and limitations under the License.
014     *
015     * When distributing Covered Code, include this CDDL HEADER in each
016     * file and include the License file at
017     * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
018     * add the following below this CDDL HEADER, with the fields enclosed
019     * by brackets "[]" replaced with your own identifying information:
020     *      Portions Copyright [yyyy] [name of copyright owner]
021     *
022     * CDDL HEADER END
023     *
024     *
025     *      Copyright 2006-2008 Sun Microsystems, Inc.
026     */
027    package org.opends.server.protocols.ldap;
028    import org.opends.messages.Message;
029    
030    
031    
032    import java.io.IOException;
033    import java.nio.channels.CancelledKeyException;
034    import java.nio.channels.SelectionKey;
035    import java.nio.channels.Selector;
036    import java.nio.channels.SocketChannel;
037    import java.util.ArrayList;
038    import java.util.Collection;
039    import java.util.Iterator;
040    import java.util.concurrent.ConcurrentLinkedQueue;
041    
042    import org.opends.server.api.ConnectionSecurityProvider;
043    import org.opends.server.api.DirectoryThread;
044    import org.opends.server.api.ServerShutdownListener;
045    import org.opends.server.core.DirectoryServer;
046    import org.opends.server.types.InitializationException;
047    
048    import org.opends.server.types.DebugLogLevel;
049    import static org.opends.server.loggers.debug.DebugLogger.*;
050    import org.opends.server.loggers.debug.DebugTracer;
051    import org.opends.server.loggers.ErrorLogger;
052    import static org.opends.messages.ProtocolMessages.*;
053    
054    import org.opends.server.types.DisconnectReason;
055    import static org.opends.server.util.StaticUtils.*;
056    
057    
058    
059    /**
060     * This class defines an LDAP request handler, which is associated with an LDAP
061     * connection handler and is responsible for reading and decoding any requests
062     * that LDAP clients may send to the server.  Multiple request handlers may be
063     * used in conjunction with a single connection handler for better performance
064     * and scalability.
065     */
066    public class LDAPRequestHandler
067           extends DirectoryThread
068           implements ServerShutdownListener
069    {
070      /**
071       * The tracer object for the debug logger.
072       */
073      private static final DebugTracer TRACER = getTracer();
074    
075    
076    
077    
078      /**
079       * The buffer size in bytes to use when reading data from a client.
080       */
081      public static final int BUFFER_SIZE = 8192;
082    
083    
084    
085      // Indicates whether the Directory Server is in the process of shutting down.
086      private boolean shutdownRequested;
087    
088      // The queue that will be used to hold the set of pending connections that
089      // need to be registered with the selector.
090      private ConcurrentLinkedQueue<LDAPClientConnection> pendingConnections;
091    
092      // The connection handler with which this request handler is associated.
093      private LDAPConnectionHandler connectionHandler;
094    
095      // The selector that will be used to monitor the client connections.
096      private Selector selector;
097    
098      // The name to use for this request handler.
099      private String handlerName;
100    
101      // Lock for preventing concurrent updates to the select keys.
102      private final Object selectorKeyLock = new Object();
103    
104    
105    
106      /**
107       * Creates a new LDAP request handler that will be associated with the
108       * provided connection handler.
109       *
110       * @param  connectionHandler  The LDAP connection handler with which this
111       *                            request handler is associated.
112       * @param  requestHandlerID   The integer value that may be used to distingush
113       *                            this request handler from others associated with
114       *                            the same connection handler.
115       *
116       * @throws  InitializationException  If a problem occurs while initializing
117       *                                   this request handler.
118       */
119      public LDAPRequestHandler(LDAPConnectionHandler connectionHandler,
120                                int requestHandlerID)
121             throws InitializationException
122      {
123        super("LDAP Request Handler " + requestHandlerID +
124              " for connection handler " + connectionHandler.toString());
125    
126    
127        this.connectionHandler = connectionHandler;
128    
129        handlerName         = getName();
130        pendingConnections = new ConcurrentLinkedQueue<LDAPClientConnection>();
131    
132        try
133        {
134          selector = Selector.open();
135        }
136        catch (Exception e)
137        {
138          if (debugEnabled())
139          {
140            TRACER.debugCaught(DebugLogLevel.ERROR, e);
141          }
142    
143          Message message = ERR_LDAP_REQHANDLER_OPEN_SELECTOR_FAILED.get(
144              handlerName, String.valueOf(e));
145          throw new InitializationException(message, e);
146        }
147    
148        try
149        {
150          // Check to see if we get an error while trying to perform a select.  If
151          // we do, then it's likely CR 6322825 and the server won't be able to
152          // handle LDAP requests in its current state.
153          selector.selectNow();
154        }
155        catch (IOException ioe)
156        {
157          StackTraceElement[] stackElements = ioe.getStackTrace();
158          if ((stackElements != null) && (stackElements.length > 0))
159          {
160            StackTraceElement ste = stackElements[0];
161            if (ste.getClassName().equals("sun.nio.ch.DevPollArrayWrapper") &&
162                (ste.getMethodName().indexOf("poll") >= 0) &&
163                ioe.getMessage().equalsIgnoreCase("Invalid argument"))
164            {
165              Message message = ERR_LDAP_REQHANDLER_DETECTED_JVM_ISSUE_CR6322825.
166                  get(String.valueOf(ioe));
167              throw new InitializationException(message, ioe);
168            }
169          }
170        }
171      }
172    
173    
174    
175      /**
176       * Operates in a loop, waiting for client requests to arrive and ensuring that
177       * they are processed properly.
178       */
179      public void run()
180      {
181        // Operate in a loop until the server shuts down.  Each time through the
182        // loop, check for new requests, then check for new connections.
183        while (! shutdownRequested)
184        {
185          int selectedKeys = 0;
186    
187          try
188          {
189            selectedKeys = selector.select();
190          }
191          catch (Exception e)
192          {
193            if (debugEnabled())
194            {
195              TRACER.debugCaught(DebugLogLevel.ERROR, e);
196            }
197    
198            // FIXME -- Should we do something else with this?
199          }
200    
201          if (selectedKeys > 0)
202          {
203            Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();
204            while (iterator.hasNext())
205            {
206              SelectionKey key = iterator.next();
207    
208              try
209              {
210                if (key.isReadable())
211                {
212                  LDAPClientConnection clientConnection = null;
213    
214                  try
215                  {
216                    clientConnection = (LDAPClientConnection) key.attachment();
217    
218                    try
219                    {
220                      ConnectionSecurityProvider securityProvider =
221                           clientConnection.getConnectionSecurityProvider();
222                      if (! securityProvider.readData())
223                      {
224                        key.cancel();
225                      }
226                    }
227                    catch (Exception e)
228                    {
229                      if (debugEnabled())
230                      {
231                        TRACER.debugCaught(DebugLogLevel.ERROR, e);
232                      }
233    
234                      // Some other error occurred while we were trying to read data
235                      // from the client.
236                      // FIXME -- Should we log this?
237                      key.cancel();
238                      clientConnection.disconnect(DisconnectReason.SERVER_ERROR,
239                                                  false, null);
240                    }
241                  }
242                  catch (Exception e)
243                  {
244                    if (debugEnabled())
245                    {
246                      TRACER.debugCaught(DebugLogLevel.ERROR, e);
247                    }
248    
249                    // We got some other kind of error.  If nothing else, cancel the
250                    // key, but if the client connection is available then
251                    // disconnect it as well.
252                    key.cancel();
253    
254                    if (clientConnection != null)
255                    {
256                      clientConnection.disconnect(DisconnectReason.SERVER_ERROR,
257                                                  false, null);
258                    }
259                  }
260                }
261                else if (! key.isValid())
262                {
263                  key.cancel();
264                }
265              }
266              catch (CancelledKeyException cke)
267              {
268                if (debugEnabled())
269                {
270                  TRACER.debugCaught(DebugLogLevel.ERROR, cke);
271                }
272    
273                // This could happen if a connection was closed between the time
274                // that select returned and the time that we try to access the
275                // associated channel.  If that was the case, we don't need to do
276                // anything.
277              }
278              catch (Exception e)
279              {
280                if (debugEnabled())
281                {
282                  TRACER.debugCaught(DebugLogLevel.ERROR, e);
283                }
284    
285                // This should not happen, and it would have caused our reader
286                // thread to die.  Log a severe error.
287                Message message = ERR_LDAP_REQHANDLER_UNEXPECTED_SELECT_EXCEPTION.
288                    get(getName(), getExceptionMessage(e));
289                ErrorLogger.logError(message);
290              }
291              finally
292              {
293                iterator.remove();
294              }
295            }
296          }
297    
298    
299          // Check to see if we have any pending connections that need to be
300          // registered with the selector.
301          while (! pendingConnections.isEmpty())
302          {
303            LDAPClientConnection c = pendingConnections.remove();
304    
305            try
306            {
307              SocketChannel socketChannel = c.getSocketChannel();
308              socketChannel.configureBlocking(false);
309              synchronized (selectorKeyLock) {
310                socketChannel.register(selector, SelectionKey.OP_READ, c);
311              }
312            }
313            catch (Exception e)
314            {
315              if (debugEnabled())
316              {
317                TRACER.debugCaught(DebugLogLevel.ERROR, e);
318              }
319    
320              c.disconnect(DisconnectReason.SERVER_ERROR, true,
321                           ERR_LDAP_REQHANDLER_CANNOT_REGISTER.get(handlerName,
322                           String.valueOf(e)));
323            }
324          }
325        }
326      }
327    
328    
329    
330      /**
331       * Registers the provided client connection with this request handler so that
332       * any requests received from that client will be processed.
333       *
334       * @param  clientConnection  The client connection to be registered with this
335       *                           request handler.
336       *
337       * @return  <CODE>true</CODE> if the client connection was properly registered
338       *          with this request handler, or <CODE>false</CODE> if not.
339       */
340      public boolean registerClient(LDAPClientConnection clientConnection)
341      {
342        // FIXME -- Need to check if the maximum client limit has been reached.
343    
344    
345        // If the server is in the process of shutting down, then we don't want to
346        // accept it.
347        if (shutdownRequested)
348        {
349          clientConnection.disconnect(DisconnectReason.SERVER_SHUTDOWN, true,
350               ERR_LDAP_REQHANDLER_REJECT_DUE_TO_SHUTDOWN.get());
351          return false;
352        }
353    
354    
355        // Try to add the new connection to the queue.  If it succeeds, then wake
356        // up the selector so it will be picked up right away.  Otherwise,
357        // disconnect the client.
358        if (pendingConnections.offer(clientConnection))
359        {
360          selector.wakeup();
361          return true;
362        }
363        else
364        {
365          clientConnection.disconnect(DisconnectReason.ADMIN_LIMIT_EXCEEDED, true,
366               ERR_LDAP_REQHANDLER_REJECT_DUE_TO_QUEUE_FULL.get(handlerName));
367          return false;
368        }
369      }
370    
371    
372    
373      /**
374       * Deregisters the provided client connection from this request handler so it
375       * will no longer look for requests from that client.
376       *
377       * @param  clientConnection  The client connection to deregister from this
378       *                           request handler.
379       */
380      public void deregisterClient(LDAPClientConnection clientConnection)
381      {
382        SelectionKey[] keyArray;
383        synchronized (selectorKeyLock) {
384          keyArray = selector.keys().toArray(new SelectionKey[0]);
385        }
386    
387        for (SelectionKey key : keyArray)
388        {
389          LDAPClientConnection conn = (LDAPClientConnection) key.attachment();
390          if (clientConnection.equals(conn))
391          {
392            try
393            {
394              key.channel().close();
395            }
396            catch (Exception e)
397            {
398              if (debugEnabled())
399              {
400                TRACER.debugCaught(DebugLogLevel.ERROR, e);
401              }
402            }
403    
404            try
405            {
406              key.cancel();
407            }
408            catch (Exception e)
409            {
410              if (debugEnabled())
411              {
412                TRACER.debugCaught(DebugLogLevel.ERROR, e);
413              }
414            }
415          }
416        }
417      }
418    
419    
420    
421      /**
422       * Deregisters all clients associated with this request handler.
423       */
424      public void deregisterAllClients()
425      {
426        SelectionKey[] keyArray;
427        synchronized (selectorKeyLock) {
428          keyArray = selector.keys().toArray(new SelectionKey[0]);
429        }
430    
431        for (SelectionKey key : keyArray)
432        {
433          try
434          {
435            key.channel().close();
436          }
437          catch (Exception e)
438          {
439            if (debugEnabled())
440            {
441              TRACER.debugCaught(DebugLogLevel.ERROR, e);
442            }
443          }
444    
445          try
446          {
447            key.cancel();
448          }
449          catch (Exception e)
450          {
451            if (debugEnabled())
452            {
453              TRACER.debugCaught(DebugLogLevel.ERROR, e);
454            }
455          }
456        }
457      }
458    
459    
460    
461      /**
462       * Retrieves the set of all client connections that are currently registered
463       * with this request handler.
464       *
465       * @return  The set of all client connections that are currently registered
466       *          with this request handler.
467       */
468      public Collection<LDAPClientConnection> getClientConnections()
469      {
470        SelectionKey[] keyArray;
471        synchronized (selectorKeyLock) {
472          keyArray = selector.keys().toArray(new SelectionKey[0]);
473        }
474    
475        ArrayList<LDAPClientConnection> connList =
476             new ArrayList<LDAPClientConnection>(keyArray.length);
477        for (SelectionKey key : keyArray)
478        {
479          connList.add((LDAPClientConnection) key.attachment());
480        }
481    
482        return connList;
483      }
484    
485    
486    
487      /**
488       * Retrieves the human-readable name for this shutdown listener.
489       *
490       * @return  The human-readable name for this shutdown listener.
491       */
492      public String getShutdownListenerName()
493      {
494        return handlerName;
495      }
496    
497    
498    
499      /**
500       * Causes this request handler to register itself as a shutdown listener with
501       * the Directory Server.  This must be called if the connection handler is
502       * shut down without closing all associated connections, otherwise the thread
503       * would not be stopped by the server.
504       */
505      public void registerShutdownListener()
506      {
507        DirectoryServer.registerShutdownListener(this);
508      }
509    
510    
511    
512      /**
513       * Indicates that the Directory Server has received a request to stop running
514       * and that this shutdown listener should take any action necessary to prepare
515       * for it.
516       *
517       * @param  reason  The human-readable reason for the shutdown.
518       */
519      public void processServerShutdown(Message reason)
520      {
521        shutdownRequested = true;
522    
523        Collection<LDAPClientConnection> clientConnections = getClientConnections();
524        deregisterAllClients();
525    
526        if (clientConnections != null)
527        {
528          for (LDAPClientConnection c : clientConnections)
529          {
530            try
531            {
532              c.disconnect(DisconnectReason.SERVER_SHUTDOWN, true,
533                           ERR_LDAP_REQHANDLER_DEREGISTER_DUE_TO_SHUTDOWN.get(
534                                   reason));
535            }
536            catch (Exception e)
537            {
538              if (debugEnabled())
539              {
540                TRACER.debugCaught(DebugLogLevel.ERROR, e);
541              }
542            }
543          }
544        }
545    
546        try
547        {
548          if (selector != null)
549          {
550            selector.wakeup();
551          }
552        }
553        catch (Exception e)
554        {
555          if (debugEnabled())
556          {
557            TRACER.debugCaught(DebugLogLevel.ERROR, e);
558          }
559        }
560      }
561    }
562