/* * The HRT Project. * Aavailable under a Creative Commons 2.0 License. */ package org.hrva.capture; import com.fourspaces.couchdb.Database; import com.fourspaces.couchdb.Document; import com.fourspaces.couchdb.Session; import java.io.*; import org.kohsuke.args4j.Argument; import org.kohsuke.args4j.CmdLineException; import org.kohsuke.args4j.CmdLineParser; import org.kohsuke.args4j.Option; import java.net.MalformedURLException; import java.net.URL; import java.text.DateFormat; import java.text.MessageFormat; import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.*; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; /** * Push HRT Feeds or Mappings to the CouchDB. * * <p>This is both a main program with a command-line interface, * as well as object that can be used to push. * </p> * * <p>Typical use case</p> * <code><pre> * CouchPush cp = new CouchPush(); * cp.open( "http://localhost:5984/database" ); * cp.push_feed( "some_feed_file.csv" ); * System.out.print( "Created "+cp.id ); * </pre></code> * * <p>When run as a main program, this uses a properties * file and command-line arguments to determine the * server, database, documents and attachments. * </p> * * <p>At the command line, it might look like this.</p> * <code><pre> * java -cp LogCapture/dist/LogCapture.jar org.hrva.capture.CouchPush -m route -e 2012-03-12 route.csv * java -cp LogCapture/dist/LogCapture.jar org.hrva.capture.CouchPush -m stop -e 2012-03-12 stop.csv * java -cp LogCapture/dist/LogCapture.jar org.hrva.capture.CouchPush -m vehicle -e 2012-03-12 vehicle.csv * </pre></code> * * <p>Depends on a <tt>hrtail.properties</tt> properties file.</p> * <dl> * <dt>couchpush.db_url</dt><dd>The database URL to use</dd> * </dl> * * <p>Uses <a href="https://github.com/mbreese/couchdb4j">CouchDB4J</a></p> * * <p>Uses <a href="http://args4j.kohsuke.org/">Args4J</a></p> * * <p>Also relevant are these projects</p> * * <p>org.apache.commons.httpclient from <a * href="http://hc.apache.org/index.html">hc.apache.org</a></p> * * <p>org.json.simple from <a * href="http://code.google.com/p/json-simple/">code.google.com</a></p> * * @author slott */ public class CouchPush { /** Properties for this application. */ Properties global= new Properties(); // Use GMT for timestamps to avoid EST/EDT problems. private TimeZone zulu = TimeZone.getTimeZone("GMT"); // Couchdb Session and Database private Session s; private Database db; // Used to format timestamps in GMT. private DateFormat fmt_date_time; /** For feeds, simply provide -f option. */ @Option(name = "-f", usage = "Feed") boolean feed= false; /** For mappings, provide -m <i>type</i>. */ @Option(name = "-m", usage = "Mapping Type (one of vehicle, route, stop)") String mapping = null; /** For mappings, provide -e <i>yyyy-mm-dd</i> effective date. */ @Option(name = "-e", usage = "Effective Date yyyy-mm-dd") String effective = null; /** Verbose debugging. */ @Option(name = "-v", usage = "Vebose logging") boolean verbose= false; /** Command-line Arguments. */ @Argument List<String> arguments = new ArrayList<String>(); /** Used to parse dates. */ DateFormat fmt_date = new SimpleDateFormat("yyyy-MM-dd"); /** Logger. */ final Log logger = LogFactory.getLog(CouchPush.class); /** * Command-line program to push a file to the HRT couch DB. * <p>All this does is read properties and invoke run_main</p> * @param args arguments */ public static void main(String[] args) { Log log = LogFactory.getLog(CouchPush.class); File prop_file = new File("hrtail.properties"); Properties config = new Properties(); try { config.load(new FileInputStream(prop_file)); } catch (IOException ex) { log.warn( "Can't find "+prop_file.getName(), ex ); try { log.debug(prop_file.getCanonicalPath()); } catch (IOException ex1) { } } CouchPush cp = new CouchPush( config); try { cp.run_main(args); } catch (CmdLineException ex1) { log.fatal("Invalid Options", ex1); } catch (MalformedURLException ex2) { log.fatal("Invalid CouchDB URL", ex2); } catch (IOException ex3) { log.fatal(ex3); } } /** * Build the CouchPush instance. * * @param global The hrtail.properties file */ public CouchPush( Properties global ) { super(); this.global= global; // Force the timzone code to be simply "Z". This only works // because the format really IS forced to GMT. fmt_date_time = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'"); fmt_date_time.setTimeZone( zulu ); } /** * Main program to parse arguments and push a feed or mapping file. * * <p>Parses command-line arguments. Builds document, posts * document and puts attachment. * </p> * * <p>Accepts the following options</p> * <dl> * <dt><tt>-f</tt></dt><dd>This is a feed.</dd> * <dt><tt>-m <i>type</i></tt></dt><dd>This is a mapping of type vehicle, route, stop.</dd> * <dt><tt>-e <i>yyyy-mm-d</i></tt></dt><dd>The effective date of this mapping.</dd> * <dt><tt>-v</tt></dt><dd>Verbose logging.</dd> * </dl> * <p>This gets properties from the <tt>hrtail.properties</tt> file.</p> * @param args * @throws CmdLineException * @throws MalformedURLException * @throws IOException */ public void run_main(String[] args) throws CmdLineException, MalformedURLException, IOException { CmdLineParser parser = new CmdLineParser(this); parser.parseArgument(args); if (feed) { if (mapping != null || effective != null) { throw new CmdLineException("Cannot use both -f and -m/-e options."); } } else { if (mapping == null || effective == null) { throw new CmdLineException("Mapping must have -m type and -e yyyy-mm-dd"); } try { Date eff_dt = fmt_date.parse(effective); } catch (ParseException ex) { throw new CmdLineException("Invalid effective date format"); } } if (arguments.isEmpty()) { throw new CmdLineException("Missing file name"); } open(); for (String filename : arguments) { Object[] details = { mapping, effective, filename, s.getHost(), db.getName() }; File attachment= new File(filename); Document doc; if (feed) { String msg = MessageFormat.format( "Push Feed {2} to {3}/{4}", details ); logger.info( msg ); doc= push_feed(attachment); } else { String msg = MessageFormat.format( "Push {0} Mapping {2} to {3}/{4}", details ); logger.info(msg); doc= push_mapping(mapping, effective, attachment); } if( doc != null ) { logger.info("Created " + doc.getId()); } } } /** * Opens the default CouchDB session and database. * * <p>This uses the <tt>couchpush.db_url</tt> property. By * default this is <tt>http://localhost:5984/couchdbkit_test</tt> * * @throws MalformedURLException * @throws IOException */ public void open() throws MalformedURLException, IOException { String default_url= global.getProperty("couchpush.db_url","http://localhost:5984/couchdbkit_test"); open( default_url ); } /** * Opens the given CouchDB session and databsae. * * @param url_override * @throws MalformedURLException * @throws IOException */ public void open(String url_override) throws MalformedURLException, IOException { URL details= new URL( url_override ); s = new Session(details.getHost(), details.getPort()); db = s.getDatabase(details.getPath()); } /** * Pushes a single feed file. Creates a feed document with * timestamp, status and doc_type of "Feed". * * <code> * {"timestamp":"2012-03-03T04:03:12Z", * "status":"new", * "doc_type":"Feed" * } * </code> * * @param attachment the File to push * @return Document object that was created. * @throws FileNotFoundException */ public Document push_feed(File attachment) throws FileNotFoundException { Date modified = new Date(attachment.lastModified()); Document document = new Document(); document.put("timestamp", fmt_date_time.format(modified)); document.put("status", "new"); document.put("doc_type", "Feed"); boolean ok= push(document, "feed", new FileReader(attachment) ); if( ok ) return document; return null; } /** * Pushes a single mapping file. Creates a feed document * with timestamp, mapping type, effective date and doc_type of "Mapping". * * <code> * {"timestamp":"2012-03-03T04:03:12Z", * "mapping_type":"vehicle", * "effective_date":"2012-03-12", * "doc_type":"Mapping" * } * </code> * * @param mapping type of mapping; generally vehicle, route or stop. * @param effective effective date string in the form yyyy-mm-dd. * @param attachment the File to push * @return Document object that was created. * @throws FileNotFoundException */ public Document push_mapping(String mapping, String effective, File attachment) throws FileNotFoundException { Date modified = new Date(attachment.lastModified()); Document document = new Document(); document.put("timestamp", fmt_date_time.format(modified)); document.put("mapping_type", mapping); document.put("effective_date", effective); document.put("doc_type", "Mapping"); boolean ok=push(document, "content", new FileReader(attachment) ); if( ok ) return document; return null; } /** * Generic POST of a document following by a PUSH of an attachment. * * @param document the CouchDB Document instance to push; * this is updated it id and rev. * @param name the attachment name (generally feed or content) * @param attachment a Reader for a File to attach * @return True if the push was successful. */ public boolean push(Document document, String name, Reader attachment) { try { BufferedReader rdr = new BufferedReader(attachment); db.saveDocument(document); String id = (String) document.getId(); String rev1 = (String) document.getRev(); if( verbose ) { logger.debug( document ); } StringBuilder content = new StringBuilder(); String line = rdr.readLine(); while (line != null) { content.append(line); content.append('\n'); line = rdr.readLine(); } Document resp= db.putAttachment(id, rev1, name, "text/csv", content.toString()); if( verbose ) { logger.debug( resp ); } return resp.getBoolean("ok"); } catch (java.io.FileNotFoundException ex1) { logger.error(ex1); } catch (java.io.IOException ex2) { logger.error(ex2); } return false; } }