001    /*
002     * Copyright 2005 Stephen J. McConnell.
003     *
004     * Licensed  under the  Apache License,  Version 2.0  (the "License");
005     * you may not use  this file  except in  compliance with the License.
006     * You may obtain a copy of the License at
007     *
008     *   http://www.apache.org/licenses/LICENSE-2.0
009     *
010     * Unless required by applicable law or agreed to in writing, software
011     * distributed  under the  License is distributed on an "AS IS" BASIS,
012     * WITHOUT  WARRANTIES OR CONDITIONS  OF ANY KIND, either  express  or
013     * implied.
014     *
015     * See the License for the specific language governing permissions and
016     * limitations under the License.
017     */
018    
019    package net.dpml.station.console;
020    
021    import java.io.IOException;
022    import java.io.InputStreamReader;
023    import java.io.BufferedReader;
024    import java.lang.reflect.InvocationTargetException;
025    import java.net.URI;
026    import java.net.URL;
027    import java.rmi.RemoteException;
028    import java.rmi.ConnectException;
029    import java.rmi.registry.LocateRegistry;
030    import java.rmi.registry.Registry;
031    import java.rmi.server.UnicastRemoteObject;
032    import java.util.ArrayList;
033    import java.util.List;
034    import java.util.Iterator;
035    import java.util.Set;
036    import java.util.HashSet;
037    import java.util.Properties;
038    
039    import net.dpml.station.Application;
040    import net.dpml.station.Manager;
041    import net.dpml.station.Station;
042    import net.dpml.station.StationException;
043    import net.dpml.station.info.StartupPolicy;
044    import net.dpml.station.ApplicationRegistry;
045    import net.dpml.station.info.ApplicationDescriptor;
046    import net.dpml.station.server.RemoteApplicationRegistry;
047    import net.dpml.station.server.OutputStreamReader;
048    import net.dpml.station.server.ErrorStreamReader;
049    
050    import net.dpml.component.Provider;
051    
052    import net.dpml.state.State;
053    import net.dpml.state.Operation;
054    import net.dpml.state.Transition;
055    import net.dpml.state.Interface;
056    
057    import net.dpml.transit.Artifact;
058    import net.dpml.util.Logger;
059    import net.dpml.lang.PID;
060    import net.dpml.transit.Disposable;
061    import net.dpml.lang.ValueDirective;
062    import net.dpml.util.ExceptionHelper;
063    import net.dpml.util.PropertyResolver;
064    
065    import net.dpml.lang.UnknownKeyException;
066    import net.dpml.lang.DuplicateKeyException;
067    
068    import net.dpml.cli.Option;
069    import net.dpml.cli.Group;
070    import net.dpml.cli.CommandLine;
071    import net.dpml.cli.commandline.Parser;
072    import net.dpml.cli.util.HelpFormatter;
073    import net.dpml.cli.OptionException;
074    import net.dpml.cli.DisplaySetting;
075    import net.dpml.cli.builder.ArgumentBuilder;
076    import net.dpml.cli.builder.GroupBuilder;
077    import net.dpml.cli.builder.DefaultOptionBuilder;
078    import net.dpml.cli.builder.CommandBuilder;
079    import net.dpml.cli.option.PropertyOption;
080    import net.dpml.cli.validation.EnumValidator;
081    import net.dpml.cli.validation.URIValidator;
082    import net.dpml.cli.validation.NumberValidator;
083    
084    
085    /**
086     * Plugin that handles station commands.
087     *
088     * @author <a href="http://www.dpml.net">Digital Product Meta Library</a>
089     * @version 1.0.3
090     */
091    public class StationPlugin implements Disposable
092    {
093        // ------------------------------------------------------------------------
094        // state
095        // ------------------------------------------------------------------------
096    
097        private final Logger m_logger;
098        
099        private boolean m_flag = false; // true if we have to take down the registry on exit
100        
101        private ApplicationRegistry m_registry; // contains the ref to the registry used during disposal
102        
103        // ------------------------------------------------------------------------
104        // constructor
105        // ------------------------------------------------------------------------
106    
107       /**
108        * Creation of a new station plugin.  The station plugin handles console
109        * based commmandline interaction with the application registry and a 
110        * server station.
111        *
112        * @param logger the assigned logging channel
113        * @param args command line arguments
114        * @exception Exception if an error occurs during plugin establishment
115        */
116        public StationPlugin( Logger logger, String[] args ) throws Exception
117        {
118            m_logger = logger;
119            
120            ClassLoader context = Station.class.getClassLoader();
121            Thread.currentThread().setContextClassLoader( context );
122            
123            // log the raw arguments
124            
125            if( m_logger.isDebugEnabled() )
126            {
127                logRawArguments( logger, args );
128            }
129            
130            // handle commands
131            
132            Parser parser = new Parser();
133            parser.setGroup( COMMAND_GROUP );
134            
135            try
136            {
137                CommandLine line = parser.parse( args );
138                if( line.hasOption( HELP_COMMAND ) )
139                {
140                    processHelp();
141                }
142                else if( line.hasOption( STARTUP_COMMAND ) )
143                {
144                    processStartup( line );
145                }
146                else if( line.hasOption( SHUTDOWN_COMMAND ) )
147                {
148                    processShutdown( line );
149                }
150                else if( line.hasOption( ADD_COMMAND ) )
151                {
152                    processAddCommand( line );
153                }
154                else if( line.hasOption( SET_COMMAND ) )
155                {
156                    processSetCommand( line );
157                }
158                else if( line.hasOption( INFO_COMMAND ) )
159                {
160                    processInfoCommand( line );
161                }
162                else if( line.hasOption( START_COMMAND ) )
163                {
164                    processStartCommand( line );
165                }
166                else if( line.hasOption( CONTROL_COMMAND ) )
167                {
168                    processControlCommand( line );
169                }
170                else if( line.hasOption( STOP_COMMAND ) )
171                {
172                    processStopCommand( line );
173                }
174                else if( line.hasOption( RESTART_COMMAND ) )
175                {
176                    processRestartCommand( line );
177                }
178                else if( line.hasOption( REMOVE_COMMAND ) )
179                {
180                    processRemoveCommand( line );
181                }
182                else
183                {
184                    List options = line.getOptions();
185                    Iterator iterator = options.iterator();
186                    while( iterator.hasNext() )
187                    {
188                        Option option = (Option) iterator.next();
189                        System.out.println( 
190                          "# UNPROCESSED OPTION: " 
191                          + option 
192                          + " [" 
193                          + option.getId() 
194                          + "]" );
195                    }
196                }
197            }
198            catch( OptionException e )
199            {
200                m_logger.error( e.getMessage() );
201            }
202        }
203        
204        // ------------------------------------------------------------------------
205        // Disposable
206        // ------------------------------------------------------------------------
207        
208       /**
209        * Initiate disposal of the station plugin.  If the pugin has established 
210        * the applications registry (occurs if the station is not running as a remote
211        * process and the implementation loads the application repository).
212        */
213        public void dispose()
214        {
215            if( m_flag && ( null != m_registry ) )
216            {
217                getLogger().debug( "retracting registry from rmi" );
218                try
219                {
220                    UnicastRemoteObject.unexportObject( m_registry, true );
221                    m_registry = null;
222                }
223                catch( Throwable e )
224                {
225                    e.printStackTrace();
226                }
227            }
228        }
229        
230        // ------------------------------------------------------------------------
231        // commandline interegation
232        // ------------------------------------------------------------------------
233        
234        private Manager getManager( CommandLine line ) throws Exception
235        {
236            int port = getPortValue( line, Registry.REGISTRY_PORT );
237            Registry registry = getLocalRegistry( port );
238            if( null != registry )
239            {
240                try
241                {
242                    return (Manager) registry.lookup( Station.STATION_KEY );
243                }
244                catch( ConnectException e )
245                {
246                    throw e;
247                }
248                catch( Exception e )
249                {
250                    System.out.println( "# ERROR: " + e );
251                    final String error = 
252                      "Unable to locate station.";
253                    throw new StationException( error, e );
254                }
255            }
256            else
257            {
258                final String error = 
259                  "Unable to locate station.";
260                throw new StationException( error );
261            }
262        }
263        
264        private URI getRegistryURI( CommandLine line )
265        {
266            if( line.hasOption( REGISTRY_URI_OPTION ) )
267            {
268                return null;
269            }
270            else
271            {
272                return (URI) line.getValue( REGISTRY_URI_OPTION, null );
273            }
274        }
275        
276        private URI getConfigurationURI( CommandLine line, URI fallback )
277        {
278            if( line.hasOption( CONFIGURATION_URI_OPTION ) )
279            {
280                return fallback;
281            }
282            else
283            {
284                return (URI) line.getValue( CONFIGURATION_URI_OPTION, fallback );
285            }
286        }
287        
288       /**
289        * Return a properties instance composed of the <tt>-D&lt;key&gt;=&lt;value&gt;</tt>
290        * commandline arguments.
291        * @param line the commandline
292        * @return the resolved properties
293        */
294        private Properties getCommandLineProperties( CommandLine line, Properties properties )
295        {
296            Set propertyValue = line.getProperties();
297            Iterator iterator = propertyValue.iterator();
298            while( iterator.hasNext() )
299            {
300                String name = (String) iterator.next();
301                String value = line.getProperty( name );
302                properties.setProperty( name, value );
303            }
304            return properties;
305        }
306    
307       /**
308        * Return the basedir option value.
309        * @param line the commandline
310        * @return the base path
311        */
312        private String getBasedir( CommandLine line, String current )
313        {
314            return (String) line.getValue( BASEDIR_OPTION, current );
315        }
316        
317       /** 
318        * Get the application title.
319        * @param line the commandline
320        * @param title a default title if no title specified on the commandline
321        * @return the application title
322        */
323        private String getTitle( CommandLine line, String title )
324        {
325            return (String) line.getValue( TITLE_OPTION, title );
326        }
327        
328       /**
329        * Return the startup policy declared on the commandline.
330        * @param line the commandline
331        * @param policy the default policy to apply
332        */
333        private StartupPolicy getStartupPolicy( CommandLine line, StartupPolicy policy )
334        {
335            final String policyValue = (String) line.getValue( STARTUP_POLICY_OPTION, null );
336            if( null == policyValue )
337            {
338                return policy;
339            }
340            else
341            {
342                return StartupPolicy.parse( policyValue );
343            }
344        }
345        
346        private int getPortValue( CommandLine line, int defaultPort )
347        {
348            if( line.hasOption( PORT_OPTION ) )
349            {
350                Number number = (Number) line.getValue( PORT_OPTION, null );
351                if( null != number )
352                {
353                    return number.intValue();
354                }
355            }
356            return defaultPort;
357        }
358        
359        private int getStartupTimeout( CommandLine line, int fallback )
360        {
361            if( line.hasOption( STARTUP_TIMEOUT_OPTION ) )
362            {
363                Number number = (Number) line.getValue( STARTUP_TIMEOUT_OPTION, null );
364                if( null != number )
365                {
366                    return number.intValue();
367                }
368            }
369            return fallback;
370        }
371        
372        private int getShutdownTimeout( CommandLine line, int fallback )
373        {
374            if( line.hasOption( SHUTDOWN_TIMEOUT_OPTION ) )
375            {
376                Number number = (Number) line.getValue( SHUTDOWN_TIMEOUT_OPTION, null );
377                if( null != number )
378                {
379                    return number.intValue();
380                }
381            }
382            return fallback;
383        }
384        
385        private ApplicationRegistry getApplicationRegistry( CommandLine line ) throws Exception
386        {
387            URI uri = getRegistryURI( line );
388            if( null != uri )
389            {
390                return getApplicationRegistry( uri );
391            }
392            else
393            {
394                try
395                {
396                    Manager manager = getManager( line );
397                    getLogger().debug( "using remote registry" );
398                    return manager.getApplicationRegistry();
399                }
400                catch( Exception e )
401                {
402                    getLogger().debug( "using local registry" );
403                    return getApplicationRegistry( (URI) null );
404                }
405            }
406        }
407        
408        // ------------------------------------------------------------------------
409        // command handling
410        // ------------------------------------------------------------------------
411        
412       /**
413        * List general command help to the console.
414        * @exception IOException if an I/O error occurs
415        */
416        private void processHelp() throws IOException
417        {
418            HelpFormatter formatter = new HelpFormatter( 
419              HelpFormatter.DEFAULT_GUTTER_LEFT, 
420              HelpFormatter.DEFAULT_GUTTER_CENTER, 
421              HelpFormatter.DEFAULT_GUTTER_RIGHT, 
422              100, 50 );
423            
424            formatter.getDisplaySettings().add( DisplaySetting.DISPLAY_GROUP_OUTER );
425            formatter.getDisplaySettings().add( DisplaySetting.DISPLAY_PROPERTY_OPTION );
426            formatter.getDisplaySettings().add( DisplaySetting.DISPLAY_ARGUMENT_BRACKETED );
427            
428            formatter.getFullUsageSettings().add( DisplaySetting.DISPLAY_OPTIONAL );
429            formatter.getFullUsageSettings().add( DisplaySetting.DISPLAY_GROUP_OUTER );
430            formatter.getFullUsageSettings().add( DisplaySetting.DISPLAY_PROPERTY_OPTION );
431            formatter.getFullUsageSettings().add( DisplaySetting.DISPLAY_OPTIONAL );
432            formatter.getFullUsageSettings().add( DisplaySetting.DISPLAY_ARGUMENT_BRACKETED );
433            formatter.getFullUsageSettings().remove( DisplaySetting.DISPLAY_PARENT_CHILDREN );
434            
435            formatter.getLineUsageSettings().add( DisplaySetting.DISPLAY_PROPERTY_OPTION );
436            formatter.getLineUsageSettings().add( DisplaySetting.DISPLAY_ARGUMENT_BRACKETED );
437            formatter.getLineUsageSettings().remove( DisplaySetting.DISPLAY_PARENT_CHILDREN );
438            formatter.getLineUsageSettings().remove( DisplaySetting.DISPLAY_GROUP_EXPANDED );
439            
440            formatter.setGroup( COMMAND_GROUP );
441            formatter.setShellCommand( "station" );
442            formatter.print();
443        }
444        
445       /**
446        * Startup the station.
447        */
448        private void processStartup( CommandLine line ) throws Exception
449        {
450            getLogger().debug( "processing startup request" );
451            int port = getPortValue( line, Registry.REGISTRY_PORT );
452            if( null != getStation( port ) )
453            {
454                final String message = 
455                  "Station already deployed on port: " + port;
456                getLogger().warn( message );
457                return;
458            }
459            
460            ArrayList list = new ArrayList();
461            list.add( "station" );
462            list.add( "-server" );
463            list.add( "" + port );
464            
465            list.add( "-Ddpml.logging.config=local:properties:dpml/station/server" );
466            list.add( "-Ddpml.subprocess=true" );
467            if( "true".equals( System.getProperty( "dpml.trace" ) ) )
468            {
469                list.add( "-trace" );
470            }
471            else if( "true".equals( System.getProperty( "dpml.debug" ) ) )
472            {
473                list.add( "-debug" );
474            }
475            Set propertyValue = line.getProperties();
476            Iterator iterator = propertyValue.iterator();
477            while( iterator.hasNext() )
478            {
479                String name = (String) iterator.next();
480                String value = line.getProperty( name );
481                list.add( "-D" + name + "=" + value );
482            }
483            
484            URI uri = getRegistryURI( line );
485            if( null != uri )
486            {
487                list.add( "-registry" );
488                list.add( uri.toASCIIString() );
489            }
490            String[] args = (String[]) list.toArray( new String[0] );
491            
492            String message = "server startup command:";
493            String info = getRawArguments( message, args );
494            getLogger().debug( info );
495            
496            Process process = Runtime.getRuntime().exec( args, null );
497            getLogger().info( "waiting for server" );
498            OutputStreamReader output = new OutputStreamReader( getLogger(), process.getInputStream() );
499            ErrorStreamReader err = new ErrorStreamReader( getLogger(), process.getErrorStream() );
500            output.setDaemon( true );
501            err.setDaemon( true );
502            output.start();
503            err.start();
504            
505            int n = 0;
506            while( ( null == getStation( port ) && n < 20 ) )
507            {
508                try
509                {
510                    Thread.currentThread().sleep( 600 );
511                }
512                catch( Exception e )
513                {
514                }
515                n++;
516            }
517            
518            if( null == getStation( port ) )
519            {
520                getLogger().info( "server failed to start" );
521            }
522            else
523            {
524                getLogger().info( "station started" );
525            }
526        }
527            
528        private void processShutdown( CommandLine line ) throws Exception
529        {
530            try
531            {
532                Manager manager = getManager( line );
533                getLogger().info( "initiating station shutdown" );
534                try
535                {
536                    manager.shutdown();
537                }
538                catch( Exception e )
539                {
540                    getLogger().warn( e.getClass().getName() );
541                }    
542                getLogger().info( "station shutdown complete" );
543            }
544            catch( Exception e )
545            {
546                getLogger().info( "station is not running" );
547            }
548        }
549        
550       /**
551        * List application registry information.
552        * @param line the commandline
553        */
554        private void processInfoCommand( CommandLine line ) throws Exception
555        {
556            System.out.println( "" );
557            Thread.currentThread().setContextClassLoader( Provider.class.getClassLoader() );
558            Manager manager = null;
559            try
560            {
561                manager = getManager( line );
562                System.out.println( "  Server is operational." );
563                try
564                {
565                    String[] info = manager.getInfo();
566                    for( int i=0; i<info.length; i++ )
567                    {
568                        String value = info[i];
569                        System.out.println( "  " + value );
570                    }
571                }
572                catch( RemoteException e )
573                {
574                    getLogger().error( "Remote exception.", e );
575                }
576            }
577            catch( Exception e )
578            {
579                System.out.println( "  Server is not running." );
580            }
581            
582            ApplicationRegistry registry = getApplicationRegistry( line );
583            String key = (String) line.getValue( INFO_COMMAND, null );
584            
585            if( null == key )
586            {
587                StringBuffer buffer = new StringBuffer( "\n" );
588                try
589                {
590                    String[] keys = registry.getKeys();
591                    buffer.append( "\nProfile count: " + keys.length + "\n" );
592                    for( int i=0; i<keys.length; i++ )
593                    {
594                        String k = keys[i];
595                        ApplicationDescriptor profile = 
596                          registry.getApplicationDescriptor( k );
597                        buffer.append( 
598                          "\n (" 
599                          + ( i+1 ) 
600                          + ") " 
601                          + k
602                        );
603                        if( null != manager )
604                        {
605                            Application application = manager.getApplication( k );
606                            buffer.append( "\t" + application.getProcessState() );
607                        }
608                        else
609                        {
610                            buffer.append( "\t" );
611                        }
612                        buffer.append( "\t" + profile.getCodeBaseURI() );
613                    }
614                    buffer.append( "\n" );
615                    System.out.println( buffer.toString() );
616                }
617                catch( RemoteException e )
618                {
619                    getLogger().error( "Remote exception.", e );
620                }
621                catch( UnknownKeyException e )
622                {
623                    // ignore
624                }
625            }
626            else
627            {
628                try
629                {
630                    ApplicationDescriptor profile = registry.getApplicationDescriptor( key );
631                    listProfile( profile );
632                    if( null != manager )
633                    {
634                        Application application = manager.getApplication( key );
635                        PID pid = application.getPID();
636                        if( null != pid )
637                        {
638                            // 
639                            System.out.println( 
640                              "  Process: " 
641                              + application.getPID()
642                              + " ("
643                              + application.getProcessState() 
644                              + ")" );
645                        }
646                        else
647                        {
648                            System.out.println( 
649                              "  Process: " 
650                              + application.getProcessState() );
651                        }
652                        
653                        Provider instance = application.getProvider();
654                        if( null != instance )
655                        {
656                            State state = instance.getState();
657                            System.out.println( "  State: " + state );
658                            Operation[] operations = state.getOperations();
659                            if( operations.length > 0 ) 
660                            {
661                                System.out.println( "  Operations: (" + operations.length + ")" );
662                                for( int i=0; i<operations.length; i++ )
663                                {
664                                    System.out.println( "    " + operations[i] );
665                                }
666                            }
667                            Interface[] interfaces = state.getInterfaces();
668                            if( interfaces.length > 0 ) 
669                            {
670                                System.out.println( "  Interfaces: (" + interfaces.length + ")" );
671                                for( int i=0; i<interfaces.length; i++ )
672                                {
673                                    System.out.println( "    " + interfaces[i] );
674                                }
675                            }
676                            Transition[] transitions = state.getTransitions();
677                            if( transitions.length > 0 ) 
678                            {
679                                System.out.println( "  Transitions: (" + transitions.length + ")" );
680                                for( int i=0; i<transitions.length; i++ )
681                                {
682                                    System.out.println( "    " + transitions[i] );
683                                }
684                            }
685                        }
686                    }
687                }
688                catch( UnknownKeyException e )
689                {
690                    getLogger().warn( "Unknown application key [" + key + "]." );
691                }
692                catch( RemoteException e )
693                {
694                    getLogger().error( "Remote exception.", e );
695                }
696            }
697        }
698        
699       /**
700        * Handle a request for the startup of an application process.
701        * @param line the commandline
702        */
703        private void processStartCommand( CommandLine line ) throws Exception
704        {
705            try
706            {
707                Manager manager = getManager( line );
708                String value = (String) line.getValue( START_COMMAND, null );
709                processStartCommand( manager, value );
710            }
711            catch( ConnectException ce )
712            {
713                final String message = 
714                  "\nCannot start application because the station is not running. "
715                  + "\nUse 'station startup' to start the station process.";
716                System.out.println( message );
717            }
718        }
719        
720       /**
721        * Handle a request for the startup of an application process.
722        * @param key the application key
723        */
724        private void processStartCommand( Manager manager, String key ) throws Exception
725        {
726            getLogger().info( "starting application [" + key + "]" );
727            Application application = manager.getApplication( key );
728            application.start();
729        }
730        
731       /**
732        * Handle a request for control of an application process.
733        * @param line the commandline
734        */
735        private void processControlCommand( CommandLine line ) throws Exception
736        {
737            try
738            {
739                Manager manager = getManager( line );
740                String value = (String) line.getValue( CONTROL_COMMAND, null );
741                processControlCommand( manager, value );
742            }
743            catch( ConnectException ce )
744            {
745                final String message = 
746                  "\nCannot start application because the station is not running. "
747                  + "\nUse 'station startup' to start the station process.";
748                System.out.println( message );
749            }
750        }
751        
752       /**
753        * Handle a request for control of an application process.
754        * @param key the application key
755        */
756        private void processControlCommand( Manager manager, String key ) throws Exception
757        {
758            Application application = manager.getApplication( key );
759            InputStreamReader isr = new InputStreamReader( System.in );
760            BufferedReader reader = new BufferedReader( isr );
761            String line = null;
762            PID pid = application.getPID();
763            String prompt = "> ";
764            if( null != pid )
765            {
766                prompt = pid.toString() + "> ";
767            }
768            
769            System.out.println( prompt + "Connected to application [" + key + "]" );
770            System.out.print( prompt );
771            Parser parser = new Parser();
772            parser.setGroup( CONTROLLER_GROUP );
773            while( ( line = reader.readLine() ) != null )
774            {
775                if( "".equals( line ) )
776                {
777                    boolean ignoreLine = true;
778                }
779                else
780                {
781                    try
782                    {
783                        String[] arguments = line.split( " " );
784                        String[] args = expandArgs( arguments );
785                        CommandLine commandline = parser.parse( args );
786                        if( commandline.hasOption( CONTROL_HELP_COMMAND ) )
787                        {
788                            HelpFormatter formatter = new HelpFormatter();
789                            formatter.setGroup( CONTROLLER_GROUP );
790                            formatter.print();
791                        }
792                        else if( commandline.hasOption( CONTROL_EXIT_COMMAND ) )
793                        {
794                            System.exit( 0 );
795                        }
796                        else if( commandline.hasOption( CONTROL_INFO_COMMAND ) )
797                        {
798                            listInfo( application );
799                        }
800                        else if( commandline.hasOption( CONTROL_APPLY_COMMAND ) )
801                        {
802                            applyTransition( application, commandline );
803                        }
804                        else if( commandline.hasOption( CONTROL_EXEC_COMMAND ) )
805                        {
806                            execOperation( application, commandline );
807                        }
808                        else if( commandline.hasOption( CONTROL_INVOKE_COMMAND ) )
809                        {
810                            invokeOperation( application, commandline );
811                        }
812                    }
813                    catch( Exception e )
814                    {
815                        System.out.println( e.getMessage() );
816                    }
817                }
818                System.out.print( prompt );
819            }
820        }
821        
822        private String[] expandArgs( String[] args )
823        {
824            String[] result = new String[ args.length ];
825            for( int i=0; i<args.length; i++ )
826            {
827                String arg = args[i];
828                String value = PropertyResolver.resolve( arg );
829                result[i] = value;
830            }
831            return result;
832        }
833        
834        private String[] getExecArgs( CommandLine line )
835        {
836            return getArgs( CONTROL_EXEC_COMMAND, line );
837        }
838        
839        private String[] getInvokeArgs( CommandLine line )
840        {
841            return getArgs( CONTROL_INVOKE_COMMAND, line );
842        }
843        
844        private String[] getArgs( Option option, CommandLine line )
845        {
846            List args = line.getValues( option );
847            String[] elements = (String[]) args.toArray( new String[0] );
848            if( elements.length < 2 )
849            {
850                return new String[0];
851            }
852            String[] result = new String[ elements.length - 1 ];
853            for( int i=1; i<elements.length; i++ )
854            {
855                result[i-1] = elements[i];
856            }
857            return result;
858        }
859        
860        private void listInfo( Application application ) throws Exception
861        {
862            System.out.println( "" );
863            Provider provider = application.getProvider();
864            if( null == provider )
865            {
866                System.out.println( "Provider unavailable." );
867            }
868            else
869            {
870                System.out.println( "Application: " + application.getID() );
871                State state = provider.getState();
872                System.out.println( "Current State: " + state );
873                Transition[] transitions = state.getTransitions();
874                System.out.println( "Transitions: " + transitions.length );
875                for( int i=0; i<transitions.length; i++ )
876                {
877                    Transition transition = transitions[i];
878                    System.out.println( "  [" + ( i+1 ) + "] " + transition.getName() );
879                }
880                Operation[] operations = state.getOperations();
881                System.out.println( "Operations: " + operations.length );
882                for( int i=0; i<operations.length; i++ )
883                {
884                    Operation operation = operations[i];
885                    System.out.println( "  [" + ( i+1 ) + "] " + operation.getName() );
886                }
887                Interface[] interfaces = state.getInterfaces();
888                System.out.println( "Interfaces: " + interfaces.length );
889                for( int i=0; i<interfaces.length; i++ )
890                {
891                    System.out.println( "  [" + ( i+1 ) + "] " + interfaces[i] );
892                }
893            }
894            System.out.println( "" );
895        }
896        
897        private void applyTransition( Application application, CommandLine commandline ) throws Exception
898        {
899            String id = (String) commandline.getValue( CONTROL_APPLY_COMMAND, null );
900            System.out.println( "\napplying transition: " + id );
901            State state = application.getProvider().apply( id );
902            System.out.println( "current state: " + state );
903            System.out.println( "" );
904        }
905        
906        private void invokeOperation( Application application, CommandLine commandline ) throws Exception
907        {
908            String method = (String) commandline.getValues( CONTROL_INVOKE_COMMAND ).get( 0 );
909            System.out.println( "\ninvoking operation: " + method );
910            try
911            {
912                String[] applyArgs = getInvokeArgs( commandline );
913                Object result = application.getProvider().invoke( method, applyArgs );
914                if( null != result )
915                {
916                    System.out.println( "listing return value\n" );
917                    if( result instanceof Object[] )
918                    {
919                        Object[] values = (Object[]) result;
920                        for( int i=0; i<values.length; i++ )
921                        {
922                            System.out.println( values[i].toString() );
923                        }
924                    }
925                    else
926                    {
927                        System.out.println( result.toString() );
928                    }
929                    System.out.println( "\ndone" );
930                }
931                else
932                {
933                    System.out.println( "done" );
934                }
935            }
936            catch( InvocationTargetException e )
937            {
938                Throwable cause = e.getCause();
939                if( null != cause )
940                {
941                    String error = ExceptionHelper.packException( cause, true );
942                    System.out.println( error );
943                }
944                else
945                {
946                    String error = ExceptionHelper.packException( e, true );
947                    System.out.println( error );
948                }
949            }
950            catch( Throwable e )
951            {
952                System.out.println( e.toString() );
953            }
954        }
955        
956        private void execOperation( Application application, CommandLine commandline ) throws Exception
957        {
958            String id = (String) commandline.getValues( CONTROL_EXEC_COMMAND ).get( 0 );
959            System.out.println( "\napplying operation: " + id );
960            try
961            {
962                String[] applyArgs = getExecArgs( commandline );
963                Object result = application.getProvider().exec( id, applyArgs );
964                if( null != result )
965                {
966                    System.out.println( "listing return value\n" );
967                    if( result instanceof Object[] )
968                    {
969                        Object[] values = (Object[]) result;
970                        for( int i=0; i<values.length; i++ )
971                        {
972                            System.out.println( values[i].toString() );
973                        }
974                    }
975                    else
976                    {
977                        System.out.println( result.toString() );
978                    }
979                    System.out.println( "\ndone" );
980                }
981                else
982                {
983                    System.out.println( "done" );
984                }
985            }
986            catch( InvocationTargetException e )
987            {
988                Throwable cause = e.getCause();
989                if( null != cause )
990                {
991                    String error = ExceptionHelper.packException( cause, true );
992                    System.out.println( error );
993                }
994                else
995                {
996                    String error = ExceptionHelper.packException( e, true );
997                    System.out.println( error );
998                }
999            }
1000            catch( Throwable e )
1001            {
1002                System.out.println( e.toString() );
1003            }
1004        }
1005        
1006       /**
1007        * Handle a request for the shutdown of an application process.
1008        * @param line the commandline
1009        */
1010        private void processStopCommand( CommandLine line ) throws Exception
1011        {
1012            try
1013            {
1014                Manager manager = getManager( line );
1015                String value = (String) line.getValue( STOP_COMMAND, null );
1016                processStopCommand( manager, value );
1017            }
1018            catch( ConnectException ce )
1019            {
1020                final String message = 
1021                  "\nCannot stop application because the station is not running. "
1022                  + "\nUse 'station startup' to start the station process.";
1023                System.out.println( message );
1024            }
1025        }
1026        
1027       /**
1028        * Handle a request for the shutdown of an application process.
1029        * @param key the application key
1030        */
1031        private void processStopCommand( Manager manager, String key ) throws Exception
1032        {
1033            getLogger().info( "stopping application [" + key + "]" );
1034            Application application = manager.getApplication( key );
1035            application.stop();
1036        }
1037        
1038       /**
1039        * Handle a request for the restart of an application process.
1040        * @param line the commandline
1041        */
1042        private void processRestartCommand( CommandLine line ) throws Exception
1043        {
1044            try
1045            {
1046                Manager manager = getManager( line );
1047                String value = (String) line.getValue( RESTART_COMMAND, null );
1048                processRestartCommand( manager, value );
1049            }
1050            catch( ConnectException ce )
1051            {
1052                final String message = 
1053                  "\nCannot restart application because the station is not running. "
1054                  + "\nUse 'station startup' to start the station process.";
1055                System.out.println( message );
1056            }
1057        }
1058        
1059       /**
1060        * Handle a request for the restart of an application process.
1061        * @param key the application key
1062        */
1063        private void processRestartCommand( Manager manager, String key ) throws Exception
1064        {
1065            getLogger().info( "restarting application [" + key + "]" );
1066            Application application = manager.getApplication( key );
1067            application.restart();
1068        }
1069        
1070        private void processAddCommand( CommandLine line ) throws Exception
1071        {
1072            ApplicationRegistry registry = getApplicationRegistry( line );
1073            String key = (String) line.getValue( ADD_COMMAND, null );
1074            URI uri = (URI) line.getValue( REQUIRED_URI_OPTION, null );
1075            Properties properties = getCommandLineProperties( line, new Properties() );
1076            String baseDir = getBasedir( line, null );
1077            String title = getTitle( line, key );
1078            StartupPolicy policy = getStartupPolicy( line, StartupPolicy.MANUAL );
1079            int startup = getStartupTimeout( line, ApplicationDescriptor.DEFAULT_STARTUP_TIMEOUT );
1080            int shutdown = getShutdownTimeout( line, ApplicationDescriptor.DEFAULT_SHUTDOWN_TIMEOUT );
1081            URI config = getConfigurationURI( line, null );
1082            processAddCommand( 
1083              registry, key, title, uri, startup, shutdown, properties, baseDir, policy, config );
1084        }
1085        
1086       /**
1087        * Add a profile to the registry.
1088        * @param key the application key
1089        * @param title the application title
1090        * @param uri the codebase uri
1091        * @param properties system properties
1092        * @param base process base directory
1093        * @param policy application startup policy
1094        */
1095        private void processAddCommand( 
1096          ApplicationRegistry registry, String key, String title, URI uri, 
1097          int startup, int shutdown, Properties properties, String base, StartupPolicy policy, 
1098          URI config )
1099        {
1100            try
1101            {
1102                ApplicationDescriptor descriptor = 
1103                  new ApplicationDescriptor( 
1104                    uri, 
1105                    title,
1106                    new ValueDirective[0], 
1107                    base, 
1108                    policy, 
1109                    startup, 
1110                    shutdown, 
1111                    properties,
1112                    config );
1113                registry.addApplicationDescriptor( key, descriptor );
1114                registry.flush();
1115                System.out.println( "\nAdded new profile [" + key + "]" );
1116                listProfile( descriptor );
1117            }
1118            catch( DuplicateKeyException e )
1119            {
1120                final String error = 
1121                  "Cannot add application profile because the key [" 
1122                  + key
1123                  + "] is already assigned to another profile.";
1124                getLogger().error( error );
1125            }
1126            catch( Throwable e )
1127            {
1128                final String error = 
1129                  "Unexpected error while processing add request.";
1130                getLogger().error( error, e );
1131                getLogger().error( Thread.currentThread().getContextClassLoader().toString() );
1132            }
1133        }
1134    
1135        private void processSetCommand( CommandLine line ) throws Exception
1136        {
1137            ApplicationRegistry registry = getApplicationRegistry( line );
1138            String key = (String) line.getValue( SET_COMMAND, null );
1139            ApplicationDescriptor descriptor = registry.getApplicationDescriptor( key );
1140            URI uri = (URI) line.getValue( OPTIONAL_URI_OPTION, descriptor.getCodeBaseURI() );
1141            Properties properties = getCommandLineProperties( line, descriptor.getSystemProperties() );
1142            String baseDir = getBasedir( line, descriptor.getBasePath() );
1143            String title = getTitle( line, descriptor.getTitle() );
1144            StartupPolicy policy = getStartupPolicy( line, descriptor.getStartupPolicy() );
1145            int startup = getStartupTimeout( line, descriptor.getStartupTimeout() );
1146            int shutdown = getShutdownTimeout( line, descriptor.getShutdownTimeout() );
1147            URI config = getConfigurationURI( line, descriptor.getConfigurationURI() );
1148            processSetCommand(
1149              registry, descriptor, key, title, uri, startup, shutdown, properties, baseDir, policy, config );
1150        }
1151        
1152       /**
1153        * Update a profile.
1154        * @param key the application key
1155        * @param title the application title
1156        * @param uri the codebase uri
1157        * @param properties system properties
1158        * @param base process base directory
1159        * @param policy application startup policy
1160        */
1161        private void processSetCommand( 
1162          ApplicationRegistry registry, ApplicationDescriptor profile, String key, String title, URI uri, 
1163          int startup, int shutdown, Properties properties, String base, 
1164          StartupPolicy policy, URI config )
1165          throws IOException, UnknownKeyException
1166        {
1167            if( null == registry )
1168            {
1169                throw new NullPointerException( "registry" );
1170            }
1171            if( null == key )
1172            {
1173                throw new NullPointerException( "key" );
1174            }
1175            if( null == uri )
1176            {
1177                throw new NullPointerException( "uri" );
1178            }
1179            if( null == title )
1180            {
1181                throw new NullPointerException( "title" );
1182            }
1183            if( null == policy )
1184            {
1185                throw new NullPointerException( "policy" );
1186            }
1187            if( null == properties )
1188            {
1189                throw new NullPointerException( "properties" );
1190            }
1191            try
1192            {
1193                ApplicationDescriptor descriptor = 
1194                  new ApplicationDescriptor( 
1195                    uri, 
1196                    title,
1197                    new ValueDirective[0], 
1198                    base, 
1199                    policy, 
1200                    startup, 
1201                    shutdown, 
1202                    properties, 
1203                    config );
1204                registry.updateApplicationDescriptor( key, descriptor );
1205                registry.flush();
1206            }
1207            catch( UnknownKeyException e )
1208            {
1209                final String error = 
1210                  "Cannot update application profile because the key [" 
1211                  + key
1212                  + "] is unknown.";
1213                getLogger().error( error );
1214            }
1215            catch( Throwable e )
1216            {
1217                final String error = 
1218                  "Unexpected error while processing set command.";
1219                getLogger().error( error, e );
1220                getLogger().error( Thread.currentThread().getContextClassLoader().toString() );
1221                registry.updateApplicationDescriptor( key, profile );
1222            }
1223            
1224            try
1225            {
1226                ApplicationDescriptor newProfile = registry.getApplicationDescriptor( key );
1227                System.out.println( "\nUpdated profile [" + key + "]" );
1228                listProfile( newProfile );
1229            }
1230            catch( UnknownKeyException e )
1231            {
1232                final String error = 
1233                  "Unexpected error - updated profile not found.";
1234                getLogger().error( error, e );
1235            }
1236        }
1237        
1238        private void processRemoveCommand( CommandLine line ) throws Exception
1239        {
1240            ApplicationRegistry registry = getApplicationRegistry( line );
1241            String value = (String) line.getValue( REMOVE_COMMAND, null );
1242            try
1243            {
1244                Manager manager = getManager( line );
1245                Application application = manager.getApplication( value );
1246                application.stop();
1247            }
1248            catch( ConnectException ce )
1249            {
1250                boolean ignorable = true;
1251            }
1252            catch( UnknownKeyException ce )
1253            {
1254                boolean ignorable = true;
1255            }
1256            processRemoveCommand( registry, value );
1257        }
1258        
1259        private void processRemoveCommand( ApplicationRegistry registry, String key )
1260        {
1261            try
1262            {
1263                registry.removeApplicationDescriptor( key );
1264                registry.flush();
1265                getLogger().info( "removed application [" + key + "]" );
1266            }
1267            catch( UnknownKeyException e )
1268            {
1269                final String error = 
1270                  "Cannot add application profile because the key [" 
1271                  + key
1272                  + "] is unknown.";
1273                getLogger().error( error );
1274            }
1275            catch( Exception e )
1276            {
1277                final String error = 
1278                  "Unexpected error while processing remove request.";
1279                getLogger().error( error, e );
1280            }
1281        }
1282        
1283        // ------------------------------------------------------------------------
1284        // internal utilities
1285        // ------------------------------------------------------------------------
1286        
1287        private Logger getLogger()
1288        {
1289            return m_logger;
1290        }
1291        
1292        private void logRawArguments( Logger logger, String[] args )
1293        {
1294            StringBuffer buffer = 
1295              new StringBuffer( 
1296                "Processing [" 
1297                + args.length 
1298                + "] args." );
1299            for( int i=0; i<args.length; i++ )
1300            {
1301                buffer.append( 
1302                  "\n  " 
1303                  + ( i+1 ) 
1304                  + " " 
1305                  + args[i] );
1306            }
1307            String message = buffer.toString();
1308            logger.debug( message );
1309        }
1310        
1311        private String getRawArguments( String message, String[] args )
1312        {
1313            StringBuffer buffer = new StringBuffer( message );
1314            buffer.append( "\n  command elements: " + args.length );
1315            for( int i=0; i<args.length; i++ )
1316            {
1317                buffer.append( 
1318                  "\n  " 
1319                  + ( i+1 ) 
1320                  + " " 
1321                  + args[i] );
1322            }
1323            return buffer.toString();
1324        }
1325        
1326        private Station getStation( int port ) throws Exception
1327        {
1328            Registry registry = getLocalRegistry( port );
1329            if( null != registry )
1330            {
1331                try
1332                {
1333                    return (Station) registry.lookup( Station.STATION_KEY );
1334                }
1335                catch( Throwable e )
1336                {
1337                    return null;
1338                }
1339            }
1340            else
1341            {
1342                return null;
1343            }
1344        }
1345        
1346        private ApplicationRegistry getApplicationRegistry( int port ) throws Exception
1347        {
1348            Station station = getStation( port );
1349            if( null != station )
1350            {
1351                Manager manager = (Manager) station;
1352                return manager.getApplicationRegistry();
1353            }
1354            else
1355            {
1356                return getApplicationRegistry( (URI) null );
1357            }
1358        }
1359        
1360        private Registry getLocalRegistry( int port )
1361        {
1362            try
1363            {
1364                return LocateRegistry.getRegistry( port );
1365            }
1366            catch( RemoteException e )
1367            {
1368                return null;
1369            }
1370        }
1371        
1372        private ApplicationRegistry getApplicationRegistry( URI uri ) throws IOException
1373        {
1374            if( null == uri )
1375            {
1376                return getLocalApplicationRegistry( ApplicationRegistry.DEFAULT_STORAGE_URI );
1377            }
1378            else if( uri.getScheme().startsWith( "registry:" ) )
1379            {
1380                return (ApplicationRegistry) Artifact.createArtifact( uri ).toURL().getContent();
1381            }
1382            else
1383            {
1384                return getLocalApplicationRegistry( uri );
1385            }
1386        }
1387        
1388       /**
1389        * Create a new local application registry using the supplied uri.
1390        * As a side effect the internal state of this class is updated to reflect 
1391        * the fact that this class is responsible for RMI cleanup.
1392        * @param uri the registry stoarage uri
1393        * @return the aplication registry
1394        */
1395        private ApplicationRegistry getLocalApplicationRegistry( URI uri )
1396        {
1397            try
1398            {
1399                Logger logger = getLogger();
1400                URL url = getStorageURL( uri );
1401                m_flag = true;
1402                m_registry = new RemoteApplicationRegistry( logger, url );
1403                return m_registry;
1404            }
1405            catch( Exception e )
1406            {
1407                final String error = 
1408                  "Unexpected error while loading application registry from the uri ["
1409                  + uri
1410                  + "].";
1411                getLogger().error( error, e );
1412                throw new RuntimeException( error, e );
1413            }
1414        }
1415    
1416       /**
1417        * Return the storage uri as a url.
1418        * @param uri the uri
1419        * @return the url
1420        * @exception Exception if the uri could not be converted to a url
1421        */
1422        public URL getStorageURL( URI uri ) throws Exception
1423        {
1424            if( Artifact.isRecognized( uri ) )
1425            {
1426                return Artifact.createArtifact( uri ).toURL();
1427            }
1428            else
1429            {
1430                return uri.toURL();
1431            }
1432        }
1433        
1434       /**
1435        * List infomation to console about the supplied profile.
1436        * @param profile the application profile
1437        */
1438        private void listProfile( ApplicationDescriptor profile )
1439        {
1440            StringBuffer buffer = new StringBuffer( "\n" );
1441            buffer.append( "\n  Codebase: " + profile.getCodeBaseURI() );
1442            buffer.append( "\n  Working Directory Path: " + profile.getBasePath() );
1443            buffer.append( "\n  Startup Timeout: " + profile.getStartupTimeout() );
1444            buffer.append( "\n  Shutdown Timeout: " + profile.getShutdownTimeout() );
1445            buffer.append( "\n  Startup Policy: " + profile.getStartupPolicy() );
1446            Properties properties = profile.getSystemProperties();
1447            buffer.append( "\n  System Properties: " + properties.size() );
1448            buffer.append( "\n" );
1449            System.out.println( buffer.toString() );
1450        }
1451        
1452        // ------------------------------------------------------------------------
1453        // static utilities
1454        // ------------------------------------------------------------------------
1455        
1456        private static final String DEPOT_STATION_PLUGIN_URI = "artifact:part:dpml/station/dpml-station-server#1.0.3";
1457        private static final Set STARTUP_POLICY_SET = createStartupPolicySet();
1458        private static final String[] SUPPORTED_URI_SCHEMES = 
1459          new String[]{"link", "artifact", "local"};
1460        
1461        private static final DefaultOptionBuilder OPTION_BUILDER = new DefaultOptionBuilder();
1462        private static final ArgumentBuilder ARGUMENT_BUILDER = new ArgumentBuilder();
1463        private static final CommandBuilder COMMAND_BUILDER = new CommandBuilder();
1464        private static final GroupBuilder GROUP_BUILDER = new GroupBuilder();
1465        
1466        private static final Option STARTUP_POLICY_OPTION = 
1467          OPTION_BUILDER
1468            .withShortName( "policy" )
1469            .withDescription( "Startup policy." )
1470            .withRequired( false )
1471            .withArgument(
1472              ARGUMENT_BUILDER 
1473                .withDescription( "disabled|manual|automatic" )
1474                .withName( "policy" )
1475                .withMinimum( 1 )
1476                .withMaximum( 1 )
1477                .withValidator( new EnumValidator( STARTUP_POLICY_SET ) )
1478                .create() )
1479            .create();
1480        
1481        private static final Option BASEDIR_OPTION = 
1482          OPTION_BUILDER
1483            .withShortName( "dir" )
1484            .withShortName( "basedir" )
1485            .withDescription( "Base directory." )
1486            .withRequired( false )
1487            .withArgument(
1488              ARGUMENT_BUILDER 
1489                .withDescription( "Directory path." )
1490                .withName( "path" )
1491                .withMinimum( 1 )
1492                .withMaximum( 1 )
1493                .create() )
1494            .create();
1495        
1496        private static final Option TITLE_OPTION = 
1497          OPTION_BUILDER
1498            .withShortName( "title" )
1499            .withDescription( "Application title." )
1500            .withRequired( false )
1501            .withArgument(
1502              ARGUMENT_BUILDER 
1503                .withDescription( "Description." )
1504                .withName( "title" )
1505                .withMinimum( 1 )
1506                .withMaximum( 1 )
1507                .create() )
1508            .create();
1509        
1510        private static final PropertyOption PROPERTY_OPTION = new PropertyOption();
1511        private static final Option REQUIRED_URI_OPTION = buildURIOption( true );
1512        private static final Option OPTIONAL_URI_OPTION = buildURIOption( false );
1513        private static final NumberValidator INTERGER_VALIDATOR = NumberValidator.getIntegerInstance();
1514        
1515        private static final Option REGISTRY_URI_OPTION = 
1516            OPTION_BUILDER
1517              .withShortName( "registry" )
1518              .withDescription( "Application registry store." )
1519              .withRequired( false )
1520              .withArgument(
1521                ARGUMENT_BUILDER 
1522                  .withDescription( "Local or remote artifact reference." )
1523                  .withName( "artifact" )
1524                  .withMinimum( 1 )
1525                  .withMaximum( 1 )
1526                  .withValidator( new URIValidator() )
1527                  .create() )
1528              .create();
1529        
1530        private static final Option CONFIGURATION_URI_OPTION = 
1531            OPTION_BUILDER
1532              .withShortName( "config" )
1533              .withDescription( "Application configuration." )
1534              .withRequired( false )
1535              .withArgument(
1536                ARGUMENT_BUILDER 
1537                  .withDescription( "Configuration uri." )
1538                  .withName( "uri" )
1539                  .withMinimum( 1 )
1540                  .withMaximum( 1 )
1541                  .withValidator( new URIValidator() )
1542                  .create() )
1543              .create();
1544        
1545        private static final Option PORT_OPTION = 
1546          OPTION_BUILDER
1547            .withShortName( "port" )
1548            .withDescription( "RMI Registry port." )
1549            .withRequired( false )
1550            .withArgument(
1551              ARGUMENT_BUILDER 
1552                .withDescription( "Registry port." )
1553                .withName( "port" )
1554                .withMinimum( 0 )
1555                .withMaximum( 1 )
1556                .withValidator( INTERGER_VALIDATOR )
1557                .create() )
1558            .create();
1559        
1560        private static final Option STARTUP_TIMEOUT_OPTION = 
1561          OPTION_BUILDER
1562            .withShortName( "startup" )
1563            .withDescription( "Startup timeout." )
1564            .withRequired( false )
1565            .withArgument(
1566              ARGUMENT_BUILDER 
1567                .withDescription( "Timeout in seconds." )
1568                .withName( "seconds" )
1569                .withMinimum( 1 )
1570                .withMaximum( 1 )
1571                .withValidator( INTERGER_VALIDATOR )
1572                .create() )
1573            .create();
1574        
1575        private static final Option SHUTDOWN_TIMEOUT_OPTION = 
1576          OPTION_BUILDER
1577            .withShortName( "shutdown" )
1578            .withDescription( "Shutdown timeout." )
1579            .withRequired( false )
1580            .withArgument(
1581              ARGUMENT_BUILDER 
1582                .withDescription( "Timeout in seconds." )
1583                .withName( "seconds" )
1584                .withMinimum( 1 )
1585                .withMaximum( 1 )
1586                .withValidator( INTERGER_VALIDATOR )
1587                .create() )
1588            .create();
1589        
1590        private static final Group APPLICATION_REGISTRY_GROUP =
1591          GROUP_BUILDER
1592            .withMinimum( 0 )
1593            .withMaximum( 1 )
1594            .withOption( PORT_OPTION )
1595            .withOption( REGISTRY_URI_OPTION )
1596            .create();
1597        
1598        private static final Group STARTUP_GROUP =
1599          GROUP_BUILDER
1600            .withMinimum( 0 )
1601            .withMaximum( 2 )
1602            .withOption( PORT_OPTION )
1603            .withOption( REGISTRY_URI_OPTION )
1604            .withOption( PROPERTY_OPTION )
1605            .create();
1606        
1607        private static final Option STARTUP_COMMAND =
1608          COMMAND_BUILDER
1609            .withName( "startup" )
1610            .withDescription( "Startup the station." )
1611            .withChildren( STARTUP_GROUP )
1612            .create();
1613    
1614        private static final Option SHUTDOWN_COMMAND =
1615          COMMAND_BUILDER
1616            .withName( "shutdown" )
1617            .withDescription( "Shutdown the station." )
1618            .create();
1619    
1620        private static final Option HELP_COMMAND =
1621          COMMAND_BUILDER
1622            .withName( "help" )
1623            .withDescription( "Print command help." )
1624            .create();
1625        
1626        private static final Group ADD_OPTIONS_GROUP =
1627          GROUP_BUILDER
1628            .withMinimum( 1 )
1629            .withOption( REQUIRED_URI_OPTION )
1630            .withOption( STARTUP_POLICY_OPTION )
1631            .withOption( PROPERTY_OPTION )
1632            .withOption( BASEDIR_OPTION )
1633            .withOption( TITLE_OPTION )
1634            .withOption( REGISTRY_URI_OPTION )
1635            .withOption( STARTUP_TIMEOUT_OPTION )
1636            .withOption( SHUTDOWN_TIMEOUT_OPTION )
1637            .withOption( CONFIGURATION_URI_OPTION )
1638            .create();
1639            
1640        private static final Option ADD_COMMAND =
1641          COMMAND_BUILDER
1642            .withName( "add" )
1643            .withDescription( "Add a profile." )
1644            .withArgument(
1645              ARGUMENT_BUILDER 
1646                .withDescription( "Unique application key." )
1647                .withName( "key" )
1648                .withMinimum( 1 )
1649                .withMaximum( 1 )
1650                .create() )
1651            .withChildren( ADD_OPTIONS_GROUP )
1652            .create();
1653            
1654        private static final Group SET_OPTIONS_GROUP =
1655          GROUP_BUILDER
1656            .withMinimum( 0 )
1657            .withOption( OPTIONAL_URI_OPTION )
1658            .withOption( STARTUP_POLICY_OPTION )
1659            .withOption( PROPERTY_OPTION )
1660            .withOption( BASEDIR_OPTION )
1661            .withOption( TITLE_OPTION )
1662            .withOption( REGISTRY_URI_OPTION )
1663            .withOption( STARTUP_TIMEOUT_OPTION )
1664            .withOption( SHUTDOWN_TIMEOUT_OPTION )
1665            .withOption( CONFIGURATION_URI_OPTION )
1666            .create();
1667        
1668        private static final Option SET_COMMAND =
1669          COMMAND_BUILDER
1670            .withName( "set" )
1671            .withDescription( "Set an application feature." )
1672            .withArgument(
1673              ARGUMENT_BUILDER 
1674                .withDescription( "application key" )
1675                .withName( "key" )
1676                .withMinimum( 1 )
1677                .withMaximum( 1 )
1678                .create() )
1679            .withChildren( SET_OPTIONS_GROUP )
1680            .create();
1681            
1682        private static final Option CONTROL_COMMAND =
1683          COMMAND_BUILDER
1684            .withName( "control" )
1685            .withDescription( "Interactive control of an application." )
1686            .withArgument(
1687              ARGUMENT_BUILDER 
1688                .withDescription( "application key" )
1689                .withName( "key" )
1690                .withMinimum( 1 )
1691                .withMaximum( 1 )
1692                .create() )
1693            .create();
1694            
1695        private static final Option START_COMMAND =
1696          COMMAND_BUILDER
1697            .withName( "start" )
1698            .withDescription( "Start application." )
1699            .withArgument(
1700              ARGUMENT_BUILDER 
1701                .withDescription( "Application key." )
1702                .withName( "key" )
1703                .withMinimum( 1 )
1704                .withMaximum( 1 )
1705                .create() )
1706            .create();
1707        
1708        private static final Option STOP_COMMAND =
1709          COMMAND_BUILDER
1710            .withName( "stop" )
1711            .withDescription( "Stop application." )
1712            .withArgument(
1713              ARGUMENT_BUILDER 
1714                .withDescription( "Application key." )
1715                .withName( "key" )
1716                .withMinimum( 1 )
1717                .withMaximum( 1 )
1718                .create() )
1719            .create();
1720        
1721        private static final Option RESTART_COMMAND =
1722          COMMAND_BUILDER
1723            .withName( "restart" )
1724            .withDescription( "Restart application." )
1725            .withArgument(
1726              ARGUMENT_BUILDER 
1727                .withDescription( "Application key." )
1728                .withName( "key" )
1729                .withMinimum( 1 )
1730                .withMaximum( 1 )
1731                .create() )
1732            .create();
1733        
1734        private static final Option REMOVE_COMMAND =
1735          COMMAND_BUILDER
1736            .withName( "remove" )
1737            .withDescription( "Remove profile." )
1738            .withArgument(
1739              ARGUMENT_BUILDER 
1740                .withDescription( "unique application key" )
1741                .withName( "key" )
1742                .withMinimum( 1 )
1743                .withMaximum( 1 )
1744                .create() )
1745            .withChildren( APPLICATION_REGISTRY_GROUP )
1746            .create();
1747            
1748        private static final Option INFO_COMMAND =
1749          COMMAND_BUILDER
1750            .withName( "info" )
1751            .withDescription( "Station or profile info." )
1752            .withArgument(
1753              ARGUMENT_BUILDER 
1754                .withDescription( "Application key." )
1755                .withName( "key" )
1756                .withMinimum( 0 )
1757                .withMaximum( 1 )
1758                .create() )
1759            .withChildren( APPLICATION_REGISTRY_GROUP )
1760            .create();
1761    
1762        private static final Group COMMAND_GROUP =
1763          GROUP_BUILDER
1764            .withName( "options" )
1765            .withOption( STARTUP_COMMAND )
1766            .withOption( ADD_COMMAND )
1767            .withOption( SET_COMMAND )
1768            .withOption( START_COMMAND )
1769            .withOption( CONTROL_COMMAND )
1770            .withOption( STOP_COMMAND )
1771            .withOption( RESTART_COMMAND )
1772            .withOption( INFO_COMMAND )
1773            .withOption( REMOVE_COMMAND )
1774            .withOption( SHUTDOWN_COMMAND )
1775            .withOption( HELP_COMMAND )
1776            .withMinimum( 1 )
1777            .withMaximum( 1 )
1778            .create();
1779        
1780        // micro control cli spec
1781        
1782        private static final Option CONTROL_INFO_COMMAND =
1783          COMMAND_BUILDER
1784            .withName( "info" )
1785            .withDescription( "List state info." )
1786            .create();
1787        
1788        private static final Option CONTROL_EXIT_COMMAND =
1789          COMMAND_BUILDER
1790            .withName( "exit" )
1791            .withDescription( "Exit the console." )
1792            .create();
1793        
1794        private static final Option CONTROL_APPLY_COMMAND =
1795          COMMAND_BUILDER
1796            .withName( "apply" )
1797            .withDescription( "Apply a state transition." )
1798            .withArgument(
1799              ARGUMENT_BUILDER 
1800                .withDescription( "Transition name." )
1801                .withName( "id" )
1802                .withMinimum( 1 )
1803                .withMaximum( 1 )
1804                .create() )
1805            .create();
1806        
1807        private static final Option CONTROL_EXEC_COMMAND =
1808          COMMAND_BUILDER
1809            .withName( "exec" )
1810            .withDescription( "Execute a management operation." )
1811            .withArgument(
1812              ARGUMENT_BUILDER 
1813                .withDescription( "Operation name." )
1814                .withName( "id" )
1815                .withMinimum( 1 )
1816                .create() )
1817            .create();
1818        
1819        private static final Option CONTROL_INVOKE_COMMAND =
1820          COMMAND_BUILDER
1821            .withName( "invoke" )
1822            .withDescription( "Invoke a management operation." )
1823            .withArgument(
1824              ARGUMENT_BUILDER 
1825                .withDescription( "Operation name." )
1826                .withName( "method" )
1827                .withMinimum( 1 )
1828                .create() )
1829            .create();
1830        
1831        private static final Option CONTROL_HELP_COMMAND =
1832          COMMAND_BUILDER
1833            .withName( "help" )
1834            .withDescription( "List controller help info." )
1835            .create();
1836        
1837        private static final Group CONTROLLER_GROUP =
1838          GROUP_BUILDER
1839            .withName( "options" )
1840            .withOption( CONTROL_INFO_COMMAND )
1841            .withOption( CONTROL_APPLY_COMMAND )
1842            .withOption( CONTROL_INVOKE_COMMAND )
1843            .withOption( CONTROL_EXEC_COMMAND )
1844            .withOption( CONTROL_EXIT_COMMAND )
1845            .withOption( CONTROL_HELP_COMMAND )
1846            .withMinimum( 1 )
1847            .withMaximum( 1 )
1848            .create();
1849        
1850        private static Option buildURIOption( boolean required )
1851        {
1852            return OPTION_BUILDER
1853              .withShortName( "uri" )
1854              .withDescription( "Codebase uri." )
1855              .withRequired( required )
1856              .withArgument(
1857                ARGUMENT_BUILDER 
1858                  .withDescription( "Codebase uri." )
1859                  .withName( "artifact" )
1860                  .withMinimum( 1 )
1861                  .withMaximum( 1 )
1862                  .withValidator( new URIValidator() )
1863                  .create() )
1864              .create();
1865        }
1866        
1867        private static Set createStartupPolicySet()
1868        {
1869            Set set = new HashSet();
1870            StartupPolicy[] values = StartupPolicy.values();
1871            for( int i=0; i<values.length; i++ )
1872            {
1873                StartupPolicy policy = values[i];
1874                set.add( policy.getName() );
1875            }
1876            return set;
1877        }
1878    }
1879