001 package 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
022 import com.mks.api.Command;
023 import com.mks.api.MultiValue;
024 import com.mks.api.Option;
025 import com.mks.api.response.APIException;
026 import com.mks.api.response.Field;
027 import com.mks.api.response.Item;
028 import com.mks.api.response.Response;
029 import com.mks.api.response.WorkItem;
030 import com.mks.api.response.WorkItemIterator;
031 import com.mks.api.si.SIModelTypeName;
032 import org.apache.maven.scm.ChangeFile;
033 import org.apache.maven.scm.ChangeSet;
034 import org.apache.maven.scm.ScmFile;
035 import org.apache.maven.scm.ScmFileStatus;
036 import org.apache.maven.scm.command.changelog.ChangeLogSet;
037 import org.codehaus.plexus.util.StringUtils;
038
039 import java.io.File;
040 import java.text.SimpleDateFormat;
041 import java.util.ArrayList;
042 import java.util.Date;
043 import java.util.Hashtable;
044 import java.util.Iterator;
045 import 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 */
055 public 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 }