View Javadoc

1   /*
2    *  Licensed to the Apache Software Foundation (ASF) under one
3    *  or more contributor license agreements.  See the NOTICE file
4    *  distributed with this work for additional information
5    *  regarding copyright ownership.  The ASF licenses this file
6    *  to you under the Apache License, Version 2.0 (the
7    *  "License"); you may not use this file except in compliance
8    *  with the License.  You may obtain a copy of the License at
9    *  
10   *    http://www.apache.org/licenses/LICENSE-2.0
11   *  
12   *  Unless required by applicable law or agreed to in writing,
13   *  software distributed under the License is distributed on an
14   *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   *  KIND, either express or implied.  See the License for the
16   *  specific language governing permissions and limitations
17   *  under the License. 
18   *  
19   */
20  package org.apache.directory.server.ldap.handlers.extended;
21  
22  
23  import java.util.ArrayList;
24  import java.util.Collections;
25  import java.util.HashSet;
26  import java.util.Iterator;
27  import java.util.List;
28  import java.util.Set;
29  
30  import org.apache.directory.server.ldap.ExtendedOperationHandler;
31  import org.apache.directory.server.ldap.LdapService;
32  import org.apache.directory.server.ldap.LdapSession;
33  import org.apache.directory.shared.ldap.message.ExtendedRequest;
34  import org.apache.directory.shared.ldap.message.ResultCodeEnum;
35  import org.apache.directory.shared.ldap.message.extended.GracefulDisconnect;
36  import org.apache.directory.shared.ldap.message.extended.GracefulShutdownRequest;
37  import org.apache.directory.shared.ldap.message.extended.GracefulShutdownResponse;
38  import org.apache.directory.shared.ldap.message.extended.NoticeOfDisconnect;
39  import org.apache.mina.common.IoAcceptor;
40  import org.apache.mina.common.IoSession;
41  import org.apache.mina.common.WriteFuture;
42  import org.slf4j.Logger;
43  import org.slf4j.LoggerFactory;
44  
45  
46  /**
47   * @org.apache.xbean.XBean
48   *
49   */
50  public class GracefulShutdownHandler implements ExtendedOperationHandler
51  {
52      private static final Logger LOG = LoggerFactory.getLogger( GracefulShutdownHandler.class );
53      public static final Set<String> EXTENSION_OIDS;
54  
55      static
56      {
57          Set<String> set = new HashSet<String>( 3 );
58          set.add( GracefulShutdownRequest.EXTENSION_OID );
59          set.add( GracefulShutdownResponse.EXTENSION_OID );
60          set.add( GracefulDisconnect.EXTENSION_OID );
61          EXTENSION_OIDS = Collections.unmodifiableSet( set );
62      }
63  
64  
65      public String getOid()
66      {
67          return GracefulShutdownRequest.EXTENSION_OID;
68      }
69  
70  
71      public void handleExtendedOperation( LdapSession requestor, ExtendedRequest req ) throws Exception
72      {
73          // make sue only the administrator can issue this shutdown request if 
74          // not we respond to the requestor with with insufficientAccessRights(50)
75          if ( ! requestor.getCoreSession().isAnAdministrator() )
76          {
77              if ( LOG.isInfoEnabled() )
78              {
79                  LOG.info( "Rejected with insufficientAccessRights to attempt for server shutdown by "
80                      + requestor.getCoreSession().getEffectivePrincipal().getName() );
81              }
82  
83              requestor.getIoSession().write( new GracefulShutdownResponse( 
84                  req.getMessageId(), ResultCodeEnum.INSUFFICIENT_ACCESS_RIGHTS ) );
85              return;
86          }
87  
88          // -------------------------------------------------------------------
89          // handle the body of this operation below here
90          // -------------------------------------------------------------------
91  
92          IoAcceptor acceptor = ( IoAcceptor ) requestor.getIoSession().getService();
93          List<IoSession> sessions = new ArrayList<IoSession>(
94                  acceptor.getManagedSessions( requestor.getIoSession().getServiceAddress() ) );
95          GracefulShutdownRequest gsreq = ( GracefulShutdownRequest ) req;
96  
97          // build the graceful disconnect message with replicationContexts
98          GracefulDisconnect notice = getGracefulDisconnect( gsreq.getTimeOffline(), gsreq.getDelay() );
99  
100         // send (synch) the GracefulDisconnect to each client before unbinding
101         sendGracefulDisconnect( sessions, notice, requestor.getIoSession() );
102 
103         // wait for the specified delay before we unbind the service 
104         waitForDelay( gsreq.getDelay() );
105 
106         // -------------------------------------------------------------------
107         // unbind the server socket for the LDAP service here so no new 
108         // connections are accepted while we process this shutdown request
109         // note that the following must be issued before binding the ldap
110         // service in order to prevent client disconnects on service unbind:
111         // 
112         // minaRegistry.getAcceptor( service.getTransportType() )
113         //                       .setDisconnectClientsOnUnbind( false );
114         // -------------------------------------------------------------------
115         // This might not work, either.
116         acceptor.unbind( requestor.getIoSession().getServiceAddress() );
117 
118         // -------------------------------------------------------------------
119         // synchronously send a NoD to clients that are not aware of this resp
120         // after sending the NoD the client is disconnected if still connected
121         // -------------------------------------------------------------------
122         sendNoticeOfDisconnect( sessions, requestor.getIoSession() );
123 
124         // -------------------------------------------------------------------
125         // respond back to the client that requested the graceful shutdown w/
126         // a success resultCode which confirms all clients have been notified
127         // via the graceful disconnect or NoD and the service has been unbound
128         // preventing new connections; after recieving this response the 
129         // requestor should disconnect and stop using the connection
130         // -------------------------------------------------------------------
131         sendShutdownResponse( requestor.getIoSession(), req.getMessageId() );
132     }
133 
134 
135     /**
136      * Sends a successful response.
137      * 
138      * @param requestor the session of the requestor
139      * @param messageId the message id associaed with this shutdown request
140      */
141     public static void sendShutdownResponse( IoSession requestor, int messageId )
142     {
143         GracefulShutdownResponse msg = new GracefulShutdownResponse( messageId, ResultCodeEnum.SUCCESS );
144         WriteFuture future = requestor.write( msg );
145         future.join();
146         if ( future.isWritten() )
147         {
148             if ( LOG.isInfoEnabled() )
149             {
150                 LOG.info( "Sent GracefulShutdownResponse to client: " + requestor.getRemoteAddress() );
151             }
152         }
153         else
154         {
155             LOG.error( "Failed to write GracefulShutdownResponse to client: " + requestor.getRemoteAddress() );
156         }
157         requestor.close();
158     }
159 
160 
161     /**
162      * Blocks to synchronously send the same GracefulDisconnect message to all 
163      * managed sessions except for the requestor of the GracefulShutdown.
164      * 
165      * @param msg the graceful disconnec extended request to send
166      * @param requestor the session of the graceful shutdown requestor
167      * @param sessions the IoSessions to send disconnect message to
168      */
169     public static void sendGracefulDisconnect( List<IoSession> sessions, GracefulDisconnect msg, IoSession requestor )
170     {
171         List<WriteFuture> writeFutures = new ArrayList<WriteFuture>();
172 
173         // asynchronously send GracefulDisconnection messages to all connected
174         // clients giving time for the message to arrive before we block 
175         // waiting for message delivery to the client in the loop below
176 
177         if ( sessions != null )
178         {
179             for ( IoSession session : sessions )
180             {
181                 // make sure we do not send the disconnect mesasge to the
182                 // client which sent the initiating GracefulShutdown request
183                 if ( session.equals( requestor ) )
184                 {
185                     continue;
186                 }
187 
188                 try
189                 {
190                     writeFutures.add( session.write( msg ) );
191                 }
192                 catch ( Exception e )
193                 {
194                     LOG.warn( "Failed to write GracefulDisconnect to client session: " + session, e );
195                 }
196             }
197         }
198 
199         // wait for GracefulDisconnect messages to be sent before returning
200         for ( WriteFuture future : writeFutures )
201         {
202             try
203             {
204                 future.join( 1000 );
205             }
206             catch ( Exception e )
207             {
208                 LOG.warn( "Failed to sent GracefulDisconnect", e );
209             }
210         }
211     }
212 
213 
214     /**
215      * Blocks to synchronously send the a NoticeOfDisconnect message with
216      * the resultCode set to unavailable(52) to all managed sessions except 
217      * for the requestor of the GracefulShutdown.
218      * 
219      * @param requestor the session of the graceful shutdown requestor
220      * @param sessions the sessions from mina
221      */
222     public static void sendNoticeOfDisconnect( List<IoSession> sessions, IoSession requestor )
223     {
224         List<WriteFuture> writeFutures = new ArrayList<WriteFuture>();
225 
226         // Send Notification of Disconnection messages to all connected clients.
227         if ( sessions != null )
228         {
229             for ( IoSession session : sessions )
230             {
231                 // make sure we do not send the disconnect mesasge to the
232                 // client which sent the initiating GracefulShutdown request
233                 if ( session.equals( requestor ) )
234                 {
235                     continue;
236                 }
237 
238                 try
239                 {
240                     writeFutures.add( session.write( NoticeOfDisconnect.UNAVAILABLE ) );
241                 }
242                 catch ( Exception e )
243                 {
244                     LOG.warn( "Failed to sent NoD for client: " + session, e );
245                 }
246             }
247         }
248 
249         // And close the connections when the NoDs are sent.
250         Iterator<IoSession> sessionIt = sessions.iterator();
251         
252         for ( WriteFuture future : writeFutures )
253         {
254             try
255             {
256                 future.join( 1000 );
257                 sessionIt.next().close();
258             }
259             catch ( Exception e )
260             {
261                 LOG.warn( "Failed to sent NoD.", e );
262             }
263         }
264     }
265 
266 
267     public static GracefulDisconnect getGracefulDisconnect( int timeOffline, int delay )
268     {
269         // build the graceful disconnect message with replicationContexts
270         // @todo add the referral objects for replication contexts using setup code below
271         //        Iterator list = nexus.listSuffixes( true );
272         //        while ( list.hasNext() )
273         //        {
274         //            LdapName dn = new LdapName( ( String ) list.next() );
275         //            DirectoryPartition partition = nexus.getPartition( dn );
276         //        }
277         return new GracefulDisconnect( timeOffline, delay );
278     }
279 
280 
281     public static void waitForDelay( int delay )
282     {
283         if ( delay > 0 )
284         {
285             // delay is in seconds
286             long delayMillis = delay * 1000L;
287             long startTime = System.currentTimeMillis();
288 
289             while ( ( System.currentTimeMillis() - startTime ) < delayMillis )
290             {
291                 try
292                 {
293                     Thread.sleep( 250 );
294                 }
295                 catch ( InterruptedException e )
296                 {
297                     LOG.warn( "Got interrupted while waiting for delay before shutdown", e );
298                 }
299             }
300         }
301     }
302 
303 
304     public Set<String> getExtensionOids()
305     {
306         return EXTENSION_OIDS;
307     }
308 
309 
310     public void setLdapServer( LdapService ldapService )
311     {
312     }
313 }