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.tools; 021 022 023 import java.util.Hashtable; 024 025 import javax.naming.CommunicationException; 026 import javax.naming.ldap.InitialLdapContext; 027 import javax.naming.ldap.LdapContext; 028 029 import org.apache.commons.cli.CommandLine; 030 import org.apache.commons.cli.Option; 031 import org.apache.commons.cli.Options; 032 import org.apache.directory.daemon.AvailablePortFinder; 033 import org.apache.directory.server.i18n.I18n; 034 import org.apache.directory.shared.ldap.message.extended.GracefulShutdownRequest; 035 036 037 /** 038 * A command used to send a graceful disconnect to established clients 039 * while allowing them time to complete operations already in progress. 040 * 041 * @see <a href="http://docs.safehaus.org/display/APACHEDS/LDAP+Extensions+for+Graceful+Shutdown"> 042 * Graceful Shutdown</a> 043 * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a> 044 * @version $Rev: 434420 $ 045 */ 046 public class GracefulShutdownCommand extends ToolCommand 047 { 048 public static final String PORT_RANGE = "(" + AvailablePortFinder.MIN_PORT_NUMBER + ", " 049 + AvailablePortFinder.MAX_PORT_NUMBER + ")"; 050 051 private static final int DELAY_MAX = 86400; 052 053 private static final int TIME_OFFLINE_MAX = 720; 054 055 private int port = 10389; 056 private String host = "localhost"; 057 private String password = "secret"; 058 private int delay; 059 private int timeOffline; 060 061 062 protected GracefulShutdownCommand() 063 { 064 super( "graceful" ); 065 } 066 067 private boolean isWaiting; 068 private boolean isSuccess = false; 069 private Thread executeThread = null; 070 071 072 public void execute( CommandLine cmd ) throws Exception 073 { 074 executeThread = Thread.currentThread(); 075 processOptions( cmd ); 076 077 if ( isDebugEnabled() ) 078 { 079 System.out.println( "Parameters for GracefulShutdown extended request:" ); 080 System.out.println( "port = " + port ); 081 System.out.println( "host = " + host ); 082 System.out.println( "password = " + password ); 083 System.out.println( "delay = " + delay ); 084 System.out.println( "timeOffline = " + timeOffline ); 085 } 086 087 Hashtable env = new Hashtable(); 088 env.put( "java.naming.factory.initial", "com.sun.jndi.ldap.LdapCtxFactory" ); 089 env.put( "java.naming.provider.url", "ldap://" + host + ":" + port ); 090 env.put( "java.naming.security.principal", "uid=admin,ou=system" ); 091 env.put( "java.naming.security.credentials", password ); 092 env.put( "java.naming.security.authentication", "simple" ); 093 094 LdapContext ctx = new InitialLdapContext( env, null ); 095 if ( !isQuietEnabled() ) 096 { 097 System.out.println( "Connection to the server established.\n" 098 + "Sending extended request and blocking for shutdown:" ); 099 isWaiting = true; 100 Thread t = new Thread( new Ticker() ); 101 t.start(); 102 } 103 try 104 { 105 ctx.extendedOperation( new GracefulShutdownRequest( 0, timeOffline, delay ) ); 106 isSuccess = true; 107 } 108 catch ( Throwable t ) 109 { 110 /* 111 * Sometimes because of timing issues we show a failure when the 112 * shutdown has succeeded so we should check if the server is up 113 * before we set success to false. 114 */ 115 try 116 { 117 new InitialLdapContext( env, null ); 118 isSuccess = false; 119 System.err.print( "shutdown request failed with error: " + t.getLocalizedMessage() ); 120 } 121 catch ( CommunicationException e ) 122 { 123 isSuccess = true; 124 } 125 } 126 isWaiting = false; 127 ctx.close(); 128 } 129 130 class Ticker implements Runnable 131 { 132 public void run() 133 { 134 if ( !isQuietEnabled() ) 135 System.out.print( "[waiting for shutdown] " ); 136 while ( isWaiting ) 137 { 138 try 139 { 140 Thread.sleep( 1000 ); 141 } 142 catch ( InterruptedException e ) 143 { 144 // TODO Auto-generated catch block 145 e.printStackTrace(); 146 } 147 if ( !isQuietEnabled() ) 148 System.out.print( "." ); 149 } 150 if ( isSuccess ) 151 { 152 if ( !isQuietEnabled() ) 153 System.out.println( "\n[shutdown complete]" ); 154 try 155 { 156 executeThread.join( 1000 ); 157 } 158 catch ( InterruptedException e ) 159 { 160 e.printStackTrace(); 161 } 162 System.exit( 0 ); 163 } 164 else 165 { 166 if ( !isQuietEnabled() ) 167 System.out.println( "\n[shutdown failed]" ); 168 try 169 { 170 executeThread.join( 1000 ); 171 } 172 catch ( InterruptedException e ) 173 { 174 e.printStackTrace(); 175 } 176 System.exit( 1 ); 177 } 178 } 179 } 180 181 182 private void processOptions( CommandLine cmd ) 183 { 184 if ( isDebugEnabled() ) 185 { 186 System.out.println( "Processing options for graceful shutdown ..." ); 187 } 188 189 // ------------------------------------------------------------------- 190 // figure out and error check the port value 191 // ------------------------------------------------------------------- 192 193 if ( cmd.hasOption( 'p' ) ) // - user provided port w/ -p takes precedence 194 { 195 String val = cmd.getOptionValue( 'p' ); 196 try 197 { 198 port = Integer.parseInt( val ); 199 } 200 catch ( NumberFormatException e ) 201 { 202 System.err.println( I18n.err( I18n.ERR_193, val ) ); 203 System.exit( 1 ); 204 } 205 206 if ( port > AvailablePortFinder.MAX_PORT_NUMBER ) 207 { 208 System.err.println( I18n.err( I18n.ERR_194, val, AvailablePortFinder.MAX_PORT_NUMBER ) ); 209 System.exit( 1 ); 210 } 211 else if ( port < AvailablePortFinder.MIN_PORT_NUMBER ) 212 { 213 System.err.println( I18n.err( I18n.ERR_195, val, AvailablePortFinder.MIN_PORT_NUMBER ) ); 214 System.exit( 1 ); 215 } 216 217 if ( isDebugEnabled() ) 218 { 219 System.out.println( "port overriden by -p option: " + port ); 220 } 221 } 222 else if ( getApacheDS() != null ) 223 { 224 port = getApacheDS().getLdapServer().getPort(); 225 226 if ( isDebugEnabled() ) 227 { 228 System.out.println( "port overriden by server.xml configuration: " + port ); 229 } 230 } 231 else if ( isDebugEnabled() ) 232 { 233 System.out.println( "port set to default: " + port ); 234 } 235 236 // ------------------------------------------------------------------- 237 // figure out the host value 238 // ------------------------------------------------------------------- 239 240 if ( cmd.hasOption( 'h' ) ) 241 { 242 host = cmd.getOptionValue( 'h' ); 243 244 if ( isDebugEnabled() ) 245 { 246 System.out.println( "host overriden by -h option: " + host ); 247 } 248 } 249 else if ( isDebugEnabled() ) 250 { 251 System.out.println( "host set to default: " + host ); 252 } 253 254 // ------------------------------------------------------------------- 255 // figure out the password value 256 // ------------------------------------------------------------------- 257 258 if ( cmd.hasOption( 'w' ) ) 259 { 260 password = cmd.getOptionValue( 'w' ); 261 262 if ( isDebugEnabled() ) 263 { 264 System.out.println( "password overriden by -w option: " + password ); 265 } 266 } 267 else if ( isDebugEnabled() ) 268 { 269 System.out.println( "password set to default: " + password ); 270 } 271 272 // ------------------------------------------------------------------- 273 // figure out the delay value 274 // ------------------------------------------------------------------- 275 276 if ( cmd.hasOption( 'e' ) ) 277 { 278 String val = cmd.getOptionValue( 'e' ); 279 try 280 { 281 delay = Integer.parseInt( val ); 282 } 283 catch ( NumberFormatException e ) 284 { 285 System.err.println( I18n.err( I18n.ERR_197, val ) ); 286 System.exit( 1 ); 287 } 288 289 if ( delay > DELAY_MAX ) 290 { 291 System.err.println( I18n.err( I18n.ERR_198, val, DELAY_MAX ) ); 292 System.exit( 1 ); 293 } 294 else if ( delay < 0 ) 295 { 296 System.err.println( I18n.err( I18n.ERR_199, val ) ); 297 System.exit( 1 ); 298 } 299 300 if ( isDebugEnabled() ) 301 { 302 System.out.println( "delay seconds overriden by -e option: " + delay ); 303 } 304 } 305 else if ( isDebugEnabled() ) 306 { 307 System.out.println( "Using default delay value of " + delay ); 308 } 309 310 // ------------------------------------------------------------------- 311 // figure out the timeOffline value 312 // ------------------------------------------------------------------- 313 314 if ( cmd.hasOption( 't' ) ) 315 { 316 String val = cmd.getOptionValue( 't' ); 317 try 318 { 319 timeOffline = Integer.parseInt( val ); 320 } 321 catch ( NumberFormatException e ) 322 { 323 System.err.println( I18n.err( I18n.ERR_200, val ) ); 324 System.exit( 1 ); 325 } 326 327 if ( timeOffline > TIME_OFFLINE_MAX ) 328 { 329 System.err.println( I18n.err( I18n.ERR_201, val, TIME_OFFLINE_MAX ) ); 330 System.exit( 1 ); 331 } 332 else if ( timeOffline < 0 ) 333 { 334 System.err.println( I18n.err( I18n.ERR_202, val ) ); 335 System.exit( 1 ); 336 } 337 338 if ( isDebugEnabled() ) 339 { 340 System.out.println( "timeOffline seconds overriden by -t option: " + timeOffline ); 341 } 342 } 343 else if ( isDebugEnabled() ) 344 { 345 System.out.println( "Using default timeOffline value of " + delay ); 346 } 347 } 348 349 350 public Options getOptions() 351 { 352 Options opts = new Options(); 353 Option op = new Option( "h", "host", true, "server host: defaults to localhost" ); 354 op.setRequired( false ); 355 opts.addOption( op ); 356 op = new Option( "p", "port", true, "server port: defaults to 10389 or server.xml specified port" ); 357 op.setRequired( false ); 358 opts.addOption( op ); 359 op = new Option( "e", "delay", true, "delay (seconds) before shutdown: defaults to 0" ); 360 op.setRequired( false ); 361 opts.addOption( op ); 362 op = new Option( "w", "password", true, "the apacheds administrator's password: defaults to secret" ); 363 op.setRequired( false ); 364 opts.addOption( op ); 365 op = new Option( "t", "time-offline", true, "server offline time (minutes): defaults to 0 (indefinate)" ); 366 op.setRequired( false ); 367 opts.addOption( op ); 368 op = new Option( "i", "install-path", true, "path to apacheds installation directory" ); 369 op.setRequired( false ); 370 opts.addOption( op ); 371 return opts; 372 } 373 }