EQMessageProductCreator.java

  1. package gov.usgs.earthquake.eids;

  2. import java.io.ByteArrayOutputStream;
  3. import java.io.File;
  4. import java.math.BigDecimal;
  5. import java.util.Date;
  6. import java.util.Iterator;
  7. import java.util.LinkedList;
  8. import java.util.List;
  9. import java.util.Map;
  10. import java.util.logging.Level;
  11. import java.util.logging.Logger;

  12. //Does it make sense to import objects from quakeml when we're parsing eqxml?
  13. //import org.quakeml.FocalMechanism;
  14. //import org.quakeml.NodalPlane;
  15. //import org.quakeml.NodalPlanes;

  16. import gov.usgs.ansseqmsg.Action;
  17. import gov.usgs.ansseqmsg.Comment;
  18. import gov.usgs.ansseqmsg.EQMessage;
  19. import gov.usgs.ansseqmsg.EventAction;
  20. import gov.usgs.ansseqmsg.EventScope;
  21. import gov.usgs.ansseqmsg.EventType;
  22. import gov.usgs.ansseqmsg.EventUsage;
  23. import gov.usgs.ansseqmsg.Fault;
  24. import gov.usgs.ansseqmsg.Magnitude;
  25. import gov.usgs.ansseqmsg.Method;
  26. import gov.usgs.ansseqmsg.MomentTensor;
  27. import gov.usgs.ansseqmsg.NodalPlanes;
  28. import gov.usgs.ansseqmsg.Origin;
  29. import gov.usgs.ansseqmsg.Event;
  30. import gov.usgs.ansseqmsg.ProductLink;
  31. import gov.usgs.ansseqmsg.Tensor;
  32. import gov.usgs.earthquake.cube.CubeAddon;
  33. import gov.usgs.earthquake.cube.CubeDelete;
  34. import gov.usgs.earthquake.cube.CubeEvent;
  35. import gov.usgs.earthquake.cube.CubeMessage;
  36. import gov.usgs.earthquake.distribution.ProductCreator;
  37. import gov.usgs.earthquake.eqxml.EQMessageParser;
  38. import gov.usgs.earthquake.event.Converter;
  39. import gov.usgs.earthquake.product.ByteContent;
  40. import gov.usgs.earthquake.product.Content;
  41. import gov.usgs.earthquake.product.Product;
  42. import gov.usgs.earthquake.product.ProductId;
  43. import gov.usgs.util.FileUtils;
  44. import gov.usgs.util.StreamUtils;
  45. import gov.usgs.util.XmlUtils;

  46. /**
  47.  * Convert EQXML messages to Products.
  48.  *
  49.  * <p>
  50.  * Product source is EQMessage/Source.
  51.  * </p>
  52.  * <p>
  53.  * Product type is "origin", "magnitude", or "addon". Types may be prefixed by
  54.  * non-Public Event/Scope, and suffixed by non-Actual Event/Usage
  55.  * (internal-magnitude-scenario).
  56.  * </p>
  57.  * <p>
  58.  * Product code is Event/DataSource + Event/EventId. When an addon product,
  59.  * either ProductLink/Code or Comment/TypeKey is appended to code.
  60.  * </p>
  61.  * <p>
  62.  * Product updateTime is EQMessage/Sent.
  63.  * </p>
  64.  *
  65.  * <p>
  66.  * Origin properties appear only on origin type products. Magnitude properties
  67.  * appear on both magnitude and origin products.
  68.  * </p>
  69.  */
  70. public class EQMessageProductCreator implements ProductCreator {

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

  72.   /** Static var for the xml content type */
  73.   public static final String XML_CONTENT_TYPE = "application/xml";

  74.   /** Path to content where source message is stored in created product. */
  75.   public static final String EQMESSAGE_CONTENT_PATH = "eqxml.xml";
  76.   /** Path to contests xml */
  77.   public static final String CONTENTS_XML_PATH = "contents.xml";

  78.   /**
  79.    * When phases exist is is a "phase" type product. When this flag is set to
  80.    * true, a lightweight, origin-only type product is also sent.
  81.    */
  82.   private boolean sendOriginWhenPhasesExist = false;

  83.   /**
  84.    * Whether to validate when parsing and serializing. When validating, only
  85.    * native EQXML is supported via the ProductCreator interface.
  86.    */
  87.   private boolean validate = false;

  88.   // the eqmessage currently being processed.
  89.   private EQMessage eqmessage;

  90.   // xml for the eqmessage currently being processed
  91.   private String eqmessageXML;
  92.   private String eqmessageSource;
  93.   private Date eqmessageSent;

  94.   private String eventDataSource;
  95.   private String eventEventId;
  96.   private String eventVersion;
  97.   private EventAction eventAction;
  98.   private EventUsage eventUsage;
  99.   private EventScope eventScope;

  100.   private BigDecimal originLatitude;
  101.   private BigDecimal originLongitude;
  102.   private BigDecimal originDepth;
  103.   private Date originEventTime;

  104.   private BigDecimal magnitude;

  105.   /**
  106.    * Default, empty constructor.
  107.    */
  108.   public EQMessageProductCreator() {
  109.   }

  110.   /**
  111.    * Get all the products contained in an EQMessage.
  112.    *
  113.    * Same as getEQMessageProducts(message, null).
  114.    *
  115.    * @param message the EQMessage containing products.
  116.    * @return a list of created products.
  117.    * @throws Exception if error occurs
  118.    */
  119.   public synchronized List<Product> getEQMessageProducts(final EQMessage message) throws Exception {
  120.     return getEQMessageProducts(message, null);
  121.   }

  122.   /**
  123.    * Get all the products contained in an EQMessage.
  124.    *
  125.    * Parses rawEqxml string into an EQMessage, but preserves raw eqxml in created
  126.    * products.
  127.    *
  128.    * Same as getEQMessageProducts(EQMessageParser.parse(rawEqxml), rawEqxml);
  129.    *
  130.    * @param rawEqxml the raw EQXML message.
  131.    * @return a list of created products.
  132.    * @throws Exception if error occurs
  133.    */
  134.   public synchronized List<Product> getEQMessageProducts(final String rawEqxml) throws Exception {
  135.     EQMessage message = EQMessageParser.parse(rawEqxml, validate);
  136.     return getEQMessageProducts(message, rawEqxml);
  137.   }

  138.   /**
  139.    * Get all the products contained in an EQMessage.
  140.    *
  141.    * @param message  the EQMessage containing products.
  142.    * @param rawEqxml the raw EQXML message. When null, an EQXML message is
  143.    *                 serialized from the object.
  144.    * @return a list of created products.
  145.    * @throws Exception if error occurs
  146.    */
  147.   public synchronized List<Product> getEQMessageProducts(final EQMessage message, final String rawEqxml)
  148.       throws Exception {
  149.     List<Product> products = new LinkedList<Product>();

  150.     if (message == null) {
  151.       return products;
  152.     }

  153.     this.eqmessage = message;
  154.     this.eqmessageSource = message.getSource();
  155.     this.eqmessageSent = message.getSent();

  156.     if (this.eqmessageSent == null) {
  157.       this.eqmessageSent = new Date();
  158.     }

  159.     // convert to xml
  160.     if (rawEqxml != null) {
  161.       this.eqmessageXML = rawEqxml;
  162.     } else {
  163.       ByteArrayOutputStream baos = new ByteArrayOutputStream();
  164.       EQMessageParser.serialize(message, baos, validate);
  165.       this.eqmessageXML = baos.toString();
  166.     }

  167.     // process each event
  168.     List<Event> events = message.getEvent();
  169.     if (events != null) {
  170.       Iterator<Event> iter = events.iterator();
  171.       while (iter.hasNext()) {
  172.         products.addAll(getEventProducts(iter.next()));
  173.       }
  174.     }

  175.     this.eqmessageSource = null;
  176.     this.eqmessageSent = null;
  177.     this.eqmessageXML = null;

  178.     return products;
  179.   }

  180.   /**
  181.    * Get products from an event.
  182.    *
  183.    * @param event the event containing products.
  184.    * @return a list of created products.
  185.    * @throws Exception if error occurs
  186.    */
  187.   protected synchronized List<Product> getEventProducts(final Event event) throws Exception {
  188.     List<Product> products = new LinkedList<Product>();
  189.     if (event == null) {
  190.       return products;
  191.     }

  192.     eventDataSource = event.getDataSource();
  193.     eventEventId = event.getEventID();
  194.     eventVersion = event.getVersion();
  195.     eventAction = event.getAction();
  196.     eventUsage = event.getUsage();
  197.     eventScope = event.getScope();

  198.     // default values
  199.     if (eventAction == null) {
  200.       eventAction = EventAction.UPDATE;
  201.     }
  202.     if (eventUsage == null) {
  203.       eventUsage = EventUsage.ACTUAL;
  204.     }
  205.     if (eventScope == null) {
  206.       eventScope = EventScope.PUBLIC;
  207.     }

  208.     if (eventAction == EventAction.DELETE) {
  209.       // delete origin product (only product with location)
  210.       Product deleteProduct = getProduct("origin", eventAction.toString());
  211.       products.add(deleteProduct);
  212.     } else {
  213.       // update origin product
  214.       products.addAll(getOriginProducts(event.getOrigin(), event));
  215.     }

  216.     for (ProductLink eventLink : event.getProductLink()) {
  217.       products.addAll(getProductLinkProducts(eventLink));
  218.     }
  219.     for (Comment eventComment : event.getComment()) {
  220.       products.addAll(getCommentProducts(eventComment));
  221.     }

  222.     eventDataSource = null;
  223.     eventEventId = null;
  224.     eventVersion = null;
  225.     eventAction = null;
  226.     eventUsage = null;
  227.     eventScope = null;

  228.     return products;
  229.   }

  230.   /**
  231.    * Get origin product(s).
  232.    *
  233.    * This implementation only creates one origin (the first one) regardless of how
  234.    * many origins are provided.
  235.    *
  236.    * @param origins the list of origins.
  237.    * @param event   A specific event
  238.    * @return a list of created products.
  239.    * @throws Exception if error occurs
  240.    */
  241.   protected synchronized List<Product> getOriginProducts(final List<Origin> origins, final Event event)
  242.       throws Exception {
  243.     List<Product> products = new LinkedList<Product>();
  244.     if (origins == null || origins.size() == 0) {
  245.       return products;
  246.     }

  247.     // only process first origin
  248.     Origin origin = origins.get(0);

  249.     // get sub-products
  250.     products.addAll(getFocalMechanismProducts(origin.getMomentTensor()));

  251.     this.originLatitude = origin.getLatitude();
  252.     this.originLongitude = origin.getLongitude();
  253.     this.originDepth = origin.getDepth();
  254.     this.originEventTime = origin.getTime();

  255.     boolean preferred = (origin.getPreferredFlag() == null || origin.getPreferredFlag());

  256.     // only process "preferred" origins
  257.     // this is how hydra differentiates between origins as input parameters
  258.     // to focal mechanisms, and origins as origins
  259.     if (preferred && this.originLatitude != null && this.originLongitude != null && this.originEventTime != null) {
  260.       // create an origin/magnitude product only if origin has
  261.       // lat+lon+time

  262.       List<Product> magnitudeProducts = getMagnitudeProducts(origin.getMagnitude());

  263.       // now build origin product
  264.       Action originAction = origin.getAction();
  265.       Product originProduct = getProduct("origin", originAction == null ? null : originAction.toString());
  266.       // origin specific properties
  267.       Map<String, String> properties = originProduct.getProperties();

  268.       // set event type
  269.       properties.put("event-type",
  270.           (event.getType() == null ? EventType.EARTHQUAKE : event.getType()).value().toLowerCase());

  271.       if (magnitudeProducts.size() > 0) {
  272.         // transfer magnitude product properties to origin
  273.         properties.putAll(magnitudeProducts.get(0).getProperties());
  274.       }

  275.       if (origin.getSourceKey() != null) {
  276.         properties.put("origin-source", origin.getSourceKey());
  277.       }
  278.       if (origin.getAzimGap() != null) {
  279.         properties.put("azimuthal-gap", origin.getAzimGap().toString());
  280.       }
  281.       if (origin.getDepthError() != null) {
  282.         properties.put("depth-error", origin.getDepthError().toString());
  283.       }
  284.       if (origin.getDepthMethod() != null) {
  285.         properties.put("depth-method", origin.getDepthMethod());
  286.       }
  287.       if (origin.getErrh() != null) {
  288.         properties.put("horizontal-error", origin.getErrh().toString());
  289.       }
  290.       if (origin.getErrz() != null) {
  291.         properties.put("vertical-error", origin.getErrz().toString());
  292.       }
  293.       if (origin.getLatError() != null) {
  294.         properties.put("latitude-error", origin.getLatError().toString());
  295.       }
  296.       if (origin.getLonError() != null) {
  297.         properties.put("longitude-error", origin.getLonError().toString());
  298.       }
  299.       if (origin.getMinDist() != null) {
  300.         properties.put("minimum-distance", origin.getMinDist().toString());
  301.       }
  302.       if (origin.getNumPhaUsed() != null) {
  303.         properties.put("num-phases-used", origin.getNumPhaUsed().toString());
  304.       }
  305.       if (origin.getNumStaUsed() != null) {
  306.         properties.put("num-stations-used", origin.getNumStaUsed().toString());
  307.       }
  308.       if (origin.getRegion() != null) {
  309.         properties.put("region", origin.getRegion());
  310.       }
  311.       if (origin.getStatus() != null) {
  312.         properties.put("review-status", origin.getStatus().toString());
  313.       }
  314.       if (origin.getStdError() != null) {
  315.         properties.put("standard-error", origin.getStdError().toString());
  316.       }

  317.       // origin method
  318.       Iterator<Method> methods = origin.getMethod().iterator();
  319.       if (methods.hasNext()) {
  320.         Method method = methods.next();
  321.         if (method.getClazz() != null) {
  322.           properties.put("location-method-class", method.getClazz());
  323.         }
  324.         if (method.getAlgorithm() != null) {
  325.           properties.put("location-method-algorithm", method.getAlgorithm());
  326.         }
  327.         if (method.getModel() != null) {
  328.           properties.put("location-method-model", method.getModel());
  329.         }

  330.         String cubeLocationMethod = getCubeCode(method.getComment());
  331.         if (cubeLocationMethod != null) {
  332.           properties.put("cube-location-method", cubeLocationMethod);
  333.         }
  334.       }

  335.       if (origin.getPhase() != null && origin.getPhase().size() > 0) {
  336.         originProduct.getId().setType("phase-data");
  337.         products.add(originProduct);

  338.         if (sendOriginWhenPhasesExist) {
  339.           // create lightweight origin product
  340.           Product lightweightOrigin = new Product(originProduct);
  341.           lightweightOrigin.getId().setType("origin");
  342.           lightweightOrigin.getContents().remove(EQMESSAGE_CONTENT_PATH);

  343.           // seek and destroy phases
  344.           Iterator<Origin> iter = origins.iterator();
  345.           while (iter.hasNext()) {
  346.             Origin next = iter.next();
  347.             if (next.getPhase() != null) {
  348.               next.getPhase().clear();
  349.             }
  350.           }

  351.           // serialize xml without phase data
  352.           ByteArrayOutputStream baos = new ByteArrayOutputStream();
  353.           EQMessageParser.serialize(this.eqmessage, baos, validate);
  354.           lightweightOrigin.getContents().put(EQMESSAGE_CONTENT_PATH, new ByteContent(baos.toByteArray()));

  355.           products.add(0, lightweightOrigin);
  356.         }
  357.       } else {
  358.         // insert origin at start of list
  359.         products.add(0, originProduct);
  360.       }
  361.     }

  362.     this.originDepth = null;
  363.     this.originEventTime = null;
  364.     this.originLatitude = null;
  365.     this.originLongitude = null;
  366.     this.magnitude = null;

  367.     for (Comment originComment : origin.getComment()) {
  368.       products.addAll(getCommentProducts(originComment));
  369.     }

  370.     return products;
  371.   }

  372.   /**
  373.    * Build magnitude products.
  374.    *
  375.    * This implementation builds at most one magnitude product (the first).
  376.    *
  377.    * @param magnitudes a list of candidate magsnitude objects.
  378.    * @return a list of built magnitude products, which may be empty.
  379.    */
  380.   protected synchronized List<Product> getMagnitudeProducts(final List<Magnitude> magnitudes) {
  381.     List<Product> products = new LinkedList<Product>();
  382.     if (magnitudes == null || magnitudes.size() == 0) {
  383.       return products;
  384.     }

  385.     // build product based on the first magnitude
  386.     Magnitude magnitude = magnitudes.get(0);
  387.     // set "globalish" property before getProduct()
  388.     this.magnitude = magnitude.getValue();

  389.     Action magnitudeAction = magnitude.getAction();
  390.     // build magnitude product
  391.     Product product = getProduct("magnitude", magnitudeAction == null ? null : magnitudeAction.toString());

  392.     Map<String, String> properties = product.getProperties();
  393.     if (magnitude.getSourceKey() != null) {
  394.       properties.put("magnitude-source", magnitude.getSourceKey());
  395.     }
  396.     if (magnitude.getTypeKey() != null) {
  397.       properties.put("magnitude-type", magnitude.getTypeKey());
  398.     }
  399.     if (magnitude.getAzimGap() != null) {
  400.       properties.put("magnitude-azimuthal-gap", magnitude.getAzimGap().toString());
  401.     }
  402.     if (magnitude.getError() != null) {
  403.       properties.put("magnitude-error", magnitude.getError().toString());
  404.     }
  405.     if (magnitude.getNumStations() != null) {
  406.       properties.put("magnitude-num-stations-used", magnitude.getNumStations().toString());
  407.     }

  408.     String cubeMagnitudeType = getCubeCode(magnitude.getComment());
  409.     if (cubeMagnitudeType != null) {
  410.       properties.put("cube-magnitude-type", cubeMagnitudeType);
  411.     }

  412.     // don't clear property here, so origin can borrow magnitude property
  413.     // this.magnitude = null;

  414.     products.add(product);
  415.     return products;
  416.   }

  417.   /**
  418.    * Gets a list of Focal Mechanism products from momentTensors
  419.    *
  420.    * @param momentTensors List of Moment Tensors
  421.    * @return a list of products
  422.    */
  423.   protected synchronized List<Product> getFocalMechanismProducts(final List<MomentTensor> momentTensors) {
  424.     List<Product> products = new LinkedList<Product>();
  425.     if (momentTensors == null || momentTensors.size() == 0) {
  426.       return products;
  427.     }

  428.     // build moment tensors
  429.     Iterator<MomentTensor> iter = momentTensors.iterator();
  430.     while (iter.hasNext()) {
  431.       MomentTensor mt = iter.next();

  432.       Action mtAction = mt.getAction();
  433.       // may be set to "moment-tensor" below
  434.       Product product = getProduct("focal-mechanism", mtAction == null ? null : mtAction.toString());

  435.       Map<String, String> properties = product.getProperties();

  436.       // fill in product properties
  437.       if (mt.getSourceKey() != null) {
  438.         properties.put("beachball-source", mt.getSourceKey());
  439.       }
  440.       if (mt.getTypeKey() != null) {
  441.         properties.put("beachball-type", mt.getTypeKey());
  442.         // append source+type to code
  443.         ProductId productId = product.getId();
  444.         productId.setCode((productId.getCode() + "-" + mt.getSourceKey() + "-" + mt.getTypeKey()).toLowerCase());
  445.       }
  446.       if (mt.getMagMw() != null) {
  447.         product.setMagnitude(mt.getMagMw());
  448.       }
  449.       if (mt.getM0() != null) {
  450.         properties.put("scalar-moment", mt.getM0().toString());
  451.       }

  452.       if (mt.getTensor() != null) {
  453.         // if the tensor is included, it is a "moment-tensor" instead of
  454.         // a "focal-mechanism"
  455.         product.getId().setType("moment-tensor");

  456.         Tensor t = mt.getTensor();
  457.         if (t.getMtt() != null) {
  458.           properties.put("tensor-mtt", t.getMtt().toString());
  459.         }
  460.         if (t.getMpp() != null) {
  461.           properties.put("tensor-mpp", t.getMpp().toString());
  462.         }
  463.         if (t.getMrr() != null) {
  464.           properties.put("tensor-mrr", t.getMrr().toString());
  465.         }
  466.         if (t.getMtp() != null) {
  467.           properties.put("tensor-mtp", t.getMtp().toString());
  468.         }
  469.         if (t.getMrp() != null) {
  470.           properties.put("tensor-mrp", t.getMrp().toString());
  471.         }
  472.         if (t.getMrt() != null) {
  473.           properties.put("tensor-mrt", t.getMrt().toString());
  474.         }
  475.       }

  476.       if (mt.getNodalPlanes() != null) {
  477.         NodalPlanes np = mt.getNodalPlanes();
  478.         List<Fault> faults = np.getFault();
  479.         if (faults.size() == 2) {
  480.           Fault fault1 = faults.get(0);
  481.           if (fault1.getDip() != null) {
  482.             properties.put("nodal-plane-1-dip", fault1.getDip().toString());
  483.           }
  484.           if (fault1.getSlip() != null) {
  485.             properties.put("nodal-plane-1-slip", fault1.getSlip().toString());
  486.           }
  487.           if (fault1.getStrike() != null) {
  488.             properties.put("nodal-plane-1-strike", fault1.getStrike().toString());
  489.           }
  490.           Fault fault2 = faults.get(1);
  491.           if (fault2.getDip() != null) {
  492.             properties.put("nodal-plane-2-dip", fault2.getDip().toString());
  493.           }
  494.           if (fault2.getSlip() != null) {
  495.             properties.put("nodal-plane-2-slip", fault2.getSlip().toString());
  496.           }
  497.           if (fault2.getStrike() != null) {
  498.             properties.put("nodal-plane-2-strike", fault2.getStrike().toString());
  499.           }
  500.         }
  501.       }

  502.       if (mt.getDerivedOriginTime() != null) {
  503.         properties.put("derived-eventtime", XmlUtils.formatDate(mt.getDerivedOriginTime()));
  504.       }
  505.       if (mt.getDerivedLatitude() != null) {
  506.         properties.put("derived-latitude", mt.getDerivedLatitude().toString());
  507.       }
  508.       if (mt.getDerivedLongitude() != null) {
  509.         properties.put("derived-longitude", mt.getDerivedLongitude().toString());
  510.       }
  511.       if (mt.getDerivedDepth() != null) {
  512.         properties.put("derived-depth", mt.getDerivedDepth().toString());
  513.       }

  514.       if (mt.getPerDblCpl() != null) {
  515.         properties.put("percent-double-couple", mt.getPerDblCpl().toString());
  516.       }
  517.       if (mt.getNumObs() != null) {
  518.         properties.put("num-stations-used", mt.getNumObs().toString());
  519.       }

  520.       // attach original message as product content
  521.       ByteContent xml = new ByteContent(eqmessageXML.getBytes());
  522.       xml.setLastModified(eqmessageSent);
  523.       xml.setContentType("application/xml");
  524.       product.getContents().put(EQMESSAGE_CONTENT_PATH, xml);

  525.       // add to list of built products
  526.       products.add(product);
  527.     }

  528.     return products;
  529.   }

  530.   /**
  531.    * Get product(s) from a ProductLink object.
  532.    *
  533.    * @param link the link object.
  534.    * @return a list of found products.
  535.    * @throws Exception if error occurs
  536.    */
  537.   protected synchronized List<Product> getProductLinkProducts(final ProductLink link) throws Exception {
  538.     List<Product> products = new LinkedList<Product>();

  539.     String linkType = getLinkAddonProductType(link.getCode());
  540.     if (linkType == null) {
  541.       LOGGER.finer("No product type found for productlink with code '" + link.getCode() + "', skipping");
  542.       return products;
  543.     }

  544.     Action linkAction = link.getAction();
  545.     Product linkProduct = getProduct(linkType, linkAction == null ? null : linkAction.toString());
  546.     // remove the EQXML, only send product link attributes with link
  547.     // products
  548.     linkProduct.getContents().clear();

  549.     // add addon code to product code
  550.     ProductId id = linkProduct.getId();
  551.     id.setCode(id.getCode() + "-" + link.getCode().toLowerCase());

  552.     if (link.getVersion() != null) {
  553.       linkProduct.setVersion(link.getVersion());
  554.     }

  555.     Map<String, String> properties = linkProduct.getProperties();
  556.     if (link.getLink() != null) {
  557.       properties.put("url", link.getLink());
  558.     }
  559.     if (link.getNote() != null) {
  560.       properties.put("text", link.getNote());
  561.     }
  562.     if (link.getCode() != null) {
  563.       properties.put("addon-code", link.getCode());
  564.     }
  565.     properties.put("addon-type", link.getTypeKey());

  566.     products.add(linkProduct);
  567.     return products;
  568.   }

  569.   /**
  570.    * Get product(s) from a Comment object.
  571.    *
  572.    * @param comment the comment object.
  573.    * @return a list of found products.
  574.    * @throws Exception if error occurs
  575.    */
  576.   protected synchronized List<Product> getCommentProducts(final Comment comment) throws Exception {
  577.     List<Product> products = new LinkedList<Product>();

  578.     // CUBE_Codes are attributes of the containing product.
  579.     String typeKey = comment.getTypeKey();
  580.     if (typeKey != null && !typeKey.equals("CUBE_Code")) {
  581.       String commentType = getTextAddonProductType(typeKey);
  582.       if (commentType == null) {
  583.         LOGGER.finer("No product type found for comment with type '" + comment.getTypeKey() + "'");
  584.         return products;
  585.       }

  586.       Action commentAction = comment.getAction();
  587.       Product commentProduct = getProduct(commentType, commentAction == null ? null : commentAction.toString());
  588.       // remove the EQXML, only send comment text with comment products
  589.       commentProduct.getContents().clear();

  590.       // one product per comment type
  591.       ProductId id = commentProduct.getId();
  592.       id.setCode(id.getCode() + "_" + comment.getTypeKey().toLowerCase());

  593.       Map<String, String> properties = commentProduct.getProperties();
  594.       properties.put("addon-type", "comment");
  595.       if (comment.getTypeKey() != null) {
  596.         properties.put("code", comment.getTypeKey());
  597.       }

  598.       // store the comment text as content instead of a property, it may
  599.       // contain newlines
  600.       commentProduct.getContents().put("", new ByteContent(comment.getText().getBytes()));
  601.       products.add(commentProduct);
  602.     }

  603.     return products;
  604.   }

  605.   /**
  606.    * Build a product skeleton based on the current state.
  607.    *
  608.    * Product type is : [internal-](origin,magnitude,addon)[-(scenario|test)] where
  609.    * the optional scope is not "Public", and the optional usage is not "Actual".
  610.    *
  611.    * @param type   short product type, like "origin", "magnitude".
  612.    * @param action override the global message action.
  613.    * @return a Product so that properties and content can be added.
  614.    */
  615.   protected synchronized Product getProduct(final String type, final String action) {

  616.     String productType = type;
  617.     // prepend type with non Public scopes (Internal)
  618.     if (eventScope != EventScope.PUBLIC) {
  619.       productType = eventScope.toString() + "-" + productType;
  620.     }
  621.     // append to type with non Actual usages
  622.     if (eventUsage != EventUsage.ACTUAL) {
  623.       productType = productType + "-" + eventUsage.toString();
  624.     }
  625.     // make it all lower case
  626.     productType = productType.toLowerCase();

  627.     // use event id
  628.     String productCode = eventDataSource + eventEventId;
  629.     productCode = productCode.toLowerCase();

  630.     ProductId id = new ProductId(eqmessageSource.toLowerCase(), productType, productCode, eqmessageSent);
  631.     Product product = new Product(id);

  632.     // figure out whether this is a delete
  633.     String productAction = action;
  634.     if (productAction == null) {
  635.       productAction = eventAction.toString();
  636.       if (productAction == null) {
  637.         productAction = "Update";
  638.       }
  639.     }
  640.     String productStatus;
  641.     if (productAction.equalsIgnoreCase("Delete")) {
  642.       productStatus = Product.STATUS_DELETE;
  643.     } else {
  644.       productStatus = Product.STATUS_UPDATE;
  645.     }
  646.     product.setStatus(productStatus);

  647.     if (eventDataSource != null && eventEventId != null) {
  648.       product.setEventId(eventDataSource, eventEventId);
  649.     }
  650.     if (originEventTime != null) {
  651.       product.setEventTime(originEventTime);
  652.     }
  653.     if (originLongitude != null) {
  654.       product.setLongitude(originLongitude);
  655.     }
  656.     if (originLatitude != null) {
  657.       product.setLatitude(originLatitude);
  658.     }
  659.     if (originDepth != null) {
  660.       product.setDepth(originDepth);
  661.     }
  662.     if (magnitude != null) {
  663.       product.setMagnitude(magnitude);
  664.     }
  665.     if (eventVersion != null) {
  666.       product.setVersion(eventVersion);
  667.     }

  668.     /*
  669.      * Map<String, String> properties = product.getProperties(); if (eventUsage !=
  670.      * null) { properties.put("eqxml-usage", eventUsage.toString()); } if
  671.      * (eventScope != null) { properties.put("eqxml-scope", eventScope.toString());
  672.      * } if (eventAction != null) { properties.put("eqxml-action",
  673.      * eventAction.toString()); }
  674.      */

  675.     ByteContent xml = new ByteContent(eqmessageXML.getBytes());
  676.     xml.setLastModified(eqmessageSent);
  677.     product.getContents().put(EQMESSAGE_CONTENT_PATH, xml);

  678.     // add contents.xml to product to describe above content
  679.     product.getContents().put(CONTENTS_XML_PATH, getContentsXML());

  680.     return product;
  681.   }

  682.   /**
  683.    * @return a buffer of XML content
  684.    */
  685.   protected Content getContentsXML() {
  686.     StringBuffer buf = new StringBuffer();
  687.     buf.append("<?xml version=\"1.0\"?>\n");
  688.     buf.append("<contents xmlns=\"http://earthquake.usgs.gov/earthquakes/event/contents\">\n");
  689.     buf.append("<file title=\"Earthquake XML (EQXML)\" id=\"eqxml\">\n");
  690.     buf.append("<format type=\"xml\" href=\"").append(EQMESSAGE_CONTENT_PATH).append("\"/>\n");
  691.     buf.append("</file>\n");
  692.     buf.append("<page title=\"Location\" slug=\"location\">\n");
  693.     buf.append("<file refid=\"eqxml\"/>\n");
  694.     buf.append("</page>\n");
  695.     buf.append("</contents>\n");

  696.     ByteContent content = new ByteContent(buf.toString().getBytes());
  697.     content.setLastModified(eqmessageSent);
  698.     // this breaks things
  699.     // content.setContentType("application/xml");
  700.     return content;
  701.   }

  702.   /**
  703.    * Extract a CUBE_Code from a Comment.
  704.    *
  705.    * This is the ISTI convention for preserving CUBE information in EQXML
  706.    * messages. Checks a list of Comment objects for one with TypeKey="CUBE_Code"
  707.    * and Text="CUBE_Code X", where X is the returned cube code.
  708.    *
  709.    * @param comments the list of comments.
  710.    * @return the cube code, or null if not found.
  711.    */
  712.   protected synchronized String getCubeCode(final List<Comment> comments) {
  713.     String cubeCode = null;

  714.     if (comments != null) {
  715.       Iterator<Comment> iter = comments.iterator();
  716.       while (iter.hasNext()) {
  717.         Comment comment = iter.next();
  718.         if (comment.getTypeKey().equals("CUBE_Code")) {
  719.           cubeCode = comment.getText().replace("CUBE_Code ", "");
  720.           break;
  721.         }
  722.       }
  723.     }

  724.     return cubeCode;
  725.   }

  726.   /**
  727.    * @return boolean sendOriginWhenPhasesExist
  728.    */
  729.   @Override
  730.   public boolean isSendOriginWhenPhasesExist() {
  731.     return sendOriginWhenPhasesExist;
  732.   }

  733.   /** @param sendOriginWhenPhasesExist boolean to set */
  734.   @Override
  735.   public void setSendOriginWhenPhasesExist(boolean sendOriginWhenPhasesExist) {
  736.     this.sendOriginWhenPhasesExist = sendOriginWhenPhasesExist;
  737.   }

  738.   /** Unsupported functionality for an EQMessageProductCreator */
  739.   @Override
  740.   public void setSendMechanismWhenPhasesExist(boolean sendMechanismWhenPhasesExist) {
  741.     LOGGER.warning("sendMechanismWhenPhasesExist is not supported for EQMessageProductCreator");
  742.     return;
  743.   }

  744.   /** Unsupported functionality for an EQMessageProductCreator */
  745.   @Override
  746.   public boolean isSendMechanismWhenPhasesExist() {
  747.     return false;
  748.   }

  749.   @Override
  750.   public boolean isValidate() {
  751.     return validate;
  752.   }

  753.   @Override
  754.   public void setValidate(boolean validate) {
  755.     this.validate = validate;
  756.   }

  757.   @Override
  758.   public List<Product> getProducts(File file) throws Exception {
  759.     EQMessage eqxml = null;
  760.     String content = new String(FileUtils.readFile(file));
  761.     String rawEqxml = null;

  762.     // try to read eqxml
  763.     try {
  764.       eqxml = EQMessageParser.parse(StreamUtils.getInputStream(content.getBytes()), validate);
  765.       rawEqxml = content;
  766.     } catch (Exception e) {
  767.       if (validate) {
  768.         throw e;
  769.       }

  770.       // try to read cube
  771.       try {
  772.         Converter converter = new Converter();
  773.         CubeMessage cube = converter.getCubeMessage(content);
  774.         eqxml = converter.getEQMessage(cube);
  775.       } catch (Exception e2) {
  776.         if (content.startsWith(CubeEvent.TYPE) || content.startsWith(CubeDelete.TYPE)
  777.             || content.startsWith(CubeAddon.TYPE)) {
  778.           // throw cube parsing exception
  779.           throw e2;
  780.         } else {
  781.           // log cube parsing exception
  782.           LOGGER.log(Level.FINE, "Unable to parse cube message", e2);
  783.         }

  784.         // try to read eventaddon xml
  785.         try {
  786.           EventAddonParser parser = new EventAddonParser();
  787.           parser.parse(content);
  788.           eqxml = parser.getAddon().getEQMessage();
  789.         } catch (Exception e3) {
  790.           // log eventaddon parsing exception
  791.           LOGGER.log(Level.FINE, "Unable to parse eventaddon", e3);
  792.           // throw original exception
  793.           throw e;
  794.         }
  795.       }
  796.     }

  797.     return this.getEQMessageProducts(eqxml, rawEqxml);
  798.   }

  799.   /** Type for general text */
  800.   public static final String GENERAL_TEXT_TYPE = "general-text";
  801.   /** Empty string array for general text addons */
  802.   public static final String[] GENERAL_TEXT_ADDONS = new String[] {};

  803.   /** Type for scitech text */
  804.   public static final String SCITECH_TEXT_TYPE = "scitech-text";
  805.   /** Empty string array for scitech text addons */
  806.   public static final String[] SCITECH_TEXT_ADDONS = new String[] {};

  807.   /** Type for impact text */
  808.   public static final String IMPACT_TEXT_TYPE = "impact-text";
  809.   /** String array for impact text addons */
  810.   public static final String[] IMPACT_TEXT_ADDONS = new String[] { "feltreports" };

  811.   /** Selected link type products have a mapping. */
  812.   public static final String GENERAL_LINK_TYPE = "general-link";
  813.   /** String array for general link addons */
  814.   public static final String[] GENERAL_LINK_ADDONS = new String[] { "aftershock", "afterwarn", "asw", "generalmisc" };

  815.   /** Type for scitech link */
  816.   public static final String SCITECH_LINK_TYPE = "scitech-link";
  817.   /** String array for scitech link */
  818.   public static final String[] SCITECH_LINK_ADDONS = new String[] { "energy", "focalmech", "ncfm", "histmomenttensor",
  819.       "finitefault", "momenttensor", "mtensor", "phase", "seiscrosssec", "seisrecsec", "traveltimes", "waveform",
  820.       "seismograms", "scitechmisc" };

  821.   /** Type for impact link */
  822.   public static final String IMPACT_LINK_TYPE = "impact-link";
  823.   /** String array for impact link */
  824.   public static final String[] IMPACT_LINK_ADDONS = new String[] { "tsunamilinks", "impactmisc" };

  825.   /**
  826.    * Map from cube style link addon to product type.
  827.    *
  828.    * @param addonType String to find correct link type
  829.    * @return null if link should not be converted to a product.
  830.    */
  831.   public String getLinkAddonProductType(final String addonType) {
  832.     String c = addonType.toLowerCase();

  833.     for (String general : GENERAL_LINK_ADDONS) {
  834.       if (c.startsWith(general)) {
  835.         return GENERAL_LINK_TYPE;
  836.       }
  837.     }

  838.     for (String scitech : SCITECH_LINK_ADDONS) {
  839.       if (c.startsWith(scitech)) {
  840.         return SCITECH_LINK_TYPE;
  841.       }
  842.     }

  843.     for (String impact : IMPACT_LINK_ADDONS) {
  844.       if (c.startsWith(impact)) {
  845.         return IMPACT_LINK_TYPE;
  846.       }
  847.     }

  848.     return null;
  849.   }

  850.   /**
  851.    * Map from cube style text addon to product type.
  852.    *
  853.    * @param addonType to find correct addon type
  854.    * @return null if comment should not be converted to a product.
  855.    */
  856.   public String getTextAddonProductType(final String addonType) {
  857.     String c = addonType.toLowerCase();

  858.     for (String general : GENERAL_TEXT_ADDONS) {
  859.       if (c.startsWith(general)) {
  860.         return GENERAL_TEXT_TYPE;
  861.       }
  862.     }

  863.     for (String impact : IMPACT_TEXT_ADDONS) {
  864.       if (c.startsWith(impact)) {
  865.         return IMPACT_TEXT_TYPE;
  866.       }
  867.     }

  868.     for (String scitech : SCITECH_TEXT_ADDONS) {
  869.       if (c.startsWith(scitech)) {
  870.         return SCITECH_TEXT_TYPE;
  871.       }
  872.     }

  873.     return null;
  874.   }

  875. }