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     */
020    package org.apache.directory.server.ldap.handlers.extended;
021    
022    
023    import java.util.ArrayList;
024    import java.util.Collections;
025    import java.util.HashSet;
026    import java.util.Iterator;
027    import java.util.List;
028    import java.util.Set;
029    
030    import org.apache.directory.server.ldap.ExtendedOperationHandler;
031    import org.apache.directory.server.ldap.LdapServer;
032    import org.apache.directory.server.ldap.LdapSession;
033    import org.apache.directory.shared.ldap.message.InternalExtendedRequest;
034    import org.apache.directory.shared.ldap.message.ResultCodeEnum;
035    import org.apache.directory.shared.ldap.message.extended.GracefulDisconnect;
036    import org.apache.directory.shared.ldap.message.extended.GracefulShutdownRequest;
037    import org.apache.directory.shared.ldap.message.extended.GracefulShutdownResponse;
038    import org.apache.directory.shared.ldap.message.extended.NoticeOfDisconnect;
039    import org.apache.mina.core.future.WriteFuture;
040    import org.apache.mina.core.service.IoAcceptor;
041    import org.apache.mina.core.session.IoSession;
042    import org.slf4j.Logger;
043    import org.slf4j.LoggerFactory;
044    
045    
046    /**
047     * @org.apache.xbean.XBean
048     *
049     */
050    public class GracefulShutdownHandler implements ExtendedOperationHandler
051    {
052        private static final Logger LOG = LoggerFactory.getLogger( GracefulShutdownHandler.class );
053        public static final Set<String> EXTENSION_OIDS;
054    
055        static
056        {
057            Set<String> set = new HashSet<String>( 3 );
058            set.add( GracefulShutdownRequest.EXTENSION_OID );
059            set.add( GracefulShutdownResponse.EXTENSION_OID );
060            set.add( GracefulDisconnect.EXTENSION_OID );
061            EXTENSION_OIDS = Collections.unmodifiableSet( set );
062        }
063    
064    
065        public String getOid()
066        {
067            return GracefulShutdownRequest.EXTENSION_OID;
068        }
069    
070    
071        public void handleExtendedOperation( LdapSession requestor, InternalExtendedRequest req ) throws Exception
072        {
073            // make sue only the administrator can issue this shutdown request if 
074            // not we respond to the requestor with with insufficientAccessRights(50)
075            if ( ! requestor.getCoreSession().isAnAdministrator() )
076            {
077                if ( LOG.isInfoEnabled() )
078                {
079                    LOG.info( "Rejected with insufficientAccessRights to attempt for server shutdown by "
080                        + requestor.getCoreSession().getEffectivePrincipal().getName() );
081                }
082    
083                requestor.getIoSession().write( new GracefulShutdownResponse( 
084                    req.getMessageId(), ResultCodeEnum.INSUFFICIENT_ACCESS_RIGHTS ) );
085                return;
086            }
087    
088            // -------------------------------------------------------------------
089            // handle the body of this operation below here
090            // -------------------------------------------------------------------
091    
092            IoAcceptor acceptor = ( IoAcceptor ) requestor.getIoSession().getService();
093            List<IoSession> sessions = new ArrayList<IoSession>(
094                    acceptor.getManagedSessions().values() );
095            GracefulShutdownRequest gsreq = ( GracefulShutdownRequest ) req;
096    
097            // build the graceful disconnect message with replicationContexts
098            GracefulDisconnect notice = getGracefulDisconnect( gsreq.getTimeOffline(), gsreq.getDelay() );
099    
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.awaitUninterruptibly();
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( true );
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.awaitUninterruptibly( 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.awaitUninterruptibly( 1000 );
257                    sessionIt.next().close( true );
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( LdapServer ldapServer )
311        {
312        }
313    }