XmlProductSource.java

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

  5. import java.io.IOException;
  6. import java.io.InputStream;
  7. import java.io.PipedInputStream;
  8. import java.io.PipedOutputStream;
  9. import java.net.URI;
  10. import java.net.URL;
  11. import java.util.Base64;
  12. import java.util.Date;

  13. import org.xml.sax.Attributes;
  14. import org.xml.sax.SAXException;
  15. import org.xml.sax.helpers.DefaultHandler;

  16. import gov.usgs.util.StreamUtils;
  17. import gov.usgs.util.XmlUtils;
  18. import gov.usgs.util.CryptoUtils.Version;
  19. import gov.usgs.earthquake.product.InputStreamContent;
  20. import gov.usgs.earthquake.product.URLContent;
  21. import gov.usgs.earthquake.product.ProductId;


  22. /**
  23.  * Load a product from an InputStream containing XML.
  24.  */
  25. public class XmlProductSource extends DefaultHandler implements ProductSource {

  26.     /** The input stream where xml is read. */
  27.     private InputStream in;

  28.     /** The ProductOutput where events are sent. */
  29.     private ProductHandler out;

  30.     /** The Product being parsed. */
  31.     private ProductId id;

  32.     /** Used to read content in ProductOutput while parsing continues. */
  33.     private ContentOutputThread contentOutputThread;

  34.     /** Used to send content to ProductOutput as it is read. */
  35.     private PipedOutputStream contentOutputStream;

  36.     /** Content being read. */
  37.     private InputStreamContent content;

  38.     /** Used for signature ProductOutput. */
  39.     private StringBuffer signatureBuffer;

  40.     /**
  41.      * Create a new XmlProductSource.
  42.      *
  43.      * @param in
  44.      *            the input stream where xml is read.
  45.      */
  46.     public XmlProductSource(final InputStream in) {
  47.         this.in = in;
  48.     }

  49.     /**
  50.      * Create a new XmlProductSource for embedding in another default handler.
  51.      *
  52.      * @param out
  53.      *            the ProductHandler to receive product events.
  54.      */
  55.     public XmlProductSource(final ProductHandler out) {
  56.         this.out = out;
  57.     }

  58.     /**
  59.      * Begin reading the input stream, sending events to out.
  60.      *
  61.      * @param out
  62.      *            the receiving ProductOutput.
  63.      */
  64.     public synchronized void streamTo(ProductHandler out) throws Exception {
  65.         try {
  66.             this.out = out;
  67.             XmlUtils.parse(in, this);
  68.         } finally {
  69.             StreamUtils.closeStream(in);
  70.         }
  71.     }

  72.     /**
  73.      * Override DefaultHandler startElement. Adds a new element content buffer
  74.      * and calls onStartElement.
  75.      *
  76.      * @param uri
  77.      *            element uri.
  78.      * @param localName
  79.      *            element localName.
  80.      * @param qName
  81.      *            element qName.
  82.      * @param attributes
  83.      *            element attributes.
  84.      * @throws SAXException
  85.      *             if onStartElement throws a SAXException.
  86.      */
  87.     public synchronized void startElement(final String uri,
  88.             final String localName, final String qName,
  89.             final Attributes attributes) throws SAXException {

  90.         if (XmlProductHandler.PRODUCT_XML_NAMESPACE.equals(uri)) {
  91.             // PRODUCT
  92.             if (XmlProductHandler.PRODUCT_ELEMENT.equals(localName)) {
  93.                 id = ProductId.parse(XmlUtils.getAttribute(attributes, uri,
  94.                         XmlProductHandler.PRODUCT_ATTRIBUTE_ID));
  95.                 id.setUpdateTime(XmlUtils.getDate(XmlUtils.getAttribute(
  96.                         attributes, uri,
  97.                         XmlProductHandler.PRODUCT_ATTRIBUTE_UPDATED)));

  98.                 String status = XmlUtils.getAttribute(attributes, uri,
  99.                         XmlProductHandler.PRODUCT_ATTRIBUTE_STATUS);

  100.                 URL trackerURL = null;
  101.                 try {
  102.                     trackerURL = new URL(XmlUtils.getAttribute(attributes, uri,
  103.                             XmlProductHandler.PRODUCT_ATTRIBUTE_TRACKER_URL));
  104.                 } catch (Exception e) {
  105.                     // ignore
  106.                 }

  107.                 try {
  108.                     out.onBeginProduct(id, status, trackerURL);
  109.                 } catch (Exception e) {
  110.                     throw new SAXException(e);
  111.                 }
  112.             }
  113.             // PROPERTY
  114.             else if (XmlProductHandler.PROPERTY_ELEMENT.equals(localName)) {
  115.                 String name = XmlUtils.getAttribute(attributes, uri,
  116.                         XmlProductHandler.PROPERTY_ATTRIBUTE_NAME);
  117.                 String value = XmlUtils.getAttribute(attributes, uri,
  118.                         XmlProductHandler.PROPERTY_ATTRIBUTE_VALUE);

  119.                 try {
  120.                     out.onProperty(id, name, value);
  121.                 } catch (Exception e) {
  122.                     throw new SAXException(e);
  123.                 }
  124.             }
  125.             // LINK
  126.             else if (XmlProductHandler.LINK_ELEMENT.equals(localName)) {
  127.                 String relation = XmlUtils.getAttribute(attributes, uri,
  128.                         XmlProductHandler.LINK_ATTRIBUTE_RELATION);
  129.                 URI href = null;
  130.                 try {
  131.                     href = new URI(XmlUtils.getAttribute(attributes, uri,
  132.                             XmlProductHandler.LINK_ATTRIBUTE_HREF));
  133.                 } catch (Exception e) {
  134.                     return;
  135.                 }

  136.                 try {
  137.                     out.onLink(id, relation, href);
  138.                 } catch (Exception e) {
  139.                     throw new SAXException(e);
  140.                 }
  141.             }
  142.             // CONTENT
  143.             else if (XmlProductHandler.CONTENT_ELEMENT.equals(localName)) {
  144.                 try {
  145.                     String type = XmlUtils.getAttribute(attributes, uri,
  146.                             XmlProductHandler.CONTENT_ATTRIBUTE_TYPE);
  147.                     Long length = Long.valueOf(XmlUtils.getAttribute(
  148.                             attributes, uri,
  149.                             XmlProductHandler.CONTENT_ATTRIBUTE_LENGTH));
  150.                     Date modified = XmlUtils.getDate(XmlUtils.getAttribute(
  151.                             attributes, uri,
  152.                             XmlProductHandler.CONTENT_ATTRIBUTE_MODIFIED));
  153.                     String path = XmlUtils.getAttribute(attributes, uri,
  154.                             XmlProductHandler.CONTENT_ATTRIBUTE_PATH);
  155.                     String encoded = XmlUtils.getAttribute(attributes, uri,
  156.                             XmlProductHandler.CONTENT_ATTRIBUTE_ENCODED);
  157.                     String href = XmlUtils.getAttribute(attributes, uri,
  158.                             XmlProductHandler.CONTENT_ATTRIBUTE_HREF);

  159.                     if (href != null) {
  160.                         // URL CONTENT
  161.                         URL url = null;
  162.                         try {
  163.                             url = new URL(href);
  164.                         } catch (Exception e) {
  165.                             throw new SAXException(e);
  166.                         }
  167.                         URLContent content = new URLContent(url);
  168.                         content.setContentType(type);
  169.                         content.setLength(length);
  170.                         content.setLastModified(modified);

  171.                         out.onContent(id, path, content);
  172.                         return;
  173.                     }

  174.                     else {
  175.                         // EMBEDDED CONTENT
  176.                         // set up a piped stream
  177.                         InputStream contentInputStream = openContentStream(
  178.                                 encoded != null && "true".equals(encoded));

  179.                         content = new InputStreamContent(
  180.                                 contentInputStream);
  181.                         content.setContentType(type);
  182.                         content.setLength(length);
  183.                         content.setLastModified(modified);

  184.                         // call onContent in separate thread so parsing thread
  185.                         // can continue. Element content is fed during the
  186.                         // characters method.
  187.                         contentOutputThread = new ContentOutputThread(out, id, path, content);
  188.                         contentOutputThread.start();
  189.                     }

  190.                 } catch (Exception e) {
  191.                     closeContent();
  192.                     throw new SAXException(e);
  193.                 }
  194.             }
  195.             // SIGNATURE
  196.             else if (XmlProductHandler.SIGNATURE_ELEMENT.equals(localName)) {
  197.                 String version = XmlUtils.getAttribute(attributes, uri,
  198.                         XmlProductHandler.SIGNATURE_ATTRIBUTE_VERSION);
  199.                 try {
  200.                     out.onSignatureVersion(id,
  201.                             version == null
  202.                             ? Version.SIGNATURE_V1
  203.                             : Version.fromString(version));
  204.                 } catch (Exception e) {
  205.                     throw new SAXException(e);
  206.                 }
  207.                 signatureBuffer = new StringBuffer();
  208.             }
  209.         }
  210.     }

  211.     /**
  212.      * Override DefaultHandler endElement. Retrieves element content buffer and
  213.      * passes it to onEndElement.
  214.      *
  215.      * @param uri
  216.      *            element uri.
  217.      * @param localName
  218.      *            element localName.
  219.      * @param qName
  220.      *            element qName.
  221.      * @throws SAXException
  222.      *             if onEndElement throws a SAXException.
  223.      */
  224.     public synchronized void endElement(final String uri,
  225.             final String localName, final String qName) throws SAXException {
  226.         if (XmlProductHandler.PRODUCT_XML_NAMESPACE.equals(uri)) {
  227.             try {
  228.                 if (XmlProductHandler.CONTENT_ELEMENT.equals(localName)) {
  229.                     // done reading content content, close piped stream to
  230.                     // signal EOF.
  231.                     closeContent();
  232.                 } else if (XmlProductHandler.SIGNATURE_ELEMENT
  233.                         .equals(localName)) {
  234.                     String signature = signatureBuffer.toString();
  235.                     signatureBuffer = null;
  236.                     out.onSignature(id, signature);
  237.                 } else if (XmlProductHandler.PRODUCT_ELEMENT.equals(localName)) {
  238.                     out.onEndProduct(id);
  239.                 }
  240.             } catch (Exception e) {
  241.                 closeContent();
  242.                 throw new SAXException(e);
  243.             }
  244.         }
  245.     }

  246.     /**
  247.      * Override DefaultHandler characters. Appends content to current element
  248.      * buffer, or skips if before first element.
  249.      *
  250.      * @param ch
  251.      *            content.
  252.      * @param start
  253.      *            position in content to read.
  254.      * @param length
  255.      *            lenth of content to read.
  256.      * @throws SAXException
  257.      *             never.
  258.      */
  259.     public synchronized void characters(final char[] ch, final int start,
  260.             final int length) throws SAXException {
  261.         String chars = new String(ch, start, length);
  262.         if (contentOutputStream != null) {
  263.             try {
  264.                 contentOutputStream.write(chars.getBytes());
  265.             } catch (Exception e) {
  266.                 // close the piped stream if there was an exception
  267.                 closeContent();
  268.                 throw new SAXException(e);
  269.             }
  270.         } else if (signatureBuffer != null) {
  271.             signatureBuffer.append(chars);
  272.         } else {
  273.             // ignore, only interested in content or signature
  274.         }
  275.     }

  276.     /** @return ProductHandler */
  277.     protected synchronized ProductHandler getHandler() {
  278.         return out;
  279.     }

  280.     /** @param out ProductHandler to set */
  281.     protected synchronized void setHandler(ProductHandler out) {
  282.         this.out = out;
  283.     }


  284.     /**
  285.      * Free any resources associated with this handler.
  286.      */
  287.     @Override
  288.     public void close() {
  289.         closeContent();

  290.         StreamUtils.closeStream(in);
  291.         if (out != null) {
  292.             out.close();
  293.         }
  294.     }

  295.     /**
  296.      * Set up a piped output stream used during parsing.
  297.      *
  298.      * The XmlProductSource parsing thread starts a background thread
  299.      * to deliver content to the handler, then continues parsing XML
  300.      * and delivers parsed content via the piped streams.
  301.      *
  302.      * If xml parsing completes as expected, the parsing thread
  303.      * will close the connection in {@link #closeContent()}.  If
  304.      * errors occur, the objects handling the product source object
  305.      * call closeContent to ensure the resource is closed.
  306.      *
  307.      * @param encoded if it needs to decode base 64 content
  308.      * @return a input stream of the Piped output stream
  309.      * @throws IOException if io error occurs
  310.      */
  311.     @SuppressWarnings("resource")
  312.     public InputStream openContentStream(boolean encoded) throws IOException {
  313.         // EMBEDDED CONTENT
  314.         contentOutputStream = new PipedOutputStream();
  315.         InputStream contentInputStream = new PipedInputStream(
  316.                 contentOutputStream);

  317.         // decode base 64 encoded content
  318.         if (encoded) {
  319.             // this stream is closed by closeContent()
  320.             // either in this thread if everything succeeds,
  321.             // or by the objects using the ProductSource.
  322.             contentInputStream = Base64.getDecoder()
  323.                     .wrap(contentInputStream);
  324.         }

  325.         return contentInputStream;
  326.     }

  327.     /**
  328.      * Closes an open output stream
  329.      */
  330.     public void closeContent() {
  331.         StreamUtils.closeStream(contentOutputStream);
  332.         contentOutputStream = null;
  333.         if (contentOutputThread != null) {
  334.             try {
  335.                 contentOutputThread.join();
  336.             } catch (Exception e) {
  337.                 // ignore
  338.             }
  339.         }
  340.         contentOutputThread = null;
  341.         if (content != null) {
  342.             content.close();
  343.         }
  344.     }

  345. }