1 37 package net.sourceforge.cruisecontrol.sourcecontrols; 38 39 import net.sourceforge.cruisecontrol.CruiseControlException; 40 import net.sourceforge.cruisecontrol.Modification; 41 import net.sourceforge.cruisecontrol.SourceControl; 42 import net.sourceforge.cruisecontrol.util.Commandline; 43 import net.sourceforge.cruisecontrol.util.StreamPumper; 44 import net.sourceforge.cruisecontrol.util.Util; 45 import net.sourceforge.cruisecontrol.util.ValidationHelper; 46 import org.apache.log4j.Logger; 47 import org.jdom.Element; 48 49 import java.io.BufferedReader ; 50 import java.io.IOException ; 51 import java.io.InputStream ; 52 import java.io.InputStreamReader ; 53 import java.io.PrintWriter ; 54 import java.text.DateFormat ; 55 import java.text.ParseException ; 56 import java.text.SimpleDateFormat ; 57 import java.util.ArrayList ; 58 import java.util.Calendar ; 59 import java.util.Date ; 60 import java.util.HashMap ; 61 import java.util.Hashtable ; 62 import java.util.Iterator ; 63 import java.util.List ; 64 import java.util.Map ; 65 import java.util.NoSuchElementException ; 66 import java.util.StringTokenizer ; 67 68 89 public class P4 implements SourceControl { 90 91 private static final Logger LOG = Logger.getLogger(P4.class); 92 93 private String p4Port; 94 private String p4Client; 95 private String p4User; 96 private String p4View; 97 private String p4Passwd; 98 private boolean correctForServerTime = true; 99 private boolean useP4Email = true; 100 101 private final SimpleDateFormat p4RevisionDateFormatter = 102 new SimpleDateFormat ("yyyy/MM/dd:HH:mm:ss"); 103 104 105 private static final String SERVER_DATE = "Server date: "; 106 private static final String P4_SERVER_DATE_FORMAT = "yyyy/MM/dd HH:mm:ss"; 107 108 private final SimpleDateFormat p4ServerDateFormatter = 109 new SimpleDateFormat (P4_SERVER_DATE_FORMAT); 110 111 112 private Hashtable properties = new Hashtable (); 113 114 public void setPort(String p4Port) { 115 this.p4Port = p4Port; 116 } 117 118 public void setClient(String p4Client) { 119 this.p4Client = p4Client; 120 } 121 122 public void setUser(String p4User) { 123 this.p4User = p4User; 124 } 125 126 public void setView(String p4View) { 127 this.p4View = p4View; 128 } 129 130 public void setPasswd(String p4Passwd) { 131 this.p4Passwd = p4Passwd; 132 } 133 134 140 public void setCorrectForServerTime(boolean flag) { 141 this.correctForServerTime = flag; 142 } 143 144 149 public void setUseP4Email(boolean flag) { 150 useP4Email = flag; 151 } 152 153 public Map getProperties() { 154 return properties; 155 } 156 157 public void validate() throws CruiseControlException { 158 ValidationHelper.assertIsSet(p4Port, "port", this.getClass()); 159 ValidationHelper.assertIsSet(p4Client, "client", this.getClass()); 160 ValidationHelper.assertIsSet(p4User, "user", this.getClass()); 161 ValidationHelper.assertIsSet(p4View, "view", this.getClass()); 162 ValidationHelper.assertNotEmpty(p4Passwd, "passwd", this.getClass()); 163 } 164 165 176 public List getModifications(Date lastBuild, Date now) { 177 List mods = new ArrayList (); 178 try { 179 String [] changelistNumbers = collectChangelistSinceLastBuild(lastBuild, now); 180 if (changelistNumbers.length == 0) { 181 return mods; 182 } 183 mods = describeAllChangelistsAndBuildOutput(changelistNumbers); 184 } catch (Exception e) { 185 LOG.error("Log command failed to execute succesfully", e); 186 } 187 188 return mods; 189 } 190 191 private List describeAllChangelistsAndBuildOutput(String [] changelistNumbers) 192 throws IOException , InterruptedException { 193 194 Commandline command = buildDescribeCommand(changelistNumbers); 195 LOG.info(command.toString()); 196 Process p = Runtime.getRuntime().exec(command.getCommandline()); 197 198 logErrorStream(p.getErrorStream()); 199 InputStream p4Stream = p.getInputStream(); 200 List mods = parseChangeDescriptions(p4Stream); 201 getRidOfLeftoverData(p4Stream); 202 203 if ((mods.size() > 0) && useP4Email) { 205 getEmailAddresses(mods); 206 } 207 208 p.waitFor(); 209 p.getInputStream().close(); 210 p.getOutputStream().close(); 211 p.getErrorStream().close(); 212 213 return mods; 214 } 215 216 223 private void getEmailAddresses(List mods) throws IOException , 224 InterruptedException { 225 Iterator iter = mods.iterator(); 226 Map users = new HashMap (); 227 228 while (iter.hasNext()) { 229 P4Modification change = (P4Modification) iter.next(); 230 231 if ((change.userName != null) && (change.userName.length() > 0)) { 232 change.emailAddress = (String ) users.get(change.userName); 233 234 if (change.emailAddress == null) { 235 change.emailAddress = getUserEmailAddress(change.userName); 236 users.put(change.userName, change.emailAddress); 237 } 238 } 239 240 } 241 242 } 243 244 252 private String getUserEmailAddress(String username) throws IOException , 253 InterruptedException { 254 String emailaddr = null; 255 256 Commandline command = buildUserCommand(username); 257 LOG.info(command.toString()); 258 Process p = Runtime.getRuntime().exec(command.getCommandline()); 259 260 logErrorStream(p.getErrorStream()); 261 InputStream p4Stream = p.getInputStream(); 262 BufferedReader reader = new BufferedReader (new InputStreamReader ( 263 p4Stream)); 264 265 String line; 267 while ((line = readToNotPast(reader, "info: Email:", 268 "I really don't care")) != null) { 269 StringTokenizer st = new StringTokenizer (line); 270 271 try { 272 st.nextToken(); st.nextToken(); emailaddr = st.nextToken(); 275 } catch (NoSuchElementException ex) { 276 } 278 } 279 280 getRidOfLeftoverData(p4Stream); 281 282 p.waitFor(); 283 p.getInputStream().close(); 284 p.getOutputStream().close(); 285 p.getErrorStream().close(); 286 287 return (emailaddr); 288 } 289 290 private String [] collectChangelistSinceLastBuild(Date lastBuild, Date now) 291 throws IOException , InterruptedException { 292 293 Commandline command = buildChangesCommand(lastBuild, now, Util.isWindows()); 294 LOG.debug("Executing: " + command.toString()); 295 Process p = Runtime.getRuntime().exec(command.getCommandline()); 296 297 logErrorStream(p.getErrorStream()); 298 InputStream p4Stream = p.getInputStream(); 299 300 String [] changelistNumbers = parseChangelistNumbers(p4Stream); 301 302 getRidOfLeftoverData(p4Stream); 303 p.waitFor(); 304 p.getInputStream().close(); 305 p.getOutputStream().close(); 306 p.getErrorStream().close(); 307 308 return changelistNumbers; 309 } 310 311 private void getRidOfLeftoverData(InputStream stream) { 312 StreamPumper outPumper = new StreamPumper(stream, (PrintWriter ) null); 313 new Thread (outPumper).start(); 314 } 315 316 protected String [] parseChangelistNumbers(InputStream is) throws IOException { 317 ArrayList changelists = new ArrayList (); 318 319 BufferedReader reader = new BufferedReader (new InputStreamReader (is)); 320 String line; 321 while ((line = reader.readLine()) != null) { 322 if (line.startsWith("error:")) { 323 throw new IOException ("Error reading P4 stream: P4 says: " + line); 324 } else if (line.startsWith("exit: 1")) { 325 throw new IOException ("Error reading P4 stream: P4 says: " + line); 326 } else if (line.startsWith("exit: 0")) { 327 break; 328 } else if (line.startsWith("info:")) { 329 StringTokenizer st = new StringTokenizer (line); 330 st.nextToken(); st.nextToken(); changelists.add(st.nextToken()); 333 } 334 } 335 if (line == null) { 336 throw new IOException ("Error reading P4 stream: Unexpected EOF reached"); 337 } 338 String [] changelistNumbers = new String [ 0 ]; 339 return (String []) changelists.toArray(changelistNumbers); 340 } 341 342 protected List parseChangeDescriptions(InputStream is) throws IOException { 343 344 ArrayList changelists = new ArrayList (); 345 346 BufferedReader reader = new BufferedReader (new InputStreamReader (is)); 347 348 String line; 350 while ((line = readToNotPast(reader, "text: Change", "exit:")) != null) { 351 352 P4Modification changelist = new P4Modification(); 353 if (line.startsWith("error:")) { 354 throw new IOException ("Error reading P4 stream: P4 says: " + line); 355 } else if (line.startsWith("exit: 1")) { 356 throw new IOException ("Error reading P4 stream: P4 says: " + line); 357 } else if (line.startsWith("exit: 0")) { 358 return changelists; 359 } else if (line.startsWith("text: Change")) { 360 StringTokenizer st = new StringTokenizer (line); 361 362 st.nextToken(); st.nextToken(); changelist.revision = st.nextToken(); 365 st.nextToken(); 367 StringTokenizer st2 = new StringTokenizer (st.nextToken(), "@"); 369 changelist.userName = st2.nextToken(); 370 changelist.client = st2.nextToken(); 371 372 st.nextToken(); String date = st.nextToken() + ":" + st.nextToken(); 374 try { 375 changelist.modifiedTime = p4RevisionDateFormatter.parse(date); 376 } catch (ParseException xcp) { 377 changelist.modifiedTime = new Date (); 378 } 379 } 380 381 reader.readLine(); StringBuffer descriptionBuffer = new StringBuffer (); 383 String previousLine = null; 385 while ((line = reader.readLine()) != null 386 && line.startsWith("text:") 387 && !line.startsWith("text: Affected files ...")) { 388 389 if (previousLine != null) { 390 if (descriptionBuffer.length() > 0) { 391 descriptionBuffer.append('\n'); 392 } 393 descriptionBuffer.append(previousLine); 394 } 395 try { 396 previousLine = line.substring(5).trim(); 397 } catch (Exception e) { 398 LOG.error("Error parsing Perforce description, line that caused problem was: [" 399 + line 400 + "]"); 401 } 402 403 } 404 405 changelist.comment = descriptionBuffer.toString(); 406 407 if (line != null) { 409 reader.readLine(); while ((line = readToNotPast(reader, "info1:", "text:")) != null 411 && line.startsWith("info1:")) { 412 413 String fileName = line.substring(7, line.lastIndexOf("#")); 414 Modification.ModifiedFile affectedFile = changelist.createModifiedFile(fileName, null); 415 affectedFile.action = line.substring(line.lastIndexOf(" ") + 1); 416 affectedFile.revision = 417 line.substring(line.lastIndexOf("#") + 1, line.lastIndexOf(" ")); 418 } 419 } 420 changelists.add(changelist); 421 } 422 423 return changelists; 424 } 425 426 private void logErrorStream(InputStream is) { 427 StreamPumper errorPumper = new StreamPumper(is, new PrintWriter (System.err, true)); 428 new Thread (errorPumper).start(); 429 } 430 431 434 public Commandline buildChangesCommand(Date lastBuildTime, Date now, boolean isWindows) { 435 436 if (this.correctForServerTime) { 439 try { 440 int offset = (int) calculateServerTimeOffset(); 441 Calendar cal = Calendar.getInstance(); 442 443 cal.setTime(lastBuildTime); 444 cal.add(Calendar.MILLISECOND, offset); 445 lastBuildTime = cal.getTime(); 446 447 cal.setTime(now); 448 cal.add(Calendar.MILLISECOND, offset); 449 now = cal.getTime(); 450 451 } catch (IOException ioe) { 452 LOG.error("Unable to execute \'p4 info\' to get server time: " 453 + ioe.getMessage() 454 + "\nProceeding without time offset value."); 455 } 456 } else { 457 LOG.debug("No server time offset determined."); 458 } 459 460 461 Commandline commandLine = buildBaseP4Command(); 462 463 commandLine.createArgument().setValue("changes"); 464 commandLine.createArgument().setValue("-s"); 465 commandLine.createArgument().setValue("submitted"); 466 commandLine.createArgument().setValue(p4View 467 + "@" 468 + p4RevisionDateFormatter.format(lastBuildTime) 469 + ",@" 470 + p4RevisionDateFormatter.format(now)); 471 472 return commandLine; 473 } 474 475 478 public Commandline buildDescribeCommand(String [] changelistNumbers) { 479 Commandline commandLine = buildBaseP4Command(); 480 481 483 commandLine.createArgument().setValue("describe"); 484 commandLine.createArgument().setValue("-s"); 485 486 for (int i = 0; i < changelistNumbers.length; i++) { 487 commandLine.createArgument().setValue(changelistNumbers[i]); 488 } 489 490 return commandLine; 491 } 492 493 496 public Commandline buildUserCommand(String username) { 497 Commandline commandLine = buildBaseP4Command(); 498 commandLine.createArgument().setValue("user"); 499 commandLine.createArgument().setValue("-o"); 500 commandLine.createArgument().setValue(username); 501 502 return commandLine; 503 } 504 505 513 protected long calculateServerTimeOffset() throws IOException { 514 Commandline command = new Commandline(); 515 command.setExecutable("p4"); 516 command.createArgument().setValue("info"); 517 518 LOG.debug("Executing: " + command.toString()); 519 LOG.info(command.toString()); 520 Process p = Runtime.getRuntime().exec(command.getCommandline()); 521 logErrorStream(p.getErrorStream()); 522 523 return parseServerInfo(p.getInputStream()); 524 } 525 526 protected long parseServerInfo(InputStream is) throws IOException { 527 528 BufferedReader p4reader = new BufferedReader (new InputStreamReader (is)); 529 530 Date ccServerTime = new Date (); 531 Date p4ServerTime; 532 533 String line; 534 long offset = 0; 535 while ((line = p4reader.readLine()) != null && offset == 0) { 536 if (line.startsWith(SERVER_DATE)) { 537 try { 538 String dateString = line.substring(SERVER_DATE.length(), 539 SERVER_DATE.length() + P4_SERVER_DATE_FORMAT.length()); 540 p4ServerTime = p4ServerDateFormatter.parse(dateString); 541 offset = p4ServerTime.getTime() - ccServerTime.getTime(); 542 } catch (ParseException pe) { 543 LOG.error("Unable to parse p4 server time from line \'" 544 + line 545 + "\'. " + pe.getMessage() 546 + "; Proceeding without time offset."); 547 } 548 } 549 } 550 551 LOG.info("Perforce server time offset: " + offset + " ms"); 552 return offset; 553 } 554 555 556 private Commandline buildBaseP4Command() { 557 Commandline commandLine = new Commandline(); 558 commandLine.setExecutable("p4"); 559 commandLine.createArgument().setValue("-s"); 560 561 if (p4Client != null) { 562 commandLine.createArgument().setValue("-c"); 563 commandLine.createArgument().setValue(p4Client); 564 } 565 566 if (p4Port != null) { 567 commandLine.createArgument().setValue("-p"); 568 commandLine.createArgument().setValue(p4Port); 569 } 570 571 if (p4User != null) { 572 commandLine.createArgument().setValue("-u"); 573 commandLine.createArgument().setValue(p4User); 574 } 575 576 if (p4Passwd != null) { 577 commandLine.createArgument().setValue("-P"); 578 commandLine.createArgument().setValue(p4Passwd); 579 } 580 return commandLine; 581 } 582 583 589 private String readToNotPast(BufferedReader reader, String beginsWith, String notPast) 590 throws IOException { 591 592 String nextLine = reader.readLine(); 593 594 while (!(nextLine == null 597 || nextLine.startsWith(beginsWith) 598 || nextLine.startsWith(notPast))) { 599 nextLine = reader.readLine(); 600 } 601 return nextLine; 602 } 603 604 private static class P4Modification extends Modification { 605 public String client; 606 607 public int compareTo(Object o) { 608 P4Modification modification = (P4Modification) o; 609 return getChangelistNumber() - modification.getChangelistNumber(); 610 } 611 612 public boolean equals(Object o) { 613 if (o == null || !(o instanceof P4Modification)) { 614 return false; 615 } 616 617 P4Modification modification = (P4Modification) o; 618 return getChangelistNumber() == modification.getChangelistNumber(); 619 } 620 621 public int hashCode() { 622 return getChangelistNumber(); 623 } 624 625 private int getChangelistNumber() { 626 return Integer.parseInt(revision); 627 } 628 629 P4Modification() { 630 super("p4"); 631 } 632 633 public Element toElement(DateFormat format) { 634 635 Element element = super.toElement(format); 636 LOG.debug("client = " + client); 637 638 Element clientElement = new Element("client"); 639 clientElement.addContent(client); 640 element.addContent(clientElement); 641 642 return element; 643 } 644 } 645 646 static String getQuoteChar(boolean isWindows) { 647 return isWindows ? "\"" : "'"; 648 } 649 } 650 | Popular Tags |