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    }