DefaultIndexerModule.java

/*
 * DefaultIndexerModule
 */
package gov.usgs.earthquake.indexer;

import gov.usgs.earthquake.distribution.ContinuableListenerException;
import gov.usgs.earthquake.distribution.SignatureVerifier;
import gov.usgs.earthquake.geoserve.ANSSRegionsFactory;
import gov.usgs.earthquake.product.Product;
import gov.usgs.earthquake.qdm.Point;
import gov.usgs.earthquake.qdm.Regions;
import gov.usgs.util.Config;
import gov.usgs.util.DefaultConfigurable;
import gov.usgs.util.StringUtils;

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

/**
 * Default implementation of the IndexerModule interface, implements ANSS
 * Authoritative Region logic.
 *
 * Provides a basic level of support for any type of product. Creates a
 * ProductSummary using the ProductSummary(product) constructor, which copies
 * all properties, and links from the product.
 */
public class DefaultIndexerModule extends DefaultConfigurable implements IndexerModule {

  private static final Logger LOGGER = Logger.getLogger(DefaultIndexerModule.class.getName());

  /** Property for ignoreRegions */
  public static final String IGNORE_REGIONS_PROPERTY = "ignoreRegions";

  /** Initial preferred weight. */
  public static final long DEFAULT_PREFERRED_WEIGHT = 1;

  /** Weight added when product source is same as event source. */
  public static final long SAME_SOURCE_WEIGHT = 5;

  /** Weight added when product author is in its authoritative region. */
  public static final long AUTHORITATIVE_WEIGHT = 100;

  /** Weight added when product refers to an authoritative event. */
  public static final long AUTHORITATIVE_EVENT_WEIGHT = 50;

  /** Weight added when product author has an authoritative region. */
  public static final long ANSS_CONTRIBUTOR_WEIGHT = 1;

  /** Weight added when product author is NEIC. */
  public static final long NEIC_CONTRIBUTOR_WEIGHT = 2;

  /** Signature verifier, configured by indexer. */
  private SignatureVerifier signatureVerifier = new SignatureVerifier();

  private List<String> ignoreRegions = new ArrayList<String>();

  @Override
  public void configure(final Config config) throws Exception {
    final String ignore = config.getProperty(IGNORE_REGIONS_PROPERTY);
    if (ignore != null) {
      ignoreRegions.addAll(StringUtils.split(ignore, ","));
      LOGGER.config("[" + getName() + "] ignore regions = " + ignore);
    }
  }

  /**
   * Create a ProductSummary from a Product.
   *
   * Uses the ProductSummary(Product) constructor, which copies product
   * information. Checks whether product is within its authoritative region, and
   * if so boosts preferredWeight by AUTHORITATIVE_WEIGHT.
   *
   * @param product the product to summarize.
   * @return ProductSummary for Product object.
   */
  public ProductSummary getProductSummary(final Product product) throws Exception {
    ProductSummary summary = new ProductSummary(product);

    // allow sender to assign preferredWeight if we add them to the keychain
    String preferredWeight = product.getProperties().get("preferredWeight");
    if (preferredWeight != null && signatureVerifier.verifySignature(product)) {
      LOGGER.fine("Signature verified, using sender assigned preferredWeight " + preferredWeight);
      summary.setPreferredWeight(Long.valueOf(preferredWeight));
    } else {
      summary.setPreferredWeight(getPreferredWeight(summary));
    }
    return summary;
  }

  /**
   * Calculate the preferred weight for a product summary.
   *
   * This method is called after creating a product summary, but before returning
   * the created summary. It's return value is used to assign the product summary
   * preferred weight.
   *
   * Within each type of product, the summary with the largest preferred weight is
   * considered preferred.
   *
   * @param summary the summary to calculate a preferred weight.
   * @return the absolute preferred weight.
   * @throws Exception if error occurs
   */
  protected long getPreferredWeight(final ProductSummary summary) throws Exception {
    long preferredWeight = DEFAULT_PREFERRED_WEIGHT;

    final String source = summary.getId().getSource();
    final String eventSource = summary.getEventSource();

    // check ignore regions here for subclasses that use this method.
    if (ignoreRegions.contains(source)) {
      // source gets no region boost
      return preferredWeight;
    }

    final Regions regions = ANSSRegionsFactory.getFactory().getRegions();
    if (regions == null) {
      throw new ContinuableListenerException("Unable to load ANSS Authoritative Regions");
    }

    final BigDecimal latitude = summary.getEventLatitude();
    final BigDecimal longitude = summary.getEventLongitude();
    Point location = null;
    if (latitude != null && longitude != null) {
      location = new Point(longitude.doubleValue(), latitude.doubleValue());
    }

    // authoritative check
    if (location != null) {
      if (regions.isAuthor(source, location)) {
        // based on product source, who authored this product.
        preferredWeight += AUTHORITATIVE_WEIGHT;
      }
      if (eventSource != null && regions.isAuthor(eventSource, location)) {
        // based on event source, which event this product is about
        preferredWeight += AUTHORITATIVE_EVENT_WEIGHT;
      }
    }

    // anss source check
    if (regions.isValidnetID(source)) {
      preferredWeight += ANSS_CONTRIBUTOR_WEIGHT;
    }

    // neic source check
    if (regions.isDefaultNetID(source)) {
      preferredWeight += NEIC_CONTRIBUTOR_WEIGHT;
    }

    // same source check
    if (eventSource != null && eventSource.equalsIgnoreCase(source)) {
      preferredWeight += SAME_SOURCE_WEIGHT;
    }

    return preferredWeight;
  }

  /**
   * Remove "internal-" prefix and "-scenario" suffix from product type".
   *
   * @param type product type.
   * @return base product type (without any known prefix or suffix).
   */
  public String getBaseProductType(String type) {
    if (type.startsWith("internal-")) {
      type = type.replace("internal-", "");
    }

    if (type.endsWith("-scenario")) {
      type = type.replace("-scenario", "");
    }

    return type;
  }

  /** @return ignoreRegions */
  public List<String> getIgnoreRegions() {
    return ignoreRegions;
  }

  /**
   * This module provides a default level of support for any type of product.
   *
   * @param product the product to test.
   * @return IndexerModule.LEVEL_DEFAULT.
   */
  public int getSupportLevel(final Product product) {
    return IndexerModule.LEVEL_DEFAULT;
  }

  /** @return signatureVerifier */
  public SignatureVerifier getSignatureVerifier() {
    return signatureVerifier;
  }

  /** @param signatureVerifier to set */
  public void setSignatureVerifier(SignatureVerifier signatureVerifier) {
    this.signatureVerifier = signatureVerifier;
  }

}