Product.java

  1. /*
  2.  * Product
  3.  */
  4. package gov.usgs.earthquake.product;

  5. import gov.usgs.util.CryptoUtils;
  6. import gov.usgs.util.XmlUtils;
  7. import gov.usgs.util.CryptoUtils.Version;

  8. import java.security.PublicKey;
  9. import java.security.PrivateKey;

  10. import java.math.BigDecimal;
  11. import java.net.URI;
  12. import java.net.URL;

  13. import java.util.Date;
  14. import java.util.List;
  15. import java.util.LinkedList;
  16. import java.util.Map;
  17. import java.util.HashMap;
  18. import java.util.logging.Level;
  19. import java.util.logging.Logger;

  20. /**
  21.  * One or more pieces of Content with metadata.
  22.  *
  23.  * <dl>
  24.  * <dt><strong>ID</strong></dt>
  25.  * <dd>
  26.  * Products each have a unique {@link ProductId}.
  27.  * </dd>
  28.  *
  29.  * <dt><strong>Versioning</strong></dt>
  30.  * <dd>
  31.  * It is possible to create multiple versions of the same product,
  32.  * by reusing the same <code>source</code>, <code>type</code>, and
  33.  * <code>code</code>, with a different <code>updateTime</code>.
  34.  * <br>
  35.  * More recent (newer) <code>updateTime</code>s <strong>supersede</strong>
  36.  * Less recent (older) <code>updateTime</code>s.
  37.  * </dd>
  38.  *
  39.  * <dt><strong>Status</strong></dt>
  40.  * <dd>
  41.  * To <strong>delete</strong> a product, create a new version (updateTime)
  42.  * and set it's status to {@link STATUS_DELETE}.  All other statuses
  43.  * ({@link STATUS_UPDATE} by default) are considered updates, and any
  44.  * value can be used in product-specific ways.
  45.  * </dd>
  46.  *
  47.  * <dt><strong>Properties</strong></dt>
  48.  * <dd>
  49.  * Products have key/value attributes that are Strings.
  50.  * These can be useful to convey summary information about a product,
  51.  * so consumers can quickly decide whether to process before opening
  52.  * any product contents.
  53.  * </dd>
  54.  *
  55.  * <dt><strong>Links</strong></dt>
  56.  * <dd>
  57.  * Similar to properties, links allow a Product to specify a
  58.  * <code>relation</code> and one or more <code>link</code> for each
  59.  * relation type.
  60.  * Links must be {@link java.net.URI}s, and may be {@link ProductId}s.
  61.  * </dd>
  62.  *
  63.  * <dt><strong>Contents</strong></dt>
  64.  * <dd>
  65.  * Many Products start as a directory of files, and metadata is determined later.
  66.  * It's also possible to create products without any Contents attached,
  67.  * if all the necessary information can be encoded using Properties or Links.
  68.  * <br>
  69.  * One special "empty path" content, literally at the empty-string path,
  70.  * is handled differently; since an empty path cannot be written to a file.
  71.  * PDL typically reads this in from standard input, or delivers this on
  72.  * standard input to external processes.
  73.  * </dd>
  74.  *
  75.  * <dt><strong>Signature</strong></dt>
  76.  * <dd>
  77.  * A product can have a digital signature, based on a digest of all
  78.  * product contents and metadata.  These are required for most purposes.
  79.  * {@link CryptoUtils} provides utilities for working with OpenSSH keypairs.
  80.  * </dd>
  81.  *
  82.  * <dt><strong>Tracker URL (Deprecated)</strong></dt>
  83.  * <dd>
  84.  * Tracker URLs were initially used to track processing status as
  85.  * distribution progressed.  These are no longer supported, and often
  86.  * introduced new problems.
  87.  * </dd>
  88.  * </dl>
  89.  */
  90. public class Product {

  91.     private static final Logger LOGGER = Logger.getLogger(Product.class
  92.             .getName());

  93.     /** The status message when a product is being updated. */
  94.     public static final String STATUS_UPDATE = "UPDATE";

  95.     /** The status message when a product is being deleted. */
  96.     public static final String STATUS_DELETE = "DELETE";

  97.     /** Property for eventsource */
  98.     public static final String EVENTSOURCE_PROPERTY = "eventsource";
  99.     /** Property for eventsourcecode */
  100.     public static final String EVENTSOURCECODE_PROPERTY = "eventsourcecode";
  101.     /** Property for eventtime */
  102.     public static final String EVENTTIME_PROPERTY = "eventtime";
  103.     /** Property for magnitude */
  104.     public static final String MAGNITUDE_PROPERTY = "magnitude";
  105.     /** Property for latitude */
  106.     public static final String LATITUDE_PROPERTY = "latitude";
  107.     /** Property for longitude */
  108.     public static final String LONGITUDE_PROPERTY = "longitude";
  109.     /** Property for depth */
  110.     public static final String DEPTH_PROPERTY = "depth";
  111.     /** Property for version */
  112.     public static final String VERSION_PROPERTY = "version";

  113.     /** A unique identifier for this product. */
  114.     private ProductId id;

  115.     /** A terse status message. */
  116.     private String status;

  117.     /** Properties of this product. */
  118.     private Map<String, String> properties = new HashMap<String, String>();

  119.     /** Links to other products and related resources. */
  120.     private Map<String, List<URI>> links = new HashMap<String, List<URI>>();

  121.     /** Product contents. Mapping from path to content. */
  122.     private Map<String, Content> contents = new HashMap<String, Content>();

  123.     /** A URL where status updates are sent. */
  124.     private URL trackerURL = null;

  125.     /** A signature generated by the product creator. */
  126.     private String signature = null;

  127.     /** Signature version. */
  128.     private Version signatureVersion = Version.SIGNATURE_V1;

  129.     /**
  130.      * Construct a new Product with status "UPDATE".
  131.      *
  132.      * @param id
  133.      *            the product's unique Id.
  134.      */
  135.     public Product(final ProductId id) {
  136.         this(id, STATUS_UPDATE);
  137.     }

  138.     /**
  139.      * Construct a new Product.
  140.      *
  141.      * @param id
  142.      *            the product's unique Id.
  143.      * @param status
  144.      *            the product's status.
  145.      */
  146.     public Product(final ProductId id, final String status) {
  147.         setId(id);
  148.         setStatus(status);
  149.     }

  150.     /**
  151.      * Copy constructor.
  152.      *
  153.      * @param that
  154.      *            the product to copy.
  155.      */
  156.     public Product(final Product that) {
  157.         this(new ProductId(that.getId().getSource(), that.getId().getType(),
  158.                 that.getId().getCode(), that.getId().getUpdateTime()), that
  159.                 .getStatus());
  160.         this.setTrackerURL(that.getTrackerURL());
  161.         this.setProperties(that.getProperties());
  162.         this.setLinks(that.getLinks());
  163.         this.setContents(that.getContents());
  164.         this.setSignature(that.getSignature());
  165.     }

  166.     /**
  167.      * @return the id
  168.      */
  169.     public ProductId getId() {
  170.         return id;
  171.     }

  172.     /**
  173.      * @param id
  174.      *            the id to set
  175.      */
  176.     public void setId(final ProductId id) {
  177.         this.id = id;
  178.     }

  179.     /**
  180.      * @return the status
  181.      */
  182.     public String getStatus() {
  183.         return status;
  184.     }

  185.     /**
  186.      * @param status
  187.      *            the status to set
  188.      */
  189.     public void setStatus(final String status) {
  190.         this.status = status;
  191.     }

  192.     /**
  193.      * Product.STATUS_DELETE.equalsIgnoreCase(status).
  194.      *
  195.      * @return whether this product is deleted
  196.      */
  197.     public boolean isDeleted() {
  198.         if (STATUS_DELETE.equalsIgnoreCase(this.status)) {
  199.             return true;
  200.         } else {
  201.             return false;
  202.         }
  203.     }

  204.     /**
  205.      * @return the properties
  206.      */
  207.     public Map<String, String> getProperties() {
  208.         return properties;
  209.     }

  210.     /**
  211.      * @param properties
  212.      *            the properties to set
  213.      */
  214.     public void setProperties(final Map<String, String> properties) {
  215.         this.properties.putAll(properties);
  216.     }

  217.     /**
  218.      * Returns a reference to the links map.
  219.      *
  220.      * @return the links
  221.      */
  222.     public Map<String, List<URI>> getLinks() {
  223.         return links;
  224.     }

  225.     /**
  226.      * Copies entries from provided map.
  227.      *
  228.      * @param links
  229.      *            the links to set
  230.      */
  231.     public void setLinks(final Map<String, List<URI>> links) {
  232.         this.links.putAll(links);
  233.     }

  234.     /**
  235.      * Add a link to a product.
  236.      *
  237.      * @param relation
  238.      *            how link is related to product.
  239.      * @param href
  240.      *            actual link.
  241.      */
  242.     public void addLink(final String relation, final URI href) {
  243.         List<URI> relationLinks = links.get(relation);
  244.         if (relationLinks == null) {
  245.             relationLinks = new LinkedList<URI>();
  246.             links.put(relation, relationLinks);
  247.         }
  248.         relationLinks.add(href);
  249.     }

  250.     /**
  251.      * Returns a reference to the contents map.
  252.      *
  253.      * @return the contents
  254.      */
  255.     public Map<String, Content> getContents() {
  256.         return contents;
  257.     }

  258.     /**
  259.      * Copies entries from provided map.
  260.      *
  261.      * @param contents
  262.      *            the contents to set
  263.      */
  264.     public void setContents(final Map<String, Content> contents) {
  265.         this.contents.clear();
  266.         this.contents.putAll(contents);
  267.     }

  268.     /**
  269.      * @return the trackerURL
  270.      */
  271.     public URL getTrackerURL() {
  272.         return trackerURL;
  273.     }

  274.     /**
  275.      * @param trackerURL
  276.      *            the trackerURL to set
  277.      */
  278.     public void setTrackerURL(final URL trackerURL) {
  279.         this.trackerURL = trackerURL;
  280.     }

  281.     /**
  282.      * @return the signature
  283.      */
  284.     public String getSignature() {
  285.         return signature;
  286.     }

  287.     /**
  288.      * @return the signature
  289.      */
  290.     public Version getSignatureVersion() {
  291.         return signatureVersion;
  292.     }

  293.     /**
  294.      * @param signature
  295.      *            the signature to set
  296.      */
  297.     public void setSignature(final String signature) {
  298.         this.signature = signature;
  299.     }

  300.     /**
  301.      * @param version
  302.      *            the signature version to set
  303.      */
  304.     public void setSignatureVersion(final Version version) {
  305.         this.signatureVersion = version;
  306.     }

  307.     /**
  308.      * Sign this product using a PrivateKey and signature v1.
  309.      * @param privateKey used to sign
  310.      * @throws Exception if error occurs
  311.      */
  312.     public void sign(final PrivateKey privateKey) throws Exception {
  313.         this.sign(privateKey, Version.SIGNATURE_V1);
  314.     }

  315.     /**
  316.      * Sign this product using a PrivateKey.
  317.      *
  318.      * @param privateKey
  319.      *            a DSAPrivateKey or RSAPrivateKey.
  320.      * @param version
  321.      *            the signature version to use.
  322.      * @throws Exception if error occurs
  323.      */
  324.     public void sign(final PrivateKey privateKey, final Version version) throws Exception {
  325.         setSignature(CryptoUtils.sign(
  326.                 privateKey,
  327.                 ProductDigest.digestProduct(this, version),
  328.                 version));
  329.         setSignatureVersion(version);
  330.     }

  331.     /**
  332.      * Verify this product's signature using Signature V1.
  333.      * @param publicKeys Array of public keys to verify
  334.      * @throws Exception if error occurs
  335.      * @return true if valid, false otherwise.
  336.      */
  337.     public boolean verifySignature(final PublicKey[] publicKeys)
  338.             throws Exception {
  339.         return verifySignature(publicKeys, getSignatureVersion());
  340.     }

  341.     /**
  342.      * Verify this product's signature.
  343.      *
  344.      * When a product has no signature, this method returns false. The array of
  345.      * public keys corresponds to one or more keys that may have generated the
  346.      * signature. If any of the keys verify, this method returns true.
  347.      *
  348.      * @param publicKeys
  349.      *            an array of publicKeys to test.
  350.      * @param version
  351.      *            the signature version to use.
  352.      * @return true if valid, false otherwise.
  353.      * @throws Exception if error occurs
  354.      */
  355.     public boolean verifySignature(final PublicKey[] publicKeys, final Version version)
  356.             throws Exception {
  357.         return verifySignatureKey(publicKeys, version) != null;
  358.     }

  359.     /**
  360.      * Try to verify using multiple candidate keys.
  361.      * @param publicKeys an array of publicKeys to test
  362.      * @param version the signature version to use.
  363.      * @return true if valid, false otherwise.
  364.      * @throws Exception if error occurs
  365.      */
  366.     public PublicKey verifySignatureKey(final PublicKey[] publicKeys, final Version version) throws Exception {
  367.         if (signature == null) {
  368.             return null;
  369.         }

  370.         byte[] digest = ProductDigest.digestProduct(this, version);
  371.         for (PublicKey key : publicKeys) {
  372.             try {
  373.                 if (CryptoUtils.verify(key, digest, getSignature(), version)) {
  374.                     return key;
  375.                 }
  376.             } catch (Exception e) {
  377.                 LOGGER.log(Level.FINEST, "Exception while verifying signature",
  378.                                 e);
  379.             }
  380.         }
  381.         return null;
  382.     }

  383.     /**
  384.      * Get the event id.
  385.      *
  386.      * The event id is the combination of event source and event source code.
  387.      *
  388.      * @return the event id, or null if either event source or event source code
  389.      *         is null.
  390.      */
  391.     public String getEventId() {
  392.         String eventSource = getEventSource();
  393.         String eventSourceCode = getEventSourceCode();
  394.         if (eventSource == null && eventSourceCode == null) {
  395.             return null;
  396.         }
  397.         return (eventSource + eventSourceCode).toLowerCase();
  398.     }

  399.     /**
  400.      * Set both the network and networkId at the same time.
  401.      *
  402.      * @param source
  403.      *            the originating network.
  404.      * @param sourceCode
  405.      *            the originating network's id.
  406.      */
  407.     public void setEventId(final String source, final String sourceCode) {
  408.         setEventSource(source);
  409.         setEventSourceCode(sourceCode);
  410.     }

  411.     /**
  412.      * Get the event source property.
  413.      *
  414.      * @return the event source property, or null if no event source property
  415.      *         set.
  416.      */
  417.     public String getEventSource() {
  418.         return this.properties.get(EVENTSOURCE_PROPERTY);
  419.     }

  420.     /**
  421.      * Set the event source property.
  422.      *
  423.      * @param eventSource
  424.      *            the event source to set.
  425.      */
  426.     public void setEventSource(final String eventSource) {
  427.         if (eventSource == null) {
  428.             this.properties.remove(EVENTSOURCE_PROPERTY);
  429.         } else {
  430.             this.properties
  431.                     .put(EVENTSOURCE_PROPERTY, eventSource.toLowerCase());
  432.         }
  433.     }

  434.     /**
  435.      * Get the event source code property.
  436.      *
  437.      * @return the event source code property, or null if no event source code
  438.      *         property set.
  439.      */
  440.     public String getEventSourceCode() {
  441.         return this.properties.get(EVENTSOURCECODE_PROPERTY);
  442.     }

  443.     /**
  444.      * Set the event id property.
  445.      *
  446.      * @param eventSourceCode
  447.      *            the event id to set.
  448.      */
  449.     public void setEventSourceCode(final String eventSourceCode) {
  450.         if (eventSourceCode == null) {
  451.             this.properties.remove(EVENTSOURCECODE_PROPERTY);
  452.         } else {
  453.             this.properties.put(EVENTSOURCECODE_PROPERTY,
  454.                     eventSourceCode.toLowerCase());
  455.         }
  456.     }

  457.     /**
  458.      * Get the event time property as a date.
  459.      *
  460.      * @return the event time property as a date, or null if no date property
  461.      *         set.
  462.      */
  463.     public Date getEventTime() {
  464.         String strDate = this.properties.get(EVENTTIME_PROPERTY);
  465.         if (strDate == null) {
  466.             return null;
  467.         }
  468.         return XmlUtils.getDate(strDate);
  469.     }

  470.     /**
  471.      * Set the event time property as a date.
  472.      *
  473.      * @param eventTime
  474.      *            the event time to set.
  475.      */
  476.     public void setEventTime(final Date eventTime) {
  477.         if (eventTime == null) {
  478.             this.properties.remove(EVENTTIME_PROPERTY);
  479.         } else {
  480.             this.properties.put(EVENTTIME_PROPERTY,
  481.                     XmlUtils.formatDate(eventTime));
  482.         }
  483.     }

  484.     /**
  485.      * Get the magnitude property as a big decimal.
  486.      *
  487.      * @return the magnitude property as a big decimal, or null if no magnitude
  488.      *         property set.
  489.      */
  490.     public BigDecimal getMagnitude() {
  491.         String strMag = this.properties.get(MAGNITUDE_PROPERTY);
  492.         if (strMag == null) {
  493.             return null;
  494.         }
  495.         return new BigDecimal(strMag);
  496.     }

  497.     /**
  498.      * Set the magnitude property as a big decimal.
  499.      *
  500.      * @param magnitude
  501.      *            the magnitude to set.
  502.      */
  503.     public void setMagnitude(final BigDecimal magnitude) {
  504.         if (magnitude == null) {
  505.             this.properties.remove(MAGNITUDE_PROPERTY);
  506.         } else {
  507.             this.properties.put(MAGNITUDE_PROPERTY, magnitude.toPlainString());
  508.         }
  509.     }

  510.     /**
  511.      * Get the latitude property as a big decimal.
  512.      *
  513.      * @return latitude property as a big decimal, or null if no latitude
  514.      *         property set.
  515.      */
  516.     public BigDecimal getLatitude() {
  517.         String strLat = this.properties.get(LATITUDE_PROPERTY);
  518.         if (strLat == null) {
  519.             return null;
  520.         }
  521.         return new BigDecimal(strLat);
  522.     }

  523.     /**
  524.      * Set the latitude property as a big decimal.
  525.      *
  526.      * @param latitude
  527.      *            the latitude to set.
  528.      */
  529.     public void setLatitude(final BigDecimal latitude) {
  530.         if (latitude == null) {
  531.             this.properties.remove(LATITUDE_PROPERTY);
  532.         } else {
  533.             this.properties.put(LATITUDE_PROPERTY, latitude.toPlainString());
  534.         }
  535.     }

  536.     /**
  537.      * Get the longitude property as a big decimal.
  538.      *
  539.      * @return longitude property as a big decimal, or null if no longitude
  540.      *         property set.
  541.      */
  542.     public BigDecimal getLongitude() {
  543.         String strLon = this.properties.get(LONGITUDE_PROPERTY);
  544.         if (strLon == null) {
  545.             return null;
  546.         }
  547.         return new BigDecimal(strLon);
  548.     }

  549.     /**
  550.      * Set the longitude property as a big decimal.
  551.      *
  552.      * @param longitude
  553.      *            the longitude to set.
  554.      */
  555.     public void setLongitude(final BigDecimal longitude) {
  556.         if (longitude == null) {
  557.             this.properties.remove(LONGITUDE_PROPERTY);
  558.         } else {
  559.             this.properties.put(LONGITUDE_PROPERTY, longitude.toPlainString());
  560.         }
  561.     }

  562.     /**
  563.      * Get the depth property as a big decimal.
  564.      *
  565.      * @return depth property as big decimal, or null if no depth property set.
  566.      */
  567.     public BigDecimal getDepth() {
  568.         String strDepth = this.properties.get(DEPTH_PROPERTY);
  569.         if (strDepth == null) {
  570.             return null;
  571.         }
  572.         return new BigDecimal(strDepth);
  573.     }

  574.     /**
  575.      * Set the depth property as a big decimal.
  576.      *
  577.      * @param depth
  578.      *            the depth to set.
  579.      */
  580.     public void setDepth(final BigDecimal depth) {
  581.         if (depth == null) {
  582.             this.properties.remove(DEPTH_PROPERTY);
  583.         } else {
  584.             this.properties.put(DEPTH_PROPERTY, depth.toPlainString());
  585.         }
  586.     }

  587.     /**
  588.      * Get the version property.
  589.      *
  590.      * @return the version property, or null if no version property set.
  591.      */
  592.     public String getVersion() {
  593.         return this.properties.get(VERSION_PROPERTY);
  594.     }

  595.     /**
  596.      * Set the version property.
  597.      *
  598.      * @param version
  599.      *            the version to set.
  600.      */
  601.     public void setVersion(final String version) {
  602.         if (version == null) {
  603.             this.properties.remove(VERSION_PROPERTY);
  604.         } else {
  605.             this.properties.put(VERSION_PROPERTY, version);
  606.         }
  607.     }

  608. }