1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16 package net.sourceforge.buildmonitor.monitors;
17
18 import java.io.BufferedReader;
19 import java.io.File;
20 import java.io.FileInputStream;
21 import java.io.FileNotFoundException;
22 import java.io.FileOutputStream;
23 import java.io.IOException;
24 import java.io.InputStreamReader;
25 import java.io.StringReader;
26 import java.net.ConnectException;
27 import java.net.HttpURLConnection;
28 import java.net.SocketException;
29 import java.net.URI;
30 import java.net.URISyntaxException;
31 import java.net.URL;
32 import java.net.URLEncoder;
33 import java.net.UnknownHostException;
34 import java.text.ParseException;
35 import java.text.SimpleDateFormat;
36 import java.util.ArrayList;
37 import java.util.Date;
38 import java.util.Hashtable;
39 import java.util.List;
40 import java.util.Map;
41 import java.util.Properties;
42
43 import javax.xml.xpath.XPath;
44 import javax.xml.xpath.XPathExpressionException;
45 import javax.xml.xpath.XPathFactory;
46
47 import net.sourceforge.buildmonitor.BuildMonitor;
48 import net.sourceforge.buildmonitor.BuildReport;
49 import net.sourceforge.buildmonitor.MonitoringException;
50 import net.sourceforge.buildmonitor.BuildReport.Status;
51 import net.sourceforge.buildmonitor.dialogs.BambooPropertiesDialog;
52
53 import org.xml.sax.InputSource;
54
55
56
57
58
59
60
61 public class BambooMonitor implements Monitor
62 {
63
64
65
66
67
68
69
70 private class BambooProperties
71 {
72
73
74
75
76 private static final String BAMBOO_PASSWORD_PROPERTY_KEY = "bamboo.password";
77
78 private static final String BAMBOO_USERNAME_PROPERTY_KEY = "bamboo.username";
79
80 private static final String UPDATE_PERIOD_IN_SECONDS_PROPERTY_KEY = "update.period.in.seconds";
81
82 private static final String BAMBOO_SERVER_BASE_URL_PROPERTY_KEY = "bamboo.server.base.url";
83
84 private static final String USER_PROPERTIES_FILE = "bamboo-monitor.properties";
85
86
87
88
89
90 private String serverBaseUrl = null;
91
92 private String username = null;
93
94 private String password = null;
95
96 private Integer updatePeriodInSeconds = null;
97
98
99
100
101
102
103
104
105
106 public String getServerBaseUrl()
107 {
108 return this.serverBaseUrl;
109 }
110
111
112
113
114
115 public void setServerBaseUrl(String theServerBaseUrl)
116 {
117 this.serverBaseUrl = theServerBaseUrl;
118
119 if (this.serverBaseUrl != null && this.serverBaseUrl.endsWith("/"))
120 {
121 this.serverBaseUrl = this.serverBaseUrl.substring(0, this.serverBaseUrl.length() - 1);
122 }
123 }
124
125
126
127
128
129 public Integer getUpdatePeriodInSeconds()
130 {
131 return this.updatePeriodInSeconds;
132 }
133
134
135
136
137
138 public void setUpdatePeriodInSeconds(Integer theUpdatePeriodInSeconds)
139 {
140 this.updatePeriodInSeconds = theUpdatePeriodInSeconds;
141 }
142
143
144
145
146
147 public String getUsername()
148 {
149 return this.username;
150 }
151
152
153
154
155
156 public void setUsername(String theUsername)
157 {
158 this.username = theUsername;
159 }
160
161
162
163
164 public String getPassword()
165 {
166 return this.password;
167 }
168
169
170
171
172
173 public void setPassword(String thePassword)
174 {
175 this.password = thePassword;
176 }
177
178
179
180
181
182
183
184
185
186 public void loadFromFile() throws FileNotFoundException, IOException
187 {
188
189 Properties bambooMonitorProperties = new Properties();
190 File bambooMonitorPropertiesFile = new File(System.getProperty("user.home"), USER_PROPERTIES_FILE);
191 if (bambooMonitorPropertiesFile.exists())
192 {
193 FileInputStream bambooMonitorPropertiesFileIS = new FileInputStream(bambooMonitorPropertiesFile);
194 bambooMonitorProperties.load(bambooMonitorPropertiesFileIS);
195 bambooMonitorPropertiesFileIS.close();
196 }
197
198
199 synchronized (this)
200 {
201 setServerBaseUrl(bambooMonitorProperties.getProperty(BAMBOO_SERVER_BASE_URL_PROPERTY_KEY));
202
203 String updatePeriodInSecondsAsString = bambooMonitorProperties.getProperty(UPDATE_PERIOD_IN_SECONDS_PROPERTY_KEY);
204 if (updatePeriodInSecondsAsString != null)
205 {
206 try
207 {
208 setUpdatePeriodInSeconds(Integer.parseInt(updatePeriodInSecondsAsString));
209 }
210 catch(NumberFormatException e)
211 {
212
213 setUpdatePeriodInSeconds(300);
214 }
215 }
216 setUsername(bambooMonitorProperties.getProperty(BAMBOO_USERNAME_PROPERTY_KEY));
217 setPassword(bambooMonitorProperties.getProperty(BAMBOO_PASSWORD_PROPERTY_KEY));
218 }
219 }
220
221
222
223
224
225 public void saveToFile() throws FileNotFoundException, IOException
226 {
227
228 Properties bambooMonitorProperties = new Properties();
229 synchronized (this)
230 {
231 bambooMonitorProperties.setProperty(BAMBOO_SERVER_BASE_URL_PROPERTY_KEY, this.serverBaseUrl);
232 bambooMonitorProperties.setProperty(BAMBOO_USERNAME_PROPERTY_KEY, this.username);
233 bambooMonitorProperties.setProperty(BAMBOO_PASSWORD_PROPERTY_KEY, this.password);
234 bambooMonitorProperties.setProperty(UPDATE_PERIOD_IN_SECONDS_PROPERTY_KEY, "" + this.getUpdatePeriodInSeconds());
235 }
236
237
238 File bambooMonitorPropertiesFile = new File(System.getProperty("user.home"), USER_PROPERTIES_FILE);
239 FileOutputStream buildMonitorPropertiesOutputStream = new FileOutputStream(bambooMonitorPropertiesFile);
240 bambooMonitorProperties.store(buildMonitorPropertiesOutputStream, "File last updated on " + new Date());
241 buildMonitorPropertiesOutputStream.close();
242 }
243 }
244
245
246
247
248
249 private static final String REST_LOGIN_URL = "/api/rest/login.action";
250
251 private static final String REST_LIST_BUILD_NAMES_URL = "/api/rest/listBuildNames.action";
252
253 private static final String REST_GET_LATEST_BUILD_RESULTS_URL = "/api/rest/getLatestBuildResults.action";
254
255 private static final String URL_ENCODING = "UTF-8";
256
257
258
259
260
261 private BuildMonitor buildMonitorInstance = null;
262
263 private boolean stop = false;
264
265 private BambooProperties bambooProperties = new BambooProperties();
266
267 private BambooPropertiesDialog optionsDialog = null;
268
269
270
271
272
273 public BambooMonitor(BuildMonitor theBuildMonitorInstance) throws FileNotFoundException, IOException
274 {
275 this.buildMonitorInstance = theBuildMonitorInstance;
276
277
278 this.optionsDialog = new BambooPropertiesDialog(null, true);
279 this.optionsDialog.setIconImage(this.buildMonitorInstance.getDialogsDefaultIcon());
280 this.optionsDialog.setTitle("Bamboo server monitoring parameters");
281 this.optionsDialog.pack();
282
283
284 this.bambooProperties.loadFromFile();
285
286
287 if ((this.bambooProperties.getServerBaseUrl() == null) || (this.bambooProperties.getUpdatePeriodInSeconds() == null) || (this.bambooProperties.getUsername() == null) || (this.bambooProperties.getPassword() == null))
288 {
289 displayOptionsDialog(true);
290 if (this.optionsDialog.getLastClickedButton() != BambooPropertiesDialog.BUTTON_OK)
291 {
292 System.exit(0);
293 }
294 }
295 }
296
297
298
299
300
301
302
303
304 public void run()
305 {
306 String authenticationIdentifier = getNewBambooTicket();
307 while (!this.stop)
308 {
309
310 try
311 {
312 String bambooServerBaseUrl = null;
313 Integer updatePeriodInSeconds = null;
314 synchronized (this.bambooProperties)
315 {
316 bambooServerBaseUrl = this.bambooProperties.getServerBaseUrl();
317 updatePeriodInSeconds = this.bambooProperties.getUpdatePeriodInSeconds();
318 }
319 Map<String, String> builds = listBuildNames(bambooServerBaseUrl, authenticationIdentifier);
320 List<BuildReport> lastBuildStatus = new ArrayList<BuildReport>();
321 for (String key : builds.keySet())
322 {
323 BuildReport lastBuildReport = getLatestBuildResults(bambooServerBaseUrl, authenticationIdentifier, key);
324 lastBuildReport.setName(builds.get(key));
325 lastBuildStatus.add(lastBuildReport);
326 }
327 this.buildMonitorInstance.updateBuildStatus(lastBuildStatus);
328
329
330 try
331 {
332 Thread.sleep(updatePeriodInSeconds * 1000);
333 } catch (InterruptedException e)
334 {
335
336 }
337 }
338 catch (MonitoringException e)
339 {
340 if (e.getCause() != null && e.getCause() instanceof BambooTicketNeedToBeRenewedError)
341 {
342
343 authenticationIdentifier = getNewBambooTicket();
344 }
345 else
346 {
347 this.buildMonitorInstance.reportMonitoringException(e);
348
349 try
350 {
351 Thread.sleep(1000);
352 } catch (InterruptedException e2)
353 {
354
355 }
356 }
357 }
358 }
359 }
360
361
362
363
364
365 private String getNewBambooTicket()
366 {
367 while (true)
368 {
369 try
370 {
371 String bambooUsername = null;
372 String bambooPassword = null;
373 String bambooServerBaseUrl = null;
374 synchronized (this.bambooProperties)
375 {
376 bambooUsername = this.bambooProperties.getUsername();
377 bambooPassword = this.bambooProperties.getPassword();
378 bambooServerBaseUrl = this.bambooProperties.getServerBaseUrl();
379 }
380 return login(bambooServerBaseUrl, bambooUsername, bambooPassword);
381 }
382 catch (MonitoringException e)
383 {
384 this.buildMonitorInstance.reportMonitoringException(e);
385 try
386 {
387 Thread.sleep(15000);
388 } catch (InterruptedException ie)
389 {
390 }
391 }
392 }
393 }
394
395
396
397
398 public void stop()
399 {
400 this.stop = true;
401 }
402
403
404
405
406 public URI getMainPageURI()
407 {
408 URI returnedValue = null;
409 try
410 {
411 synchronized (this.bambooProperties)
412 {
413 returnedValue = new URI(this.bambooProperties.getServerBaseUrl());
414 }
415 }
416 catch (URISyntaxException e)
417 {
418 throw new RuntimeException(e);
419 }
420 return returnedValue;
421 }
422
423
424
425
426 public URI getBuildURI(String theIdOfTheBuild)
427 {
428 URI returnedValue = null;
429 try
430 {
431 synchronized (this.bambooProperties)
432 {
433 returnedValue = new URI(this.bambooProperties.getServerBaseUrl() + "/browse/" + theIdOfTheBuild);
434 }
435 }
436 catch (URISyntaxException e)
437 {
438 throw new RuntimeException(e);
439 }
440 return returnedValue;
441 }
442
443
444
445
446 public String getSystemTrayIconTooltipHeader()
447 {
448 synchronized (this.bambooProperties)
449 {
450 return "Monitoring Bamboo server at " + this.bambooProperties.getServerBaseUrl();
451 }
452 }
453
454
455
456
457 public void displayOptionsDialog()
458 {
459 displayOptionsDialog(false);
460 }
461
462
463
464
465 public String getMonitoredBuildSystemName()
466 {
467 return "Bamboo server";
468 }
469
470
471
472
473
474 private void displayOptionsDialog(boolean isDialogOpenedForPropertiesCreation)
475 {
476 if (!this.optionsDialog.isVisible())
477 {
478
479 if (this.bambooProperties.getServerBaseUrl() != null)
480 {
481 this.optionsDialog.baseURLField.setText(this.bambooProperties.getServerBaseUrl());
482 }
483 else
484 {
485 this.optionsDialog.baseURLField.setText("http://localhost:8085");
486 }
487
488
489 if (this.bambooProperties.getUsername() != null)
490 {
491 this.optionsDialog.usernameField.setText(this.bambooProperties.getUsername());
492 }
493 else
494 {
495 this.optionsDialog.usernameField.setText("");
496 }
497
498
499 if (this.bambooProperties.getPassword() != null)
500 {
501 this.optionsDialog.passwordField.setText(this.bambooProperties.getPassword());
502 }
503 else
504 {
505 this.optionsDialog.passwordField.setText("");
506 }
507
508
509 if (this.bambooProperties.getUpdatePeriodInSeconds() != null)
510 {
511 this.optionsDialog.updatePeriodField.setValue(this.bambooProperties.getUpdatePeriodInSeconds() / 60);
512 }
513 else
514 {
515 this.optionsDialog.updatePeriodField.setValue(5);
516 }
517
518
519 if (!isDialogOpenedForPropertiesCreation)
520 {
521 this.optionsDialog.updateBaseURLFieldStatus();
522 this.optionsDialog.updateUsernameFieldStatus();
523 this.optionsDialog.updatePasswordFieldStatus();
524 }
525
526
527 if (!this.optionsDialog.isDisplayable())
528 {
529 this.optionsDialog.pack();
530 }
531 this.optionsDialog.setVisible(true);
532 this.optionsDialog.toFront();
533
534 if (this.optionsDialog.getLastClickedButton() == BambooPropertiesDialog.BUTTON_OK)
535 {
536
537 synchronized (this.bambooProperties)
538 {
539 this.bambooProperties.setServerBaseUrl(this.optionsDialog.baseURLField.getText());
540 this.bambooProperties.setUsername(this.optionsDialog.usernameField.getText());
541 this.bambooProperties.setPassword(new String(this.optionsDialog.passwordField.getPassword()));
542 this.bambooProperties.setUpdatePeriodInSeconds((Integer) (this.optionsDialog.updatePeriodField.getValue()) * 60);
543 }
544 try
545 {
546 this.bambooProperties.saveToFile();
547 }
548 catch (FileNotFoundException e)
549 {
550 throw new RuntimeException(e);
551 }
552 catch (IOException e)
553 {
554 throw new RuntimeException(e);
555 }
556
557
558 this.buildMonitorInstance.reportConfigurationUpdatedToBeTakenIntoAccountImmediately();
559 }
560 }
561 else
562 {
563
564 this.optionsDialog.setVisible(true);
565 this.optionsDialog.toFront();
566 }
567 }
568
569
570
571
572
573
574 private String login(String theBambooServerBaseURL, String theUsername, String thePassword) throws MonitoringException
575 {
576 String returnedValue = null;
577 try
578 {
579
580 URL methodURL = new URL(theBambooServerBaseURL + REST_LOGIN_URL + "?username=" + URLEncoder.encode(theUsername, URL_ENCODING) + "&password=" + URLEncoder.encode(thePassword, URL_ENCODING));
581 String serverResponse = returnedValue = callBambooApi(methodURL);
582 InputSource serverResponseIS = new InputSource(new StringReader(serverResponse));
583 returnedValue = XPathFactory.newInstance().newXPath().evaluate("/response/auth", serverResponseIS);
584 }
585 catch(MonitoringException e)
586 {
587 throw e;
588 }
589 catch (Throwable e)
590 {
591 throw new MonitoringException(e, null);
592 }
593 return returnedValue;
594 }
595
596
597
598
599
600
601
602 private Map<String, String> listBuildNames(String theBambooServerBaseURL, String theAuthenticationIdentifier) throws MonitoringException
603 {
604 Map<String, String> returnedValue = new Hashtable<String, String>();
605 try
606 {
607
608 URL methodURL = new URL(theBambooServerBaseURL + REST_LIST_BUILD_NAMES_URL + "?auth=" + URLEncoder.encode(theAuthenticationIdentifier, URL_ENCODING));
609 String serverResponse = callBambooApi(methodURL);
610 int currentBuildNameIndex = 1;
611 boolean moreBuildNames = true;
612 while (moreBuildNames)
613 {
614 InputSource serverResponseIS = new InputSource(new StringReader(serverResponse));
615 String currentBuildName = XPathFactory.newInstance().newXPath().evaluate("/response/build[" + currentBuildNameIndex + "]/name", serverResponseIS);
616 serverResponseIS = new InputSource(new StringReader(serverResponse));
617 String currentBuildKey = XPathFactory.newInstance().newXPath().evaluate("/response/build[" + currentBuildNameIndex + "]/key", serverResponseIS);
618 if ("".equals(currentBuildKey))
619 {
620 moreBuildNames = false;
621 }
622 else
623 {
624 returnedValue.put(currentBuildKey, currentBuildName);
625 currentBuildNameIndex++;
626 }
627 }
628 }
629 catch(MonitoringException e)
630 {
631 throw e;
632 }
633 catch (Throwable e)
634 {
635 throw new MonitoringException(e, null);
636 }
637 return returnedValue;
638 }
639
640
641
642
643
644
645
646 private BuildReport getLatestBuildResults(String theBambooServerBaseURL, String theAuthenticationIdentifier, String theBuildKey) throws MonitoringException
647 {
648 BuildReport returnedValue = null;
649 try
650 {
651 URL methodURL = new URL(theBambooServerBaseURL + REST_GET_LATEST_BUILD_RESULTS_URL + "?auth=" + URLEncoder.encode(theAuthenticationIdentifier, URL_ENCODING) + "&buildKey=" + URLEncoder.encode(theBuildKey, URL_ENCODING));
652 String serverResponse = callBambooApi(methodURL);
653 returnedValue = new BuildReport();
654 returnedValue.setId(theBuildKey);
655 InputSource serverResponseIS = new InputSource(new StringReader(serverResponse));
656 String buildState = XPathFactory.newInstance().newXPath().evaluate("/response/buildState", serverResponseIS);
657 if ("Successful".equals(buildState))
658 {
659 returnedValue.setStatus(Status.OK);
660 }
661 else if ("Failed".equals(buildState))
662 {
663 returnedValue.setStatus(Status.FAILED);
664 }
665 else
666 {
667 throw new MonitoringException("Unknown build state " + buildState + " returned for build " + theBuildKey, null);
668 }
669 serverResponseIS = new InputSource(new StringReader(serverResponse));
670 String buildTime = XPathFactory.newInstance().newXPath().evaluate("/response/buildTime", serverResponseIS);
671
672 SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss");
673 try
674 {
675 returnedValue.setDate(dateFormat.parse(buildTime));
676 }
677 catch (ParseException e)
678 {
679
680 }
681 serverResponseIS = new InputSource(new StringReader(serverResponse));
682 }
683 catch(MonitoringException e)
684 {
685 throw e;
686 }
687 catch (Throwable t)
688 {
689 throw new MonitoringException(t, null);
690 }
691 return returnedValue;
692 }
693
694
695
696
697
698
699
700
701 private String callBambooApi(URL theURL) throws MonitoringException, BambooTicketNeedToBeRenewedError
702 {
703 String returnedValue = null;
704 HttpURLConnection urlConnection = null;
705 BufferedReader urlConnectionReader = null;
706 try
707 {
708
709 urlConnection = (HttpURLConnection) theURL.openConnection();
710 urlConnection.connect();
711 urlConnectionReader = new BufferedReader(new InputStreamReader(urlConnection.getInputStream()));
712 String line = null;
713 StringBuffer serverResponse = new StringBuffer();
714 while ((line = urlConnectionReader.readLine()) != null)
715 {
716 serverResponse.append(line);
717 }
718
719 returnedValue = serverResponse.toString();
720
721 if (returnedValue.contains("<title>Bamboo Setup Wizard - Atlassian Bamboo</title>"))
722 {
723
724 throw new MonitoringException("Your Bamboo server installation is not finished ! Double click here to complete the Bamboo Setup Wizard !", getMainPageURI());
725 }
726 InputSource is = new InputSource(new StringReader(serverResponse.toString()));
727 XPath xpath = XPathFactory.newInstance().newXPath();
728 String error = xpath.evaluate("/errors/error", is);
729 if (!"".equals(error))
730 {
731 if ("User not authenticated yet, or session timed out.".equals(error))
732 {
733
734 throw new BambooTicketNeedToBeRenewedError();
735 }
736 else
737 {
738 boolean isErrorOptionsRelated = false;
739 URI uriForNonOptionsRelatedErrors = getMainPageURI();
740 if ("Invalid username or password.".equals(error))
741 {
742 isErrorOptionsRelated = true;
743 }
744 if ("The remote API has been disabled.".equals(error))
745 {
746 error += " Double click here to enable it.";
747
748 try
749 {
750 synchronized (this.bambooProperties)
751 {
752 uriForNonOptionsRelatedErrors = new URI(this.bambooProperties.getServerBaseUrl() + "/admin/configure!default.action");
753 }
754 }
755 catch (URISyntaxException e)
756 {
757 throw new RuntimeException(e);
758 }
759 }
760
761 throw new MonitoringException("Error reported by the Bamboo server: " + error, isErrorOptionsRelated, uriForNonOptionsRelatedErrors);
762 }
763 }
764 }
765 catch (ClassCastException e)
766 {
767
768 throw new MonitoringException("Problem: the base URL defined for the Bamboo server in Options is not an http URL.", true, null);
769 }
770 catch (UnknownHostException e)
771 {
772 throw new MonitoringException("Problem: cannot find host " + theURL.getHost() + " on the network.", true, null);
773 }
774 catch (ConnectException e)
775 {
776 throw new MonitoringException("Problem: cannot connect to port " + theURL.getPort() + " on host " + theURL.getHost() + ".", true, null);
777 }
778 catch (FileNotFoundException e)
779 {
780 throw new MonitoringException("Problem: cannot find the Bamboo server REST api using the base URL defined for the Bamboo server in Options. Seems that this URL is not the one to your Bamboo server home page...", true, null);
781 }
782 catch(SocketException e)
783 {
784 throw new MonitoringException("Problem: network error, connection lost.", null);
785 }
786 catch (XPathExpressionException e)
787 {
788 throw new MonitoringException("Problem: the Bamboo Server returned an unexpected content for attribute <error>: " + returnedValue, null);
789 }
790 catch (MonitoringException e)
791 {
792 throw e;
793 }
794 catch (Throwable t)
795 {
796 throw new MonitoringException(t, null);
797 }
798 finally
799 {
800 if (urlConnectionReader != null)
801 {
802 try
803 {
804 urlConnectionReader.close();
805 }
806 catch (IOException e)
807 {
808
809 }
810 }
811 if (urlConnection != null)
812 {
813 urlConnection.disconnect();
814 }
815 }
816 return returnedValue;
817 }
818
819 }