AbstractCommand.java

  1. /*
  2.  * Copyright (C) 2009 Christian Schulte <cs@schulte.it>
  3.  * All rights reserved.
  4.  *
  5.  * Redistribution and use in source and binary forms, with or without
  6.  * modification, are permitted provided that the following conditions
  7.  * are met:
  8.  *
  9.  *   o Redistributions of source code must retain the above copyright
  10.  *     notice, this list of conditions and the following disclaimer.
  11.  *
  12.  *   o Redistributions in binary form must reproduce the above copyright
  13.  *     notice, this list of conditions and the following disclaimer in
  14.  *     the documentation and/or other materials provided with the
  15.  *     distribution.
  16.  *
  17.  * THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
  18.  * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
  19.  * AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
  20.  * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY DIRECT, INDIRECT,
  21.  * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
  22.  * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
  23.  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
  24.  * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
  25.  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
  26.  * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  27.  *
  28.  * $JOMC: AbstractCommand.java 5299 2016-08-30 01:50:13Z schulte $
  29.  *
  30.  */
  31. package org.jomc.cli.commands;

  32. import java.util.List;
  33. import java.util.Locale;
  34. import java.util.concurrent.CopyOnWriteArrayList;
  35. import java.util.concurrent.ExecutorService;
  36. import java.util.concurrent.Executors;
  37. import java.util.concurrent.ThreadFactory;
  38. import java.util.concurrent.atomic.AtomicInteger;
  39. import java.util.logging.Level;
  40. import org.apache.commons.cli.CommandLine;
  41. import org.jomc.cli.Command;

  42. /**
  43.  * Base {@code Command} implementation.
  44.  *
  45.  * @author <a href="mailto:cs@schulte.it">Christian Schulte</a>
  46.  */
  47. public abstract class AbstractCommand implements Command
  48. {

  49.     /**
  50.      * Default log level.
  51.      */
  52.     private static volatile Level defaultLogLevel;

  53.     /**
  54.      * Log level of the instance.
  55.      */
  56.     private volatile Level logLevel;

  57.     /**
  58.      * The listeners of the instance.
  59.      */
  60.     private volatile List<Listener> listeners = new CopyOnWriteArrayList<Listener>();

  61.     /**
  62.      * The {@code ExecutorService} of the command.
  63.      *
  64.      * @since 1.10
  65.      */
  66.     private volatile ExecutorService executorService;

  67.     /**
  68.      * Creates a new {@code AbstractCommand} instance.
  69.      */
  70.     public AbstractCommand()
  71.     {
  72.         super();
  73.     }

  74.     /**
  75.      * Gets the default log level events are logged at.
  76.      * <p>
  77.      * The default log level is controlled by system property
  78.      * {@code org.jomc.cli.commands.AbstractCommand.defaultLogLevel} holding the log level to log events at by
  79.      * default. If that property is not set, the {@code WARNING} default is returned.
  80.      * </p>
  81.      *
  82.      * @return The log level events are logged at by default.
  83.      *
  84.      * @see #getLogLevel()
  85.      * @see Level#parse(java.lang.String)
  86.      */
  87.     public static Level getDefaultLogLevel()
  88.     {
  89.         if ( defaultLogLevel == null )
  90.         {
  91.             defaultLogLevel = Level.parse( System.getProperty(
  92.                 "org.jomc.cli.commands.AbstractCommand.defaultLogLevel", Level.WARNING.getName() ) );

  93.         }

  94.         return defaultLogLevel;
  95.     }

  96.     /**
  97.      * Sets the default log level events are logged at.
  98.      *
  99.      * @param value The new default level events are logged at or {@code null}.
  100.      *
  101.      * @see #getDefaultLogLevel()
  102.      */
  103.     public static void setDefaultLogLevel( final Level value )
  104.     {
  105.         defaultLogLevel = value;
  106.     }

  107.     /**
  108.      * Gets the log level of the instance.
  109.      *
  110.      * @return The log level of the instance.
  111.      *
  112.      * @see #getDefaultLogLevel()
  113.      * @see #setLogLevel(java.util.logging.Level)
  114.      * @see #isLoggable(java.util.logging.Level)
  115.      */
  116.     public final Level getLogLevel()
  117.     {
  118.         if ( this.logLevel == null )
  119.         {
  120.             this.logLevel = getDefaultLogLevel();

  121.             if ( this.isLoggable( Level.CONFIG ) )
  122.             {
  123.                 this.log( Level.CONFIG, Messages.getMessage( "defaultLogLevelInfo", this.logLevel.getLocalizedName() ),
  124.                           null );

  125.             }
  126.         }

  127.         return this.logLevel;
  128.     }

  129.     /**
  130.      * Sets the log level of the instance.
  131.      *
  132.      * @param value The new log level of the instance or {@code null}.
  133.      *
  134.      * @see #getLogLevel()
  135.      * @see #isLoggable(java.util.logging.Level)
  136.      */
  137.     public final void setLogLevel( final Level value )
  138.     {
  139.         this.logLevel = value;
  140.     }

  141.     /**
  142.      * Gets the list of registered listeners.
  143.      * <p>
  144.      * This accessor method returns a reference to the live list, not a snapshot. Therefore any modification you make
  145.      * to the returned list will be present inside the object. This is why there is no {@code set} method for the
  146.      * listeners property.
  147.      * </p>
  148.      *
  149.      * @return The list of registered listeners.
  150.      *
  151.      * @see #log(java.util.logging.Level, java.lang.String, java.lang.Throwable)
  152.      */
  153.     public final List<Listener> getListeners()
  154.     {
  155.         return this.listeners;
  156.     }

  157.     /**
  158.      * Checks if a message at a given level is provided to the listeners of the instance.
  159.      *
  160.      * @param level The level to test.
  161.      *
  162.      * @return {@code true}, if messages at {@code level} are provided to the listeners of the instance;
  163.      * {@code false}, if messages at {@code level} are not provided to the listeners of the instance.
  164.      *
  165.      * @throws NullPointerException if {@code level} is {@code null}.
  166.      *
  167.      * @see #getLogLevel()
  168.      * @see #setLogLevel(java.util.logging.Level)
  169.      */
  170.     protected boolean isLoggable( final Level level )
  171.     {
  172.         if ( level == null )
  173.         {
  174.             throw new NullPointerException( "level" );
  175.         }

  176.         return level.intValue() >= this.getLogLevel().intValue();
  177.     }

  178.     /**
  179.      * Notifies registered listeners.
  180.      *
  181.      * @param level The level of the event.
  182.      * @param message The message of the event or {@code null}.
  183.      * @param throwable The throwable of the event {@code null}.
  184.      *
  185.      * @throws NullPointerException if {@code level} is {@code null}.
  186.      *
  187.      * @see #getListeners()
  188.      * @see #isLoggable(java.util.logging.Level)
  189.      */
  190.     protected void log( final Level level, final String message, final Throwable throwable )
  191.     {
  192.         if ( level == null )
  193.         {
  194.             throw new NullPointerException( "level" );
  195.         }

  196.         if ( this.isLoggable( level ) )
  197.         {
  198.             for ( final Listener l : this.getListeners() )
  199.             {
  200.                 l.onLog( level, message, throwable );
  201.             }
  202.         }
  203.     }

  204.     @Override
  205.     public org.apache.commons.cli.Options getOptions()
  206.     {
  207.         final org.apache.commons.cli.Options options = new org.apache.commons.cli.Options();
  208.         options.addOption( Options.THREADS_OPTION );
  209.         return options;
  210.     }

  211.     /**
  212.      * Gets the {@code ExecutorService} used to run tasks in parallel.
  213.      *
  214.      * @param commandLine The {@code CommandLine} to use for setting up an executor service when not already created.
  215.      *
  216.      * @return The {@code ExecutorService} used to run tasks in parallel or {@code null}.
  217.      *
  218.      * @since 1.10
  219.      */
  220.     protected final ExecutorService getExecutorService( final CommandLine commandLine )
  221.     {
  222.         if ( this.executorService == null )
  223.         {
  224.             final String formular =
  225.                 commandLine.hasOption( Options.THREADS_OPTION.getOpt() )
  226.                     ? commandLine.getOptionValue( Options.THREADS_OPTION.getOpt() ).toLowerCase( new Locale( "" ) )
  227.                     : "1.0c";

  228.             final Double parallelism =
  229.                 formular.contains( "c" )
  230.                     ? Double.valueOf( formular.replace( "c", "" ) ) * Runtime.getRuntime().availableProcessors()
  231.                     : Double.valueOf( formular );

  232.             if ( parallelism.intValue() > 1 )
  233.             {
  234.                 this.executorService = Executors.newFixedThreadPool(
  235.                     parallelism.intValue(), new ThreadFactory()
  236.                 {

  237.                     private final ThreadGroup group;

  238.                     private final AtomicInteger threadNumber = new AtomicInteger( 1 );


  239.                     {
  240.                         final SecurityManager s = System.getSecurityManager();
  241.                         this.group = s != null
  242.                                          ? s.getThreadGroup()
  243.                                          : Thread.currentThread().getThreadGroup();

  244.                     }

  245.                     @Override
  246.                     public Thread newThread( final Runnable r )
  247.                     {
  248.                         final Thread t =
  249.                             new Thread( this.group, r, "jomc-cli-" + this.threadNumber.getAndIncrement(), 0 );

  250.                         if ( t.isDaemon() )
  251.                         {
  252.                             t.setDaemon( false );
  253.                         }
  254.                         if ( t.getPriority() != Thread.NORM_PRIORITY )
  255.                         {
  256.                             t.setPriority( Thread.NORM_PRIORITY );
  257.                         }

  258.                         return t;
  259.                     }

  260.                 } );
  261.             }
  262.         }

  263.         return this.executorService;
  264.     }

  265.     @Override
  266.     public final int execute( final CommandLine commandLine )
  267.     {
  268.         if ( commandLine == null )
  269.         {
  270.             throw new NullPointerException( "commandLine" );
  271.         }

  272.         int status = STATUS_FAILURE;

  273.         try
  274.         {
  275.             if ( this.isLoggable( Level.INFO ) )
  276.             {
  277.                 this.log( Level.INFO, Messages.getMessage( "separator" ), null );
  278.                 this.log( Level.INFO, Messages.getMessage( "applicationTitle" ), null );
  279.                 this.log( Level.INFO, Messages.getMessage( "separator" ), null );
  280.                 this.log( Level.INFO, Messages.getMessage( "commandInfo", this.getName() ), null );
  281.             }

  282.             this.preExecuteCommand( commandLine );
  283.             this.executeCommand( commandLine );
  284.             status = STATUS_SUCCESS;
  285.         }
  286.         catch ( final Throwable t )
  287.         {
  288.             this.log( Level.SEVERE, null, t );
  289.             status = STATUS_FAILURE;
  290.         }
  291.         finally
  292.         {
  293.             try
  294.             {
  295.                 this.postExecuteCommand( commandLine );
  296.             }
  297.             catch ( final Throwable t )
  298.             {
  299.                 this.log( Level.SEVERE, null, t );
  300.                 status = STATUS_FAILURE;
  301.             }
  302.             finally
  303.             {
  304.                 if ( this.executorService != null )
  305.                 {
  306.                     this.executorService.shutdown();
  307.                     this.executorService = null;
  308.                 }
  309.             }
  310.         }

  311.         if ( this.isLoggable( Level.INFO ) )
  312.         {
  313.             if ( status == STATUS_SUCCESS )
  314.             {
  315.                 this.log( Level.INFO, Messages.getMessage( "commandSuccess", this.getName() ), null );
  316.             }
  317.             else if ( status == STATUS_FAILURE )
  318.             {
  319.                 this.log( Level.INFO, Messages.getMessage( "commandFailure", this.getName() ), null );
  320.             }

  321.             this.log( Level.INFO, Messages.getMessage( "separator" ), null );
  322.         }

  323.         return status;
  324.     }

  325.     /**
  326.      * Called by the {@code execute} method prior to the {@code executeCommand} method.
  327.      *
  328.      * @param commandLine The command line to execute.
  329.      *
  330.      * @throws NullPointerException if {@code commandLine} is {@code null}.
  331.      * @throws CommandExecutionException if executing the command fails.
  332.      *
  333.      * @see #execute(org.apache.commons.cli.CommandLine)
  334.      */
  335.     protected void preExecuteCommand( final CommandLine commandLine ) throws CommandExecutionException
  336.     {
  337.         if ( commandLine == null )
  338.         {
  339.             throw new NullPointerException( "commandLine" );
  340.         }
  341.     }

  342.     /**
  343.      * Called by the {@code execute} method prior to the {@code postExecuteCommand} method.
  344.      *
  345.      * @param commandLine The command line to execute.
  346.      *
  347.      * @throws CommandExecutionException if executing the command fails.
  348.      *
  349.      * @see #execute(org.apache.commons.cli.CommandLine)
  350.      */
  351.     protected abstract void executeCommand( final CommandLine commandLine ) throws CommandExecutionException;

  352.     /**
  353.      * Called by the {@code execute} method after the {@code preExecuteCommand}/{@code executeCommand} methods even if
  354.      * those methods threw an exception.
  355.      *
  356.      * @param commandLine The command line to execute.
  357.      *
  358.      * @throws NullPointerException if {@code commandLine} is {@code null}.
  359.      * @throws CommandExecutionException if executing the command fails.
  360.      *
  361.      * @see #execute(org.apache.commons.cli.CommandLine)
  362.      */
  363.     protected void postExecuteCommand( final CommandLine commandLine ) throws CommandExecutionException
  364.     {
  365.         if ( commandLine == null )
  366.         {
  367.             throw new NullPointerException( "commandLine" );
  368.         }
  369.     }

  370. }