CLIProductBuilder.java

  1. /*
  2.  * CLIProductBuilder
  3.  */
  4. package gov.usgs.earthquake.distribution;

  5. import gov.usgs.earthquake.aws.AwsProductSender;
  6. import gov.usgs.earthquake.product.ByteContent;
  7. import gov.usgs.earthquake.product.FileContent;
  8. import gov.usgs.earthquake.product.InputStreamContent;
  9. import gov.usgs.earthquake.product.Product;
  10. import gov.usgs.earthquake.product.ProductId;
  11. import gov.usgs.util.Config;
  12. import gov.usgs.util.CryptoUtils;
  13. import gov.usgs.util.DefaultConfigurable;
  14. import gov.usgs.util.StreamUtils;
  15. import gov.usgs.util.XmlUtils;
  16. import gov.usgs.util.CryptoUtils.Version;
  17. import gov.usgs.util.StringUtils;

  18. import java.io.File;
  19. import java.math.BigDecimal;
  20. import java.net.URI;
  21. import java.net.URL;

  22. import java.util.ArrayList;
  23. import java.util.HashMap;
  24. import java.util.Iterator;
  25. import java.util.List;
  26. import java.util.LinkedList;
  27. import java.util.Map;
  28. import java.util.logging.Level;
  29. import java.util.logging.Logger;

  30. /**
  31.  * Command Line Interface Product Builder.
  32.  *
  33.  * This class is used to build and send products. It is typically called by
  34.  * using the --build argument with the standard ProductClient.
  35.  *
  36.  * The CLIProductBuilder implements the Configurable interface and uses the
  37.  * following configuration parameters:
  38.  *
  39.  * <dl>
  40.  * <dt>senders</dt>
  41.  * <dd>(Required). A comma separated list of section names that should be loaded
  42.  * as ProductSender objects. Each sender in this list will be used to send any
  43.  * built products. See each type of ProductSender for more configuration
  44.  * details.</dd>
  45.  * </dl>
  46.  */
  47. public class CLIProductBuilder extends DefaultConfigurable {

  48.     /** Logging object. */
  49.     private static final Logger LOGGER = Logger
  50.             .getLogger(CLIProductBuilder.class.getName());

  51.     /** Exit code used when an invalid combination of arguments is used. */
  52.     public static final int EXIT_INVALID_ARGUMENTS = 1;

  53.     /** Exit code used when unable to build a product. */
  54.     public static final int EXIT_UNABLE_TO_BUILD = 2;

  55.     /** Exit code used when errors occur while sending. */
  56.     public static final int EXIT_UNABLE_TO_SEND = 3;

  57.     /** Exit code when errors occur while sending, but not to all senders. */
  58.     public static final int EXIT_PARTIALLY_SENT = 4;

  59.     /** product id type argument */
  60.     public static final String TYPE_ARGUMENT = "--type=";
  61.     /** product id code argument */
  62.     public static final String CODE_ARGUMENT = "--code=";
  63.     /** product id source argument */
  64.     public static final String SOURCE_ARGUMENT = "--source=";
  65.     /** product id updateTime argument */
  66.     public static final String UPDATE_TIME_ARGUMENT = "--updateTime=";

  67.     /** product status argument */
  68.     public static final String STATUS_ARGUMENT = "--status=";
  69.     /** product delete argument */
  70.     public static final String DELETE_ARGUMENT = "--delete";

  71.     /** tracker url argument */
  72.     public static final String TRACKER_URL_ARGUMENT = "--trackerURL=";

  73.   /** property argument */
  74.     public static final String PROPERTY_ARGUMENT = "--property-";
  75.     /** eventID argument */
  76.     public static final String EVENTID_ARGUMENT = "--eventid=";
  77.     /** eventsource argument */
  78.     public static final String EVENTSOURCE_ARGUMENT = "--eventsource=";
  79.     /** eventsourcecode argument */
  80.     public static final String EVENTSOURCECODE_ARGUMENT = "--eventsourcecode=";
  81.     /** eventCode argument */
  82.     public static final String EVENTCODE_ARGUMENT = "--eventcode=";
  83.     /** eventtime argument */
  84.     public static final String EVENTTIME_ARGUMENT = "--eventtime=";
  85.     /** latitude argument */
  86.     public static final String LATITUDE_ARGUMENT = "--latitude=";
  87.     /** longitude argument */
  88.     public static final String LONGITUDE_ARGUMENT = "--longitude=";
  89.     /** depth argument */
  90.     public static final String DEPTH_ARGUMENT = "--depth=";
  91.     /** magnitude argument */
  92.     public static final String MAGNITUDE_ARGUMENT = "--magnitude=";
  93.     /** version argument */
  94.     public static final String VERSION_ARGUMENT = "--version=";

  95.     /** product link argument */
  96.     public static final String LINK_ARGUMENT = "--link-";

  97.     /** product content argument */
  98.     public static final String CONTENT_ARGUMENT = "--content";
  99.     /** product content type argument */
  100.     public static final String CONTENT_TYPE_ARGUMENT = "--contentType=";
  101.     /** product directory argument */
  102.     public static final String DIRECTORY_ARGUMENT = "--directory=";
  103.     /** product file argument */
  104.     public static final String FILE_ARGUMENT = "--file=";

  105.     /** private key argument */
  106.     public static final String PRIVATE_KEY_ARGUMENT = "--privateKey=";
  107.     /** signature version argument */
  108.     public static final String SIGNATURE_VERSION_ARGUMENT = "--signatureVersion=";

  109.     /** Property name used for configuring a tracker url. */
  110.     public static final String TRACKER_URL_CONFIG_PROPERTY = "trackerURL";

  111.     /** Property name used for configuring the list of senders. */
  112.     public static final String SENDERS_CONFIG_PROPERTY = "senders";

  113.     /** Arguments for configuring servers and connectTimeouts. */
  114.     public static final String SERVERS_ARGUMENT = "--servers=";
  115.     /** connectionTimeout argument */
  116.     public static final String CONNECT_TIMEOUT_ARGUMENT = "--connectTimeout=";
  117.     /** Default connectionTimeout argument. 15s */
  118.     public static final Integer DEFAULT_CONNECT_TIMEOUT = 15000;
  119.     /** binaryFormat argument */
  120.     public static final String BINARY_FORMAT_ARGUMENT = "--binaryFormat";
  121.     /** disableDeflate argument */
  122.     public static final String DISABLE_DEFLATE = "--disableDeflate";
  123.     /** disableParallelSend argument */
  124.     public static final String DISABLE_PARALLEL_SEND = "--disableParallelSend";
  125.     /** parallelSendTimeout argument */
  126.     public static final String PARALLEL_SEND_TIMEOUT_ARGUMENT = "--parallelSendTimeout=";

  127.     /** Tracker URL that is used when not overriden by an argument. */
  128.     private URL defaultTrackerURL;

  129.     /** ProductSenders that send the product after it is built. */
  130.     private List<ProductSender> senders = new LinkedList<ProductSender>();

  131.     /** The command line arguments being parsed. */
  132.     private String[] args;

  133.     private Integer connectTimeout = DEFAULT_CONNECT_TIMEOUT;
  134.     private boolean parallelSend = Boolean.valueOf(ProductBuilder.DEFAULT_PARALLEL_SEND);
  135.     private long parallelSendTimeout = Long.valueOf(ProductBuilder.DEFAULT_PARALLEL_SEND_TIMEOUT);

  136.     /**
  137.      * This class is not intended to be instantiated directly.
  138.      * @param args arguments
  139.      */
  140.     protected CLIProductBuilder(final String[] args) {
  141.         this.args = args;
  142.     }

  143.     /**
  144.      * @return the senders
  145.      */
  146.     public List<ProductSender> getSenders() {
  147.         return senders;
  148.     }

  149.     /**
  150.      * @return the defaultTrackerURL
  151.      */
  152.     public URL getDefaultTrackerURL() {
  153.         return defaultTrackerURL;
  154.     }

  155.     /**
  156.      * @param defaultTrackerURL
  157.      *            the defaultTrackerURL to set
  158.      */
  159.     public void setDefaultTrackerURL(URL defaultTrackerURL) {
  160.         this.defaultTrackerURL = defaultTrackerURL;
  161.     }

  162.     /**
  163.      * Load ProductSenders that will send any built Products.
  164.      *
  165.      * There should be a property "senders" containing a comma delimited list of
  166.      * sender names to be loaded.
  167.      *
  168.      * @param config
  169.      *            the Config to load.
  170.      */
  171.     public void configure(final Config config) throws Exception {
  172.         Iterator<String> iter = StringUtils.split(
  173.                 config.getProperty(SENDERS_CONFIG_PROPERTY), ",").iterator();
  174.         while (iter.hasNext()) {
  175.             String senderName = iter.next();

  176.             LOGGER.config("Loading sender '" + senderName + "'");
  177.             // names reference global configuration objects.

  178.             ProductSender sender = (ProductSender) Config.getConfig()
  179.                     .getObject(senderName);

  180.             if (sender == null) {
  181.                 throw new ConfigurationException("Sender '" + senderName
  182.                         + "' is not properly configured");
  183.             }

  184.             senders.add(sender);
  185.         }

  186.         String trackerURLProperty = config
  187.                 .getProperty(TRACKER_URL_CONFIG_PROPERTY);
  188.         if (trackerURLProperty != null) {
  189.             defaultTrackerURL = new URL(trackerURLProperty);
  190.         }

  191.         parallelSend = Boolean.valueOf(config.getProperty(
  192.                 ProductBuilder.PARALLEL_SEND_PROPERTY,
  193.                 ProductBuilder.DEFAULT_PARALLEL_SEND));
  194.         parallelSendTimeout = Long.valueOf(config.getProperty(
  195.                 ProductBuilder.PARALLEL_SEND_TIMEOUT_PROPERTY,
  196.                 ProductBuilder.DEFAULT_PARALLEL_SEND_TIMEOUT));
  197.         LOGGER.config("[" + getName() + "] parallel send enabled="
  198.                 + parallelSend + ", timeout=" + parallelSendTimeout);
  199.     }

  200.     /**
  201.      * Called when the client is shutting down.
  202.      */
  203.     public void shutdown() throws Exception {
  204.         Iterator<ProductSender> iter = senders.iterator();
  205.         while (iter.hasNext()) {
  206.             try {
  207.                 iter.next().shutdown();
  208.             } catch (Exception e) {
  209.                 LOGGER.log(Level.WARNING, "Exception shutting down sender", e);
  210.             }
  211.         }
  212.     }

  213.     /**
  214.      * Called when the client is done configuring.
  215.      */
  216.     public void startup() throws Exception {
  217.         Iterator<ProductSender> iter = senders.iterator();
  218.         while (iter.hasNext()) {
  219.             iter.next().startup();
  220.         }
  221.     }

  222.     /**
  223.      * Send a product to all configured ProductSenders.
  224.      *
  225.      * @param product
  226.      *            the product to send.
  227.      * @return exceptions that occured while sending. If map is empty, there
  228.      *         were no exceptions.
  229.      */
  230.     public Map<ProductSender, Exception> sendProduct(final Product product) {
  231.         Map<ProductSender, Exception> sendExceptions = new HashMap<ProductSender, Exception>();

  232.         Iterator<ProductSender> iter = senders.iterator();
  233.         while (iter.hasNext()) {
  234.             ProductSender sender = iter.next();
  235.             try {
  236.                 sender.sendProduct(product);
  237.             } catch (Exception e) {
  238.                 sendExceptions.put(sender, e);
  239.             }
  240.         }

  241.         return sendExceptions;
  242.     }

  243.     /**
  244.      * Build a product using command line arguments.
  245.      *
  246.      * @return Product
  247.      * @throws Exception if error occurs
  248.      */
  249.     public Product buildProduct() throws Exception {
  250.         // start product id with null values, and verify they are all set after
  251.         // all arguments are parsed.
  252.         Product product = new Product(new ProductId(null, null, null));
  253.         product.setTrackerURL(defaultTrackerURL);

  254.         // These things are also processed after all arguments are parsed.
  255.         // used with inline content
  256.         boolean hasStdinContent = false;
  257.         String contentType = null;
  258.         // used when signing products
  259.         File privateKey = null;
  260.         Version signatureVersion = Version.SIGNATURE_V1;
  261.         boolean binaryFormat = false;
  262.         boolean enableDeflate = true;

  263.         for (String arg : args) {
  264.             if (arg.startsWith(TYPE_ARGUMENT)) {
  265.                 product.getId().setType(arg.replace(TYPE_ARGUMENT, ""));
  266.             } else if (arg.startsWith(CODE_ARGUMENT)) {
  267.                 product.getId().setCode(arg.replace(CODE_ARGUMENT, ""));
  268.             } else if (arg.startsWith(SOURCE_ARGUMENT)) {
  269.                 product.getId().setSource(arg.replace(SOURCE_ARGUMENT, ""));
  270.             } else if (arg.startsWith(UPDATE_TIME_ARGUMENT)) {
  271.                 product.getId()
  272.                         .setUpdateTime(
  273.                                 XmlUtils.getDate(arg.replace(
  274.                                         UPDATE_TIME_ARGUMENT, "")));
  275.             } else if (arg.startsWith(STATUS_ARGUMENT)) {
  276.                 product.setStatus(arg.replace(STATUS_ARGUMENT, ""));
  277.             } else if (arg.equals(DELETE_ARGUMENT)) {
  278.                 product.setStatus(Product.STATUS_DELETE);
  279.             } else if (arg.startsWith(TRACKER_URL_ARGUMENT)) {
  280.                 product.setTrackerURL(new URL(arg.replace(TRACKER_URL_ARGUMENT,
  281.                         "")));
  282.             } else if (arg.startsWith(PROPERTY_ARGUMENT)) {
  283.                 String[] props = arg.replace(PROPERTY_ARGUMENT, "").split("=",
  284.                         2);
  285.                 try {
  286.                     product.getProperties().put(props[0], props[1]);
  287.                 } catch (IndexOutOfBoundsException ioobe) {
  288.                     throw new IllegalArgumentException(
  289.                             "Invalid property argument, must have value");
  290.                 }
  291.             } else if (arg.startsWith(EVENTID_ARGUMENT)) {
  292.                 String id = arg.replace(EVENTID_ARGUMENT, "").toLowerCase();
  293.                 String eventNetwork = id.substring(0, 2);
  294.                 String eventNetworkId = id.substring(2);
  295.                 product.setEventId(eventNetwork, eventNetworkId);
  296.             } else if (arg.startsWith(EVENTSOURCE_ARGUMENT)) {
  297.                 product.setEventSource(arg.replace(EVENTSOURCE_ARGUMENT, "")
  298.                         .toLowerCase());
  299.             } else if (arg.startsWith(EVENTSOURCECODE_ARGUMENT)) {
  300.                 product.setEventSourceCode(arg.replace(
  301.                         EVENTSOURCECODE_ARGUMENT, "").toLowerCase());
  302.             } else if (arg.startsWith(EVENTCODE_ARGUMENT)) {
  303.                 product.setEventSourceCode(arg.replace(EVENTCODE_ARGUMENT, "")
  304.                         .toLowerCase());
  305.             } else if (arg.startsWith(EVENTTIME_ARGUMENT)) {
  306.                 product.setEventTime(XmlUtils.getDate(arg.replace(
  307.                         EVENTTIME_ARGUMENT, "")));
  308.             } else if (arg.startsWith(MAGNITUDE_ARGUMENT)) {
  309.                 product.setMagnitude(new BigDecimal(arg.replace(
  310.                         MAGNITUDE_ARGUMENT, "")));
  311.             } else if (arg.startsWith(LATITUDE_ARGUMENT)) {
  312.                 product.setLatitude(new BigDecimal(arg.replace(
  313.                         LATITUDE_ARGUMENT, "")));
  314.             } else if (arg.startsWith(LONGITUDE_ARGUMENT)) {
  315.                 product.setLongitude(new BigDecimal(arg.replace(
  316.                         LONGITUDE_ARGUMENT, "")));
  317.             } else if (arg.startsWith(DEPTH_ARGUMENT)) {
  318.                 product.setDepth(new BigDecimal(arg.replace(DEPTH_ARGUMENT, "")));
  319.             } else if (arg.startsWith(VERSION_ARGUMENT)) {
  320.                 product.setVersion(arg.replace(VERSION_ARGUMENT, ""));
  321.             } else if (arg.startsWith(LINK_ARGUMENT)) {
  322.                 String[] props = arg.replace(LINK_ARGUMENT, "").split("=", 2);
  323.                 try {
  324.                     product.addLink(props[0], new URI(props[1]));
  325.                 } catch (IndexOutOfBoundsException ioobe) {
  326.                     throw new IllegalArgumentException(
  327.                             "Invalid link, must have URL as value");
  328.                 }
  329.             } else if (arg.equals(CONTENT_ARGUMENT)) {
  330.                 hasStdinContent = true;
  331.             } else if (arg.startsWith(CONTENT_TYPE_ARGUMENT)) {
  332.                 contentType = arg.replace(CONTENT_TYPE_ARGUMENT, "");
  333.             } else if (arg.startsWith(DIRECTORY_ARGUMENT)) {
  334.                 product.getContents().putAll(
  335.                         FileContent.getDirectoryContents(new File(arg.replace(
  336.                                 DIRECTORY_ARGUMENT, ""))));
  337.             } else if (arg.startsWith(FILE_ARGUMENT)) {
  338.                 File file = new File(arg.replace(FILE_ARGUMENT, ""));
  339.                 product.getContents()
  340.                         .put(file.getName(), new FileContent(file));
  341.             } else if (arg.startsWith(PRIVATE_KEY_ARGUMENT)) {
  342.                 privateKey = new File(arg.replace(PRIVATE_KEY_ARGUMENT, ""));
  343.             } else if (arg.startsWith(SIGNATURE_VERSION_ARGUMENT)) {
  344.                 signatureVersion = Version.fromString(
  345.                         arg.replace(SIGNATURE_VERSION_ARGUMENT, ""));
  346.             } else if (arg.startsWith(SERVERS_ARGUMENT)) {
  347.                 senders.clear();
  348.                 senders.addAll(parseServers(arg.replace(SERVERS_ARGUMENT, ""),
  349.                         connectTimeout, binaryFormat, enableDeflate));
  350.             } else if (arg.startsWith(CONNECT_TIMEOUT_ARGUMENT)) {
  351.                 connectTimeout = Integer.valueOf(arg.replace(
  352.                         CONNECT_TIMEOUT_ARGUMENT, ""));
  353.             } else if (arg.equals(BINARY_FORMAT_ARGUMENT)) {
  354.                 binaryFormat = true;
  355.             } else if (arg.equals(DISABLE_DEFLATE)) {
  356.                 enableDeflate = false;
  357.             } else if (arg.equals(DISABLE_PARALLEL_SEND)) {
  358.                 parallelSend = false;
  359.             } else if (arg.startsWith(PARALLEL_SEND_TIMEOUT_ARGUMENT)) {
  360.                 parallelSendTimeout = Long.valueOf(
  361.                         arg.replace(PARALLEL_SEND_TIMEOUT_ARGUMENT, ""));
  362.             } else {
  363.                 // not a builder argument
  364.             }
  365.         }

  366.         // validate product
  367.         ProductId id = product.getId();
  368.         if (id.getType() == null || id.getSource() == null
  369.                 || id.getCode() == null || id.getUpdateTime() == null) {
  370.             throw new IllegalArgumentException("Incomplete ProductId: source="
  371.                     + id.getSource() + ", type=" + id.getType() + ", code="
  372.                     + id.getCode() + ", updateTime=" + id.getUpdateTime());
  373.         }

  374.         // tracker url is required
  375.         if (product.getTrackerURL() == null) {
  376.             throw new IllegalArgumentException("Tracker URL is required");
  377.         }

  378.         if (hasStdinContent) {
  379.             LOGGER.info("Reading content on standard input");

  380.             ByteContent stdinContent = new ByteContent(new InputStreamContent(
  381.                     System.in));
  382.             if (contentType != null) {
  383.                 stdinContent.setContentType(contentType);
  384.             }
  385.             product.getContents().put("", stdinContent);
  386.         }

  387.         // products that aren't being deleted should have content
  388.         if (product.getContents().size() == 0 && !product.isDeleted()) {
  389.             LOGGER.warning("Product has no content, are you sure this is intended?");
  390.         }

  391.         // mark which version of client was used to create product
  392.         product.getProperties().put(ProductClient.PDL_CLIENT_VERSION_PROPERTY,
  393.                 ProductClient.RELEASE_VERSION);

  394.         if (privateKey != null) {
  395.             LOGGER.fine("Signing product");
  396.             product.sign(
  397.                     CryptoUtils.readOpenSSHPrivateKey(
  398.                             StreamUtils.readStream(StreamUtils.getInputStream(privateKey)),
  399.                             null),
  400.                     signatureVersion);
  401.         }

  402.         return product;
  403.     }

  404.     /**
  405.      * Parse servers for list of product senders
  406.      * @param servers CSV string of servers
  407.      * @param connectTimeout timeout
  408.      * @param binaryFormat if binaryFormat
  409.      * @param enableDeflate if enableDeflate
  410.      * @return List of product senders
  411.      * */
  412.     public static List<ProductSender> parseServers(final String servers,
  413.             final Integer connectTimeout, final boolean binaryFormat,
  414.             final boolean enableDeflate) throws Exception {
  415.         List<ProductSender> senders = new ArrayList<ProductSender>();

  416.         Iterator<String> iter = StringUtils.split(servers, ",").iterator();
  417.         while (iter.hasNext()) {
  418.             String server = iter.next();
  419.             if (server.startsWith("https://")) {
  420.                 AwsProductSender sender = new AwsProductSender(new URL(server));
  421.                 senders.add(sender);
  422.             } else {
  423.                 String[] parts = server.split(":");
  424.                 SocketProductSender sender = new SocketProductSender(parts[0],
  425.                         Integer.parseInt(parts[1]), connectTimeout);
  426.                 sender.setBinaryFormat(binaryFormat);
  427.                 sender.setEnableDeflate(enableDeflate);
  428.                 senders.add(sender);
  429.             }
  430.         }

  431.         return senders;
  432.     }

  433.     /**
  434.      * Entry point into CLIProductBuilder.
  435.      *
  436.      * Called by Main if the --build argument is present.
  437.      *
  438.      * @param args arguments
  439.      * @throws Exception if error occurs
  440.      */
  441.     public static void main(final String[] args) throws Exception {
  442.         CLIProductBuilder builder = new CLIProductBuilder(args);
  443.         builder.configure(Config.getConfig());
  444.         builder.startup();

  445.         Product product = null;
  446.         try {
  447.             product = builder.buildProduct();
  448.         } catch (Exception e) {
  449.             if (e.getMessage() == null) {
  450.                 LOGGER.log(Level.SEVERE, "Error building product", e);
  451.             } else {
  452.                 LOGGER.severe("Invalid arguments: " + e.getMessage());
  453.             }
  454.             System.exit(EXIT_INVALID_ARGUMENTS);
  455.         }

  456.         if (product == null) {
  457.             LOGGER.severe("Unable to build product");
  458.             System.exit(EXIT_UNABLE_TO_BUILD);
  459.         }

  460.         // send tracker update
  461.         new ProductTracker(product.getTrackerURL()).productCreated(
  462.                 SocketProductSender.class.getName(), product.getId());

  463.         // send the product
  464.         Map<ProductSender, Exception> sendExceptions = builder.parallelSend
  465.                 ? ProductBuilder.parallelSendProduct(
  466.                             builder.senders,
  467.                             product,
  468.                             builder.parallelSendTimeout)
  469.                 : builder.sendProduct(product);

  470.         // handle any send exceptions
  471.         if (sendExceptions.size() != 0) {
  472.             Iterator<ProductSender> senders = sendExceptions.keySet()
  473.                     .iterator();
  474.             // log the exceptions
  475.             while (senders.hasNext()) {
  476.                 ProductSender sender = senders.next();
  477.                 if (sender instanceof SocketProductSender) {
  478.                     // put more specific information about socket senders
  479.                     SocketProductSender socketSender = (SocketProductSender) sender;
  480.                     LOGGER.log(
  481.                             Level.WARNING,
  482.                             "Exception sending product to "
  483.                                     + socketSender.getHost() + ":"
  484.                                     + socketSender.getPort(),
  485.                             sendExceptions.get(sender));
  486.                 } else {
  487.                     LOGGER.log(Level.WARNING, "Exception sending product "
  488.                             + sendExceptions.get(sender));
  489.                 }
  490.             }

  491.             if (sendExceptions.size() < builder.getSenders().size()) {
  492.                 LOGGER.warning("Partial failure sending product,"
  493.                         + " at least one sender accepted product."
  494.                         + " Check the tracker for more information.");
  495.                 // still output built product id
  496.                 System.out.println(product.getId().toString());
  497.                 // but exit with partial failure
  498.                 System.exit(EXIT_PARTIALLY_SENT);
  499.             } else {
  500.                 LOGGER.severe("Total failure sending product");
  501.                 System.exit(EXIT_UNABLE_TO_SEND);
  502.             }

  503.         }

  504.         // otherwise output built product id
  505.         System.out.println(product.getId().toString());

  506.         // normal exit
  507.         builder.shutdown();
  508.         System.exit(0);
  509.     }
  510.     /**
  511.      * Function on how to use command
  512.      * @return string
  513.      */
  514.     public static String getUsage() {
  515.         StringBuffer buf = new StringBuffer();

  516.         buf.append("Product identification\n");
  517.         buf.append("--source=SOURCE          product source, e.g. us, nc\n");
  518.         buf.append("--type=TYPE              product type, e.g. shakemap, pager\n");
  519.         buf.append("--code=CODE              product code, e.g. us2009abcd, nc12345678\n");
  520.         buf.append("[--updateTime=TIME]      when the product was updated\n");
  521.         buf.append("                         e.g. 2010-02-11T15:16:17+0000\n");
  522.         buf.append("                         default is now\n");
  523.         buf.append("[--status=STATUS]        product status\n");
  524.         buf.append("                         default is UPDATE\n");
  525.         buf.append("[--delete]               same as --status=DELETE\n");
  526.         buf.append("\n");

  527.         buf.append("Product contents\n");
  528.         buf.append("[--directory=DIR]        read content from a directory, preserves hierarchy\n");
  529.         buf.append("[--file=FILE]            read content from a file, added at top level of product\n");
  530.         buf.append("[--content]              read content from STDIN\n");
  531.         buf.append("[--contentType=MIMETYPE] used with --content to specify STDIN mime type\n");
  532.         buf.append("\n");

  533.         buf.append("Product metadata\n");
  534.         buf.append("[--link-RELATION=URI]    link to another product or resource\n");
  535.         buf.append("[--property-NAME=VALUE]  attributes of this product\n");
  536.         buf.append("[--latitude=LAT]         Latitude of associated event.\n");
  537.         buf.append("                             Decimal degrees\n");
  538.         buf.append("                             Same as --property-latitude=LAT\n");
  539.         buf.append("[--longitude=LNG]        Longitude of associated event.\n");
  540.         buf.append("                             Decimal degrees\n");
  541.         buf.append("                             Same as --property-longitude=LNG\n");
  542.         buf.append("[--eventtime=TIME]       Time of associated event.\n");
  543.         buf.append("                             Example: 2010-02-11T15:16:17+0000\n");
  544.         buf.append("                             Same as --property-eventtime=TIME\n");
  545.         buf.append("[--magnitude=MAG]        Magnitude of associated event.\n");
  546.         buf.append("                             Same as --property-magnitude=MAG\n");
  547.         buf.append("[--depth=DEPTH]          Depth of associated event.\n");
  548.         buf.append("                             Kilometers.\n");
  549.         buf.append("                             Same as --property-depth=DEPTH\n");
  550.         buf.append("[--eventsource=SOURCE]   Network of associated event.\n");
  551.         buf.append("                             Examples: us, nc, ci\n");
  552.         buf.append("                             Same as --property-eventsource=SOURCE\n");
  553.         buf.append("[--eventcode=CODE]       NetworkID of associated event.\n");
  554.         buf.append("                             Examples: 2010abcd, 12345678\n");
  555.         buf.append("                             Same as --property-eventsourcecode=CODE\n");
  556.         buf.append("[--eventid=EVENTID]      Deprecated, use --eventsource and --eventsourcecode.\n");
  557.         buf.append("                             Assumes a 10 character eventid: \n");
  558.         buf.append("                                 assigns first 2 characters as 'eventsource',\n");
  559.         buf.append("                                 rest as 'eventsourcecode'\n");
  560.         buf.append("[--version=VERSION]      Internal product version.\n");
  561.         buf.append("                             Same as --property-version=VERSION\n");
  562.         buf.append("\n");

  563.         buf.append("[--trackerURL=URL]       tracker url\n");
  564.         buf.append("                         Override a configured default trackerURL\n");
  565.         buf.append("[--privateKey=FILE]      OpenSSH DSA private key used to sign products\n");
  566.         buf.append("                         A product signature may or may not be optional\n");
  567.         buf.append("[--signatureVersion=v1]  signature version\n");
  568.         buf.append("                         valid values are 'v1' or 'v2'\n");
  569.         buf.append("\n");

  570.         buf.append("Where product is sent\n");
  571.         buf.append("[--connectTimeout=15000] Connect timeout in milliseconds\n");
  572.         buf.append("                         Only used with --servers argument\n");
  573.         buf.append("                         Must appear before --servers argument.\n");
  574.         buf.append("[--binaryFormat]         Send to hub using binary format.\n");
  575.         buf.append("                         Only used with --servers argument\n");
  576.         buf.append("                         Must appear before --servers argument.\n");
  577.         buf.append("[--disableDeflate]       Send to hub without using deflate compression.\n");
  578.         buf.append("                         Only used with --servers argument\n");
  579.         buf.append("                         Must appear before --servers argument.\n");
  580.         buf.append("[--disableParallelSend]  Send to servers sequentially.\n");
  581.         buf.append("[--parallelSendTimeout=300]\n");
  582.         buf.append("                         timeout for parallel send in seconds.\n");
  583.         buf.append("[--servers=SERVERLIST]   server:port[,server:port]\n");
  584.         buf.append("                         Overrides any configured senders\n");
  585.         buf.append("                         Example: pdldevel.cr.usgs.gov:11235\n");
  586.         buf.append("\n");

  587.         return buf.toString();
  588.     }
  589. }