001package org.apache.maven.scm.provider.integrity; 002 003/** 004 * Licensed to the Apache Software Foundation (ASF) under one 005 * or more contributor license agreements. See the NOTICE file 006 * distributed with this work for additional information 007 * regarding copyright ownership. The ASF licenses this file 008 * to you under the Apache License, Version 2.0 (the 009 * "License"); you may not use this file except in compliance 010 * with the License. You may obtain a copy of the License at 011 * 012 * http://www.apache.org/licenses/LICENSE-2.0 013 * 014 * Unless required by applicable law or agreed to in writing, 015 * software distributed under the License is distributed on an 016 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 017 * KIND, either express or implied. See the License for the 018 * specific language governing permissions and limitations 019 * under the License. 020 */ 021 022import com.mks.api.Command; 023import com.mks.api.MultiValue; 024import com.mks.api.Option; 025import com.mks.api.response.APIException; 026import com.mks.api.response.Field; 027import com.mks.api.response.Item; 028import com.mks.api.response.Response; 029import com.mks.api.response.WorkItem; 030import com.mks.api.response.WorkItemIterator; 031import com.mks.api.si.SIModelTypeName; 032import org.apache.maven.scm.ChangeFile; 033import org.apache.maven.scm.ChangeSet; 034import org.apache.maven.scm.ScmFile; 035import org.apache.maven.scm.ScmFileStatus; 036import org.apache.maven.scm.command.changelog.ChangeLogSet; 037import org.codehaus.plexus.util.StringUtils; 038 039import java.io.File; 040import java.text.SimpleDateFormat; 041import java.util.ArrayList; 042import java.util.Date; 043import java.util.Hashtable; 044import java.util.Iterator; 045import java.util.List; 046 047/** 048 * This class represents an MKS Integrity Sandbox and provides an encapsulation 049 * for executing typical Sandbox operations 050 * 051 * @author <a href="mailto:cletus@mks.com">Cletus D'Souza</a> 052 * @version $Id: Sandbox.java 1.11 2011/08/22 13:06:50EDT Cletus D'Souza (dsouza) Exp $ 053 * @since 1.6 054 */ 055public class Sandbox 056{ 057 // Our date format 058 public static final SimpleDateFormat RLOG_DATEFORMAT = new SimpleDateFormat( "MMMMM d, yyyy - h:mm:ss a" ); 059 060 // File Separator 061 private String fs = System.getProperty( "file.separator" ); 062 063 // MKS API Session Object 064 private APISession api; 065 066 // Other sandbox specific class variables 067 private Project siProject; 068 069 private String sandboxDir; 070 071 private String cpid; 072 073 // Flag to indicate the overall add operation was successful 074 private boolean addSuccess; 075 076 // Flag to indicate the overall check-in operation was successful 077 private boolean ciSuccess; 078 079 /** 080 * Fixes the default includes/excludes patterns for compatibility with MKS Integrity's 'si viewnonmembers' command 081 * 082 * @param pattern String pattern representing the includes/excludes file/directory list 083 */ 084 public static String formatFilePatterns( String pattern ) 085 { 086 StringBuilder sb = new StringBuilder(); 087 if ( null != pattern && pattern.length() > 0 ) 088 { 089 String[] tokens = StringUtils.split( pattern, "," ); 090 for ( int i = 0; i < tokens.length; i++ ) 091 { 092 String tkn = tokens[i].trim(); 093 if ( tkn.indexOf( "file:" ) != 0 && tkn.indexOf( "dir:" ) != 0 ) 094 { 095 sb.append( tkn.indexOf( '.' ) > 0 096 ? StringUtils.replaceOnce( tkn, "**/", "file:" ) 097 : StringUtils.replaceOnce( tkn, "**/", "dir:" ) ); 098 } 099 else 100 { 101 sb.append( tkn ); 102 } 103 sb.append( i < tokens.length ? "," : "" ); 104 } 105 } 106 return sb.toString(); 107 } 108 109 /** 110 * The Sandbox constructor 111 * 112 * @param api MKS API Session object 113 * @param cmProject Project object 114 * @param dir Absolute path to the location for the Sandbox directory 115 */ 116 public Sandbox( APISession api, Project cmProject, String dir ) 117 { 118 siProject = cmProject; 119 sandboxDir = dir; 120 this.api = api; 121 cpid = System.getProperty( "maven.scm.integrity.cpid" ); 122 cpid = ( ( null == cpid || cpid.length() == 0 ) ? ":none" : cpid ); 123 addSuccess = true; 124 ciSuccess = true; 125 } 126 127 /** 128 * Attempts to figure out if the current sandbox already exists and is valid 129 * 130 * @param sandbox The client-side fully qualified path to the sandbox pj 131 * @return true/false depending on whether or not this location has a valid sandbox 132 * @throws APIException 133 */ 134 private boolean isValidSandbox( String sandbox ) 135 throws APIException 136 { 137 Command cmd = new Command( Command.SI, "sandboxinfo" ); 138 cmd.addOption( new Option( "sandbox", sandbox ) ); 139 140 api.getLogger().debug( "Validating existing sandbox: " + sandbox ); 141 Response res = api.runCommand( cmd ); 142 WorkItemIterator wit = res.getWorkItems(); 143 try 144 { 145 WorkItem wi = wit.next(); 146 return wi.getField( "fullConfigSyntax" ).getValueAsString().equalsIgnoreCase( 147 siProject.getConfigurationPath() ); 148 } 149 catch ( APIException aex ) 150 { 151 ExceptionHandler eh = new ExceptionHandler( aex ); 152 api.getLogger().error( "MKS API Exception: " + eh.getMessage() ); 153 api.getLogger().debug( eh.getCommand() + " completed with exit code " + eh.getExitCode() ); 154 return false; 155 } 156 } 157 158 /** 159 * Inspects the MKS API Response object's Item field to determine whether or nor a working file delta exists 160 * 161 * @param wfdelta MKS API Response object's Item representing the Working File Delta 162 * @return true if the working file is a delta; false otherwise 163 */ 164 private boolean isDelta( Item wfdelta ) 165 { 166 // Return false if there is no working file 167 if ( wfdelta.getField( "isDelta" ).getBoolean().booleanValue() ) 168 { 169 return true; 170 } 171 else 172 { 173 return false; 174 } 175 } 176 177 /** 178 * Executes a 'si add' command using the message for the description 179 * 180 * @param memberFile Full path to the new member's location 181 * @param message Description for the new member's archive 182 * @return MKS API Response object 183 * @throws APIException 184 */ 185 private Response add( File memberFile, String message ) 186 throws APIException 187 { 188 // Setup the add command 189 api.getLogger().info( "Adding member: " + memberFile.getAbsolutePath() ); 190 Command siAdd = new Command( Command.SI, "add" ); 191 siAdd.addOption( new Option( "onExistingArchive", "sharearchive" ) ); 192 siAdd.addOption( new Option( "cpid", cpid ) ); 193 if ( null != message && message.length() > 0 ) 194 { 195 siAdd.addOption( new Option( "description", message ) ); 196 } 197 siAdd.addOption( new Option( "cwd", memberFile.getParentFile().getAbsolutePath() ) ); 198 siAdd.addSelection( memberFile.getName() ); 199 return api.runCommand( siAdd ); 200 } 201 202 /** 203 * Executes a 'si ci' command using the relativeName for the member name and message for the description 204 * 205 * @param memberFile Full path to the member's current sandbox location 206 * @param relativeName Relative path from the nearest subproject or project 207 * @param message Description for checking in the new update 208 * @return MKS API Response object 209 * @throws APIException 210 */ 211 private Response checkin( File memberFile, String relativeName, String message ) 212 throws APIException 213 { 214 // Setup the check-in command 215 api.getLogger().info( "Checking in member: " + memberFile.getAbsolutePath() ); 216 Command sici = new Command( Command.SI, "ci" ); 217 sici.addOption( new Option( "cpid", cpid ) ); 218 if ( null != message && message.length() > 0 ) 219 { 220 sici.addOption( new Option( "description", message ) ); 221 } 222 sici.addOption( new Option( "cwd", memberFile.getParentFile().getAbsolutePath() ) ); 223 sici.addSelection( relativeName ); 224 return api.runCommand( sici ); 225 } 226 227 /** 228 * Executes a 'si drop' command using the relativeName for the member name 229 * 230 * @param memberFile Full path to the member's current sandbox location 231 * @param relativeName Relative path from the nearest subproject or project 232 * @return MKS API Response object 233 * @throws APIException 234 */ 235 private Response dropMember( File memberFile, String relativeName ) 236 throws APIException 237 { 238 // Setup the drop command 239 api.getLogger().info( "Dropping member " + memberFile.getAbsolutePath() ); 240 Command siDrop = new Command( Command.SI, "drop" ); 241 siDrop.addOption( new Option( "cwd", memberFile.getParentFile().getAbsolutePath() ) ); 242 siDrop.addOption( new Option( "noconfirm" ) ); 243 siDrop.addOption( new Option( "cpid", cpid ) ); 244 siDrop.addOption( new Option( "delete" ) ); 245 siDrop.addSelection( relativeName ); 246 return api.runCommand( siDrop ); 247 } 248 249 /** 250 * Executes a 'si diff' command to see if the working file has actually changed. Even though the 251 * working file delta might be true, that doesn't always mean the file has actually changed. 252 * 253 * @param memberFile Full path to the member's current sandbox location 254 * @param relativeName Relative path from the nearest subproject or project 255 * @return MKS API Response object 256 */ 257 private boolean hasMemberChanged( File memberFile, String relativeName ) 258 { 259 // Setup the differences command 260 Command siDiff = new Command( Command.SI, "diff" ); 261 siDiff.addOption( new Option( "cwd", memberFile.getParentFile().getAbsolutePath() ) ); 262 siDiff.addSelection( relativeName ); 263 try 264 { 265 // Run the diff command... 266 Response res = api.runCommand( siDiff ); 267 try 268 { 269 // Return the changed flag... 270 return res.getWorkItems().next().getResult().getField( "resultant" ).getItem().getField( 271 "different" ).getBoolean().booleanValue(); 272 } 273 catch ( NullPointerException npe ) 274 { 275 api.getLogger().warn( "Couldn't figure out differences for file: " + memberFile.getAbsolutePath() ); 276 api.getLogger().warn( 277 "Null value found along response object for WorkItem/Result/Field/Item/Field.getBoolean()" ); 278 api.getLogger().warn( "Proceeding with the assumption that the file has changed!" ); 279 } 280 } 281 catch ( APIException aex ) 282 { 283 ExceptionHandler eh = new ExceptionHandler( aex ); 284 api.getLogger().warn( "Couldn't figure out differences for file: " + memberFile.getAbsolutePath() ); 285 api.getLogger().warn( eh.getMessage() ); 286 api.getLogger().warn( "Proceeding with the assumption that the file has changed!" ); 287 api.getLogger().debug( eh.getCommand() + " completed with exit Code " + eh.getExitCode() ); 288 } 289 return true; 290 } 291 292 /** 293 * Returns the full path name to the current Sandbox directory 294 * 295 * @return 296 */ 297 public String getSandboxDir() 298 { 299 return sandboxDir; 300 } 301 302 /** 303 * Executes a 'si lock' command using the relativeName of the file 304 * 305 * @param memberFile Full path to the member's current sandbox location 306 * @param relativeName Relative path from the nearest subproject or project 307 * @return MKS API Response object 308 * @throws APIException 309 */ 310 public Response lock( File memberFile, String relativeName ) 311 throws APIException 312 { 313 // Setup the lock command 314 api.getLogger().debug( "Locking member: " + memberFile.getAbsolutePath() ); 315 Command siLock = new Command( Command.SI, "lock" ); 316 siLock.addOption( new Option( "revision", ":member" ) ); 317 siLock.addOption( new Option( "cpid", cpid ) ); 318 siLock.addOption( new Option( "cwd", memberFile.getParentFile().getAbsolutePath() ) ); 319 siLock.addSelection( relativeName ); 320 // Execute the lock command 321 return api.runCommand( siLock ); 322 } 323 324 /** 325 * Executes a 'si unlock' command using the relativeName of the file 326 * 327 * @param memberFile Full path to the member's current sandbox location 328 * @param relativeName Relative path from the nearest subproject or project 329 * @return MKS API Response object 330 * @throws APIException 331 */ 332 public Response unlock( File memberFile, String relativeName ) 333 throws APIException 334 { 335 // Setup the unlock command 336 api.getLogger().debug( "Unlocking member: " + memberFile.getAbsolutePath() ); 337 Command siUnlock = new Command( Command.SI, "unlock" ); 338 siUnlock.addOption( new Option( "revision", ":member" ) ); 339 siUnlock.addOption( new Option( "action", "remove" ) ); 340 siUnlock.addOption( new Option( "cwd", memberFile.getParentFile().getAbsolutePath() ) ); 341 siUnlock.addSelection( relativeName ); 342 // Execute the unlock command 343 return api.runCommand( siUnlock ); 344 } 345 346 /** 347 * Removes the registration for the Sandbox in the user's profile 348 * 349 * @return The API Response associated with executing this command 350 * @throws APIException 351 */ 352 public Response drop() 353 throws APIException 354 { 355 File project = new File( siProject.getProjectName() ); 356 File sandboxpj = new File( sandboxDir + fs + project.getName() ); 357 358 // Check to see if the sandbox file already exists and its OK to use 359 api.getLogger().debug( "Sandbox Project File: " + sandboxpj.getAbsolutePath() ); 360 Command cmd = new Command( Command.SI, "dropsandbox" ); 361 cmd.addOption( new Option( "delete", "members" ) ); 362 cmd.addOption( new Option( "sandbox", sandboxpj.getAbsolutePath() ) ); 363 cmd.addOption( new Option( "cwd", sandboxDir ) ); 364 return api.runCommand( cmd ); 365 } 366 367 /** 368 * Creates a new Sandbox in the sandboxDir specified 369 * 370 * @return true if the operation is successful; false otherwise 371 * @throws APIException 372 */ 373 public boolean create() 374 throws APIException 375 { 376 File project = new File( siProject.getProjectName() ); 377 File sandboxpj = new File( sandboxDir + fs + project.getName() ); 378 379 // Check to see if the sandbox file already exists and its OK to use 380 api.getLogger().debug( "Sandbox Project File: " + sandboxpj.getAbsolutePath() ); 381 if ( sandboxpj.isFile() ) 382 { 383 // Validate this sandbox 384 if ( isValidSandbox( sandboxpj.getAbsolutePath() ) ) 385 { 386 api.getLogger().debug( 387 "Reusing existing Sandbox in " + sandboxDir + " for project " + siProject.getConfigurationPath() ); 388 return true; 389 } 390 else 391 { 392 api.getLogger().error( 393 "An invalid Sandbox exists in " + sandboxDir + ". Please provide a different location!" ); 394 return false; 395 } 396 } 397 else // Create a new sandbox in the location specified 398 { 399 api.getLogger().debug( 400 "Creating Sandbox in " + sandboxDir + " for project " + siProject.getConfigurationPath() ); 401 try 402 { 403 Command cmd = new Command( Command.SI, "createsandbox" ); 404 cmd.addOption( new Option( "recurse" ) ); 405 cmd.addOption( new Option( "nopopulate" ) ); 406 cmd.addOption( new Option( "project", siProject.getConfigurationPath() ) ); 407 cmd.addOption( new Option( "cwd", sandboxDir ) ); 408 api.runCommand( cmd ); 409 } 410 catch ( APIException aex ) 411 { 412 // Check to see if this exception is due an existing sandbox registry entry 413 ExceptionHandler eh = new ExceptionHandler( aex ); 414 if ( eh.getMessage().indexOf( "There is already a registered entry" ) > 0 ) 415 { 416 // This will re-validate the sandbox, if Maven blew away the old directory 417 return create(); 418 } 419 else 420 { 421 throw aex; 422 } 423 } 424 return true; 425 } 426 } 427 428 /** 429 * Resynchronizes an existing Sandbox 430 * Assumes that the create() call has already been made to validate this sandbox 431 * 432 * @throws APIException 433 */ 434 public Response resync() 435 throws APIException 436 { 437 api.getLogger().debug( 438 "Resynchronizing Sandbox in " + sandboxDir + " for project " + siProject.getConfigurationPath() ); 439 Command cmd = new Command( Command.SI, "resync" ); 440 cmd.addOption( new Option( "recurse" ) ); 441 cmd.addOption( new Option( "populate" ) ); 442 cmd.addOption( new Option( "cwd", sandboxDir ) ); 443 return api.runCommand( cmd ); 444 } 445 446 /** 447 * Executes a 'si makewritable' command to allow edits to all files in the Sandbox directory 448 * 449 * @return MKS API Response object 450 * @throws APIException 451 */ 452 public Response makeWriteable() 453 throws APIException 454 { 455 api.getLogger().debug( 456 "Setting files to writeable in " + sandboxDir + " for project " + siProject.getConfigurationPath() ); 457 Command cmd = new Command( Command.SI, "makewritable" ); 458 cmd.addOption( new Option( "recurse" ) ); 459 cmd.addOption( new Option( "cwd", sandboxDir ) ); 460 return api.runCommand( cmd ); 461 } 462 463 /** 464 * Executes a 'si revert' command to roll back changes to all files in the Sandbox directory 465 * 466 * @return MKS API Response object 467 * @throws APIException 468 */ 469 public Response revertMembers() 470 throws APIException 471 { 472 api.getLogger().debug( 473 "Reverting changes in sandbox " + sandboxDir + " for project " + siProject.getConfigurationPath() ); 474 Command cmd = new Command( Command.SI, "revert" ); 475 cmd.addOption( new Option( "recurse" ) ); 476 cmd.addOption( new Option( "cwd", sandboxDir ) ); 477 return api.runCommand( cmd ); 478 } 479 480 /** 481 * Executes a 'si viewnonmembers' command filtering the results using the exclude and include lists 482 * 483 * @param exclude Pattern containing the exclude file list 484 * @param include Pattern containing the include file list 485 * @return List of ScmFile objects representing the new files in the Sandbox 486 * @throws APIException 487 */ 488 public List<ScmFile> getNewMembers( String exclude, String include ) 489 throws APIException 490 { 491 // Store a list of files that were added to the repository 492 List<ScmFile> filesAdded = new ArrayList<ScmFile>(); 493 Command siViewNonMem = new Command( Command.SI, "viewnonmembers" ); 494 siViewNonMem.addOption( new Option( "recurse" ) ); 495 if ( null != exclude && exclude.length() > 0 ) 496 { 497 siViewNonMem.addOption( new Option( "exclude", exclude ) ); 498 } 499 if ( null != include && include.length() > 0 ) 500 { 501 siViewNonMem.addOption( new Option( "include", include ) ); 502 } 503 siViewNonMem.addOption( new Option( "noincludeFormers" ) ); 504 siViewNonMem.addOption( new Option( "cwd", sandboxDir ) ); 505 Response response = api.runCommand( siViewNonMem ); 506 for ( WorkItemIterator wit = response.getWorkItems(); wit.hasNext(); ) 507 { 508 filesAdded.add( 509 new ScmFile( wit.next().getField( "absolutepath" ).getValueAsString(), ScmFileStatus.ADDED ) ); 510 } 511 return filesAdded; 512 513 } 514 515 /** 516 * Adds a list of files to the MKS Integrity SCM Project 517 * 518 * @param exclude Pattern containing the exclude file list 519 * @param include Pattern containing the include file list 520 * @param message Description for the member's archive 521 * @return 522 */ 523 public List<ScmFile> addNonMembers( String exclude, String include, String message ) 524 { 525 // Re-initialize the overall addSuccess to be true for now 526 addSuccess = true; 527 // Store a list of files that were actually added to the repository 528 List<ScmFile> filesAdded = new ArrayList<ScmFile>(); 529 api.getLogger().debug( "Looking for new members in sandbox dir: " + sandboxDir ); 530 try 531 { 532 List<ScmFile> newFileList = getNewMembers( exclude, include ); 533 for ( Iterator<ScmFile> sit = newFileList.iterator(); sit.hasNext(); ) 534 { 535 try 536 { 537 ScmFile localFile = sit.next(); 538 // Attempt to add the file to the Integrity repository 539 add( new File( localFile.getPath() ), message ); 540 // If it was a success, then add it to the list of files that were actually added 541 filesAdded.add( localFile ); 542 } 543 catch ( APIException aex ) 544 { 545 // Set the addSuccess to false, since we ran into a problem 546 addSuccess = false; 547 ExceptionHandler eh = new ExceptionHandler( aex ); 548 api.getLogger().error( "MKS API Exception: " + eh.getMessage() ); 549 api.getLogger().debug( eh.getCommand() + " completed with exit Code " + eh.getExitCode() ); 550 } 551 } 552 } 553 catch ( APIException aex ) 554 { 555 // Set the addSuccess to false, since we ran into a problem 556 addSuccess = false; 557 ExceptionHandler eh = new ExceptionHandler( aex ); 558 api.getLogger().error( "MKS API Exception: " + eh.getMessage() ); 559 api.getLogger().debug( eh.getCommand() + " completed with exit Code " + eh.getExitCode() ); 560 } 561 return filesAdded; 562 } 563 564 /** 565 * Returns the overall success of the add operation 566 * 567 * @return 568 */ 569 public boolean getOverallAddSuccess() 570 { 571 return addSuccess; 572 } 573 574 /** 575 * Inspects the MKS API Response object's Item field to determine whether or nor a working file exists 576 * 577 * @param wfdelta MKS API Response object's Item representing the Working File Delta 578 * @return 579 */ 580 public boolean hasWorkingFile( Item wfdelta ) 581 { 582 // Return false if there is no working file 583 if ( wfdelta.getField( "noWorkingFile" ).getBoolean().booleanValue() ) 584 { 585 return false; 586 } 587 else 588 { 589 return true; 590 } 591 } 592 593 /** 594 * Executes a 'si viewsandbox' and parses the output for changed or dropped working files 595 * 596 * @return A list of MKS API Response WorkItem objects representing the changes in the Sandbox 597 * @throws APIException 598 */ 599 public List<WorkItem> getChangeList() 600 throws APIException 601 { 602 // Store a list of files that were changed/removed to the repository 603 List<WorkItem> changedFiles = new ArrayList<WorkItem>(); 604 // Setup the view sandbox command to figure out what has changed... 605 Command siViewSandbox = new Command( Command.SI, "viewsandbox" ); 606 // Create the --fields option 607 MultiValue mv = new MultiValue( "," ); 608 mv.add( "name" ); 609 mv.add( "context" ); 610 mv.add( "wfdelta" ); 611 mv.add( "memberarchive" ); 612 siViewSandbox.addOption( new Option( "fields", mv ) ); 613 siViewSandbox.addOption( new Option( "recurse" ) ); 614 siViewSandbox.addOption( new Option( "noincludeDropped" ) ); 615 siViewSandbox.addOption( new Option( "filterSubs" ) ); 616 siViewSandbox.addOption( new Option( "cwd", sandboxDir ) ); 617 618 // Run the view sandbox command 619 Response r = api.runCommand( siViewSandbox ); 620 // Check-in all changed files, drop all members with missing working files 621 for ( WorkItemIterator wit = r.getWorkItems(); wit.hasNext(); ) 622 { 623 WorkItem wi = wit.next(); 624 api.getLogger().debug( "Inspecting file: " + wi.getField( "name" ).getValueAsString() ); 625 626 if ( wi.getModelType().equals( SIModelTypeName.MEMBER ) ) 627 { 628 Item wfdeltaItem = (Item) wi.getField( "wfdelta" ).getValue(); 629 // Proceed with this entry only if it is an actual working file delta 630 if ( isDelta( wfdeltaItem ) ) 631 { 632 File memberFile = new File( wi.getField( "name" ).getValueAsString() ); 633 if ( hasWorkingFile( wfdeltaItem ) ) 634 { 635 // Only report on files that have actually changed... 636 if ( hasMemberChanged( memberFile, wi.getId() ) ) 637 { 638 changedFiles.add( wi ); 639 } 640 } 641 else 642 { 643 // Also report on dropped files 644 changedFiles.add( wi ); 645 } 646 } 647 } 648 } 649 return changedFiles; 650 } 651 652 /** 653 * Wrapper function to check-in all changes and drop members associated with missing working files 654 * 655 * @param message Description for the changes 656 * @return 657 */ 658 public List<ScmFile> checkInUpdates( String message ) 659 { 660 // Re-initialize the overall ciSuccess to be true for now 661 ciSuccess = true; 662 // Store a list of files that were changed/removed to the repository 663 List<ScmFile> changedFiles = new ArrayList<ScmFile>(); 664 api.getLogger().debug( "Looking for changed and dropped members in sandbox dir: " + sandboxDir ); 665 666 try 667 { 668 // Let the list of changed files 669 List<WorkItem> changeList = getChangeList(); 670 // Check-in all changed files, drop all members with missing working files 671 for ( Iterator<WorkItem> wit = changeList.iterator(); wit.hasNext(); ) 672 { 673 try 674 { 675 WorkItem wi = wit.next(); 676 File memberFile = new File( wi.getField( "name" ).getValueAsString() ); 677 // Check-in files that have actually changed... 678 if ( hasWorkingFile( (Item) wi.getField( "wfdelta" ).getValue() ) ) 679 { 680 // Lock each member as you go... 681 lock( memberFile, wi.getId() ); 682 // Commit the changes... 683 checkin( memberFile, wi.getId(), message ); 684 // Update the changed file list 685 changedFiles.add( new ScmFile( memberFile.getAbsolutePath(), ScmFileStatus.CHECKED_IN ) ); 686 } 687 else 688 { 689 // Drop the member if there is no working file 690 dropMember( memberFile, wi.getId() ); 691 // Update the changed file list 692 changedFiles.add( new ScmFile( memberFile.getAbsolutePath(), ScmFileStatus.DELETED ) ); 693 } 694 } 695 catch ( APIException aex ) 696 { 697 // Set the ciSuccess to false, since we ran into a problem 698 ciSuccess = false; 699 ExceptionHandler eh = new ExceptionHandler( aex ); 700 api.getLogger().error( "MKS API Exception: " + eh.getMessage() ); 701 api.getLogger().debug( eh.getCommand() + " completed with exit Code " + eh.getExitCode() ); 702 } 703 } 704 } 705 catch ( APIException aex ) 706 { 707 // Set the ciSuccess to false, since we ran into a problem 708 ciSuccess = false; 709 ExceptionHandler eh = new ExceptionHandler( aex ); 710 api.getLogger().error( "MKS API Exception: " + eh.getMessage() ); 711 api.getLogger().debug( eh.getCommand() + " completed with exit Code " + eh.getExitCode() ); 712 } 713 714 return changedFiles; 715 } 716 717 /** 718 * Returns the overall success of the check-in operation 719 * 720 * @return 721 */ 722 public boolean getOverallCheckInSuccess() 723 { 724 return ciSuccess; 725 } 726 727 /** 728 * Creates one subproject per directory, as required. 729 * 730 * @param dirPath A relative path structure of folders 731 * @return Response containing the result for the created subproject 732 * @throws APIException 733 */ 734 public Response createSubproject( String dirPath ) 735 throws APIException 736 { 737 // Setup the create subproject command 738 api.getLogger().debug( "Creating subprojects for: " + dirPath + "/project.pj" ); 739 Command siCreateSubproject = new Command( Command.SI, "createsubproject" ); 740 siCreateSubproject.addOption( new Option( "cpid", cpid ) ); 741 siCreateSubproject.addOption( new Option( "createSubprojects" ) ); 742 siCreateSubproject.addOption( new Option( "cwd", sandboxDir ) ); 743 siCreateSubproject.addSelection( dirPath + "/project.pj" ); 744 // Execute the create subproject command 745 return api.runCommand( siCreateSubproject ); 746 } 747 748 /** 749 * Executes the 'si rlog' command to generate a list of changed revision found between startDate and endDate 750 * 751 * @param startDate The date range for the beginning of the operation 752 * @param endDate The date range for the end of the operation 753 * @return ChangeLogSet containing a list of changes grouped by Change Pacakge ID 754 * @throws APIException 755 */ 756 public ChangeLogSet getChangeLog( Date startDate, Date endDate ) 757 throws APIException 758 { 759 // Initialize our return object 760 ChangeLogSet changeLog = new ChangeLogSet( startDate, endDate ); 761 // By default we're going to group-by change package 762 // Non change package changes will be lumped into one big Change Set 763 Hashtable<String, ChangeSet> changeSetHash = new Hashtable<String, ChangeSet>(); 764 765 // Lets prepare our si rlog command for execution 766 Command siRlog = new Command( Command.SI, "rlog" ); 767 siRlog.addOption( new Option( "recurse" ) ); 768 MultiValue rFilter = new MultiValue( ":" ); 769 rFilter.add( "daterange" ); 770 rFilter.add( "'" + RLOG_DATEFORMAT.format( startDate ) + "'-'" + RLOG_DATEFORMAT.format( endDate ) + "'" ); 771 siRlog.addOption( new Option( "rfilter", rFilter ) ); 772 siRlog.addOption( new Option( "cwd", sandboxDir ) ); 773 // Execute the si rlog command 774 Response response = api.runCommand( siRlog ); 775 for ( WorkItemIterator wit = response.getWorkItems(); wit.hasNext(); ) 776 { 777 WorkItem wi = wit.next(); 778 String memberName = wi.getContext(); 779 // We're going to have to do a little dance to get the correct server file name 780 memberName = memberName.substring( 0, memberName.lastIndexOf( '/' ) ); 781 memberName = memberName + '/' + wi.getId(); 782 memberName = memberName.replace( '\\', '/' ); 783 // Now lets get the revisions for this file 784 Field revisionsFld = wi.getField( "revisions" ); 785 if ( null != revisionsFld && revisionsFld.getDataType().equals( Field.ITEM_LIST_TYPE ) 786 && null != revisionsFld.getList() ) 787 { 788 @SuppressWarnings( "unchecked" ) List<Item> revList = revisionsFld.getList(); 789 for ( Iterator<Item> lit = revList.iterator(); lit.hasNext(); ) 790 { 791 Item revisionItem = lit.next(); 792 String revision = revisionItem.getId(); 793 String author = revisionItem.getField( "author" ).getItem().getId(); 794 // Attempt to get the full name, if available 795 try 796 { 797 author = revisionItem.getField( "author" ).getItem().getField( "fullname" ).getValueAsString(); 798 } 799 catch ( NullPointerException npe ) 800 { /* ignore */ } 801 String cpid = ":none"; 802 // Attempt to get the cpid for this revision 803 try 804 { 805 cpid = revisionItem.getField( "cpid" ).getItem().getId(); 806 } 807 catch ( NullPointerException npe ) 808 { /* ignore */ } 809 // Get the Change Package summary for this revision 810 String comment = cpid + ": " + revisionItem.getField( "cpsummary" ).getValueAsString(); 811 // Get the date associated with this revision 812 Date date = revisionItem.getField( "date" ).getDateTime(); 813 814 // Lets create our ChangeFile based on the information we've gathered so far 815 ChangeFile changeFile = new ChangeFile( memberName, revision ); 816 817 // Check to see if we already have a ChangeSet grouping for this revision 818 ChangeSet changeSet = changeSetHash.get( cpid ); 819 if ( null != changeSet ) 820 { 821 // Set the date of the ChangeSet to the oldest entry 822 if ( changeSet.getDate().after( date ) ) 823 { 824 changeSet.setDate( date ); 825 } 826 // Add the new ChangeFile 827 changeSet.addFile( changeFile ); 828 // Update the changeSetHash 829 changeSetHash.put( cpid, changeSet ); 830 } 831 else // Create a new ChangeSet grouping and add the ChangeFile 832 { 833 List<ChangeFile> changeFileList = new ArrayList<ChangeFile>(); 834 changeFileList.add( changeFile ); 835 changeSet = new ChangeSet( date, comment, author, changeFileList ); 836 // Update the changeSetHash with an initial entry for the cpid 837 changeSetHash.put( cpid, changeSet ); 838 } 839 } 840 } 841 842 } 843 844 // Update the Change Log with the Change Sets 845 List<ChangeSet> changeSetList = new ArrayList<ChangeSet>(); 846 changeSetList.addAll( changeSetHash.values() ); 847 changeLog.setChangeSets( changeSetList ); 848 849 return changeLog; 850 } 851}