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 return wfdelta.getField( "isDelta" ).getBoolean().booleanValue(); 168 } 169 170 /** 171 * Executes a 'si add' command using the message for the description 172 * 173 * @param memberFile Full path to the new member's location 174 * @param message Description for the new member's archive 175 * @return MKS API Response object 176 * @throws APIException 177 */ 178 private Response add( File memberFile, String message ) 179 throws APIException 180 { 181 // Setup the add command 182 api.getLogger().info( "Adding member: " + memberFile.getAbsolutePath() ); 183 Command siAdd = new Command( Command.SI, "add" ); 184 siAdd.addOption( new Option( "onExistingArchive", "sharearchive" ) ); 185 siAdd.addOption( new Option( "cpid", cpid ) ); 186 if ( null != message && message.length() > 0 ) 187 { 188 siAdd.addOption( new Option( "description", message ) ); 189 } 190 siAdd.addOption( new Option( "cwd", memberFile.getParentFile().getAbsolutePath() ) ); 191 siAdd.addSelection( memberFile.getName() ); 192 return api.runCommand( siAdd ); 193 } 194 195 /** 196 * Executes a 'si ci' command using the relativeName for the member name and message for the description 197 * 198 * @param memberFile Full path to the member's current sandbox location 199 * @param relativeName Relative path from the nearest subproject or project 200 * @param message Description for checking in the new update 201 * @return MKS API Response object 202 * @throws APIException 203 */ 204 private Response checkin( File memberFile, String relativeName, String message ) 205 throws APIException 206 { 207 // Setup the check-in command 208 api.getLogger().info( "Checking in member: " + memberFile.getAbsolutePath() ); 209 Command sici = new Command( Command.SI, "ci" ); 210 sici.addOption( new Option( "cpid", cpid ) ); 211 if ( null != message && message.length() > 0 ) 212 { 213 sici.addOption( new Option( "description", message ) ); 214 } 215 sici.addOption( new Option( "cwd", memberFile.getParentFile().getAbsolutePath() ) ); 216 sici.addSelection( relativeName ); 217 return api.runCommand( sici ); 218 } 219 220 /** 221 * Executes a 'si drop' command using the relativeName for the member name 222 * 223 * @param memberFile Full path to the member's current sandbox location 224 * @param relativeName Relative path from the nearest subproject or project 225 * @return MKS API Response object 226 * @throws APIException 227 */ 228 private Response dropMember( File memberFile, String relativeName ) 229 throws APIException 230 { 231 // Setup the drop command 232 api.getLogger().info( "Dropping member " + memberFile.getAbsolutePath() ); 233 Command siDrop = new Command( Command.SI, "drop" ); 234 siDrop.addOption( new Option( "cwd", memberFile.getParentFile().getAbsolutePath() ) ); 235 siDrop.addOption( new Option( "noconfirm" ) ); 236 siDrop.addOption( new Option( "cpid", cpid ) ); 237 siDrop.addOption( new Option( "delete" ) ); 238 siDrop.addSelection( relativeName ); 239 return api.runCommand( siDrop ); 240 } 241 242 /** 243 * Executes a 'si diff' command to see if the working file has actually changed. Even though the 244 * working file delta might be true, that doesn't always mean the file has actually changed. 245 * 246 * @param memberFile Full path to the member's current sandbox location 247 * @param relativeName Relative path from the nearest subproject or project 248 * @return MKS API Response object 249 */ 250 private boolean hasMemberChanged( File memberFile, String relativeName ) 251 { 252 // Setup the differences command 253 Command siDiff = new Command( Command.SI, "diff" ); 254 siDiff.addOption( new Option( "cwd", memberFile.getParentFile().getAbsolutePath() ) ); 255 siDiff.addSelection( relativeName ); 256 try 257 { 258 // Run the diff command... 259 Response res = api.runCommand( siDiff ); 260 try 261 { 262 // Return the changed flag... 263 return res.getWorkItems().next().getResult().getField( "resultant" ).getItem().getField( 264 "different" ).getBoolean().booleanValue(); 265 } 266 catch ( NullPointerException npe ) 267 { 268 api.getLogger().warn( "Couldn't figure out differences for file: " + memberFile.getAbsolutePath() ); 269 api.getLogger().warn( 270 "Null value found along response object for WorkItem/Result/Field/Item/Field.getBoolean()" ); 271 api.getLogger().warn( "Proceeding with the assumption that the file has changed!" ); 272 } 273 } 274 catch ( APIException aex ) 275 { 276 ExceptionHandler eh = new ExceptionHandler( aex ); 277 api.getLogger().warn( "Couldn't figure out differences for file: " + memberFile.getAbsolutePath() ); 278 api.getLogger().warn( eh.getMessage() ); 279 api.getLogger().warn( "Proceeding with the assumption that the file has changed!" ); 280 api.getLogger().debug( eh.getCommand() + " completed with exit Code " + eh.getExitCode() ); 281 } 282 return true; 283 } 284 285 /** 286 * Returns the full path name to the current Sandbox directory 287 * 288 * @return 289 */ 290 public String getSandboxDir() 291 { 292 return sandboxDir; 293 } 294 295 /** 296 * Executes a 'si lock' command using the relativeName of the file 297 * 298 * @param memberFile Full path to the member's current sandbox location 299 * @param relativeName Relative path from the nearest subproject or project 300 * @return MKS API Response object 301 * @throws APIException 302 */ 303 public Response lock( File memberFile, String relativeName ) 304 throws APIException 305 { 306 // Setup the lock command 307 api.getLogger().debug( "Locking member: " + memberFile.getAbsolutePath() ); 308 Command siLock = new Command( Command.SI, "lock" ); 309 siLock.addOption( new Option( "revision", ":member" ) ); 310 siLock.addOption( new Option( "cpid", cpid ) ); 311 siLock.addOption( new Option( "cwd", memberFile.getParentFile().getAbsolutePath() ) ); 312 siLock.addSelection( relativeName ); 313 // Execute the lock command 314 return api.runCommand( siLock ); 315 } 316 317 /** 318 * Executes a 'si unlock' command using the relativeName of the file 319 * 320 * @param memberFile Full path to the member's current sandbox location 321 * @param relativeName Relative path from the nearest subproject or project 322 * @return MKS API Response object 323 * @throws APIException 324 */ 325 public Response unlock( File memberFile, String relativeName ) 326 throws APIException 327 { 328 // Setup the unlock command 329 api.getLogger().debug( "Unlocking member: " + memberFile.getAbsolutePath() ); 330 Command siUnlock = new Command( Command.SI, "unlock" ); 331 siUnlock.addOption( new Option( "revision", ":member" ) ); 332 siUnlock.addOption( new Option( "action", "remove" ) ); 333 siUnlock.addOption( new Option( "cwd", memberFile.getParentFile().getAbsolutePath() ) ); 334 siUnlock.addSelection( relativeName ); 335 // Execute the unlock command 336 return api.runCommand( siUnlock ); 337 } 338 339 /** 340 * Removes the registration for the Sandbox in the user's profile 341 * 342 * @return The API Response associated with executing this command 343 * @throws APIException 344 */ 345 public Response drop() 346 throws APIException 347 { 348 File project = new File( siProject.getProjectName() ); 349 File sandboxpj = new File( sandboxDir + fs + project.getName() ); 350 351 // Check to see if the sandbox file already exists and its OK to use 352 api.getLogger().debug( "Sandbox Project File: " + sandboxpj.getAbsolutePath() ); 353 Command cmd = new Command( Command.SI, "dropsandbox" ); 354 cmd.addOption( new Option( "delete", "members" ) ); 355 cmd.addOption( new Option( "sandbox", sandboxpj.getAbsolutePath() ) ); 356 cmd.addOption( new Option( "cwd", sandboxDir ) ); 357 return api.runCommand( cmd ); 358 } 359 360 /** 361 * Creates a new Sandbox in the sandboxDir specified 362 * 363 * @return true if the operation is successful; false otherwise 364 * @throws APIException 365 */ 366 public boolean create() 367 throws APIException 368 { 369 File project = new File( siProject.getProjectName() ); 370 File sandboxpj = new File( sandboxDir + fs + project.getName() ); 371 372 // Check to see if the sandbox file already exists and its OK to use 373 api.getLogger().debug( "Sandbox Project File: " + sandboxpj.getAbsolutePath() ); 374 if ( sandboxpj.isFile() ) 375 { 376 // Validate this sandbox 377 if ( isValidSandbox( sandboxpj.getAbsolutePath() ) ) 378 { 379 api.getLogger().debug( 380 "Reusing existing Sandbox in " + sandboxDir + " for project " + siProject.getConfigurationPath() ); 381 return true; 382 } 383 else 384 { 385 api.getLogger().error( 386 "An invalid Sandbox exists in " + sandboxDir + ". Please provide a different location!" ); 387 return false; 388 } 389 } 390 else // Create a new sandbox in the location specified 391 { 392 api.getLogger().debug( 393 "Creating Sandbox in " + sandboxDir + " for project " + siProject.getConfigurationPath() ); 394 try 395 { 396 Command cmd = new Command( Command.SI, "createsandbox" ); 397 cmd.addOption( new Option( "recurse" ) ); 398 cmd.addOption( new Option( "nopopulate" ) ); 399 cmd.addOption( new Option( "project", siProject.getConfigurationPath() ) ); 400 cmd.addOption( new Option( "cwd", sandboxDir ) ); 401 api.runCommand( cmd ); 402 } 403 catch ( APIException aex ) 404 { 405 // Check to see if this exception is due an existing sandbox registry entry 406 ExceptionHandler eh = new ExceptionHandler( aex ); 407 if ( eh.getMessage().indexOf( "There is already a registered entry" ) > 0 ) 408 { 409 // This will re-validate the sandbox, if Maven blew away the old directory 410 return create(); 411 } 412 else 413 { 414 throw aex; 415 } 416 } 417 return true; 418 } 419 } 420 421 /** 422 * Resynchronizes an existing Sandbox 423 * Assumes that the create() call has already been made to validate this sandbox 424 * 425 * @throws APIException 426 */ 427 public Response resync() 428 throws APIException 429 { 430 api.getLogger().debug( 431 "Resynchronizing Sandbox in " + sandboxDir + " for project " + siProject.getConfigurationPath() ); 432 Command cmd = new Command( Command.SI, "resync" ); 433 cmd.addOption( new Option( "recurse" ) ); 434 cmd.addOption( new Option( "populate" ) ); 435 cmd.addOption( new Option( "cwd", sandboxDir ) ); 436 return api.runCommand( cmd ); 437 } 438 439 /** 440 * Executes a 'si makewritable' command to allow edits to all files in the Sandbox directory 441 * 442 * @return MKS API Response object 443 * @throws APIException 444 */ 445 public Response makeWriteable() 446 throws APIException 447 { 448 api.getLogger().debug( 449 "Setting files to writeable in " + sandboxDir + " for project " + siProject.getConfigurationPath() ); 450 Command cmd = new Command( Command.SI, "makewritable" ); 451 cmd.addOption( new Option( "recurse" ) ); 452 cmd.addOption( new Option( "cwd", sandboxDir ) ); 453 return api.runCommand( cmd ); 454 } 455 456 /** 457 * Executes a 'si revert' command to roll back changes to all files in the Sandbox directory 458 * 459 * @return MKS API Response object 460 * @throws APIException 461 */ 462 public Response revertMembers() 463 throws APIException 464 { 465 api.getLogger().debug( 466 "Reverting changes in sandbox " + sandboxDir + " for project " + siProject.getConfigurationPath() ); 467 Command cmd = new Command( Command.SI, "revert" ); 468 cmd.addOption( new Option( "recurse" ) ); 469 cmd.addOption( new Option( "cwd", sandboxDir ) ); 470 return api.runCommand( cmd ); 471 } 472 473 /** 474 * Executes a 'si viewnonmembers' command filtering the results using the exclude and include lists 475 * 476 * @param exclude Pattern containing the exclude file list 477 * @param include Pattern containing the include file list 478 * @return List of ScmFile objects representing the new files in the Sandbox 479 * @throws APIException 480 */ 481 public List<ScmFile> getNewMembers( String exclude, String include ) 482 throws APIException 483 { 484 // Store a list of files that were added to the repository 485 List<ScmFile> filesAdded = new ArrayList<ScmFile>(); 486 Command siViewNonMem = new Command( Command.SI, "viewnonmembers" ); 487 siViewNonMem.addOption( new Option( "recurse" ) ); 488 if ( null != exclude && exclude.length() > 0 ) 489 { 490 siViewNonMem.addOption( new Option( "exclude", exclude ) ); 491 } 492 if ( null != include && include.length() > 0 ) 493 { 494 siViewNonMem.addOption( new Option( "include", include ) ); 495 } 496 siViewNonMem.addOption( new Option( "noincludeFormers" ) ); 497 siViewNonMem.addOption( new Option( "cwd", sandboxDir ) ); 498 Response response = api.runCommand( siViewNonMem ); 499 for ( WorkItemIterator wit = response.getWorkItems(); wit.hasNext(); ) 500 { 501 filesAdded.add( 502 new ScmFile( wit.next().getField( "absolutepath" ).getValueAsString(), ScmFileStatus.ADDED ) ); 503 } 504 return filesAdded; 505 506 } 507 508 /** 509 * Adds a list of files to the MKS Integrity SCM Project 510 * 511 * @param exclude Pattern containing the exclude file list 512 * @param include Pattern containing the include file list 513 * @param message Description for the member's archive 514 * @return 515 */ 516 public List<ScmFile> addNonMembers( String exclude, String include, String message ) 517 { 518 // Re-initialize the overall addSuccess to be true for now 519 addSuccess = true; 520 // Store a list of files that were actually added to the repository 521 List<ScmFile> filesAdded = new ArrayList<ScmFile>(); 522 api.getLogger().debug( "Looking for new members in sandbox dir: " + sandboxDir ); 523 try 524 { 525 List<ScmFile> newFileList = getNewMembers( exclude, include ); 526 for ( Iterator<ScmFile> sit = newFileList.iterator(); sit.hasNext(); ) 527 { 528 try 529 { 530 ScmFile localFile = sit.next(); 531 // Attempt to add the file to the Integrity repository 532 add( new File( localFile.getPath() ), message ); 533 // If it was a success, then add it to the list of files that were actually added 534 filesAdded.add( localFile ); 535 } 536 catch ( APIException aex ) 537 { 538 // Set the addSuccess to false, since we ran into a problem 539 addSuccess = false; 540 ExceptionHandler eh = new ExceptionHandler( aex ); 541 api.getLogger().error( "MKS API Exception: " + eh.getMessage() ); 542 api.getLogger().debug( eh.getCommand() + " completed with exit Code " + eh.getExitCode() ); 543 } 544 } 545 } 546 catch ( APIException aex ) 547 { 548 // Set the addSuccess to false, since we ran into a problem 549 addSuccess = false; 550 ExceptionHandler eh = new ExceptionHandler( aex ); 551 api.getLogger().error( "MKS API Exception: " + eh.getMessage() ); 552 api.getLogger().debug( eh.getCommand() + " completed with exit Code " + eh.getExitCode() ); 553 } 554 return filesAdded; 555 } 556 557 /** 558 * Returns the overall success of the add operation 559 * 560 * @return 561 */ 562 public boolean getOverallAddSuccess() 563 { 564 return addSuccess; 565 } 566 567 /** 568 * Inspects the MKS API Response object's Item field to determine whether or nor a working file exists 569 * 570 * @param wfdelta MKS API Response object's Item representing the Working File Delta 571 * @return 572 */ 573 public boolean hasWorkingFile( Item wfdelta ) 574 { 575 // Return false if there is no working file 576 return !wfdelta.getField( "noWorkingFile" ).getBoolean().booleanValue(); 577 } 578 579 /** 580 * Executes a 'si viewsandbox' and parses the output for changed or dropped working files 581 * 582 * @return A list of MKS API Response WorkItem objects representing the changes in the Sandbox 583 * @throws APIException 584 */ 585 public List<WorkItem> getChangeList() 586 throws APIException 587 { 588 // Store a list of files that were changed/removed to the repository 589 List<WorkItem> changedFiles = new ArrayList<WorkItem>(); 590 // Setup the view sandbox command to figure out what has changed... 591 Command siViewSandbox = new Command( Command.SI, "viewsandbox" ); 592 // Create the --fields option 593 MultiValue mv = new MultiValue( "," ); 594 mv.add( "name" ); 595 mv.add( "context" ); 596 mv.add( "wfdelta" ); 597 mv.add( "memberarchive" ); 598 siViewSandbox.addOption( new Option( "fields", mv ) ); 599 siViewSandbox.addOption( new Option( "recurse" ) ); 600 siViewSandbox.addOption( new Option( "noincludeDropped" ) ); 601 siViewSandbox.addOption( new Option( "filterSubs" ) ); 602 siViewSandbox.addOption( new Option( "cwd", sandboxDir ) ); 603 604 // Run the view sandbox command 605 Response r = api.runCommand( siViewSandbox ); 606 // Check-in all changed files, drop all members with missing working files 607 for ( WorkItemIterator wit = r.getWorkItems(); wit.hasNext(); ) 608 { 609 WorkItem wi = wit.next(); 610 api.getLogger().debug( "Inspecting file: " + wi.getField( "name" ).getValueAsString() ); 611 612 if ( wi.getModelType().equals( SIModelTypeName.MEMBER ) ) 613 { 614 Item wfdeltaItem = (Item) wi.getField( "wfdelta" ).getValue(); 615 // Proceed with this entry only if it is an actual working file delta 616 if ( isDelta( wfdeltaItem ) ) 617 { 618 File memberFile = new File( wi.getField( "name" ).getValueAsString() ); 619 if ( hasWorkingFile( wfdeltaItem ) ) 620 { 621 // Only report on files that have actually changed... 622 if ( hasMemberChanged( memberFile, wi.getId() ) ) 623 { 624 changedFiles.add( wi ); 625 } 626 } 627 else 628 { 629 // Also report on dropped files 630 changedFiles.add( wi ); 631 } 632 } 633 } 634 } 635 return changedFiles; 636 } 637 638 /** 639 * Wrapper function to check-in all changes and drop members associated with missing working files 640 * 641 * @param message Description for the changes 642 * @return 643 */ 644 public List<ScmFile> checkInUpdates( String message ) 645 { 646 // Re-initialize the overall ciSuccess to be true for now 647 ciSuccess = true; 648 // Store a list of files that were changed/removed to the repository 649 List<ScmFile> changedFiles = new ArrayList<ScmFile>(); 650 api.getLogger().debug( "Looking for changed and dropped members in sandbox dir: " + sandboxDir ); 651 652 try 653 { 654 // Let the list of changed files 655 List<WorkItem> changeList = getChangeList(); 656 // Check-in all changed files, drop all members with missing working files 657 for ( Iterator<WorkItem> wit = changeList.iterator(); wit.hasNext(); ) 658 { 659 try 660 { 661 WorkItem wi = wit.next(); 662 File memberFile = new File( wi.getField( "name" ).getValueAsString() ); 663 // Check-in files that have actually changed... 664 if ( hasWorkingFile( (Item) wi.getField( "wfdelta" ).getValue() ) ) 665 { 666 // Lock each member as you go... 667 lock( memberFile, wi.getId() ); 668 // Commit the changes... 669 checkin( memberFile, wi.getId(), message ); 670 // Update the changed file list 671 changedFiles.add( new ScmFile( memberFile.getAbsolutePath(), ScmFileStatus.CHECKED_IN ) ); 672 } 673 else 674 { 675 // Drop the member if there is no working file 676 dropMember( memberFile, wi.getId() ); 677 // Update the changed file list 678 changedFiles.add( new ScmFile( memberFile.getAbsolutePath(), ScmFileStatus.DELETED ) ); 679 } 680 } 681 catch ( APIException aex ) 682 { 683 // Set the ciSuccess to false, since we ran into a problem 684 ciSuccess = false; 685 ExceptionHandler eh = new ExceptionHandler( aex ); 686 api.getLogger().error( "MKS API Exception: " + eh.getMessage() ); 687 api.getLogger().debug( eh.getCommand() + " completed with exit Code " + eh.getExitCode() ); 688 } 689 } 690 } 691 catch ( APIException aex ) 692 { 693 // Set the ciSuccess to false, since we ran into a problem 694 ciSuccess = false; 695 ExceptionHandler eh = new ExceptionHandler( aex ); 696 api.getLogger().error( "MKS API Exception: " + eh.getMessage() ); 697 api.getLogger().debug( eh.getCommand() + " completed with exit Code " + eh.getExitCode() ); 698 } 699 700 return changedFiles; 701 } 702 703 /** 704 * Returns the overall success of the check-in operation 705 * 706 * @return 707 */ 708 public boolean getOverallCheckInSuccess() 709 { 710 return ciSuccess; 711 } 712 713 /** 714 * Creates one subproject per directory, as required. 715 * 716 * @param dirPath A relative path structure of folders 717 * @return Response containing the result for the created subproject 718 * @throws APIException 719 */ 720 public Response createSubproject( String dirPath ) 721 throws APIException 722 { 723 // Setup the create subproject command 724 api.getLogger().debug( "Creating subprojects for: " + dirPath + "/project.pj" ); 725 Command siCreateSubproject = new Command( Command.SI, "createsubproject" ); 726 siCreateSubproject.addOption( new Option( "cpid", cpid ) ); 727 siCreateSubproject.addOption( new Option( "createSubprojects" ) ); 728 siCreateSubproject.addOption( new Option( "cwd", sandboxDir ) ); 729 siCreateSubproject.addSelection( dirPath + "/project.pj" ); 730 // Execute the create subproject command 731 return api.runCommand( siCreateSubproject ); 732 } 733 734 /** 735 * Executes the 'si rlog' command to generate a list of changed revision found between startDate and endDate 736 * 737 * @param startDate The date range for the beginning of the operation 738 * @param endDate The date range for the end of the operation 739 * @return ChangeLogSet containing a list of changes grouped by Change Pacakge ID 740 * @throws APIException 741 */ 742 public ChangeLogSet getChangeLog( Date startDate, Date endDate ) 743 throws APIException 744 { 745 // Initialize our return object 746 ChangeLogSet changeLog = new ChangeLogSet( startDate, endDate ); 747 // By default we're going to group-by change package 748 // Non change package changes will be lumped into one big Change Set 749 Hashtable<String, ChangeSet> changeSetHash = new Hashtable<String, ChangeSet>(); 750 751 // Lets prepare our si rlog command for execution 752 Command siRlog = new Command( Command.SI, "rlog" ); 753 siRlog.addOption( new Option( "recurse" ) ); 754 MultiValue rFilter = new MultiValue( ":" ); 755 rFilter.add( "daterange" ); 756 rFilter.add( "'" + RLOG_DATEFORMAT.format( startDate ) + "'-'" + RLOG_DATEFORMAT.format( endDate ) + "'" ); 757 siRlog.addOption( new Option( "rfilter", rFilter ) ); 758 siRlog.addOption( new Option( "cwd", sandboxDir ) ); 759 // Execute the si rlog command 760 Response response = api.runCommand( siRlog ); 761 for ( WorkItemIterator wit = response.getWorkItems(); wit.hasNext(); ) 762 { 763 WorkItem wi = wit.next(); 764 String memberName = wi.getContext(); 765 // We're going to have to do a little dance to get the correct server file name 766 memberName = memberName.substring( 0, memberName.lastIndexOf( '/' ) ); 767 memberName = memberName + '/' + wi.getId(); 768 memberName = memberName.replace( '\\', '/' ); 769 // Now lets get the revisions for this file 770 Field revisionsFld = wi.getField( "revisions" ); 771 if ( null != revisionsFld && revisionsFld.getDataType().equals( Field.ITEM_LIST_TYPE ) 772 && null != revisionsFld.getList() ) 773 { 774 @SuppressWarnings( "unchecked" ) List<Item> revList = revisionsFld.getList(); 775 for ( Iterator<Item> lit = revList.iterator(); lit.hasNext(); ) 776 { 777 Item revisionItem = lit.next(); 778 String revision = revisionItem.getId(); 779 String author = revisionItem.getField( "author" ).getItem().getId(); 780 // Attempt to get the full name, if available 781 try 782 { 783 author = revisionItem.getField( "author" ).getItem().getField( "fullname" ).getValueAsString(); 784 } 785 catch ( NullPointerException npe ) 786 { /* ignore */ } 787 String cpid = ":none"; 788 // Attempt to get the cpid for this revision 789 try 790 { 791 cpid = revisionItem.getField( "cpid" ).getItem().getId(); 792 } 793 catch ( NullPointerException npe ) 794 { /* ignore */ } 795 // Get the Change Package summary for this revision 796 String comment = cpid + ": " + revisionItem.getField( "cpsummary" ).getValueAsString(); 797 // Get the date associated with this revision 798 Date date = revisionItem.getField( "date" ).getDateTime(); 799 800 // Lets create our ChangeFile based on the information we've gathered so far 801 ChangeFile changeFile = new ChangeFile( memberName, revision ); 802 803 // Check to see if we already have a ChangeSet grouping for this revision 804 ChangeSet changeSet = changeSetHash.get( cpid ); 805 if ( null != changeSet ) 806 { 807 // Set the date of the ChangeSet to the oldest entry 808 if ( changeSet.getDate().after( date ) ) 809 { 810 changeSet.setDate( date ); 811 } 812 // Add the new ChangeFile 813 changeSet.addFile( changeFile ); 814 // Update the changeSetHash 815 changeSetHash.put( cpid, changeSet ); 816 } 817 else // Create a new ChangeSet grouping and add the ChangeFile 818 { 819 List<ChangeFile> changeFileList = new ArrayList<ChangeFile>(); 820 changeFileList.add( changeFile ); 821 changeSet = new ChangeSet( date, comment, author, changeFileList ); 822 // Update the changeSetHash with an initial entry for the cpid 823 changeSetHash.put( cpid, changeSet ); 824 } 825 } 826 } 827 828 } 829 830 // Update the Change Log with the Change Sets 831 List<ChangeSet> changeSetList = new ArrayList<ChangeSet>(); 832 changeSetList.addAll( changeSetHash.values() ); 833 changeLog.setChangeSets( changeSetList ); 834 835 return changeLog; 836 } 837}