View Javadoc
1   package gov.usgs.volcanoes.winston.db;
2   
3   import java.sql.ResultSet;
4   import java.sql.SQLException;
5   import java.sql.Statement;
6   import java.util.Iterator;
7   import java.util.List;
8   
9   import org.slf4j.Logger;
10  import org.slf4j.LoggerFactory;
11  
12  import gov.usgs.volcanoes.core.configfile.ConfigFile;
13  import gov.usgs.volcanoes.winston.Channel;
14  
15  /**
16   *
17   * @author Dan Cervelli
18   */
19  public class Admin {
20    private static final Logger LOGGER = LoggerFactory.getLogger(Admin.class);
21  
22    /** The channel expression SQL wild card. */
23    public static final String WILDCARD = "%";
24  
25    /**
26     * Main method
27     *
28     * @param args command-line args
29     */
30    public static void main(final String[] args) {
31      if (args.length == 0) {
32        printUsage(-1, null);
33      }
34  
35      int argIndex = 0;
36      String cmd = null;
37      String arg;
38      int status = 0;
39      final ConfigFile cf = new ConfigFile("Winston.config");
40      final String driver = cf.getString("winston.driver");
41      final String url = cf.getString("winston.url");
42      final String db = cf.getString("winston.prefix");
43      final Admin admin = new Admin(driver, url, db);
44      long delay = 0;
45  
46      while (argIndex < args.length && status == 0) {
47        arg = args[argIndex++];
48        // first check for options
49        if (arg.equals("--delay")) {
50          try {
51            arg = args[argIndex++];
52            // convert seconds to milliseconds
53            delay = Integer.parseInt(arg) * 1000L;
54          } catch (final Exception e) {
55            status = 2;
56          }
57        }
58        // then check the command and command arguments
59        else if (cmd != null || (cmd = arg).length() == 0) {
60          // already had a command or empty command
61          status = 2;
62        } else if (cmd.equals("--span")) {
63          admin.calculateTableSpans();
64        } else if (cmd.equals("--list")) {
65          boolean times = false;
66          if (argIndex < args.length) {
67            arg = args[argIndex++];
68            if (arg.equals("times")) {
69              times = true;
70            } else {
71              status = 2;
72            }
73          }
74          if (status == 0) {
75            admin.listChannels(times);
76          }
77        } else if (cmd.equals("--delete") || cmd.equals("--deletex")) {
78          if (argIndex < args.length) {
79            arg = args[argIndex++];
80            if (cmd.equals("--deletex")) {
81              if (admin.isChannelExpressionValid(arg)) {
82                admin.deleteChannels(arg, delay);
83              } else {
84                status = 3;
85                System.err.println(
86                    "channel expresion may not have wild card for network or station (" + arg + ")");
87              }
88            } else {
89              admin.deleteChannel(arg);
90            }
91          } else {
92            status = 2;
93          }
94        } else if (cmd.equals("--purge") || cmd.equals("--purgex")) {
95          String channel = null;
96          int days = 0;
97          try {
98            arg = args[argIndex++];
99            channel = arg;
100           arg = args[argIndex++];
101           days = Integer.parseInt(arg);
102           if (days <= 0) {
103             status = 2;
104           }
105         } catch (final Exception e) {
106           status = 2;
107         }
108 
109         if (status == 0) {
110           if (cmd.equals("--purgex")) {
111             admin.purgeChannels(channel, days, delay);
112           } else {
113             admin.purge(channel, days);
114           }
115         }
116       } else if (cmd.equals("--repair")) {
117         if (argIndex < args.length) {
118           final String day = args[argIndex++];
119           final String ch;
120           if (argIndex < args.length) {
121             ch = args[argIndex++];
122           } else {
123             ch = null;
124           }
125           admin.repair(day, ch);
126         } else {
127           status = 2;
128         }
129       }
130       // else if (cmd.equals("--deletewinston"))
131       // {
132       // admin.deleteWinston();
133       // }
134       else {
135         System.err.println("Invalid argument(" + argIndex + "): '" + arg + "'");
136         status = 1;
137       }
138     }
139 
140     if (status != 0) {
141       printUsage(status, cmd);
142     }
143   }
144 
145   private static void printUsage(final int status, final String cmd) {
146     if (status == 2) {
147       System.err.println("Missing or invalid command arguments for command (" + cmd + ")");
148     }
149     System.out.println(
150         "Winston Admin\n\n" + "A collection of commands for administering a Winston database.\n"
151             + "Information about connecting to the Winston database must be present\n"
152             + "in Winston.config in the current directory.\n\n" + "Usage:\n"
153             + "  java gov.usgs.volcanoes.winston.db.Admin [options] command [command arguments]\n"
154             + "\nValid options:\n"
155             + "  --delay seconds                 the delay between each channel for commands\n"
156             + "                                  for multiple channels\n" + "\nValid commands:\n"
157             + "  --list                          lists all channels\n"
158             + "  --list times                    lists all channels with time span\n"
159             + "  --delete channel                delete the specified channel\n"
160             + "  --deletex SSSS$CCC$NN[$LL]      delete the specified channels where:\n"
161             + "                                  SSSS is the station,\n"
162             + "                                  CCC is the channel which may contain\n"
163             + "                                  a wild card (%),\n"
164             + "                                  SSSS is the station,\n"
165             + "                                  NN is the network,\n"
166             + "                                  LL is the optional location which may contain\n"
167             + "                                  a wild card (%)\n"
168             + "  --span                          recalculate table spans\n"
169             + "  --purge channel days            purge the specified channel for the\n"
170             + "                                  specified number of days\n"
171             + "  --purgex channel days           purge the specified channel for the\n"
172             + "                                  specified number of days where the channel\n"
173             + "                                  may contain a wild card (%) anywhere\n"
174             + "  --repair YYYY_MM_DD [channel]   repair all tables on given day\n"
175             + "                                  optionally, just repair the specified channel\n" +
176             // " --deletewinston completely deletes all Winston databases\n" +
177             "");
178     if (status != 0) {
179       System.exit(status);
180     }
181   }
182 
183   private final Channels channels;
184 
185   private final Input input;
186 
187   private final WinstonDatabase winston;
188 
189   /**
190    * Constructor
191    *
192    * @param driver
193    * @param url
194    * @param db
195    */
196   public Admin(final String driver, final String url, final String db) {
197     this(new WinstonDatabase(driver, url, db));
198   }
199 
200   /**
201    * Constructor
202    *
203    * @param w WinstonDatabase
204    */
205   public Admin(final WinstonDatabase w) {
206     winston = w;
207     channels = new Channels(winston);
208     input = new Input(winston);
209   }
210 
211   /**
212    * Calculate table spans
213    */
214   public void calculateTableSpans() {
215     try {
216       final List<Channel> st = channels.getChannels();
217       for (final Iterator<Channel> it = st.iterator(); it.hasNext();) {
218         final String code = it.next().getCode();
219         System.out.print(code + "...");
220         input.calculateSpan(code);
221         System.out.println("done.");
222       }
223     } catch (final Exception e) {
224       LOGGER.error("Error during calculateTableSpans(). ({})", e.getLocalizedMessage());
225     }
226   }
227 
228   /**
229    * Check on specified table/database
230    *
231    * @param database name of database
232    * @param table name of table
233    * @return true if OK, false otherwise
234    * @throws SQLException
235    */
236   public boolean checkTable(final String database, final String table) throws SQLException {
237     final ResultSet rs =
238         winston.getStatement().executeQuery("CHECK TABLE `" + table + "` FAST QUICK");
239     rs.next();
240     final String s = rs.getString("Msg_text");
241     if (s.endsWith("doesn't exist")) {
242       LOGGER.info("{} doesn't exist.", table);
243       return false;
244     }
245     return s.equals("Table is already up to date");
246   }
247 
248   /**
249    * Remove a channel from the database
250    *
251    * @param ch channel to remove
252    */
253   public void deleteChannel(final String ch) {
254     try {
255       winston.useRootDatabase();
256       doDeleteChannel(ch);
257     } catch (final Exception e) {
258       LOGGER.error("Error during deleteChannel().");
259     }
260   }
261 
262   /**
263    * Remove channels from the database
264    *
265    * @param chx the channel expression
266    * @param delay the delay in milliseconds between channels
267    */
268   public void deleteChannels(final String chx, final long delay) {
269     String ch = chx;
270     try {
271       final List<String> channelList = channels.getChannelCodes(chx);
272       if (channelList == null || channelList.size() == 0) {
273         LOGGER.info("deleteChannels: no channels found ({})", chx);
274       } else {
275         // delete each channel
276         for (int i = 0; i < channelList.size(); i++) {
277           ch = channelList.get(i);
278           if (i != 0 && delay != 0) {
279             Thread.sleep(delay);
280           }
281           doDeleteChannel(ch);
282           LOGGER.info("Deleted channel: {}", ch);
283         }
284         ch = chx;
285       }
286     } catch (final Exception e) {
287       LOGGER.error("Error during deleteChannels({})", ch);
288     }
289   }
290 
291   /**
292    * Use with extreme caution.
293    */
294   public void deleteWinston() {
295     try {
296       final List<Channel> chs = channels.getChannels();
297       for (final Channel ch : chs) {
298         deleteChannel(ch.getCode());
299       }
300       winston.getStatement().execute("DROP DATABASE `" + winston.databasePrefix + "_ROOT`");
301     } catch (final Exception e) {
302       e.printStackTrace();
303     }
304   }
305 
306   /**
307    * Remove a channel from the database
308    *
309    * @param ch channel to remove
310    * @throws SQLException if a SQL exception occurs.
311    */
312   private void doDeleteChannel(final String ch) throws SQLException {
313     winston.getStatement().execute("DELETE FROM channels WHERE code='" + ch + "'");
314     winston.getStatement().execute("DROP DATABASE `" + winston.databasePrefix + "_" + ch + "`");
315   }
316 
317   /**
318    * Determines if the channel expression is valid. The channel
319    * expression used for <code>deleteChannels</code> is restricted
320    * and should be verified.
321    *
322    * @param chx the channel expression.
323    * @return true if valid, false otherwise.
324    * @see #deleteChannels(String, long)
325    */
326   public boolean isChannelExpressionValid(final String chx) {
327     boolean valid = true;
328     int index;
329     // ensure the station and network do not have wild card
330     final String[] ca = chx.split("\\$");
331     switch (ca.length) {
332       case 3: // station$channel$network
333         if (ca[0].indexOf(WILDCARD) >= 0
334             || ((index = ca[2].indexOf(WILDCARD)) >= 0 && index <= 1)) {
335           valid = true;
336         }
337         break;
338       case 4: // station$channel$network$location
339         if (ca[0].indexOf(WILDCARD) >= 0 || ca[2].indexOf(WILDCARD) >= 0) {
340           valid = true;
341         }
342         break;
343       default:
344         valid = true;
345         break;
346     }
347     return valid;
348   }
349 
350   /**
351    * List channels to system out
352    *
353    * @param times if true, include min & max times
354    */
355   public void listChannels(final boolean times) {
356     final List<Channel> st = channels.getChannels();
357     for (final Channel ch : st) {
358       final String code = ch.getCode();
359       System.out.print(code);
360       if (times) {
361         System.out.print("\t" + ch.getMinTime() + "\t" + ch.getMaxTime());
362       }
363       System.out.println();
364     }
365   }
366 
367   /**
368    * Purge specified number of days from specified channel
369    *
370    * @param channel
371    * @param days
372    */
373   public void purge(final String channel, final int days) {
374     input.purgeTables(channel, days, this);
375   }
376 
377   /**
378    * Purge specified number of days from specified channels
379    *
380    * @param chx the channel expression
381    * @param days the number of days
382    * @param delay the delay in milliseconds between channels
383    */
384   public void purgeChannels(final String chx, final int days, final long delay) {
385     String ch = chx;
386     try {
387       final List<String> channelList = channels.getChannelCodes(chx);
388       if (channelList == null || channelList.size() == 0) {
389         LOGGER.info("purgeChannels: no channels found ({})", chx);
390       } else {
391         for (int i = 0; i < channelList.size(); i++) {
392           ch = channelList.get(i);
393           if (i != 0 && delay != 0) {
394             Thread.sleep(delay);
395           }
396           purge(ch, days);
397           LOGGER.info("Purged channel: {}", ch);
398         }
399       }
400     } catch (final Exception e) {
401       LOGGER.error("Error during purgeChannels({})", ch);
402     }
403   }
404 
405   /**
406    * Repair specified channel for specified day
407    * If channel unspecified, repair all
408    *
409    * @param day
410    * @param chString channel to repair; null for all
411    */
412   public void repair(final String day, final String chString) {
413     if (chString == null) {
414       final List<Channel> chs = channels.getChannels();
415       for (final Channel ch : chs) {
416         repairChannel(day, ch.getCode());
417       }
418     } else {
419       repairChannel(day, chString);
420     }
421   }
422 
423   /**
424    * Leaving as-is for old importer.
425    *
426    * @param day
427    * @param ch
428    * @return true if successful, false otherwise
429    */
430   public boolean repairChannel(final String day, final String ch) {
431     try {
432       winston.useDatabase(ch);
433       boolean fix = false;
434       final Statement st = winston.getStatement();
435 
436       LOGGER.info("Checking: {} {}", ch, day);
437       ResultSet rs = st.executeQuery("CHECK TABLE `" + ch + "$$" + day + "` FAST QUICK");
438       rs.next();
439       String s = rs.getString("Msg_text");
440       if (s.endsWith("doesn't exist")) {
441         LOGGER.info("{} wave table doesn't exist.", ch);
442         return true;
443       }
444       if (!s.equals("Table is already up to date")) {
445         fix = true;
446       }
447 
448       rs = st.executeQuery("CHECK TABLE `" + ch + "$$H" + day + "` FAST QUICK");
449       rs.next();
450       s = rs.getString("Msg_text");
451       if (s.endsWith("doesn't exist")) {
452         LOGGER.info("{} helicorder table doesn't exist.", ch);
453         return true;
454       }
455       // TODO: check table existence
456       if (!rs.getString("Msg_text").equals("Table is already up to date")) {
457         fix = true;
458       }
459 
460       if (fix) {
461         LOGGER.info("Repairing: {}", ch);
462         // winston.getStatement().execute("REPAIR TABLE " + ch + "$$" + day + " QUICK");
463         // winston.getStatement().execute("REPAIR TABLE " + ch + "$$H" + day + " QUICK");
464         winston.getStatement().execute("REPAIR TABLE `" + ch + "$$" + day + "`");
465         winston.getStatement().execute("REPAIR TABLE `" + ch + "$$H" + day + "`");
466       }
467       return true;
468     } catch (final Exception e) {
469       LOGGER.error("Failed to repair: {}", ch);
470     }
471     return false;
472   }
473 
474   /**
475    * Attempts to repair a table.
476    *
477    * @param database the database
478    * @param table the table name
479    * @return flag indicating a whether table is healthy
480    */
481   public boolean repairTable(final String database, final String table) {
482     try {
483       winston.useDatabase(database);
484       LOGGER.info("Checking table: {}", table);
485       boolean ct = checkTable(database, table);
486       if (ct == true) {
487         return true;
488       }
489 
490       LOGGER.info("Repairing table: {}", table);
491       winston.getStatement().execute("REPAIR TABLE `" + table + "` QUICK");
492       ct = checkTable(database, table);
493       if (ct == true) {
494         return true;
495       }
496 
497       LOGGER.info("Still broken, attempting further repair: {}", table);
498       winston.getStatement().execute("REPAIR TABLE `" + table + "` QUICK USE_FRM");
499       return checkTable(database, table);
500     } catch (final Exception e) {
501       LOGGER.error("Failed to repair: {}", table);
502     }
503     return false;
504   }
505 }