AbstractListener.java

package gov.usgs.earthquake.product;

import gov.usgs.util.Config;
import gov.usgs.util.DefaultConfigurable;
import gov.usgs.util.StringUtils;

import java.util.ArrayList;
import java.util.List;
import java.util.logging.Logger;

/**
 * A base listener implementation for both NotificationListener and
 * IndexerListener implementations.
 *
 * The AbstractListener implements the Configurable interface and defines the
 * following configuration parameters:
 *
 * <dl>
 * <dt>includeTypes</dt>
 * <dd>(Optional) A comma separated list of product types to include. If this
 * list is defined it constitutes an "allow" list, and only types in this list
 * are allowed.</dd>
 *
 * <dt>excludeTypes</dt>
 * <dd>(Optional) A comma separated list of product types to exclude. If this
 * list is defined it constitutes a "block" list, and types in this list are not
 * allowed.</dd>
 *
 * <dt>includeSources</dt>
 * <dd>(Optional) A comma separated list of product types to include. If this
 * list is defined it constitutes an "allow" list, and only types in this list
 * are allowed.</dd>
 *
 * <dt>excludeSources</dt>
 * <dd>(Optional) A comma separated list of product types to exclude. If this
 * list is defined it constitutes a "block" list, and types in this list are not
 * allowed.</dd>
 *
 * <dt>includeTests</dt>
 * <dd>(Optional, Default false) Flag to indicate test products should be
 * accepted. Set to "true" or "yes" to enable.</dd>
 *
 * <dt>includeScenarios</dt>
 * <dd>(Optional, Default false) Flag to indicate scenario products should be
 * accepted. Set to "true" or "yes" to enable.</dd>
 *
 * <dt>includeActuals</dt>
 * <dd>(Optional, Default true) Flag to indicate Actual products should be
 * accepted. Set to "true" or "yes" to enable.</dd>
 *
 * <dt>includeInternals</dt>
 * <dd>(Optional, Default false) Flag to indicate internal products should be
 * accepted. Set to "true" or "yes" to enable.</dd>
 *
 * <dt>includeDevelopments</dt>
 * <dd>(Optional, Default false) Flag to indicate development products should be
 * accepted. Set to "true" or "yes" to enable.</dd>
 *
 * <dt>maxTries</dt>
 * <dd>(Optional, Default 1) Number of times to attempt delivery of each
 * notification, if the listener throws a ContinuableListenerException during
 * onNotification(). A value &lt;= 1 means do not re-attempt.</dd>
 *
 * <dt>timeout</dt>
 * <dd>(Optional, Default 0) Number of milliseconds before thread running
 * onNotification() is interrupted. A value &lt;= 0 means never interrupt.</dd>
 *
 * <dt>retryDelay</dt>
 * <dd>(Optional, Default 0) Number of milliseconds to wait before re-attempting
 * processing of a notification, if the listener throws a
 * ContinuableListenerException during onNotification(). A value &lt;= 0 means
 * no delay.</dd>
 * </dl>
 *
 * <p>
 * When excludeTypes (or sources) and includeTypes (or sources) are defined,
 * excludes are processed first. This is usually not recommended.
 * </p>
 */
public class AbstractListener extends DefaultConfigurable {

  /** Logging object. */
  private static final Logger LOGGER = Logger.getLogger(AbstractListener.class.getName());

  // --------------------------------------------------
  // Configurable property names
  // --------------------------------------------------

  /** Configuration parameter for include types list. */
  public static final String INCLUDE_TYPES_PROPERTY = "includeTypes";

  /** Configuration parameter for exclude types list. */
  public static final String EXCLUDE_TYPES_PROPERTY = "excludeTypes";

  /** Configuration parameter for include sources list. */
  public static final String INCLUDE_SOURCES_PROPERTY = "includeSources";

  /** Configuration parameter for exclude sources list. */
  public static final String EXCLUDE_SOURCES_PROPERTY = "excludeSources";

  /** Flag to indicate if scenario type products should be included **/
  public static final String INCLUDE_TESTS_PROPERTY = "includeTests";

  /** Flag to indicate if scenario type products should be included **/
  public static final String INCLUDE_SCENARIOS_PROPERTY = "includeScenarios";

  /** Flag to indicate if actual type products should be included **/
  public static final String INCLUDE_ACTUALS_PROPERTY = "includeActuals";

  /** Flag to indicate if scenario type products should be included **/
  public static final String INCLUDE_INTERNALS_PROPERTY = "includeInternals";

