View Javadoc
1   package org.apache.maven.scm.provider.integrity;
2   
3   /**
4    * Licensed to the Apache Software Foundation (ASF) under one
5    * or more contributor license agreements.  See the NOTICE file
6    * distributed with this work for additional information
7    * regarding copyright ownership.  The ASF licenses this file
8    * to you under the Apache License, Version 2.0 (the
9    * "License"); you may not use this file except in compliance
10   * with the License.  You may obtain a copy of the License at
11   *
12   * http://www.apache.org/licenses/LICENSE-2.0
13   *
14   * Unless required by applicable law or agreed to in writing,
15   * software distributed under the License is distributed on an
16   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17   * KIND, either express or implied.  See the License for the
18   * specific language governing permissions and limitations
19   * under the License.
20   */
21  
22  import com.mks.api.Command;
23  import com.mks.api.MultiValue;
24  import com.mks.api.Option;
25  import com.mks.api.response.APIException;
26  import com.mks.api.response.Field;
27  import com.mks.api.response.Item;
28  import com.mks.api.response.Response;
29  import com.mks.api.response.WorkItem;
30  import com.mks.api.response.WorkItemIterator;
31  import com.mks.api.si.SIModelTypeName;
32  import org.apache.maven.scm.ChangeFile;
33  import org.apache.maven.scm.ChangeSet;
34  import org.apache.maven.scm.ScmFile;
35  import org.apache.maven.scm.ScmFileStatus;
36  import org.apache.maven.scm.command.changelog.ChangeLogSet;
37  import org.codehaus.plexus.util.StringUtils;
38  
39  import java.io.File;
40  import java.text.SimpleDateFormat;
41  import java.util.ArrayList;
42  import java.util.Date;
43  import java.util.Hashtable;
44  import java.util.Iterator;
45  import java.util.List;
46  
47  /**
48   * This class represents an MKS Integrity Sandbox and provides an encapsulation
49   * for executing typical Sandbox operations
50   *
51   * @author <a href="mailto:cletus@mks.com">Cletus D'Souza</a>
52   * @version $Id: Sandbox.java 1.11 2011/08/22 13:06:50EDT Cletus D'Souza (dsouza) Exp  $
53   * @since 1.6
54   */
55  public class Sandbox
56  {
57      // Our date format
58      public static final SimpleDateFormat RLOG_DATEFORMAT = new SimpleDateFormat( "MMMMM d, yyyy - h:mm:ss a" );
59  
60      // File Separator
61      private String fs = System.getProperty( "file.separator" );
62  
63      // MKS API Session Object
64      private APISession api;
65  
66      // Other sandbox specific class variables
67      private Project siProject;
68  
69      private String sandboxDir;
70  
71      private String cpid;
72  
73      // Flag to indicate the overall add operation was successful
74      private boolean addSuccess;
75  
76      // Flag to indicate the overall check-in operation was successful
77      private boolean ciSuccess;
78  
79      /**
80       * Fixes the default includes/excludes patterns for compatibility with MKS Integrity's 'si viewnonmembers' command
81       *
82       * @param pattern String pattern representing the includes/excludes file/directory list
83       */
84      public static String formatFilePatterns( String pattern )
85      {
86          StringBuilder sb = new StringBuilder();
87          if ( null != pattern && pattern.length() > 0 )
88          {
89              String[] tokens = StringUtils.split( pattern, "," );
90              for ( int i = 0; i < tokens.length; i++ )
91              {
92                  String tkn = tokens[i].trim();
93                  if ( tkn.indexOf( "file:" ) != 0 && tkn.indexOf( "dir:" ) != 0 )
94                  {
95                      sb.append( tkn.indexOf( '.' ) > 0
96                                     ? StringUtils.replaceOnce( tkn, "**/", "file:" )
97                                     : StringUtils.replaceOnce( tkn, "**/", "dir:" ) );
98                  }
99                  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 }