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.i18n.I18n;
031    import org.apache.directory.server.ldap.ExtendedOperationHandler;
032    import org.apache.directory.server.ldap.LdapServer;
033    import org.apache.directory.server.ldap.LdapSession;
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.directory.shared.ldap.message.internal.InternalExtendedRequest;
040    import org.apache.mina.core.future.WriteFuture;
041    import org.apache.mina.core.service.IoAcceptor;
042    import org.apache.mina.core.session.IoSession;
043    import org.slf4j.Logger;
044    import org.slf4j.LoggerFactory;
045    
046    
047    /**
048     * @org.apache.xbean.XBean
049     *
050     */
051    public class GracefulShutdownHandler implements ExtendedOperationHandler
052    {
053        private static final Logger LOG = LoggerFactory.getLogger( GracefulShutdownHandler.class );
054        public static final Set<String> EXTENSION_OIDS;
055    
056        static
057        {
058            Set<String> set = new HashSet<String>( 3 );
059            set.add( GracefulShutdownRequest.EXTENSION_OID );
060            set.add( GracefulShutdownResponse.EXTENSION_OID );
061            set.add( GracefulDisconnect.EXTENSION_OID );
062            EXTENSION_OIDS = Collections.unmodifiableSet( set );
063        }
064    
065    
066        public String getOid()
067        {
068            return GracefulShutdownRequest.EXTENSION_OID;
069        }
070    
071    
072        public void handleExtendedOperation( LdapSession requestor, InternalExtendedRequest req ) throws Exception
073        {
074            // make sue only the administrator can issue this shutdown request if 
075            // not we respond to the requestor with with insufficientAccessRights(50)
076            if ( ! requestor.getCoreSession().isAnAdministrator() )
077            {
078                if ( LOG.isInfoEnabled() )
079                {
080                    LOG.info( "Rejected with insufficientAccessRights to attempt for server shutdown by "
081                        + requestor.getCoreSession().getEffectivePrincipal().getName() );
082                }
083    
084                requestor.getIoSession().write( new GracefulShutdownResponse( 
085                    req.getMessageId(), ResultCodeEnum.INSUFFICIENT_ACCESS_RIGHTS ) );
086                return;
087            }
088    
089            // -------------------------------------------------------------------
090            // handle the body of this operation below here
091            // -------------------------------------------------------------------
092    
093            IoAcceptor acceptor = ( IoAcceptor ) requestor.getIoSession().getService();
094            List<IoSession> sessions = new ArrayList<IoSession>(
095                    acceptor.getManagedSessions().values() );
096            GracefulShutdownRequest gsreq = ( GracefulShutdownRequest ) req;
097    
098            // build the graceful disconnect message with replicationContexts
099            GracefulDisconnect notice = getGracefulDisconnect( gsreq.getTimeOffline(), gsreq.getDelay() );
100    
101            // send (synch) the GracefulDisconnect to each client before unbinding
102            sendGracefulDisconnect( sessions, notice, requestor.getIoSession() );
103    
104            // wait for the specified delay before we unbind the service 
105            waitForDelay( gsreq.getDelay() );
106    
107            // -------------------------------------------------------------------
108            // unbind the server socket for the LDAP service here so no new 
109            // connections are accepted while we process this shutdown request
110            // note that the following must be issued before binding the ldap
111            // service in order to prevent client disconnects on service unbind:
112            // 
113            // minaRegistry.getAcceptor( service.getTransportType() )
114            //                       .setDisconnectClientsOnUnbind( false );
115            // -------------------------------------------------------------------
116            // This might not work, either.
117            acceptor.unbind( requestor.getIoSession().getServiceAddress() );
118    
119            // -------------------------------------------------------------------
120            // synchronously send a NoD to clients that are not aware of this resp
121            // after sending the NoD the client is disconnected if still connected
122            // -------------------------------------------------------------------
123            sendNoticeOfDisconnect( sessions, requestor.getIoSession() );
124    
125            // -------------------------------------------------------------------
126            // respond back to the client that requested the graceful shutdown w/
127            // a success resultCode which confirms all clients have been notified
128            // via the graceful disconnect or NoD and the service has been unbound
129            // preventing new connections; after recieving this response the 
130            // requestor should disconnect and stop using the connection
131            // -------------------------------------------------------------------
132            sendShutdownResponse( requestor.getIoSession(), req.getMessageId() );
133        }
134    
135    
136        /**
137         * Sends a successful response.
138         * 
139         * @param requestor the session of the requestor
140         * @param messageId the message id associaed with this shutdown request
141         */
142        public static void sendShutdownResponse( IoSession requestor, int messageId )
143        {
144            GracefulShutdownResponse msg = new GracefulShutdownResponse( messageId, ResultCodeEnum.SUCCESS );
145            WriteFuture future = requestor.write( msg );
146            future.awaitUninterruptibly();
147            if ( future.isWritten() )
148            {
149                if ( LOG.isInfoEnabled() )
150                {
151                    LOG.info( "Sent GracefulShutdownResponse to client: " + requestor.getRemoteAddress() );
152                }
153            }
154            else
155            {
156                LOG.error( I18n.err( I18n.ERR_159, requestor.getRemoteAddress() ) );
157            }
158            requestor.close( true );
159        }
160    
161    
162        /**
163         * Blocks to synchronously send the same GracefulDisconnect message to all 
164         * managed sessions except for the requestor of the GracefulShutdown.
165         * 
166         * @param msg the graceful disconnec extended request to send
167         * @param requestor the session of the graceful shutdown requestor
168         * @param sessions the IoSessions to send disconnect message to
169         */
170        public static void sendGracefulDisconnect( List<IoSession> sessions, GracefulDisconnect msg, IoSession requestor )
171        {
172            List<WriteFuture> writeFutures = new ArrayList<WriteFuture>();
173    
174            // asynchronously send GracefulDisconnection messages to all connected
175            // clients giving time for the message to arrive before we block 
176            // waiting for message delivery to the client in the loop below
177    
178            if ( sessions != null )
179            {
180                for ( IoSession session : sessions )
181                {
182                    // make sure we do not send the disconnect mesasge to the
183                    // client which sent the initiating GracefulShutdown request
184                    if ( session.equals( requestor ) )
185                    {
186                        continue;
187                    }
188    
189                    try
190                    {
191                        writeFutures.add( session.write( msg ) );
192                    }
193                    catch ( Exception e )
194                    {
195                        LOG.warn( "Failed to write GracefulDisconnect to client session: " + session, e );
196                    }
197                }
198            }
199    
200            // wait for GracefulDisconnect messages to be sent before returning
201            for ( WriteFuture future : writeFutures )
202            {
203                try
204                {
205                    future.awaitUninterruptibly( 1000 );
206                }
207                catch ( Exception e )
208                {
209                    LOG.warn( "Failed to sent GracefulDisconnect", e );
210                }
211            }
212        }
213    
214    
215        /**
216         * Blocks to synchronously send the a NoticeOfDisconnect message with
217         * the resultCode set to unavailable(52) to all managed sessions except 
218         * for the requestor of the GracefulShutdown.
219         * 
220         * @param requestor the session of the graceful shutdown requestor
221         * @param sessions the sessions from mina
222         */
223        public static void sendNoticeOfDisconnect( List<IoSession> sessions, IoSession requestor )
224        {
225            List<WriteFuture> writeFutures = new ArrayList<WriteFuture>();
226    
227            // Send Notification of Disconnection messages to all connected clients.
228            if ( sessions != null )
229            {
230                for ( IoSession session : sessions )
231                {
232                    // make sure we do not send the disconnect mesasge to the
233                    // client which sent the initiating GracefulShutdown request
234                    if ( session.equals( requestor ) )
235                    {
236                        continue;
237                    }
238    
239                    try
240                    {
241                        writeFutures.add( session.write( NoticeOfDisconnect.UNAVAILABLE ) );
242                    }
243                    catch ( Exception e )
244                    {
245                        LOG.warn( "Failed to sent NoD for client: " + session, e );
246                    }
247                }
248            }
249    
250            // And close the connections when the NoDs are sent.
251            Iterator<IoSession> sessionIt = sessions.iterator();
252            
253            for ( WriteFuture future : writeFutures )
254            {
255                try
256                {
257                    future.awaitUninterruptibly( 1000 );
258                    sessionIt.next().close( true );
259                }
260                catch ( Exception e )
261                {
262                    LOG.warn( "Failed to sent NoD.", e );
263                }
264            }
265        }
266    
267    
268        public static GracefulDisconnect getGracefulDisconnect( int timeOffline, int delay )
269        {
270            // build the graceful disconnect message with replicationContexts
271            // @todo add the referral objects for replication contexts using setup code below
272            //        Iterator list = nexus.listSuffixes( true );
273            //        while ( list.hasNext() )
274            //        {
275            //            LdapName dn = new LdapName( ( String ) list.next() );
276            //            DirectoryPartition partition = nexus.getPartition( dn );
277            //        }
278            return new GracefulDisconnect( timeOffline, delay );
279        }
280    
281    
282        public static void waitForDelay( int delay )
283        {
284            if ( delay > 0 )
285            {
286                // delay is in seconds
287                long delayMillis = delay * 1000L;
288                long startTime = System.currentTimeMillis();
289    
290                while ( ( System.currentTimeMillis() - startTime ) < delayMillis )
291                {
292                    try
293                    {
294                        Thread.sleep( 250 );
295                    }
296                    catch ( InterruptedException e )
297                    {
298                        LOG.warn( "Got interrupted while waiting for delay before shutdown", e );
299                    }
300                }
301            }
302        }
303    
304    
305        public Set<String> getExtensionOids()
306        {
307            return EXTENSION_OIDS;
308        }
309    
310    
311        public void setLdapServer( LdapServer ldapServer )
312        {
313        }
314    }