  /** Flag to indicate if scenario type products should be included **/
  public static final String INCLUDE_DEVELOPMENTS_PROPERTY = "includeDevelopments";

  /**
   * Configuration parameters for attemptCount.
   *
   * @deprecated Use MAX_TRIES_PROPERTY instead.
   */
  @Deprecated
  public static final String ATTEMPT_COUNT_PROPERTY = "attemptCount";

  /** Configuration parameters for maxTries. */
  public static final String MAX_TRIES_PROPERTY = "maxTries";

  /** Configuration parameter for timeout. */
  public static final String TIMEOUT_PROPERTY = "timeout";

  /** Configuration parameter for retryDelay. */
  public static final String RETRY_DELAY_PROPERTY = "retryDelay";

  // --------------------------------------------------
  // Default values for configurable settings
  // --------------------------------------------------

  // Default setting for include/exclude types/sources variables are to
  // remain empty lists. Default interpretation of this setting is defined in
  // the "accept" method and will accept all types/sources. This can be
  // overridden in a subclass.

  /** Default, do not include tests */
  private static final boolean DEFAULT_INCLUDE_TESTS = false;

  /** Default, do not include scenarios */
  private static final boolean DEFAULT_INCLUDE_SCENARIOS = false;

  /** Default, do include actuals */
  private static final boolean DEFAULT_INCLUDE_ACTUALS = true;

  /** Default, do not include internals */
  private static final boolean DEFAULT_INCLUDE_INTERNALS = false;

  /** Default, do not include developments */
  private static final boolean DEFAULT_INCLUDE_DEVELOPMENTS = false;

  /** Default attempt count value is 1 = don't retry. */
  public static final int DEFAULT_ATTEMPT_COUNT = 1;

  /** Default timeout is 0 = infinity. */
  public static final long DEFAULT_TIMEOUT = 0L;

  /** Default delay is 300000ms = 5 minute delay. */
  public static final long DEFAULT_RETRY_DELAY = 300000L;

  // --------------------------------------------------
  // Member variables
  // --------------------------------------------------

  /** Types of products to allow. */
  private final ArrayList<String> includeTypes = new ArrayList<String>();

  /** Types of products to block. */
  private final ArrayList<String> excludeTypes = new ArrayList<String>();

  /** Sources of products to allow. */
  private final ArrayList<String> includeSources = new ArrayList<String>();

  /** Sources of products to block. */
  private final ArrayList<String> excludeSources = new ArrayList<String>();

  /** Whether or not to include test type products */
  private boolean includeTests = DEFAULT_INCLUDE_TESTS;

  /** Whether or not to include scenario type products */
  private boolean includeScenarios = DEFAULT_INCLUDE_SCENARIOS;

  /** Whether or not to include actual type products */
  private boolean includeActuals = DEFAULT_INCLUDE_ACTUALS;

  /** Whether or not to include internal type products */
  private boolean includeInternals = DEFAULT_INCLUDE_INTERNALS;

  /** Whether or not to include development type products */
  private boolean includeDevelopments = DEFAULT_INCLUDE_DEVELOPMENTS;

  /** Number of retries when a continuable listener exception is thrown. */
  private int maxTries = DEFAULT_ATTEMPT_COUNT;

  /** Number of milliseconds before the onNotification thread is interrupted. */
  private long timeout = DEFAULT_TIMEOUT;

  /** Number of milliseconds before retries, after the first attempt fails. */
  private long retryDelay = DEFAULT_RETRY_DELAY;

  /**
   * Determines if a listener accepts a message based on the incoming product id.
   *
   * @param id The incoming id of the product that triggered the message.
   *
   * @return True if the message should be processed, false otherwise.
   */
  public boolean accept(ProductId id) {
    String type = id.getType();
    String source = id.getSource();

    // excluded type
    if (excludeTypes.size() > 0 && excludeTypes.contains(type)) {
      LOGGER.finer("[" + getName() + "] product type '" + type + "' excluded");
      return false;
    }

    // not included type
    if (includeTypes.size() > 0 && !includeTypes.contains(type)) {
      LOGGER.finer("[" + getName() + "] product type '" + type + "' not included");
      return false;
    }

    // excluded source
    if (excludeSources.size() > 0 && excludeSources.contains(source)) {
      LOGGER.finer("[" + getName() + "] product source '" + source + "' excluded");
      return false;
    }

    // not included source
    if (includeSources.size() > 0 && !includeSources.contains(source)) {
      LOGGER.finer("[" + getName() + "] product source '" + source + "' not included");
      return false;
    }

    if (type.endsWith("-test") && !includeTests) {
      LOGGER.finer("[" + getName() + "] product type was test. Not included.");
      return false;
    }

    if (type.endsWith("-scenario") && !includeScenarios) {
      LOGGER.finer("[" + getName() + "] product type was scenario. Not included.");
      return false;
    }

    if (!includeActuals && !type.endsWith("-scenario") && !type.startsWith("internal-") && !type.endsWith("-devel")) {
      LOGGER.finer("[" + getName() + "] product type was actual. Not included.");
      return false;
    }

    if (type.startsWith("internal-") && !includeInternals) {
      LOGGER.finer("[" + getName() + "] product type was internal. Not included.");
      return false;
    }

    if (type.endsWith("-devel") && !includeDevelopments) {
      LOGGER.finer("[" + getName() + "] product type was development version. Not included.");
      return false;
    }

    // otherwise accept
    return true;
  }

