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 }