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.eqxml.EQMessageParser;
  37. import gov.usgs.earthquake.event.Converter;
  38. import gov.usgs.earthquake.product.ByteContent;
  39. import gov.usgs.earthquake.product.Content;
  40. import gov.usgs.earthquake.product.Product;
  41. import gov.usgs.earthquake.product.ProductId;
  42. import gov.usgs.util.FileUtils;
  43. import gov.usgs.util.StreamUtils;
  44. import gov.usgs.util.XmlUtils;

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

  70.     private static final Logger LOGGER = Logger
  71.             .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
  116.      *            the EQMessage containing products.
  117.      * @return a list of created products.
  118.      * @throws Exception if error occurs
  119.      */
  120.     public synchronized List<Product> getEQMessageProducts(
  121.             final EQMessage message) throws Exception {
  122.         return getEQMessageProducts(message, null);
  123.     }

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

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

  156.         if (message == null) {
  157.             return products;
  158.         }

  159.         this.eqmessage = message;
  160.         this.eqmessageSource = message.getSource();
  161.         this.eqmessageSent = message.getSent();

  162.         if (this.eqmessageSent == null) {
  163.             this.eqmessageSent = new Date();
  164.         }

  165.         // convert to xml
  166.         if (rawEqxml != null) {
  167.             this.eqmessageXML = rawEqxml;
  168.         } else {
  169.             ByteArrayOutputStream baos = new ByteArrayOutputStream();
  170.             EQMessageParser.serialize(message, baos, validate);
  171.             this.eqmessageXML = baos.toString();
  172.         }

  173.         // process each event
  174.         List<Event> events = message.getEvent();
  175.         if (events != null) {
  176.             Iterator<Event> iter = events.iterator();
  177.             while (iter.hasNext()) {
  178.                 products.addAll(getEventProducts(iter.next()));
  179.             }
  180.         }

  181.         this.eqmessageSource = null;
  182.         this.eqmessageSent = null;
  183.         this.eqmessageXML = null;

  184.         return products;
  185.     }

  186.     /**
  187.      * Get products from an event.
  188.      *
  189.      * @param event
  190.      *            the event containing products.
  191.      * @return a list of created products.
  192.      * @throws Exception if error occurs
  193.      */
  194.     protected synchronized List<Product> getEventProducts(final Event event)
  195.             throws Exception {
  196.         List<Product> products = new LinkedList<Product>();
  197.         if (event == null) {
  198.             return products;
  199.         }

  200.         eventDataSource = event.getDataSource();
  201.         eventEventId = event.getEventID();
  202.         eventVersion = event.getVersion();
  203.         eventAction = event.getAction();
  204.         eventUsage = event.getUsage();
  205.         eventScope = event.getScope();

  206.         // default values
  207.         if (eventAction == null) {
  208.             eventAction = EventAction.UPDATE;
  209.         }
  210.         if (eventUsage == null) {
  211.             eventUsage = EventUsage.ACTUAL;
  212.         }
  213.         if (eventScope == null) {
  214.             eventScope = EventScope.PUBLIC;
  215.         }

  216.         if (eventAction == EventAction.DELETE) {
  217.             // delete origin product (only product with location)
  218.             Product deleteProduct = getProduct("origin", eventAction.toString());
  219.             products.add(deleteProduct);
  220.         } else {
  221.             // update origin product
  222.             products.addAll(getOriginProducts(event.getOrigin(), event));
  223.         }

  224.         for (ProductLink eventLink : event.getProductLink()) {
  225.             products.addAll(getProductLinkProducts(eventLink));
  226.         }
  227.         for (Comment eventComment : event.getComment()) {
  228.             products.addAll(getCommentProducts(eventComment));
  229.         }

  230.         eventDataSource = null;
  231.         eventEventId = null;
  232.         eventVersion = null;
  233.         eventAction = null;
  234.         eventUsage = null;
  235.         eventScope = null;

  236.         return products;
  237.     }

  238.     /**
  239.      * Get origin product(s).
  240.      *
  241.      * This implementation only creates one origin (the first one) regardless of
  242.      * how many origins are provided.
  243.      *
  244.      * @param origins
  245.      *            the list of origins.
  246.      * @param event
  247.      *            A specific event
  248.      * @return a list of created products.
  249.      * @throws Exception if error occurs
  250.      */
  251.     protected synchronized List<Product> getOriginProducts(
  252.             final List<Origin> origins, final Event event) throws Exception {
  253.         List<Product> products = new LinkedList<Product>();
  254.         if (origins == null || origins.size() == 0) {
  255.             return products;
  256.         }

  257.         // only process first origin
  258.         Origin origin = origins.get(0);

  259.         // get sub-products
  260.         products.addAll(getFocalMechanismProducts(origin.getMomentTensor()));

  261.         this.originLatitude = origin.getLatitude();
  262.         this.originLongitude = origin.getLongitude();
  263.         this.originDepth = origin.getDepth();
  264.         this.originEventTime = origin.getTime();

  265.         boolean preferred = (origin.getPreferredFlag() == null || origin
  266.                 .getPreferredFlag());

  267.         // only process "preferred" origins
  268.         // this is how hydra differentiates between origins as input parameters
  269.         // to focal mechanisms, and origins as origins
  270.         if (preferred && this.originLatitude != null
  271.                 && this.originLongitude != null && this.originEventTime != null) {
  272.             // create an origin/magnitude product only if origin has
  273.             // lat+lon+time

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

  276.             // now build origin product
  277.             Action originAction = origin.getAction();
  278.             Product originProduct = getProduct("origin",
  279.                     originAction == null ? null : originAction.toString());
  280.             // origin specific properties
  281.             Map<String, String> properties = originProduct.getProperties();

  282.             // set event type
  283.             properties.put(
  284.                     "event-type",
  285.                     (event.getType() == null ? EventType.EARTHQUAKE : event
  286.                             .getType()).value().toLowerCase());

  287.             if (magnitudeProducts.size() > 0) {
  288.                 // transfer magnitude product properties to origin
  289.                 properties.putAll(magnitudeProducts.get(0).getProperties());
  290.             }

  291.             if (origin.getSourceKey() != null) {
  292.                 properties.put("origin-source", origin.getSourceKey());
  293.             }
  294.             if (origin.getAzimGap() != null) {
  295.                 properties.put("azimuthal-gap", origin.getAzimGap().toString());
  296.             }
  297.             if (origin.getDepthError() != null) {
  298.                 properties
  299.                         .put("depth-error", origin.getDepthError().toString());
  300.             }
  301.             if (origin.getDepthMethod() != null) {
  302.                 properties.put("depth-method", origin.getDepthMethod());
  303.             }
  304.             if (origin.getErrh() != null) {
  305.                 properties.put("horizontal-error", origin.getErrh().toString());
  306.             }
  307.             if (origin.getErrz() != null) {
  308.                 properties.put("vertical-error", origin.getErrz().toString());
  309.             }
  310.             if (origin.getLatError() != null) {
  311.                 properties.put("latitude-error", origin.getLatError()
  312.                         .toString());
  313.             }
  314.             if (origin.getLonError() != null) {
  315.                 properties.put("longitude-error", origin.getLonError()
  316.                         .toString());
  317.             }
  318.             if (origin.getMinDist() != null) {
  319.                 properties.put("minimum-distance", origin.getMinDist()
  320.                         .toString());
  321.             }
  322.             if (origin.getNumPhaUsed() != null) {
  323.                 properties.put("num-phases-used", origin.getNumPhaUsed()
  324.                         .toString());
  325.             }
  326.             if (origin.getNumStaUsed() != null) {
  327.                 properties.put("num-stations-used", origin.getNumStaUsed()
  328.                         .toString());
  329.             }
  330.             if (origin.getRegion() != null) {
  331.                 properties.put("region", origin.getRegion());
  332.             }
  333.             if (origin.getStatus() != null) {
  334.                 properties.put("review-status", origin.getStatus().toString());
  335.             }
  336.             if (origin.getStdError() != null) {
  337.                 properties.put("standard-error", origin.getStdError()
  338.                         .toString());
  339.             }

  340.             // origin method
  341.             Iterator<Method> methods = origin.getMethod().iterator();
  342.             if (methods.hasNext()) {
  343.                 Method method = methods.next();
  344.                 if (method.getClazz() != null) {
  345.                     properties.put("location-method-class", method.getClazz());
  346.                 }
  347.                 if (method.getAlgorithm() != null) {
  348.                     properties.put("location-method-algorithm",
  349.                             method.getAlgorithm());
  350.                 }
  351.                 if (method.getModel() != null) {
  352.                     properties.put("location-method-model", method.getModel());
  353.                 }

  354.                 String cubeLocationMethod = getCubeCode(method.getComment());
  355.                 if (cubeLocationMethod != null) {
  356.                     properties.put("cube-location-method", cubeLocationMethod);
  357.                 }
  358.             }

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

  362.                 if (sendOriginWhenPhasesExist) {
  363.                     // create lightweight origin product
  364.                     Product lightweightOrigin = new Product(originProduct);
  365.                     lightweightOrigin.getId().setType("origin");
  366.                     lightweightOrigin.getContents().remove(
  367.                             EQMESSAGE_CONTENT_PATH);

  368.                     // seek and destroy phases
  369.                     Iterator<Origin> iter = origins.iterator();
  370.                     while (iter.hasNext()) {
  371.                         Origin next = iter.next();
  372.                         if (next.getPhase() != null) {
  373.                             next.getPhase().clear();
  374.                         }
  375.                     }

  376.                     // serialize xml without phase data
  377.                     ByteArrayOutputStream baos = new ByteArrayOutputStream();
  378.                     EQMessageParser.serialize(this.eqmessage, baos, validate);
  379.                     lightweightOrigin.getContents().put(EQMESSAGE_CONTENT_PATH,
  380.                             new ByteContent(baos.toByteArray()));

  381.                     products.add(0, lightweightOrigin);
  382.                 }
  383.             } else {
  384.                 // insert origin at start of list
  385.                 products.add(0, originProduct);
  386.             }
  387.         }

  388.         this.originDepth = null;
  389.         this.originEventTime = null;
  390.         this.originLatitude = null;
  391.         this.originLongitude = null;
  392.         this.magnitude = null;

  393.         for (Comment originComment : origin.getComment()) {
  394.             products.addAll(getCommentProducts(originComment));
  395.         }

  396.         return products;
  397.     }

  398.     /**
  399.      * Build magnitude products.
  400.      *
  401.      * This implementation builds at most one magnitude product (the first).
  402.      *
  403.      * @param magnitudes
  404.      *            a list of candidate magsnitude objects.
  405.      * @return a list of built magnitude products, which may be empty.
  406.      */
  407.     protected synchronized List<Product> getMagnitudeProducts(
  408.             final List<Magnitude> magnitudes) {
  409.         List<Product> products = new LinkedList<Product>();
  410.         if (magnitudes == null || magnitudes.size() == 0) {
  411.             return products;
  412.         }

  413.         // build product based on the first magnitude
  414.         Magnitude magnitude = magnitudes.get(0);
  415.         // set "globalish" property before getProduct()
  416.         this.magnitude = magnitude.getValue();

  417.         Action magnitudeAction = magnitude.getAction();
  418.         // build magnitude product
  419.         Product product = getProduct("magnitude",
  420.                 magnitudeAction == null ? null : magnitudeAction.toString());

  421.         Map<String, String> properties = product.getProperties();
  422.         if (magnitude.getSourceKey() != null) {
  423.             properties.put("magnitude-source", magnitude.getSourceKey());
  424.         }
  425.         if (magnitude.getTypeKey() != null) {
  426.             properties.put("magnitude-type", magnitude.getTypeKey());
  427.         }
  428.         if (magnitude.getAzimGap() != null) {
  429.             properties.put("magnitude-azimuthal-gap", magnitude.getAzimGap()
  430.                     .toString());
  431.         }
  432.         if (magnitude.getError() != null) {
  433.             properties.put("magnitude-error", magnitude.getError().toString());
  434.         }
  435.         if (magnitude.getNumStations() != null) {
  436.             properties.put("magnitude-num-stations-used", magnitude
  437.                     .getNumStations().toString());
  438.         }

  439.         String cubeMagnitudeType = getCubeCode(magnitude.getComment());
  440.         if (cubeMagnitudeType != null) {
  441.             properties.put("cube-magnitude-type", cubeMagnitudeType);
  442.         }

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

  445.         products.add(product);
  446.         return products;
  447.     }

  448.     /**
  449.      * Gets a list of Focal Mechanism products from momentTensors
  450.      * @param momentTensors List of Moment Tensors
  451.      * @return a list of products
  452.      */
  453.     protected synchronized List<Product> getFocalMechanismProducts(
  454.             final List<MomentTensor> momentTensors) {
  455.         List<Product> products = new LinkedList<Product>();
  456.         if (momentTensors == null || momentTensors.size() == 0) {
  457.             return products;
  458.         }

  459.         // build moment tensors
  460.         Iterator<MomentTensor> iter = momentTensors.iterator();
  461.         while (iter.hasNext()) {
  462.             MomentTensor mt = iter.next();

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

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

  468.             // fill in product properties
  469.             if (mt.getSourceKey() != null) {
  470.                 properties.put("beachball-source", mt.getSourceKey());
  471.             }
  472.             if (mt.getTypeKey() != null) {
  473.                 properties.put("beachball-type", mt.getTypeKey());
  474.                 // append source+type to code
  475.                 ProductId productId = product.getId();
  476.                 productId.setCode((productId.getCode() + "-"
  477.                         + mt.getSourceKey() + "-" + mt.getTypeKey())
  478.                         .toLowerCase());
  479.             }
  480.             if (mt.getMagMw() != null) {
  481.                 product.setMagnitude(mt.getMagMw());
  482.             }
  483.             if (mt.getM0() != null) {
  484.                 properties.put("scalar-moment", mt.getM0().toString());
  485.             }

  486.             if (mt.getTensor() != null) {
  487.                 // if the tensor is included, it is a "moment-tensor" instead of
  488.                 // a "focal-mechanism"
  489.                 product.getId().setType("moment-tensor");

  490.                 Tensor t = mt.getTensor();
  491.                 if (t.getMtt() != null) {
  492.                     properties.put("tensor-mtt", t.getMtt().toString());
  493.                 }
  494.                 if (t.getMpp() != null) {
  495.                     properties.put("tensor-mpp", t.getMpp().toString());
  496.                 }
  497.                 if (t.getMrr() != null) {
  498.                     properties.put("tensor-mrr", t.getMrr().toString());
  499.                 }
  500.                 if (t.getMtp() != null) {
  501.                     properties.put("tensor-mtp", t.getMtp().toString());
  502.                 }
  503.                 if (t.getMrp() != null) {
  504.                     properties.put("tensor-mrp", t.getMrp().toString());
  505.                 }
  506.                 if (t.getMrt() != null) {
  507.                     properties.put("tensor-mrt", t.getMrt().toString());
  508.                 }
  509.             }

  510.             if (mt.getNodalPlanes() != null) {
  511.                 NodalPlanes np = mt.getNodalPlanes();
  512.                 List<Fault> faults = np.getFault();
  513.                 if (faults.size() == 2) {
  514.                     Fault fault1 = faults.get(0);
  515.                     if (fault1.getDip() != null) {
  516.                         properties.put("nodal-plane-1-dip", fault1.getDip()
  517.                                 .toString());
  518.                     }
  519.                     if (fault1.getSlip() != null) {
  520.                         properties.put("nodal-plane-1-slip", fault1.getSlip()
  521.                                 .toString());
  522.                     }
  523.                     if (fault1.getStrike() != null) {
  524.                         properties.put("nodal-plane-1-strike", fault1
  525.                                 .getStrike().toString());
  526.                     }
  527.                     Fault fault2 = faults.get(1);
  528.                     if (fault2.getDip() != null) {
  529.                         properties.put("nodal-plane-2-dip", fault2.getDip()
  530.                                 .toString());
  531.                     }
  532.                     if (fault2.getSlip() != null) {
  533.                         properties.put("nodal-plane-2-slip", fault2.getSlip()
  534.                                 .toString());
  535.                     }
  536.                     if (fault2.getStrike() != null) {
  537.                         properties.put("nodal-plane-2-strike", fault2
  538.                                 .getStrike().toString());
  539.                     }
  540.                 }
  541.             }

  542.             if (mt.getDerivedOriginTime() != null) {
  543.                 properties.put("derived-eventtime",
  544.                         XmlUtils.formatDate(mt.getDerivedOriginTime()));
  545.             }
  546.             if (mt.getDerivedLatitude() != null) {
  547.                 properties.put("derived-latitude", mt.getDerivedLatitude()
  548.                         .toString());
  549.             }
  550.             if (mt.getDerivedLongitude() != null) {
  551.                 properties.put("derived-longitude", mt.getDerivedLongitude()
  552.                         .toString());
  553.             }
  554.             if (mt.getDerivedDepth() != null) {
  555.                 properties
  556.                         .put("derived-depth", mt.getDerivedDepth().toString());
  557.             }

  558.             if (mt.getPerDblCpl() != null) {
  559.                 properties.put("percent-double-couple", mt.getPerDblCpl()
  560.                         .toString());
  561.             }
  562.             if (mt.getNumObs() != null) {
  563.                 properties.put("num-stations-used", mt.getNumObs().toString());
  564.             }

  565.             // attach original message as product content
  566.             ByteContent xml = new ByteContent(eqmessageXML.getBytes());
  567.             xml.setLastModified(eqmessageSent);
  568.             xml.setContentType("application/xml");
  569.             product.getContents().put(EQMESSAGE_CONTENT_PATH, xml);

  570.             // add to list of built products
  571.             products.add(product);
  572.         }

  573.         return products;
  574.     }

  575.     /**
  576.      * Get product(s) from a ProductLink object.
  577.      *
  578.      * @param link
  579.      *            the link object.
  580.      * @return a list of found products.
  581.      * @throws Exception if error occurs
  582.      */
  583.     protected synchronized List<Product> getProductLinkProducts(
  584.             final ProductLink link) throws Exception {
  585.         List<Product> products = new LinkedList<Product>();

  586.         String linkType = getLinkAddonProductType(link.getCode());
  587.         if (linkType == null) {
  588.             LOGGER.finer("No product type found for productlink with code '"
  589.                     + link.getCode() + "', skipping");
  590.             return products;
  591.         }

  592.         Action linkAction = link.getAction();
  593.         Product linkProduct = getProduct(linkType, linkAction == null ? null
  594.                 : linkAction.toString());
  595.         // remove the EQXML, only send product link attributes with link
  596.         // products
  597.         linkProduct.getContents().clear();

  598.         // add addon code to product code
  599.         ProductId id = linkProduct.getId();
  600.         id.setCode(id.getCode() + "-" + link.getCode().toLowerCase());

  601.         if (link.getVersion() != null) {
  602.             linkProduct.setVersion(link.getVersion());
  603.         }

  604.         Map<String, String> properties = linkProduct.getProperties();
  605.         if (link.getLink() != null) {
  606.             properties.put("url", link.getLink());
  607.         }
  608.         if (link.getNote() != null) {
  609.             properties.put("text", link.getNote());
  610.         }
  611.         if (link.getCode() != null) {
  612.             properties.put("addon-code", link.getCode());
  613.         }
  614.         properties.put("addon-type", link.getTypeKey());

  615.         products.add(linkProduct);
  616.         return products;
  617.     }

  618.     /**
  619.      * Get product(s) from a Comment object.
  620.      *
  621.      * @param comment
  622.      *            the comment object.
  623.      * @return a list of found products.
  624.      * @throws Exception if error occurs
  625.      */
  626.     protected synchronized List<Product> getCommentProducts(
  627.             final Comment comment) throws Exception {
  628.         List<Product> products = new LinkedList<Product>();

  629.         // CUBE_Codes are attributes of the containing product.
  630.         String typeKey = comment.getTypeKey();
  631.         if (typeKey != null && !typeKey.equals("CUBE_Code")) {
  632.             String commentType = getTextAddonProductType(typeKey);
  633.             if (commentType == null) {
  634.                 LOGGER.finer("No product type found for comment with type '"
  635.                         + comment.getTypeKey() + "'");
  636.                 return products;
  637.             }

  638.             Action commentAction = comment.getAction();
  639.             Product commentProduct = getProduct(commentType,
  640.                     commentAction == null ? null : commentAction.toString());
  641.             // remove the EQXML, only send comment text with comment products
  642.             commentProduct.getContents().clear();

  643.             // one product per comment type
  644.             ProductId id = commentProduct.getId();
  645.             id.setCode(id.getCode() + "_" + comment.getTypeKey().toLowerCase());

  646.             Map<String, String> properties = commentProduct.getProperties();
  647.             properties.put("addon-type", "comment");
  648.             if (comment.getTypeKey() != null) {
  649.                 properties.put("code", comment.getTypeKey());
  650.             }

  651.             // store the comment text as content instead of a property, it may
  652.             // contain newlines
  653.             commentProduct.getContents().put("",
  654.                     new ByteContent(comment.getText().getBytes()));
  655.             products.add(commentProduct);
  656.         }

  657.         return products;
  658.     }

  659.     /**
  660.      * Build a product skeleton based on the current state.
  661.      *
  662.      * Product type is : [internal-](origin,magnitude,addon)[-(scenario|test)]
  663.      * where the optional scope is not "Public", and the optional usage is not
  664.      * "Actual".
  665.      *
  666.      * @param type
  667.      *            short product type, like "origin", "magnitude".
  668.      * @param action
  669.      *            override the global message action.
  670.      * @return a Product so that properties and content can be added.
  671.      */
  672.     protected synchronized Product getProduct(final String type,
  673.             final String action) {

  674.         String productType = type;
  675.         // prepend type with non Public scopes (Internal)
  676.         if (eventScope != EventScope.PUBLIC) {
  677.             productType = eventScope.toString() + "-" + productType;
  678.         }
  679.         // append to type with non Actual usages
  680.         if (eventUsage != EventUsage.ACTUAL) {
  681.             productType = productType + "-" + eventUsage.toString();
  682.         }
  683.         // make it all lower case
  684.         productType = productType.toLowerCase();

  685.         // use event id
  686.         String productCode = eventDataSource + eventEventId;
  687.         productCode = productCode.toLowerCase();

  688.         ProductId id = new ProductId(eqmessageSource.toLowerCase(),
  689.                 productType, productCode, eqmessageSent);
  690.         Product product = new Product(id);

  691.         // figure out whether this is a delete
  692.         String productAction = action;
  693.         if (productAction == null) {
  694.             productAction = eventAction.toString();
  695.             if (productAction == null) {
  696.                 productAction = "Update";
  697.             }
  698.         }
  699.         String productStatus;
  700.         if (productAction.equalsIgnoreCase("Delete")) {
  701.             productStatus = Product.STATUS_DELETE;
  702.         } else {
  703.             productStatus = Product.STATUS_UPDATE;
  704.         }
  705.         product.setStatus(productStatus);

  706.         if (eventDataSource != null && eventEventId != null) {
  707.             product.setEventId(eventDataSource, eventEventId);
  708.         }
  709.         if (originEventTime != null) {
  710.             product.setEventTime(originEventTime);
  711.         }
  712.         if (originLongitude != null) {
  713.             product.setLongitude(originLongitude);
  714.         }
  715.         if (originLatitude != null) {
  716.             product.setLatitude(originLatitude);
  717.         }
  718.         if (originDepth != null) {
  719.             product.setDepth(originDepth);
  720.         }
  721.         if (magnitude != null) {
  722.             product.setMagnitude(magnitude);
  723.         }
  724.         if (eventVersion != null) {
  725.             product.setVersion(eventVersion);
  726.         }

  727.         /*
  728.          * Map<String, String> properties = product.getProperties(); if
  729.          * (eventUsage != null) { properties.put("eqxml-usage",
  730.          * eventUsage.toString()); } if (eventScope != null) {
  731.          * properties.put("eqxml-scope", eventScope.toString()); } if
  732.          * (eventAction != null) { properties.put("eqxml-action",
  733.          * eventAction.toString()); }
  734.          */

  735.         ByteContent xml = new ByteContent(eqmessageXML.getBytes());
  736.         xml.setLastModified(eqmessageSent);
  737.         product.getContents().put(EQMESSAGE_CONTENT_PATH, xml);

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

  740.         return product;
  741.     }

  742.     /**
  743.      * @return a buffer of XML content
  744.      */
  745.     protected Content getContentsXML() {
  746.         StringBuffer buf = new StringBuffer();
  747.         buf.append("<?xml version=\"1.0\"?>\n");
  748.         buf.append("<contents xmlns=\"http://earthquake.usgs.gov/earthquakes/event/contents\">\n");
  749.         buf.append("<file title=\"Earthquake XML (EQXML)\" id=\"eqxml\">\n");
  750.         buf.append("<format type=\"xml\" href=\"")
  751.                 .append(EQMESSAGE_CONTENT_PATH).append("\"/>\n");
  752.         buf.append("</file>\n");
  753.         buf.append("<page title=\"Location\" slug=\"location\">\n");
  754.         buf.append("<file refid=\"eqxml\"/>\n");
  755.         buf.append("</page>\n");
  756.         buf.append("</contents>\n");

  757.         ByteContent content = new ByteContent(buf.toString().getBytes());
  758.         content.setLastModified(eqmessageSent);
  759.         // this breaks things
  760.         // content.setContentType("application/xml");
  761.         return content;
  762.     }

  763.     /**
  764.      * Extract a CUBE_Code from a Comment.
  765.      *
  766.      * This is the ISTI convention for preserving CUBE information in EQXML
  767.      * messages. Checks a list of Comment objects for one with
  768.      * TypeKey="CUBE_Code" and Text="CUBE_Code X", where X is the returned cube
  769.      * code.
  770.      *
  771.      * @param comments
  772.      *            the list of comments.
  773.      * @return the cube code, or null if not found.
  774.      */
  775.     protected synchronized String getCubeCode(final List<Comment> comments) {
  776.         String cubeCode = null;

  777.         if (comments != null) {
  778.             Iterator<Comment> iter = comments.iterator();
  779.             while (iter.hasNext()) {
  780.                 Comment comment = iter.next();
  781.                 if (comment.getTypeKey().equals("CUBE_Code")) {
  782.                     cubeCode = comment.getText().replace("CUBE_Code ", "");
  783.                     break;
  784.                 }
  785.             }
  786.         }

  787.         return cubeCode;
  788.     }

  789.     /**
  790.      * @return boolean sendOriginWhenPhasesExist
  791.      */
  792.     public boolean isSendOriginWhenPhasesExist() {
  793.         return sendOriginWhenPhasesExist;
  794.     }

  795.     /** @param sendOriginWhenPhasesExist boolean to set */
  796.     public void setSendOriginWhenPhasesExist(boolean sendOriginWhenPhasesExist) {
  797.         this.sendOriginWhenPhasesExist = sendOriginWhenPhasesExist;
  798.     }

  799.     @Override
  800.     public boolean isValidate() {
  801.         return validate;
  802.     }

  803.     @Override
  804.     public void setValidate(boolean validate) {
  805.         this.validate = validate;
  806.     }

  807.     @Override
  808.     public List<Product> getProducts(File file) throws Exception {
  809.         EQMessage eqxml = null;
  810.         String content = new String(FileUtils.readFile(file));
  811.         String rawEqxml = null;

  812.         // try to read eqxml
  813.         try {
  814.             eqxml = EQMessageParser.parse(
  815.                     StreamUtils.getInputStream(content.getBytes()), validate);
  816.             rawEqxml = content;
  817.         } catch (Exception e) {
  818.             if (validate) {
  819.                 throw e;
  820.             }

  821.             // try to read cube
  822.             try {
  823.                 Converter converter = new Converter();
  824.                 CubeMessage cube = converter.getCubeMessage(content);
  825.                 eqxml = converter.getEQMessage(cube);
  826.             } catch (Exception e2) {
  827.                 if (content.startsWith(CubeEvent.TYPE) ||
  828.                         content.startsWith(CubeDelete.TYPE) ||
  829.                         content.startsWith(CubeAddon.TYPE)) {
  830.                     // throw cube parsing exception
  831.                     throw e2;
  832.                 } else {
  833.                     // log cube parsing exception
  834.                     LOGGER.log(Level.FINE, "Unable to parse cube message", e2);
  835.                 }

  836.                 // try to read eventaddon xml
  837.                 try {
  838.                     EventAddonParser parser = new EventAddonParser();
  839.                     parser.parse(content);
  840.                     eqxml = parser.getAddon().getEQMessage();
  841.                 } catch (Exception e3) {
  842.                     // log eventaddon parsing exception
  843.                     LOGGER.log(Level.FINE, "Unable to parse eventaddon", e3);
  844.                     // throw original exception
  845.                     throw e;
  846.                 }
  847.             }
  848.         }

  849.         return this.getEQMessageProducts(eqxml, rawEqxml);
  850.     }

  851.     /** Type for general text */
  852.     public static final String GENERAL_TEXT_TYPE = "general-text";
  853.     /** Empty string array for general text addons */
  854.     public static final String[] GENERAL_TEXT_ADDONS = new String[] {};

  855.     /** Type for scitech text */
  856.     public static final String SCITECH_TEXT_TYPE = "scitech-text";
  857.     /** Empty string array for scitech text addons */
  858.     public static final String[] SCITECH_TEXT_ADDONS = new String[] {};

  859.     /** Type for impact text */
  860.     public static final String IMPACT_TEXT_TYPE = "impact-text";
  861.     /** String array for impact text addons */
  862.     public static final String[] IMPACT_TEXT_ADDONS = new String[] { "feltreports" };

  863.     /** Selected link type products have a mapping. */
  864.     public static final String GENERAL_LINK_TYPE = "general-link";
  865.     /** String array for general link addons */
  866.     public static final String[] GENERAL_LINK_ADDONS = new String[] {
  867.             "aftershock", "afterwarn", "asw", "generalmisc" };

  868.     /** Type for scitech link */
  869.     public static final String SCITECH_LINK_TYPE = "scitech-link";
  870.     /** String array for scitech link */
  871.     public static final String[] SCITECH_LINK_ADDONS = new String[] { "energy",
  872.             "focalmech", "ncfm", "histmomenttensor", "finitefault",
  873.             "momenttensor", "mtensor", "phase", "seiscrosssec", "seisrecsec",
  874.             "traveltimes", "waveform", "seismograms", "scitechmisc" };

  875.     /** Type for impact link */
  876.     public static final String IMPACT_LINK_TYPE = "impact-link";
  877.     /** String array for impact link */
  878.     public static final String[] IMPACT_LINK_ADDONS = new String[] {
  879.             "tsunamilinks", "impactmisc" };

  880.     /**
  881.      * Map from cube style link addon to product type.
  882.      *
  883.      * @param addonType String to find correct link type
  884.      * @return null if link should not be converted to a product.
  885.      */
  886.     public String getLinkAddonProductType(final String addonType) {
  887.         String c = addonType.toLowerCase();

  888.         for (String general : GENERAL_LINK_ADDONS) {
  889.             if (c.startsWith(general)) {
  890.                 return GENERAL_LINK_TYPE;
  891.             }
  892.         }

  893.         for (String scitech : SCITECH_LINK_ADDONS) {
  894.             if (c.startsWith(scitech)) {
  895.                 return SCITECH_LINK_TYPE;
  896.             }
  897.         }

  898.         for (String impact : IMPACT_LINK_ADDONS) {
  899.             if (c.startsWith(impact)) {
  900.                 return IMPACT_LINK_TYPE;
  901.             }
  902.         }

  903.         return null;
  904.     }

  905.     /**
  906.      * Map from cube style text addon to product type.
  907.      *
  908.      * @param addonType to find correct addon type
  909.      * @return null if comment should not be converted to a product.
  910.      */
  911.     public String getTextAddonProductType(final String addonType) {
  912.         String c = addonType.toLowerCase();

  913.         for (String general : GENERAL_TEXT_ADDONS) {
  914.             if (c.startsWith(general)) {
  915.                 return GENERAL_TEXT_TYPE;
  916.             }
  917.         }

  918.         for (String impact : IMPACT_TEXT_ADDONS) {
  919.             if (c.startsWith(impact)) {
  920.                 return IMPACT_TEXT_TYPE;
  921.             }
  922.         }

  923.         for (String scitech : SCITECH_TEXT_ADDONS) {
  924.             if (c.startsWith(scitech)) {
  925.                 return SCITECH_TEXT_TYPE;
  926.             }
  927.         }

  928.         return null;
  929.     }

  930. }