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.depot;
020    
021    import java.io.File;
022    import java.net.URL;
023    import java.net.URI;
024    import java.rmi.RMISecurityManager;
025    import java.util.ArrayList;
026    import java.util.Date;
027    import java.util.logging.LogManager;
028    
029    import net.dpml.transit.Disposable;
030    import net.dpml.transit.Transit;
031    import net.dpml.transit.TransitError;
032    import net.dpml.transit.DefaultTransitModel;
033    import net.dpml.transit.model.TransitModel;
034    import net.dpml.transit.monitor.Adapter;
035    import net.dpml.transit.monitor.LoggingAdapter;
036    import net.dpml.transit.monitor.RepositoryMonitorAdapter;
037    import net.dpml.transit.monitor.CacheMonitorAdapter;
038    import net.dpml.transit.monitor.NetworkMonitorAdapter;
039    
040    import net.dpml.lang.Enum;
041    import net.dpml.lang.PID;
042    import net.dpml.lang.Part;
043    
044    import net.dpml.util.Logger;
045    import net.dpml.util.PropertyResolver;
046    
047    /**
048     * CLI hander for the depot package.
049     *
050     * @author <a href="http://www.dpml.net">Digital Product Meta Library</a>
051     * @version 1.2.0
052     */
053    public final class Main //implements ShutdownHandler
054    {
055        private static Main m_MAIN;
056        private static final PID PROCESS_ID = new PID();
057    
058        private Object m_plugin;
059        private boolean m_debug = false;
060        private boolean m_trace = false;
061        
062       /**
063        * Processes command line options to establish the command handler plugin to deploy.
064        * Command parameters recognixed by the console include the following:
065        * <ul>
066        *   <li>-Ddpml.depot.addplication=transit|station|metro|build</li>
067        *   <li>-debug</li>
068        * </ul>
069        * @param args the command line argument array
070        * @exception Exception if a error occurs
071        */
072        public static void main( String[] args )
073          throws Exception
074        {
075            if( null != m_MAIN )
076            {
077                final String error = 
078                  "Console already established.";
079                throw new IllegalArgumentException( error );
080            }
081            else
082            {
083                m_MAIN = new Main( args );
084            }
085        }
086        
087        private Main( String[] arguments )
088        {
089            //
090            // check for debug and trace cli options
091            //
092            
093            String[] args = arguments;
094            
095            if( CLIHelper.isOptionPresent( args, "-trace" ) )
096            {
097                args = CLIHelper.consolidate( args, "-trace" );
098                System.setProperty( "dpml.trace", "true" );
099                m_trace = true;
100            }
101            
102            if( CLIHelper.isOptionPresent( args, "-debug" ) )
103            {
104                args = CLIHelper.consolidate( args, "-debug" );
105                System.setProperty( "dpml.debug", "true" );
106                m_debug = true;
107            }
108            
109            args = processSystemProperties( args );
110    
111            //
112            // handle cli sub-system establishment
113            //
114            
115            Command command = getCommand( args );
116            if( Command.STATION.equals( command ) )
117            {
118                handleStation( args );
119            }
120            else
121            {
122                if( null == System.getProperty( "dpml.logging.config" ) )
123                {
124                    if( m_trace )
125                    {
126                        System.setProperty( "dpml.logging.config", "local:properties:dpml/transit/trace" );
127                    }
128                    else if( m_debug )
129                    {
130                        System.setProperty( "dpml.logging.config", "local:properties:dpml/transit/debug" );
131                    }
132                    else
133                    {
134                        System.setProperty( "dpml.logging.config", "local:properties:dpml/transit/default" );
135                    }
136                }
137            
138                if( m_debug || m_trace )
139                {
140                    for( int i=0; i<arguments.length; i++ )
141                    {
142                        getLogger().debug( "arg[" + i + "]: " + arguments[i] );
143                    }
144                }
145                
146                if( Command.BUILD.equals( command ) )
147                {
148                    handleBuild( args );
149                }
150                else if( Command.TRANSIT.equals( command ) )
151                {
152                    handleTransit( args );
153                }
154                else if( Command.METRO.equals( command ) )
155                {
156                    handleMetro( args );
157                }
158                else
159                {
160                    final String error = 
161                      "Missing application key '" + APPLICATION_KEY + "'.";
162                    System.err.println( error );
163                    System.exit( 1 );
164                }
165            }
166        }
167        
168        private void handleBuild( String[] args )
169        {
170            if( getLogger().isTraceEnabled() )
171            {
172                getLogger().trace( "launching builder" );
173            }
174            String name = "build";
175            String spec = "link:part:dpml/depot/dpml-library-build";
176            handlePlugin( name, spec, args, false );
177        }
178    
179        private void handleMetro( String[] args )
180        {
181            if( getLogger().isTraceEnabled() )
182            {
183                getLogger().trace( "launching metro" );
184            }
185            String name = "exec";
186            String spec = "link:part:dpml/station/dpml-station-exec";
187            handlePlugin( name, spec, args, true );
188        }
189    
190        private void handleTransit( String[] args )
191        {
192            String name = "transit";
193            String spec = "link:part:dpml/transit/dpml-transit-console";
194            handlePlugin( name, spec, args, false );
195        }
196    
197        private void handleStation( String[] args )
198        {
199            new File( Transit.DPML_DATA, "logs/station" ).mkdirs();
200            if( CLIHelper.isOptionPresent( args, "-server" ) )
201            {
202                if( m_trace )
203                {
204                    System.setProperty( "dpml.logging.level", "FINEST" );
205                }
206                else if( m_debug )
207                {
208                    System.setProperty( "dpml.logging.level", "FINE" );
209                }
210                if( getLogger().isTraceEnabled() )
211                {
212                    getLogger().trace( "launching station in server mode" );
213                }
214                String name = "station";
215                args = CLIHelper.consolidate( args, "-server" );
216                String spec = "link:part:dpml/station/dpml-station-server";
217                handlePlugin( name, spec, args, true );
218            }
219            else
220            {
221                if( getLogger().isTraceEnabled() )
222                {
223                    getLogger().trace( "launching station in client mode" );
224                }
225                String name = "station";
226                String spec = "link:part:dpml/station/dpml-station-console";
227                handlePlugin( name, spec, args, false );
228            }
229        }
230    
231        private void handlePlugin( String name, String spec, String[] args, boolean wait )
232        {
233            System.setSecurityManager( new RMISecurityManager() );
234            TransitModel model = getTransitModel( args );
235            boolean waitForCompletion = deployHandler( model, name, spec, args, wait );
236            if( !waitForCompletion )
237            {
238                if( m_plugin instanceof Disposable )
239                {
240                    Disposable disposable = (Disposable) m_plugin;
241                    disposable.dispose();
242                }
243                if( model instanceof DefaultTransitModel )
244                {
245                    DefaultTransitModel disposable = (DefaultTransitModel) model;
246                    disposable.dispose();
247                }
248                System.exit( 0 );
249            }
250        }
251        
252        private boolean deployHandler( 
253          TransitModel model, String command, String path, String[] args, boolean waitFor )
254        {
255            Logger logger = getLogger();
256            if( logger.isDebugEnabled() )
257            {
258                logger.debug( "date: " + new Date() );
259                logger.debug( "system: " + command );
260                logger.debug( "uri: " + path );
261                logger.debug( "args: [" + toString( args ) + "]" );
262                logger.debug( "system classloader: [" 
263                  + System.identityHashCode( ClassLoader.getSystemClassLoader() ) 
264                  + "]" );
265            }
266            Logger log = resolveLogger( logger, command );
267            try
268            {
269                URI uri = new URI( path );
270                Transit transit = Transit.getInstance( model );
271                setupMonitors( transit, (Adapter) logger );
272                
273                Part part = Part.load( uri, true );
274                m_plugin = 
275                  part.instantiate( 
276                    new Object[]
277                    {
278                        model, 
279                        args, 
280                        log
281                    }
282                  );
283            }
284            catch( GeneralException e )
285            {
286                getLogger().error( e.getMessage() );
287                System.exit( 1 );
288            }
289            catch( Exception e )
290            {
291                Throwable cause = e.getCause();
292                if( ( null != cause ) && ( cause instanceof GeneralException ) )
293                {
294                    getLogger().error( cause.getMessage() );
295                    System.exit( 1 );
296                }
297                else
298                {
299                    getLogger().error( e.getMessage(), e.getCause() );
300                    System.exit( 1 );
301                }
302            }
303            catch( Throwable e )
304            {
305                final String error = 
306                  "Deloyment failure." 
307                  + "\nTarget: " + command 
308                  + "\n   URI: " + path;
309                getLogger().error( error, e );
310                System.exit( 1 );
311            }
312            
313            if( m_plugin instanceof Runnable )
314            {
315                getLogger().debug( "starting " + m_plugin.getClass().getName() );
316                Thread thread = new Thread( (Runnable) m_plugin );
317                thread.start();
318                setShutdownHook( thread );
319                return true;
320            }
321            else
322            {
323                getLogger().debug( "deployed " + m_plugin.getClass().getName() );
324                return waitFor;
325            }
326        }
327        
328        private Logger resolveLogger( Logger logger, String command )
329        {
330            String partition = System.getProperty( "dpml.station.partition", null );
331            if( null != partition )
332            {
333                return new LoggingAdapter( partition );
334            }
335            else
336            {
337                return logger.getChildLogger( command );
338            }
339        }
340        
341        private TransitModel getTransitModel( String[] args )
342        {
343            final String key = "dpml.transit.model";
344            String property = null;
345            for( int i=0; i<args.length; i++ )
346            {
347                String arg = args[i];
348                if( arg.startsWith( "-D" + key + "=" ) )
349                {
350                    property = arg.substring( 21 );
351                    break;
352                }
353            }
354            
355            if( null != property )
356            {
357                if( property.startsWith( "registry:" ) )
358                {
359                    try
360                    {
361                        return (TransitModel) new URL( property ).getContent( 
362                          new Class[]{TransitModel.class} );
363                    }
364                    catch( Exception e )
365                    {
366                        final String error = 
367                          "Unable to resolve registry reference: " + property;
368                        throw new TransitError( error, e );
369                    }
370                }
371                else
372                {
373                    final String error = 
374                      "System property value for the key ': "
375                      + key 
376                      + "' contains an unrecognized value: "
377                      + property;
378                    throw new TransitError( error );
379                }
380            }
381            
382            //
383            // otherwise let Transit handle model creation
384            //
385            
386            try
387            {
388                Logger logger = getLogger().getChildLogger( "transit" );
389                return DefaultTransitModel.getDefaultModel( logger );
390            }
391            catch( Exception e )
392            {
393                final String error = 
394                  "Transit model establishment failure.";
395                throw new TransitError( error, e );
396            }
397        }
398        
399        private static Logger getLogger()
400        {
401            if( null == m_LOGGER )
402            {
403                try
404                {
405                    LogManager.getLogManager().readConfiguration();
406                }
407                catch( Throwable e )
408                {
409                    e.printStackTrace();
410                }
411                String category = System.getProperty( "dpml.logging.category", "depot" );
412                m_LOGGER = new LoggingAdapter( java.util.logging.Logger.getLogger( category ) );
413            }
414            return m_LOGGER;
415        }
416    
417        private String toString( String[] args )
418        {
419            StringBuffer buffer = new StringBuffer();
420            for( int i=0; i<args.length; i++ )
421            {
422                if( i > 0 )
423                {
424                    buffer.append( ", " );
425                }
426                buffer.append( args[i] );
427            }
428            return buffer.toString();
429        }
430        
431       /**
432        * For all of the supplied command line arguments, if the 
433        * argument is in the form -Dabc=def then extract the argument from
434        * the array and apply it as a system property.  All non-system property
435        * arguments are included in the returned argument array.
436        *
437        * @param args the supplied commandline arguments including 
438        *   system property assignments
439        * @return the array of pure command line arguments (excluding
440        *   and arg values recognized as system property declarations
441        */
442        private String[] processSystemProperties( String[] args )
443        {
444            ArrayList result = new ArrayList();
445            for( int i=0; i < args.length; i++ )
446            {
447                String arg = args[i];
448                int index = arg.indexOf( "=" );
449                if( index > -1 && arg.startsWith( "-D" ) )
450                {
451                    String name = arg.substring( 2, index );
452                    String raw = arg.substring( index + 1 );
453                    String value = PropertyResolver.resolve( raw );
454                    System.setProperty( name, value );
455                }
456                else
457                {
458                    result.add( arg );
459                }
460            }
461            return (String[]) result.toArray( new String[0] );
462        }
463    
464        //--------------------------------------------------------------------------
465        // static utilities for setup of logging manager and root prefs
466        //--------------------------------------------------------------------------
467    
468       /**
469        * Setup the monitors.
470        */
471        private static void setupMonitors( Transit instance, Adapter adapter ) throws Exception
472        {
473            instance.getRepositoryMonitorRouter().addMonitor(
474              new RepositoryMonitorAdapter( adapter ) );
475            instance.getCacheMonitorRouter().addMonitor(
476              new CacheMonitorAdapter( adapter ) );
477            instance.getNetworkMonitorRouter().addMonitor(
478              new NetworkMonitorAdapter( adapter ) );
479        }
480        
481       /**
482        * Create a shutdown hook that will trigger shutdown of the supplied plugin.
483        * @param thread the application thread
484        */
485        public static void setShutdownHook( final Thread thread )
486        {
487            //
488            // Create a shutdown hook to trigger clean disposal of the
489            // controller
490            //
491            
492            Runtime.getRuntime().addShutdownHook(
493              new Thread()
494              {
495                  public void run()
496                  {
497                      try
498                      {
499                          thread.interrupt();
500                      }
501                      catch( Throwable e )
502                      {
503                          boolean ignorable = true;
504                      }
505                      System.runFinalization();
506                  }
507              }
508            );
509        }
510    
511       /**
512        * DPML build key.
513        */
514        private static final String BUILD_KEY = "dpml.build";
515    
516       /**
517        * The Depot system version.
518        */
519        private static final String BUILD_ID = "1.2.0";
520    
521        static
522        {
523            setSystemProperty( "java.protocol.handler.pkgs", "net.dpml.transit" );
524            setSystemProperty( "java.util.logging.config.class", "net.dpml.util.ConfigurationHandler" );
525            setSystemProperty( "java.rmi.server.RMIClassLoaderSpi", "net.dpml.depot.DepotRMIClassLoaderSpi" );
526            setSystemProperty( Transit.SYSTEM_KEY, Transit.DPML_SYSTEM.getAbsolutePath() );
527            setSystemProperty( Transit.HOME_KEY, Transit.DPML_HOME.getAbsolutePath() );
528            setSystemProperty( Transit.DATA_KEY, Transit.DPML_DATA.getAbsolutePath() );
529            setSystemProperty( Transit.PREFS_KEY, Transit.DPML_PREFS.getAbsolutePath() );
530            setSystemProperty( BUILD_KEY, BUILD_ID );
531        }
532    
533        private static void setSystemProperty( String key, String value )
534        {
535            if( null == System.getProperty( key ) )
536            {
537                System.setProperty( key, value );
538            }
539        } 
540    
541        private static Logger m_LOGGER = null;
542        
543        private Command getCommand( String[] args )
544        {
545            String ref = getApplicationReference( args );
546            String app = System.getProperty( APPLICATION_KEY, ref );
547            return Command.parse( app );
548        }
549    
550        private String getApplicationReference( String[] args )
551        {
552            String key = "-D" + APPLICATION_KEY + "=";
553            for( int i=0; i<args.length; i++ )
554            {
555                String arg = args[i];
556                if( arg.startsWith( key ) )
557                {
558                    return arg.substring( 25 );
559                }
560            }
561            return null;
562        }
563    
564       /**
565        * Application selection key.
566        */
567        public static final String APPLICATION_KEY = "dpml.depot.application";
568        
569       /**
570        * Application identifier enumeration.
571        */
572        private static final class Command extends Enum
573        {
574            static final long serialVersionUID = 1L;
575    
576           /**
577            * Transit command id.
578            */
579            public static final Command TRANSIT = new Command( "dpml.transit" );
580    
581           /**
582            * Metro command id.
583            */
584            public static final Command METRO = new Command( "dpml.metro" );
585        
586           /**
587            * Station command id.
588            */
589            public static final Command STATION = new Command( "dpml.station" );
590        
591           /**
592            * Builder command id.
593            */
594            public static final Command BUILD = new Command( "dpml.builder" );
595        
596           /**
597            * Internal constructor.
598            * @param label the enumeration label.
599            */
600            private Command( String label )
601            {
602                super( label );
603            }
604            
605           /**
606            * Create a now mode using a supplied mode name.
607            * @param value the mode name
608            * @return the mode
609            * @exception NullPointerException if the supplied value is null
610            * @exception IllegalArgumentException if the supplied value is not recognized
611            */
612            public static Command parse( String value ) throws NullPointerException, IllegalArgumentException
613            {
614                if( null == value )
615                {
616                    final String error = 
617                      "Undefined sub-system identifier."
618                      + "\nThe depot cli handler must be supplied with an -D"
619                      + APPLICATION_KEY + "=[id] where id is one of the value 'dpml.metro', "
620                      + "'dpml.transit', 'dpml.station' or 'dpml.build'.";
621                    throw new NullPointerException( error ); 
622                }
623                if( value.equalsIgnoreCase( "dpml.metro" ) )
624                {
625                    return METRO;
626                }
627                else if( value.equalsIgnoreCase( "dpml.transit" ) )
628                {
629                    return TRANSIT;
630                }
631                else if( value.equalsIgnoreCase( "dpml.station" ) )
632                {
633                    return STATION;
634                }
635                else if( value.equalsIgnoreCase( "dpml.builder" ) )
636                {
637                    return BUILD;
638                }
639                else
640                {
641                    final String error =
642                      "Unrecognized application id [" + value + "]";
643                    throw new IllegalArgumentException( error );
644                }
645            }
646        }
647    }
648