  /**
   * Read the include and exclude types from config.
   */
  public void configure(final Config config) throws Exception {
    String includeTypeNames = config.getProperty(INCLUDE_TYPES_PROPERTY);
    if (includeTypeNames != null) {
      includeTypes.addAll(StringUtils.split(includeTypeNames, ","));
      LOGGER.config("[" + getName() + "] includeTypes = " + includeTypeNames);
    }
    String excludeTypeNames = config.getProperty(EXCLUDE_TYPES_PROPERTY);
    if (excludeTypeNames != null) {
      excludeTypes.addAll(StringUtils.split(excludeTypeNames, ","));
      LOGGER.config("[" + getName() + "] excludeTypes = " + excludeTypeNames);
    }

    String includeSourceNames = config.getProperty(INCLUDE_SOURCES_PROPERTY);
    if (includeSourceNames != null) {
      includeSources.addAll(StringUtils.split(includeSourceNames, ","));
      LOGGER.config("[" + getName() + "] includeSources = " + includeSourceNames);
    }
    String excludeSourceNames = config.getProperty(EXCLUDE_SOURCES_PROPERTY);
    if (excludeSourceNames != null) {
      excludeSources.addAll(StringUtils.split(excludeSourceNames, ","));
      LOGGER.config("[" + getName() + "] excludeSources = " + excludeSourceNames);
    }

    String includeFlag = config.getProperty(INCLUDE_TESTS_PROPERTY);
    if (includeFlag != null) {
      includeTests = (includeFlag.equalsIgnoreCase("yes") || includeFlag.equalsIgnoreCase("true"));
      LOGGER.config("[" + getName() + "] includeTests = " + includeTests);
    } else {
      includeDevelopments = DEFAULT_INCLUDE_TESTS;
    }

    includeFlag = config.getProperty(INCLUDE_SCENARIOS_PROPERTY);
    if (includeFlag != null) {
      includeScenarios = (includeFlag.equalsIgnoreCase("yes") || includeFlag.equalsIgnoreCase("true"));
      LOGGER.config("[" + getName() + "] includeScenarios = " + includeScenarios);
    } else {
      includeDevelopments = DEFAULT_INCLUDE_SCENARIOS;
    }

    includeFlag = config.getProperty(INCLUDE_ACTUALS_PROPERTY);
    if (includeFlag != null) {
      includeActuals = !(includeFlag.equalsIgnoreCase("no") || includeFlag.equalsIgnoreCase("false"));
      LOGGER.config("[" + getName() + "] includeActuals = " + includeActuals);
    }

    includeFlag = config.getProperty(INCLUDE_INTERNALS_PROPERTY);
    if (includeFlag != null) {
      includeInternals = (includeFlag.equalsIgnoreCase("yes") || includeFlag.equalsIgnoreCase("true"));
      LOGGER.config("[" + getName() + "] includeInternals = " + includeInternals);
    } else {
      includeDevelopments = DEFAULT_INCLUDE_INTERNALS;
    }

    includeFlag = config.getProperty(INCLUDE_DEVELOPMENTS_PROPERTY);
    if (includeFlag != null) {
      includeDevelopments = (includeFlag.equalsIgnoreCase("yes") || includeFlag.equalsIgnoreCase("true"));
      LOGGER.config("[" + getName() + "] includeDevelopments = " + includeDevelopments);
    } else {
      includeDevelopments = DEFAULT_INCLUDE_DEVELOPMENTS;
    }

    String maxTriesStr = config.getProperty(ATTEMPT_COUNT_PROPERTY);
    if (maxTriesStr != null) {
      maxTries = Integer.parseInt(maxTriesStr);
      LOGGER.config("[" + getName() + "] Configured using deprecated '" + ATTEMPT_COUNT_PROPERTY
          + "' property. Prefer to use the new '" + MAX_TRIES_PROPERTY
          + "' instead. Support for the deprecated property may be " + "removed in a subsequent release.");
    }
    LOGGER.config("[" + getName() + "] maxTries = " + maxTries);

    // By checking for this parameter after the ATTEMPT_COUNT_PROPERTY, the
    // value of the MAX_TRIES_PROPERTY (if specified) will take precedence.
    maxTriesStr = config.getProperty(MAX_TRIES_PROPERTY);
    if (maxTriesStr != null) {
      maxTries = Integer.parseInt(maxTriesStr);
    }
    LOGGER.config("[" + getName() + "] maxTries = " + maxTries);

    String timeoutStr = config.getProperty(TIMEOUT_PROPERTY);
    if (timeoutStr != null) {
      timeout = Long.parseLong(timeoutStr);
    }
    LOGGER.config("[" + getName() + "] timeout = " + timeout + "ms");

    String retryDelayStr = config.getProperty(RETRY_DELAY_PROPERTY);
    if (retryDelayStr != null) {
      retryDelay = Long.parseLong(retryDelayStr);
    }
    LOGGER.config("[" + getName() + "] retry delay = " + retryDelay + "ms");
  }

