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