Add email notifications.
[cfb.git] / prod / net / jaekl / cfb / CFB.java
1 package net.jaekl.cfb;
2
3 // Comparative FindBugs
4 // 
5 // Tool to compare successive runs of FindBugs, 
6 // flagging the change from one run to the next.
7 // 
8 // Copyright (C) 2015 Christian Jaekl
9
10 import java.io.File;
11 import java.io.IOException;
12 import java.io.PrintWriter;
13 import java.sql.Connection;
14 import java.sql.SQLException;
15 import java.text.MessageFormat;
16 import java.util.Locale;
17 import java.util.Locale.Category;
18
19 import net.jaekl.cfb.analyze.Analysis;
20 import net.jaekl.cfb.analyze.Analyzer;
21 import net.jaekl.cfb.analyze.Delta;
22 import net.jaekl.cfb.analyze.HtmlReport;
23 import net.jaekl.cfb.analyze.MessageMap;
24 import net.jaekl.cfb.analyze.Notifier;
25 import net.jaekl.cfb.db.CfbSchema;
26 import net.jaekl.cfb.db.TypeMismatchException;
27 import net.jaekl.cfb.db.driver.DbDriver;
28 import net.jaekl.cfb.db.driver.PostgresqlDriver;
29 import net.jaekl.cfb.store.DbStore;
30 import net.jaekl.qd.xml.XmlParseException;
31
32 import org.apache.commons.cli.CommandLine;
33 import org.apache.commons.cli.GnuParser;
34 import org.apache.commons.cli.HelpFormatter;
35 import org.apache.commons.cli.Options;
36 import org.apache.commons.cli.ParseException;
37 import org.xml.sax.SAXException;
38
39 public class CFB {
40         DbDriver m_driver;
41         CfbSchema m_schema;
42         CfbBundle m_bundle;     
43         Locale m_locale;
44         
45         Config m_config;
46         
47         // Command-line parameters
48         File m_configFile;
49         String m_dbName; // db name
50         File m_fbp;             // FindBugsProject file
51         File m_fbDir;   // Directory where FindBugs is installed
52         String m_host;  // db host
53         int m_port;             // db port
54         String m_user;  // db user
55         String m_pass;  // db password
56         String m_buildNum; // build number (version)
57         boolean m_removeSchema; // purge DB schema
58         File m_output;  // File to which we should write our output (report)
59         
60         CFB(Locale locale) {
61                 m_driver = new PostgresqlDriver();
62                 m_schema = new CfbSchema(m_driver);
63                 m_locale = locale;
64                 m_bundle = CfbBundle.getInst(m_locale);
65                 m_config = new Config();
66                 
67                 m_configFile = new File("config.properties");
68                 m_dbName = "CFB";
69                 m_fbp    = null;
70                 m_fbDir  = null;
71                 m_host = "localhost";
72                 m_port = 5432;
73                 m_pass = "";
74                 m_user = "user";
75                 m_buildNum = null;
76                 m_removeSchema = false;
77                 m_output = null;
78         }
79         
80         Options createOptions() {
81                 Options opt = new Options();
82                 
83                 opt.addOption("c",  "config",      true,  "Properties configuration file");
84                 opt.addOption("d",  "dbname",      true,  "DB name");
85                 opt.addOption(null, "drop-tables", false, "Remove database schema (drop all data)");
86                 opt.addOption("f",  "fbp",         true,  "FindBugsProject file");
87                 opt.addOption("h",  "host",        true,  "DB hostname");
88                 opt.addOption("n",  "number",      true,  "Build number (version)");
89                 opt.addOption("o",  "outfile",     true,  "Output report filename");
90                 opt.addOption("p",  "pass",        true,  "DB password");
91                 opt.addOption("t",  "port",        true,  "DB port");
92                 opt.addOption("u",  "user",        true,  "DB username");
93                 
94                 return opt;
95         }
96         
97         boolean parseArgs(PrintWriter pw, String[] args) {
98                 Options opt = createOptions();
99                 
100                 try {
101                         CommandLine line = new GnuParser().parse(opt, args);
102                         if (line.hasOption("c")) {
103                                 m_configFile = new File(line.getOptionValue("c"));
104                         }
105                         if (line.hasOption("d")) {
106                                 m_dbName = line.getOptionValue("d");
107                         }
108                         if (line.hasOption("f")) {
109                                 m_fbp = new File(line.getOptionValue("f"));
110                         }
111                         if (line.hasOption("h")) {
112                                 m_host = line.getOptionValue("h");
113                         }
114                         if (line.hasOption("n")) {
115                                 m_buildNum = line.getOptionValue("n");
116                         }
117                         if (line.hasOption("o")) {
118                                 m_output = new File(line.getOptionValue("o"));
119                         }
120                         if (line.hasOption("p")) {
121                                 m_pass = line.getOptionValue("p");
122                         }
123                         m_removeSchema = line.hasOption("drop-tables");
124                         if (line.hasOption("t")) {
125                                 m_port = Integer.parseInt(line.getOptionValue("t"));
126                         }
127                         if (line.hasOption("u")) {
128                                 m_user = line.getOptionValue("u");
129                         }
130                 } 
131                 catch (ParseException exc) {
132                         usage(pw, opt);
133                         return false;
134                 }
135                 
136                 return true;
137         }
138         
139         void usage(PrintWriter pw, Options opt) {
140                 HelpFormatter help = new HelpFormatter();
141                 help.printHelp(pw, 80, getClass().getName(), "", opt, 0, 0, "", true);
142         }
143         
144         String trans(String key) {
145                 return m_bundle.get(key);
146         }
147         
148         String getenv(String varName) {
149                 // This is a separate function so that we can override it at unit test time
150                 return System.getenv(varName);
151         }
152         
153         String getProperty(String propName) {
154                 // This is a separate function so that we can override it at unit test time
155                 return System.getProperty(propName);
156         }
157         
158         File getFindBugsDir() {
159                 return (null != m_fbDir) ? m_fbDir : new File(".");
160         }
161         
162         void initArgs() {
163                 String findBugsDir = getenv("FINDBUGS_HOME");
164                 if (null != findBugsDir) {
165                         m_fbDir = new File(findBugsDir);
166                 }
167                 findBugsDir = getProperty("findbugs.home");
168                 if (null != findBugsDir) {
169                         m_fbDir = new File(findBugsDir);
170                 }
171         }
172         
173         void readConfig() throws IOException {
174                 if (null != m_configFile) {
175                         m_config.readFile(m_configFile);
176                 }
177         }
178         
179         void doMain(PrintWriter pw, String[] args) throws SQLException, IOException, XmlParseException, SAXException, TypeMismatchException {
180                 initArgs();     // read environment and system properties
181                 if ( ! parseArgs(pw, args) ) {
182                         return;
183                 }
184                 readConfig();
185
186                 File findBugsDir = getFindBugsDir();
187                 File workDir = new File(".");
188                 MessageMap messageMap = new MessageMap();
189                 messageMap.load(findBugsDir, Locale.getDefault(Category.DISPLAY));
190                 
191                 try (Connection con = m_driver.connect(m_host, m_port, m_dbName, m_user, m_pass)) {
192                         m_schema.setMessageMap(messageMap);
193                         
194                         if (m_removeSchema) {
195                                 m_schema.purge(con);
196                                 return;
197                         }
198                         m_schema.ensureDbInitialized(con);
199                         messageMap.loadIds(con, m_driver);
200                 }
201                 catch (SQLException exc) {
202                         reportUnableToConnect(pw, exc);
203                         return;
204                 }
205                 
206                 Analyzer analyzer = new Analyzer(messageMap);
207                 Analysis analysis = analyzer.analyze(pw, workDir, m_fbp, m_buildNum);
208                 if (null == analysis) {
209                         pw.println(trans(CfbBundle.ANALYSIS_FAILED));
210                         return;
211                 }
212                 
213                 try (Connection con = m_driver.connect(m_host, m_port, m_dbName, m_user, m_pass)) {
214                         DbStore store = new DbStore(con, m_driver, messageMap.getColl());
215                         
216                         store.put(analysis);
217                         Analysis prior = store.getPrior(analysis);
218                         Delta delta = new Delta(prior, analysis);
219
220                         HtmlReport report = new HtmlReport(m_bundle, messageMap.getColl(), delta);
221                         report.write(m_output);
222                         
223                         Notifier notifier = new Notifier(m_bundle, m_config);
224                         notifier.sendEmailIfNeeded(pw, report);
225                 }
226                 catch (SQLException exc) {
227                         reportUnableToConnect(pw, exc);
228                         return;
229                 }
230         }
231
232         private void reportUnableToConnect(PrintWriter pw, SQLException exc) {
233                 String cannotConnectFormat = trans(CfbBundle.CANNOT_CONNECT);
234                 String cannotConnect = MessageFormat.format(cannotConnectFormat, m_host, ""+m_port, m_dbName, m_user);
235                 exc.printStackTrace(pw);
236                 SQLException next = exc.getNextException();
237                 while (null != next) {
238                         next.printStackTrace(pw);
239                         next = next.getNextException();
240                 }
241                 pw.println(cannotConnect);
242         }
243         
244         public static void main(String[] args) {
245                 CFB cfb = new CFB(Locale.getDefault());
246                 
247                 try (PrintWriter pw = new PrintWriter(System.out)){
248                         cfb.doMain(pw, args);
249                         pw.flush();
250                 } catch (SQLException | IOException | XmlParseException | SAXException | TypeMismatchException exc) {
251                         exc.printStackTrace();
252                 }
253         }
254
255 }