1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31 package org.jomc.cli;
32
33 import java.io.BufferedReader;
34 import java.io.IOException;
35 import java.io.InputStreamReader;
36 import java.io.PrintWriter;
37 import java.io.StringReader;
38 import java.io.StringWriter;
39 import java.net.URL;
40 import java.util.ArrayList;
41 import java.util.Collections;
42 import java.util.Date;
43 import java.util.Enumeration;
44 import java.util.List;
45 import java.util.Locale;
46 import java.util.logging.Level;
47 import org.apache.commons.cli.CommandLine;
48 import org.apache.commons.cli.GnuParser;
49 import org.apache.commons.cli.HelpFormatter;
50 import org.apache.commons.cli.Option;
51 import org.apache.commons.cli.Options;
52 import org.apache.commons.cli.ParseException;
53 import org.apache.commons.lang.StringUtils;
54 import org.codehaus.mojo.animal_sniffer.IgnoreJRERequirement;
55
56
57
58
59
60
61 public final class Jomc
62 {
63
64
65
66
67 private static final Option DEBUG_OPTION;
68
69
70
71
72 private static final Option VERBOSE_OPTION;
73
74
75
76
77 private static final Option FAIL_ON_WARNINGS_OPTION;
78
79 static
80 {
81 DEBUG_OPTION = new Option( "D", Messages.getMessage( "debugOptionDescription" ) );
82 DEBUG_OPTION.setLongOpt( "debug" );
83 DEBUG_OPTION.setArgs( 1 );
84 DEBUG_OPTION.setOptionalArg( true );
85 DEBUG_OPTION.setArgName( Messages.getMessage( "debugOptionArgumentDescription" ) );
86
87 VERBOSE_OPTION = new Option( "v", Messages.getMessage( "verboseOptionDescription" ) );
88 VERBOSE_OPTION.setLongOpt( "verbose" );
89
90 FAIL_ON_WARNINGS_OPTION = new Option( "fw", Messages.getMessage( "failOnWarningsOptionDescription" ) );
91 FAIL_ON_WARNINGS_OPTION.setLongOpt( "fail-on-warnings" );
92 }
93
94
95
96
97
98
99 private static final Level DEFAULT_LOG_LEVEL = Level.WARNING;
100
101
102
103
104 private static volatile Level defaultLogLevel;
105
106
107
108
109 private volatile PrintWriter printWriter;
110
111
112
113
114 private volatile Level logLevel;
115
116
117
118
119 private volatile Level severity = Level.ALL;
120
121
122
123
124 public Jomc()
125 {
126 super();
127 }
128
129
130
131
132
133
134
135
136 @IgnoreJRERequirement
137 public PrintWriter getPrintWriter()
138 {
139 if ( this.printWriter == null )
140 {
141 try
142 {
143
144 Class.forName( "java.io.Console" );
145 this.printWriter = System.console() != null
146 ? System.console().writer()
147 : new PrintWriter( System.out, true );
148
149 }
150 catch ( final ClassNotFoundException e )
151 {
152 if ( this.isLoggable( Level.FINEST ) )
153 {
154 this.log( Level.FINEST, Messages.getMessage( e ), e );
155 }
156
157 this.printWriter = new PrintWriter( System.out, true );
158 }
159 }
160
161 return this.printWriter;
162 }
163
164
165
166
167
168
169
170
171 public void setPrintWriter( final PrintWriter value )
172 {
173 this.printWriter = value;
174 }
175
176
177
178
179
180
181
182
183
184
185
186
187
188 public static Level getDefaultLogLevel()
189 {
190 if ( defaultLogLevel == null )
191 {
192 defaultLogLevel = Level.parse( System.getProperty(
193 "org.jomc.cli.Jomc.defaultLogLevel", DEFAULT_LOG_LEVEL.getName() ) );
194
195 }
196
197 return defaultLogLevel;
198 }
199
200
201
202
203
204
205
206
207 public static void setDefaultLogLevel( final Level value )
208 {
209 defaultLogLevel = value;
210 }
211
212
213
214
215
216
217
218
219
220
221 public Level getLogLevel()
222 {
223 if ( this.logLevel == null )
224 {
225 this.logLevel = getDefaultLogLevel();
226
227 if ( this.isLoggable( Level.CONFIG ) )
228 {
229 this.log( Level.CONFIG, Messages.getMessage( "defaultLogLevelInfo", this.logLevel.getLocalizedName() ),
230 null );
231
232 }
233 }
234
235 return this.logLevel;
236 }
237
238
239
240
241
242
243
244
245
246 public void setLogLevel( final Level value )
247 {
248 this.logLevel = value;
249 }
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264 public boolean isLoggable( final Level level )
265 {
266 if ( level == null )
267 {
268 throw new NullPointerException( "level" );
269 }
270
271 return level.intValue() >= this.getLogLevel().intValue();
272 }
273
274
275
276
277
278
279
280
281
282
283
284 public int jomc( final String[] args )
285 {
286 Command cmd = null;
287 this.severity = Level.ALL;
288
289 try
290 {
291 final StringBuilder commandInfo = new StringBuilder( 1024 );
292
293 for ( final Command c : this.getCommands() )
294 {
295 if ( cmd == null && args != null && args.length > 0
296 && ( args[0].equals( c.getName() ) || args[0].equals( c.getAbbreviatedName() ) ) )
297 {
298 cmd = c;
299 }
300
301 commandInfo.append( StringUtils.rightPad( c.getName(), 25 ) ).
302 append( " : " ).
303 append( c.getShortDescription( Locale.getDefault() ) ).
304 append( " (" ).
305 append( c.getAbbreviatedName() ).
306 append( ")" ).
307 append( System.getProperty( "line.separator", "\n" ) );
308
309 }
310
311 if ( cmd == null )
312 {
313 this.getPrintWriter().println( Messages.getMessage( "usage", "help" ) );
314 this.getPrintWriter().println();
315 this.getPrintWriter().println( commandInfo.toString() );
316 return Command.STATUS_FAILURE;
317 }
318
319 final String[] commandArguments = new String[ args.length - 1 ];
320 System.arraycopy( args, 1, commandArguments, 0, commandArguments.length );
321
322 final Options options = cmd.getOptions();
323 options.addOption( DEBUG_OPTION );
324 options.addOption( VERBOSE_OPTION );
325 options.addOption( FAIL_ON_WARNINGS_OPTION );
326
327 if ( commandArguments.length > 0 && "help".equals( commandArguments[0] ) )
328 {
329 final StringWriter usage = new StringWriter();
330 final StringWriter opts = new StringWriter();
331 final HelpFormatter formatter = new HelpFormatter();
332
333 PrintWriter pw = new PrintWriter( usage );
334 formatter.printUsage( pw, 80, cmd.getName(), options );
335 pw.close();
336 assert !pw.checkError() : "Unexpected error printing usage.";
337
338 pw = new PrintWriter( opts );
339 formatter.printOptions( pw, 80, options, 2, 2 );
340 pw.close();
341 assert !pw.checkError() : "Unexpected error printing options.";
342
343 this.getPrintWriter().println( cmd.getShortDescription( Locale.getDefault() ) );
344 this.getPrintWriter().println();
345 this.getPrintWriter().println( usage.toString() );
346 this.getPrintWriter().println( opts.toString() );
347 this.getPrintWriter().println();
348
349 if ( cmd.getLongDescription( Locale.getDefault() ) != null )
350 {
351 this.getPrintWriter().println( cmd.getLongDescription( Locale.getDefault() ) );
352 this.getPrintWriter().println();
353 }
354
355 return Command.STATUS_SUCCESS;
356 }
357
358 cmd.getListeners().add( new Command.Listener()
359 {
360
361 public void onLog( final Level level, final String message, final Throwable t )
362 {
363 log( level, message, t );
364 }
365
366 } );
367
368
369 final CommandLine commandLine = new GnuParser().parse( options, commandArguments );
370 final boolean debug = commandLine.hasOption( DEBUG_OPTION.getOpt() );
371 final boolean verbose = commandLine.hasOption( VERBOSE_OPTION.getOpt() );
372 Level debugLevel = Level.ALL;
373
374 if ( debug )
375 {
376 final String debugOption = commandLine.getOptionValue( DEBUG_OPTION.getOpt() );
377
378 if ( debugOption != null )
379 {
380 debugLevel = Level.parse( debugOption );
381 }
382 }
383
384 if ( debug || verbose )
385 {
386 this.setLogLevel( debug ? debugLevel : Level.INFO );
387 }
388
389 cmd.setLogLevel( this.getLogLevel() );
390
391 if ( this.isLoggable( Level.FINER ) )
392 {
393 for ( int i = 0; i < args.length; i++ )
394 {
395 this.log( Level.FINER, new StringBuilder( 128 ).append( "[" ).append( i ).append( "] -> '" ).
396 append( args[i] ).append( "'" ).append( System.getProperty( "line.separator", "\n" ) ).
397 toString(), null );
398
399 }
400 }
401
402 final boolean failOnWarnings = commandLine.hasOption( FAIL_ON_WARNINGS_OPTION.getOpt() );
403
404 final int status = cmd.execute( commandLine );
405
406 if ( status == Command.STATUS_SUCCESS && failOnWarnings
407 && this.severity.intValue() >= Level.WARNING.intValue() )
408 {
409 return Command.STATUS_FAILURE;
410 }
411
412 return status;
413 }
414 catch ( final ParseException e )
415 {
416 this.log( Level.SEVERE, Messages.getMessage( "illegalArgumentsInformation", cmd.getName(), "help" ), e );
417 return Command.STATUS_FAILURE;
418 }
419 catch ( final Throwable t )
420 {
421 this.log( Level.SEVERE, null, t );
422 return Command.STATUS_FAILURE;
423 }
424 finally
425 {
426 this.getPrintWriter().flush();
427 this.severity = Level.ALL;
428 }
429 }
430
431
432
433
434
435
436 public static void main( final String[] args )
437 {
438 System.exit( run( args ) );
439 }
440
441
442
443
444
445
446
447
448
449
450
451 public static int run( final String[] args )
452 {
453 return new Jomc().jomc( args );
454 }
455
456
457
458
459
460
461
462
463
464
465 private void log( final Level level, final String message, final Throwable throwable )
466 {
467 if ( level == null )
468 {
469 throw new NullPointerException( "level" );
470 }
471
472 if ( this.severity.intValue() < level.intValue() )
473 {
474 this.severity = level;
475 }
476
477 if ( this.isLoggable( level ) )
478 {
479 if ( message != null )
480 {
481 this.getPrintWriter().print( this.formatLogLines( level, "" ) );
482 this.getPrintWriter().print( this.formatLogLines( level, message ) );
483 }
484
485 if ( throwable != null )
486 {
487 this.getPrintWriter().print( this.formatLogLines( level, "" ) );
488 final String m = Messages.getMessage( throwable );
489
490 if ( m != null && m.length() > 0 )
491 {
492 this.getPrintWriter().print( this.formatLogLines( level, m ) );
493 }
494 else
495 {
496 this.getPrintWriter().print( this.formatLogLines(
497 level, Messages.getMessage( "defaultExceptionMessage" ) ) );
498
499 }
500
501 if ( this.getLogLevel().intValue() < Level.INFO.intValue() )
502 {
503 final StringWriter stackTrace = new StringWriter();
504 final PrintWriter pw = new PrintWriter( stackTrace );
505 throwable.printStackTrace( pw );
506 pw.flush();
507 this.getPrintWriter().print( this.formatLogLines( level, stackTrace.toString() ) );
508 }
509 }
510 }
511
512 this.getPrintWriter().flush();
513 }
514
515 private String formatLogLines( final Level level, final String text )
516 {
517 BufferedReader reader = null;
518
519 try
520 {
521 final StringBuilder lines = new StringBuilder( text.length() );
522 reader = new BufferedReader( new StringReader( text ) );
523
524 for ( String line = reader.readLine(); line != null; line = reader.readLine() )
525 {
526 final boolean debug = this.getLogLevel().intValue() < Level.INFO.intValue();
527 lines.append( "[" ).append( level.getLocalizedName() );
528
529 if ( debug )
530 {
531 lines.append( "|" ).append( Thread.currentThread().getName() ).append( "|" ).
532 append( Messages.getMessage( "timePattern", new Date( System.currentTimeMillis() ) ) );
533
534 }
535
536 lines.append( "] " ).append( line ).append( System.getProperty( "line.separator", "\n" ) );
537 }
538
539 reader.close();
540 reader = null;
541
542 return lines.toString();
543 }
544 catch ( final IOException e )
545 {
546 throw new AssertionError( e );
547 }
548 finally
549 {
550 try
551 {
552 if ( reader != null )
553 {
554 reader.close();
555 }
556 }
557 catch ( final IOException e )
558 {
559 this.log( Level.SEVERE, Messages.getMessage( e ), e );
560 }
561 }
562 }
563
564
565
566
567
568
569
570
571 private List<Command> getCommands() throws IOException
572 {
573 final List<Command> commands = new ArrayList<Command>();
574
575 final Enumeration<URL> serviceResources =
576 this.getClass().getClassLoader().getResources( "META-INF/services/org.jomc.cli.Command" );
577
578 if ( serviceResources != null )
579 {
580 for ( final URL serviceResource : Collections.list( serviceResources ) )
581 {
582 BufferedReader reader = null;
583 try
584 {
585 reader = new BufferedReader( new InputStreamReader( serviceResource.openStream(), "UTF-8" ) );
586
587 for ( String line = reader.readLine(); line != null; line = reader.readLine() )
588 {
589 if ( !line.contains( "#" ) )
590 {
591 commands.add( Class.forName( line.trim() ).asSubclass( Command.class ).newInstance() );
592 }
593 }
594 }
595 catch ( final ClassNotFoundException e )
596 {
597 throw new AssertionError( e );
598 }
599 catch ( final InstantiationException e )
600 {
601 throw new AssertionError( e );
602 }
603 catch ( final IllegalAccessException e )
604 {
605 throw new AssertionError( e );
606 }
607 finally
608 {
609 try
610 {
611 if ( reader != null )
612 {
613 reader.close();
614 }
615 }
616 catch ( final IOException e )
617 {
618 this.log( Level.WARNING, Messages.getMessage( e ), e );
619 }
620 }
621 }
622 }
623
624 return Collections.unmodifiableList( commands );
625 }
626
627 }