  /**
   * @return the includeTypes
   */
  public List<String> getIncludeTypes() {
    return includeTypes;
  }

  /**
   * @return the excludeTypes
   */
  public List<String> getExcludeTypes() {
    return excludeTypes;
  }

  /**
   * @return the includeSources
   */
  public List<String> getIncludeSources() {
    return includeSources;
  }

  /**
   * @return the excludeSources
   */
  public List<String> getExcludeSources() {
    return excludeSources;
  }

  /**
   * Number of tries to deliver notification, when an Exception is thrown during
   * onNotification().
   *
   * @return the attemptCount. A value &lt; 1 means never try to deliver.
   */
  public int getMaxTries() {
    return maxTries;
  }

  /**
   * Set the maxTries.
   *
   * @param maxTries the maxTries. A value &lt; 1 means never try to deliver.
   */
  public void setMaxTries(final int maxTries) {
    this.maxTries = maxTries;
  }

  /**
   * Number of milliseconds onNotification is allowed to run before being
   * interrupted.
   *
   * @return the timeout in milliseconds. A value &lt;= 0 means never timeout.
   */
  public long getTimeout() {
    return timeout;
  }

  /**
   * Set the timeout.
   *
   * @param timeout the timeout in milliseconds. A value &lt;= 0 means never
   *                timeout.
   */
  public void setTimeout(final long timeout) {
    this.timeout = timeout;
  }

  /**
   * @return the includeTests
   */
  public boolean isIncludeTests() {
    return includeTests;
  }

  /**
   * @param includeTests the includeTests to set
   */
  public void setIncludeTests(boolean includeTests) {
    this.includeTests = includeTests;
  }

  /**
   * @return the includeScenarios
   */
  public boolean isIncludeScenarios() {
    return includeScenarios;
  }

  /**
   * @param includeScenarios the includeScenarios to set
   */
  public void setIncludeScenarios(boolean includeScenarios) {
    this.includeScenarios = includeScenarios;
  }

  /**
   * @return the includeActuals
   */
  public boolean isIncludeActuals() {
    return includeActuals;
  }

  /**
   * @param includeActuals the includeActuals to set
   */
  public void setIncludeActuals(boolean includeActuals) {
    this.includeActuals = includeActuals;
  }

  /**
   * @return the includeInternals
   */
  public boolean isIncludeInternals() {
    return includeInternals;
  }

  /**
   * @param includeInternals the includeInternals to set
   */
  public void setIncludeInternals(boolean includeInternals) {
    this.includeInternals = includeInternals;
  }

  /**
   * @return the includeDevelopments
   */
  public boolean isIncludeDevelopments() {
    return includeDevelopments;
  }

  /**
   * @param includeDevelopments the includeDevelopments to set
   */
  public void setIncludeDevelopments(boolean includeDevelopments) {
    this.includeDevelopments = includeDevelopments;
  }

  /**
   * @return the retryDelay
   */
  public long getRetryDelay() {
    return retryDelay;
  }

  /**
   * @param retryDelay the retryDelay to set
   */
  public void setRetryDelay(long retryDelay) {
    this.retryDelay = retryDelay;
  }

}