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 }