1 37 package net.sourceforge.cruisecontrol.publishers; 38 39 import java.io.File ; 40 import java.io.IOException ; 41 import java.io.StringWriter ; 42 import java.net.MalformedURLException ; 43 import java.net.URL ; 44 import java.util.Calendar ; 45 import java.util.Collection ; 46 import java.util.HashMap ; 47 import java.util.Hashtable ; 48 import java.util.Iterator ; 49 import java.util.Map ; 50 import java.util.StringTokenizer ; 51 import java.util.Vector ; 52 53 import javax.xml.transform.Transformer ; 54 import javax.xml.transform.TransformerException ; 55 import javax.xml.transform.TransformerFactory ; 56 import javax.xml.transform.stream.StreamResult ; 57 import javax.xml.transform.stream.StreamSource ; 58 59 import net.sourceforge.cruisecontrol.CruiseControlException; 60 import net.sourceforge.cruisecontrol.Publisher; 61 import net.sourceforge.cruisecontrol.util.XMLLogHelper; 62 63 import org.apache.log4j.Logger; 64 import org.apache.xmlrpc.XmlRpcClient; 65 import org.jdom.Element; 66 67 107 public class WeblogPublisher implements Publisher { 108 109 private static final Logger LOG = Logger.getLogger(WeblogPublisher.class); 110 111 private static final String APP_KEY = "CruiseControl Blog Publisher"; 112 113 private static final String DEFAULT_API = "metaweblog"; 114 115 private static final String DEFAULT_REPORTSUCCESS = "always"; 116 117 private static final boolean DEFAULT_SPAMWHILEBROKEN = true; 118 119 121 private String blogId; 122 123 private String api = DEFAULT_API; 124 125 private String username; 126 127 private String password; 128 129 private String category = ""; 130 131 private String blogUrl; 132 133 private String buildResultsURL; 134 135 private String reportSuccess = DEFAULT_REPORTSUCCESS; 136 137 private boolean spamWhileBroken = DEFAULT_SPAMWHILEBROKEN; 138 139 private String subjectPrefix; 140 141 143 private String xslFile; 144 145 private String xslDir; 146 147 private String css; 148 149 private String logDir; 150 151 private String [] xslFileNames = { "header.xsl", "maven.xsl", 152 "checkstyle.xsl", "compile.xsl", "javadoc.xsl", "unittests.xsl", 153 "modifications.xsl", "distributables.xsl" }; 154 155 private static final Map API_CLIENTS = new HashMap (); 156 static { 157 API_CLIENTS.put("metaweblog", MetaWeblogApiClient.class); 158 API_CLIENTS.put("blogger", BloggerApiClient.class); 159 API_CLIENTS.put("livejournal", LiveJournalApiClient.class); 160 } 161 162 164 169 public void setXSLFile(String fullPathToXslFile) { 170 xslFile = fullPathToXslFile; 171 } 172 173 176 public void setXSLDir(String xslDirectory) { 177 xslDir = xslDirectory; 178 } 179 180 193 protected void setXSLFileNames(String [] fileNames) { 194 if (fileNames == null) { 195 throw new IllegalArgumentException ( 196 "xslFileNames can't be null (but can be empty)"); 197 } 198 xslFileNames = fileNames; 199 } 200 201 207 protected String [] getXslFileNames() { 208 return xslFileNames; 209 } 210 211 214 public void setCSS(String cssFilename) { 215 css = cssFilename; 216 } 217 218 222 public void setLogDir(String directory) { 223 if (directory == null) { 224 throw new IllegalArgumentException ("logDir cannot be null!"); 225 } 226 this.logDir = directory; 227 } 228 229 233 public void setApi(String api) { 234 this.api = api; 235 } 236 237 241 public void setBlogId(String blogId) { 242 this.blogId = blogId; 243 } 244 245 250 public void setBlogUrl(String blogUrl) { 251 this.blogUrl = blogUrl; 252 } 253 254 257 public void setUsername(String username) { 258 this.username = username; 259 } 260 261 264 public void setPassword(String password) { 265 this.password = password; 266 } 267 268 272 public void setCategory(String category) { 273 this.category = category; 274 } 275 276 280 public void setSubjectPrefix(String prefix) { 281 this.subjectPrefix = prefix; 282 } 283 284 288 public void setBuildResultsURL(String url) { 289 this.buildResultsURL = url; 290 } 291 292 296 public void setReportSuccess(String reportSuccess) { 297 this.reportSuccess = reportSuccess; 298 } 299 300 304 public void setSpamWhileBroken(boolean spamWhileBroken) { 305 this.spamWhileBroken = spamWhileBroken; 306 } 307 308 310 316 public void publish(Element cruisecontrolLog) { 317 XMLLogHelper helper = new XMLLogHelper(cruisecontrolLog); 318 try { 319 if (shouldSend(helper)) { 320 postBlogEntry(createSubject(helper), createMessage(helper 321 .getProjectName(), helper.getLogFileName())); 322 } else { 323 LOG.debug("shouldSend() indicated we should not" 324 + " post a blog entry at this time"); 325 } 326 } catch (CruiseControlException e) { 327 LOG.error("", e); 328 } 329 } 330 331 336 interface BloggingApi { 337 346 public Object newPost(String blogUrl, String blogId, String username, 347 String password, String category, String subject, String content); 348 } 349 350 355 public static class BloggerApiClient implements BloggingApi { 356 357 public Object newPost(String blogUrl, String blogId, String username, 358 String password, String category, String subject, String content) { 359 content = "<title>" + subject + "</title>" + content; 364 Object postId = null; 365 try { 366 XmlRpcClient xmlrpc = new XmlRpcClient(blogUrl); 367 Vector params = new Vector (); 368 params.add(APP_KEY); 369 params.add(blogId); 370 params.add(username); 371 params.add(password); 372 params.add(content); 373 params.add(Boolean.TRUE); 374 postId = xmlrpc.execute("blogger.newPost", params); 375 } catch (Exception e) { 376 LOG.error("", e); 377 } 378 return postId; 379 } 380 } 381 382 387 public static class MetaWeblogApiClient implements BloggingApi { 388 389 public Object newPost(String blogUrl, String blogId, String username, 390 String password, String category, String subject, String content) { 391 Object postId = null; 392 try { 393 XmlRpcClient xmlrpc = new XmlRpcClient(blogUrl); 394 Vector params = new Vector (); 395 params.add(blogId); 396 params.add(username); 397 params.add(password); 398 399 Hashtable struct = new Hashtable (); 402 struct.put("title", subject); 403 struct.put("description", content); 404 Vector categories = new Vector (); 405 if (category != null) { 406 StringTokenizer tok = new StringTokenizer (category, ","); 407 while (tok.hasMoreTokens()) { 408 categories.add(tok.nextToken().trim()); 409 } 410 } 411 struct.put("categories", categories); 412 413 params.add(struct); 414 params.add(Boolean.TRUE); 415 postId = xmlrpc.execute("metaWeblog.newPost", params); 416 } catch (Exception e) { 417 LOG.error("", e); 418 } 419 return postId; 420 } 421 } 422 423 428 public static class LiveJournalApiClient implements BloggingApi { 429 430 434 private String stripLineFeeds(String input) { 435 StringBuffer s = new StringBuffer (); 436 char[] chars = input.toCharArray(); 437 for (int i = 0; i < chars.length; i++) { 438 if (chars[i] != '\n' && chars[i] != '\r') { 439 s.append(chars[i]); 440 } 441 } 442 return s.toString(); 443 } 444 445 public Object newPost(String blogUrl, String blogId, String username, 446 String password, String category, String subject, String content) { 447 Object postId = null; 448 try { 449 XmlRpcClient xmlrpc = new XmlRpcClient(blogUrl); 450 Vector params = new Vector (); 451 Hashtable struct = new Hashtable (); 452 struct.put("username", username); 453 454 struct.put("auth_method", "clear"); 456 struct.put("password", password); 457 458 struct.put("subject", subject); 459 struct.put("event", stripLineFeeds(content)); 460 struct.put("lineendings", "\n"); 461 struct.put("security", "public"); 462 Calendar now = Calendar.getInstance(); 463 struct.put("year", "" + now.get(Calendar.YEAR)); 464 struct.put("mon", "" + (now.get(Calendar.MONTH) + 1)); 465 struct.put("day", "" + now.get(Calendar.DAY_OF_MONTH)); 466 struct.put("hour", "" + now.get(Calendar.HOUR_OF_DAY)); 467 struct.put("min", "" + now.get(Calendar.MINUTE)); 468 params.add(struct); 469 postId = xmlrpc.execute("LJ.XMLRPC.postevent", params); 470 } catch (Exception e) { 471 LOG.error("", e); 472 } 473 return postId; 474 } 475 } 476 477 488 public BloggingApi getBloggingApiImplementation(String apiName) 489 throws CruiseControlException { 490 Class implClass = (Class ) API_CLIENTS.get(apiName); 491 if (implClass != null) { 492 LOG.debug("Mapped " + apiName + " to " + implClass.getName()); 493 try { 494 return (BloggingApi) implClass.newInstance(); 495 } catch (Exception e) { 496 throw new CruiseControlException( 497 "Failed to instantiate Blogging API implementation, " 498 + implClass.getName() + ", due to a " 499 + e.getClass().getName() + ": " 500 + e.getMessage()); 501 } 502 } 503 return null; 504 } 505 506 514 public void postBlogEntry(String subject, String content) { 515 LOG.debug("Posting a blog entry to " + blogUrl); 516 LOG.debug(" blogId=" + blogId); 517 LOG.debug(" username=" + username); 518 LOG.debug(" subject=" + subject); 519 LOG.debug(" content=" + content); 520 try { 521 BloggingApi apiClient = getBloggingApiImplementation(api); 522 if (apiClient != null) { 523 Object postId = apiClient.newPost(blogUrl, blogId, username, 524 password, category, subject, content); 525 if (postId != null) { 526 LOG.info("Blog entry " + postId + " created at " + blogUrl); 527 } else { 528 LOG.debug("Blog entry ID not available from " + blogUrl); 529 } 530 } else { 531 LOG.error("No API associated with '" + api + "'"); 532 } 533 } catch (Exception e) { 534 LOG.error("", e); 535 } 536 } 537 538 545 public void validate() throws CruiseControlException { 546 validateRequiredField("username", username); 547 validateRequiredField("password", password); 548 validateRequiredField("blogid", blogId); 549 validateRequiredField("blogurl", blogUrl); 550 validateURL("blogurl", blogUrl); 551 validateOneOf("api", API_CLIENTS.keySet(), api); 552 553 if (buildResultsURL != null) { 554 validateURL("buildresultsurl", buildResultsURL); 555 } 556 557 if (logDir != null) { 558 verifyDirectory("WeblogPublisher.logDir", logDir); 559 } else { 560 LOG.info("Using default log directory \"logs/<projectname>\""); 561 } 562 563 if (xslFile == null) { 564 verifyDirectory("WeblogPublisher.xslDir", xslDir); 565 verifyFile("WeblogPublisher.css", css); 566 567 String [] fileNames = getXslFileNames(); 568 if (fileNames == null) { 569 throw new CruiseControlException( 570 "WeblogPublisher.getXslFileNames() can't return null"); 571 } 572 for (int i = 0; i < fileNames.length; i++) { 573 verifyFile("WeblogPublisher.xslDir/" + fileNames[i], new File ( 574 xslDir, fileNames[i])); 575 } 576 } else { 577 verifyFile("WeblogPublisher.xslFile", xslFile); 578 } 579 } 580 581 private void validateOneOf(String fieldName, Collection validValues, 582 String value) throws CruiseControlException { 583 if (!validValues.contains(value)) { 584 throw new CruiseControlException("Value for '" + fieldName 585 + "' must be one of " + commaSeparated(validValues)); 586 } 587 } 588 589 private String commaSeparated(Collection values) { 590 StringBuffer s = new StringBuffer (); 591 Iterator i = values.iterator(); 592 while (i.hasNext()) { 593 s.append("'").append(i.next()).append("'"); 594 if (i.hasNext()) { 595 s.append(", "); 596 } 597 } 598 return s.toString(); 599 } 600 601 private void validateURL(String fieldName, String url) 602 throws CruiseControlException { 603 try { 604 new URL (url); 605 } catch (MalformedURLException e) { 606 throw new CruiseControlException(fieldName 607 + " must be a valid URL: " + url); 608 } 609 } 610 611 private void validateRequiredField(String fieldName, String value) 612 throws CruiseControlException { 613 if (value == null) { 614 throw new CruiseControlException("Attribute " + fieldName 615 + " is required."); 616 } 617 } 618 619 private void verifyDirectory(String dirName, String dir) 620 throws CruiseControlException { 621 if (dir == null) { 622 throw new CruiseControlException(dirName 623 + " not specified in configuration file"); 624 } 625 File dirFile = new File (dir); 626 if (!dirFile.exists()) { 627 throw new CruiseControlException(dirName + " does not exist : " 628 + dirFile.getAbsolutePath()); 629 } 630 if (!dirFile.isDirectory()) { 631 throw new CruiseControlException(dirName + " is not a directory : " 632 + dirFile.getAbsolutePath()); 633 } 634 } 635 636 private void verifyFile(String fileName, String file) 637 throws CruiseControlException { 638 if (file == null) { 639 throw new CruiseControlException(fileName 640 + " not specified in configuration file"); 641 } 642 verifyFile(fileName, new File (file)); 643 } 644 645 private void verifyFile(String fileName, File file) 646 throws CruiseControlException { 647 if (!file.exists()) { 648 throw new CruiseControlException(fileName + " does not exist: " 649 + file.getAbsolutePath()); 650 } 651 if (!file.isFile()) { 652 throw new CruiseControlException(fileName + " is not a file: " 653 + file.getAbsolutePath()); 654 } 655 } 656 657 664 boolean shouldSend(XMLLogHelper logHelper) throws CruiseControlException { 665 if (logHelper.isBuildSuccessful()) { 666 return shouldSendForSuccessfulBuild(logHelper); 667 } else { 668 return shouldSendForFailedBuild(logHelper); 669 } 670 } 671 672 679 boolean shouldSendForFailedBuild(XMLLogHelper logHelper) 680 throws CruiseControlException { 681 if (!logHelper.wasPreviousBuildSuccessful() 682 && logHelper.isBuildNecessary() && !spamWhileBroken) { 683 LOG.debug("spamWhileBroken is false, not sending email"); 684 return false; 685 } else { 686 return true; 687 } 688 } 689 690 697 boolean shouldSendForSuccessfulBuild(XMLLogHelper logHelper) 698 throws CruiseControlException { 699 if (reportSuccess.equalsIgnoreCase(DEFAULT_REPORTSUCCESS)) { 700 return true; 701 } else if (reportSuccess.equalsIgnoreCase("never")) { 702 return false; 703 } else if (reportSuccess.equalsIgnoreCase("fixes")) { 704 if (logHelper.wasPreviousBuildSuccessful()) { 705 LOG.debug("reportSuccess is set to 'fixes', " 706 + "not sending emails for repeated " 707 + "successful builds."); 708 return false; 709 } 710 } 711 return true; 712 } 713 714 721 String createSubject(XMLLogHelper logHelper) throws CruiseControlException { 722 String projectName = logHelper.getProjectName(); 723 String label = logHelper.getLabel(); 724 boolean buildSuccessful = logHelper.isBuildSuccessful(); 725 boolean isFix = logHelper.isBuildFix(); 726 return createSubject(projectName, label, buildSuccessful, isFix); 727 } 728 729 734 String createSubject(String projectName, String label, 735 boolean buildSuccessful, boolean isFix) 736 throws CruiseControlException { 737 StringBuffer subject = new StringBuffer (); 738 if (subjectPrefix != null && subjectPrefix.trim().length() > 0) { 739 subject.append(subjectPrefix).append(" "); 740 } 741 subject.append(projectName); 742 if (buildSuccessful) { 743 if (label.length() > 0) { 744 subject.append(" ").append(label); 745 } 746 subject.append(isFix ? " - Build Fixed" : " - Build Successful"); 747 } else { 748 subject.append(" - Build Failed"); 749 } 750 return subject.toString(); 751 } 752 753 758 String createMessage(String projectName, String logFileName) { 759 String message; 760 File inFile = null; 761 try { 762 if (logDir == null) { 763 logDir = getDefaultLogDir(projectName); 764 } 765 inFile = new File (logDir, logFileName); 766 message = transform(inFile); 767 } catch (Exception ex) { 768 LOG.error("error transforming " + (inFile != null ? inFile.getAbsolutePath() : ""), ex); 769 message = createLinkLine(logFileName); 770 } 771 return message; 772 } 773 774 String getDefaultLogDir(String projectName) throws CruiseControlException { 775 return "logs" + File.separator + projectName; 778 } 779 780 String transform(File xml) throws TransformerException , IOException { 781 StringBuffer messageBuffer = new StringBuffer (); 782 if (xslFile != null) { 783 transformWithSingleStylesheet(xml, messageBuffer); 784 } else { 785 messageBuffer.append(createLinkLine(xml.getName())); 786 transformWithMultipleStylesheets(xml, messageBuffer); 787 } 788 return messageBuffer.toString(); 789 } 790 791 void transformWithMultipleStylesheets(File inFile, 792 StringBuffer messageBuffer) throws IOException , 793 TransformerException { 794 TransformerFactory tFactory = TransformerFactory.newInstance(); 795 File xslDirectory = new File (xslDir); 796 String [] fileNames = getXslFileNames(); 797 for (int i = 0; i < fileNames.length; i++) { 798 String fileName = fileNames[i]; 799 File xsl = new File (xslDirectory, fileName); 800 messageBuffer.append("<p>\n"); 801 appendTransform(inFile, xsl, messageBuffer, tFactory); 802 } 803 } 804 805 void transformWithSingleStylesheet(File inFile, StringBuffer messageBuffer) 806 throws IOException , TransformerException { 807 TransformerFactory tFactory = TransformerFactory.newInstance(); 808 appendTransform(inFile, new File (xslFile), messageBuffer, tFactory); 809 } 810 811 void appendTransform(File xml, File xsl, StringBuffer messageBuffer, 812 TransformerFactory tFactory) throws TransformerException { 813 LOG.debug("Transforming file " + xml.getName() + " with " 814 + xsl.getName() + " ..."); 815 Transformer tformer = tFactory.newTransformer(new StreamSource (xsl)); 816 StringWriter sw = new StringWriter (); 817 try { 818 tformer.transform(new StreamSource (xml), new StreamResult (sw)); 819 LOG.debug("Transformed file " + xml.getName() + " with " 820 + xsl.getName() + " ..."); 821 } catch (Exception e) { 822 LOG.error("error transforming with xslFile " + xsl.getName(), e); 823 return; 824 } 825 messageBuffer.append(sw.toString()); 826 } 827 828 String createLinkLine(String logFileName) { 829 if (buildResultsURL == null) { 830 return ""; 831 } 832 String url = createBuildResultsUrl(logFileName); 833 StringBuffer linkLine = new StringBuffer (); 834 linkLine.append("<p>View results here -> <a HREF=\""); 835 linkLine.append(url); 836 linkLine.append("\">"); 837 linkLine.append(url); 838 linkLine.append("</a></p>"); 839 return linkLine.toString(); 840 } 841 842 String createBuildResultsUrl(String logFileName) { 843 int startName = logFileName.lastIndexOf(File.separator) + 1; 844 int endName = logFileName.lastIndexOf("."); 845 String baseLogFileName = logFileName.substring(startName, endName); 846 StringBuffer url = new StringBuffer (buildResultsURL); 847 if (buildResultsURL.indexOf("?") == -1) { 848 url.append("?"); 849 } else { 850 url.append("&"); 851 } 852 url.append("log="); 853 url.append(baseLogFileName); 854 return url.toString(); 855 } 856 } 857 | Popular Tags |