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