1 package org.apache.maven.scm.provider.integrity;
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
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
49
50
51
52
53
54 public class Sandbox
55 {
56
57 public static final SimpleDateFormat RLOG_DATEFORMAT = new SimpleDateFormat( "MMMMM d, yyyy - h:mm:ss a" );
58
59
60 private String fs = System.getProperty( "file.separator" );
61
62
63 private APISession api;
64
65
66 private Project siProject;
67
68 private String sandboxDir;
69
70 private String cpid;
71
72
73 private boolean addSuccess;
74
75
76 private boolean ciSuccess;
77
78
79
80
81
82
83 public static String formatFilePatterns( String pattern )
84 {
85 StringBuilder sb = new StringBuilder();
86 if ( null != pattern && pattern.length() > 0 )
87 {
88 String[] tokens = StringUtils.split( pattern, "," );
89 for ( int i = 0; i < tokens.length; i++ )
90 {
91 String tkn = tokens[i].trim();
92 if ( tkn.indexOf( "file:" ) != 0 && tkn.indexOf( "dir:" ) != 0 )
93 {
94 sb.append( tkn.indexOf( '.' ) > 0
95 ? StringUtils.replaceOnce( tkn, "**/", "file:" )
96 : StringUtils.replaceOnce( tkn, "**/", "dir:" ) );
97 }
98 else
99 {
100 sb.append( tkn );
101 }
102 sb.append( "," );
103 }
104 }
105 return sb.toString();
106 }
107
108
109
110
111
112
113
114
115 public Sandbox( APISession api, Project cmProject, String dir )
116 {
117 siProject = cmProject;
118 sandboxDir = dir;
119 this.api = api;
120 cpid = System.getProperty( "maven.scm.integrity.cpid" );
121 cpid = ( ( null == cpid || cpid.length() == 0 ) ? ":none" : cpid );
122 addSuccess = true;
123 ciSuccess = true;
124 }
125
126
127
128
129
130
131
132
133 private boolean isValidSandbox( String sandbox )
134 throws APIException
135 {
136 Command cmd = new Command( Command.SI, "sandboxinfo" );
137 cmd.addOption( new Option( "sandbox", sandbox ) );
138
139 api.getLogger().debug( "Validating existing sandbox: " + sandbox );
140 Response res = api.runCommand( cmd );
141 WorkItemIterator wit = res.getWorkItems();
142 try
143 {
144 WorkItem wi = wit.next();
145 return wi.getField( "fullConfigSyntax" ).getValueAsString().equalsIgnoreCase(
146 siProject.getConfigurationPath() );
147 }
148 catch ( APIException aex )
149 {
150 ExceptionHandler eh = new ExceptionHandler( aex );
151 api.getLogger().error( "MKS API Exception: " + eh.getMessage() );
152 api.getLogger().debug( eh.getCommand() + " completed with exit code " + eh.getExitCode() );
153 return false;
154 }
155 }
156
157
158
159
160
161
162
163 private boolean isDelta( Item wfdelta )
164 {
165
166 return wfdelta.getField( "isDelta" ).getBoolean().booleanValue();
167 }
168
169
170
171
172
173
174
175
176
177 private Response add( File memberFile, String message )
178 throws APIException
179 {
180
181 api.getLogger().info( "Adding member: " + memberFile.getAbsolutePath() );
182 Command siAdd = new Command( Command.SI, "add" );
183 siAdd.addOption( new Option( "onExistingArchive", "sharearchive" ) );
184 siAdd.addOption( new Option( "cpid", cpid ) );
185 if ( null != message && message.length() > 0 )
186 {
187 siAdd.addOption( new Option( "description", message ) );
188 }
189 siAdd.addOption( new Option( "cwd", memberFile.getParentFile().getAbsolutePath() ) );
190 siAdd.addSelection( memberFile.getName() );
191 return api.runCommand( siAdd );
192 }
193
194
195
196
197
198
199
200
201
202
203 private Response checkin( File memberFile, String relativeName, String message )
204 throws APIException
205 {
206
207 api.getLogger().info( "Checking in member: " + memberFile.getAbsolutePath() );
208 Command sici = new Command( Command.SI, "ci" );
209 sici.addOption( new Option( "cpid", cpid ) );
210 if ( null != message && message.length() > 0 )
211 {
212 sici.addOption( new Option( "description", message ) );
213 }
214 sici.addOption( new Option( "cwd", memberFile.getParentFile().getAbsolutePath() ) );
215 sici.addSelection( relativeName );
216 return api.runCommand( sici );
217 }
218
219
220
221
222
223
224
225
226
227 private Response dropMember( File memberFile, String relativeName )
228 throws APIException
229 {
230
231 api.getLogger().info( "Dropping member " + memberFile.getAbsolutePath() );
232 Command siDrop = new Command( Command.SI, "drop" );
233 siDrop.addOption( new Option( "cwd", memberFile.getParentFile().getAbsolutePath() ) );
234 siDrop.addOption( new Option( "noconfirm" ) );
235 siDrop.addOption( new Option( "cpid", cpid ) );
236 siDrop.addOption( new Option( "delete" ) );
237 siDrop.addSelection( relativeName );
238 return api.runCommand( siDrop );
239 }
240
241
242
243
244
245
246
247
248
249 private boolean hasMemberChanged( File memberFile, String relativeName )
250 {
251
252 Command siDiff = new Command( Command.SI, "diff" );
253 siDiff.addOption( new Option( "cwd", memberFile.getParentFile().getAbsolutePath() ) );
254 siDiff.addSelection( relativeName );
255 try
256 {
257
258 Response res = api.runCommand( siDiff );
259 try
260 {
261
262 return res.getWorkItems().next().getResult().getField( "resultant" ).getItem().getField(
263 "different" ).getBoolean().booleanValue();
264 }
265 catch ( NullPointerException npe )
266 {
267 api.getLogger().warn( "Couldn't figure out differences for file: " + memberFile.getAbsolutePath() );
268 api.getLogger().warn(
269 "Null value found along response object for WorkItem/Result/Field/Item/Field.getBoolean()" );
270 api.getLogger().warn( "Proceeding with the assumption that the file has changed!" );
271 }
272 }
273 catch ( APIException aex )
274 {
275 ExceptionHandler eh = new ExceptionHandler( aex );
276 api.getLogger().warn( "Couldn't figure out differences for file: " + memberFile.getAbsolutePath() );
277 api.getLogger().warn( eh.getMessage() );
278 api.getLogger().warn( "Proceeding with the assumption that the file has changed!" );
279 api.getLogger().debug( eh.getCommand() + " completed with exit Code " + eh.getExitCode() );
280 }
281 return true;
282 }
283
284
285
286
287
288
289 public String getSandboxDir()
290 {
291 return sandboxDir;
292 }
293
294
295
296
297
298
299
300
301
302 public Response lock( File memberFile, String relativeName )
303 throws APIException
304 {
305
306 api.getLogger().debug( "Locking member: " + memberFile.getAbsolutePath() );
307 Command siLock = new Command( Command.SI, "lock" );
308 siLock.addOption( new Option( "revision", ":member" ) );
309 siLock.addOption( new Option( "cpid", cpid ) );
310 siLock.addOption( new Option( "cwd", memberFile.getParentFile().getAbsolutePath() ) );
311 siLock.addSelection( relativeName );
312
313 return api.runCommand( siLock );
314 }
315
316
317
318
319
320
321
322
323
324 public Response unlock( File memberFile, String relativeName )
325 throws APIException
326 {
327
328 api.getLogger().debug( "Unlocking member: " + memberFile.getAbsolutePath() );
329 Command siUnlock = new Command( Command.SI, "unlock" );
330 siUnlock.addOption( new Option( "revision", ":member" ) );
331 siUnlock.addOption( new Option( "action", "remove" ) );
332 siUnlock.addOption( new Option( "cwd", memberFile.getParentFile().getAbsolutePath() ) );
333 siUnlock.addSelection( relativeName );
334
335 return api.runCommand( siUnlock );
336 }
337
338
339
340
341
342
343
344 public Response drop()
345 throws APIException
346 {
347 File project = new File( siProject.getProjectName() );
348 File sandboxpj = new File( sandboxDir + fs + project.getName() );
349
350
351 api.getLogger().debug( "Sandbox Project File: " + sandboxpj.getAbsolutePath() );
352 Command cmd = new Command( Command.SI, "dropsandbox" );
353 cmd.addOption( new Option( "delete", "members" ) );
354 cmd.addOption( new Option( "sandbox", sandboxpj.getAbsolutePath() ) );
355 cmd.addOption( new Option( "cwd", sandboxDir ) );
356 return api.runCommand( cmd );
357 }
358
359
360
361
362
363
364
365 public boolean create()
366 throws APIException
367 {
368 File project = new File( siProject.getProjectName() );
369 File sandboxpj = new File( sandboxDir + fs + project.getName() );
370
371
372 api.getLogger().debug( "Sandbox Project File: " + sandboxpj.getAbsolutePath() );
373 if ( sandboxpj.isFile() )
374 {
375
376 if ( isValidSandbox( sandboxpj.getAbsolutePath() ) )
377 {
378 api.getLogger().debug(
379 "Reusing existing Sandbox in " + sandboxDir + " for project " + siProject.getConfigurationPath() );
380 return true;
381 }
382 else
383 {
384 api.getLogger().error(
385 "An invalid Sandbox exists in " + sandboxDir + ". Please provide a different location!" );
386 return false;
387 }
388 }
389 else
390 {
391 api.getLogger().debug(
392 "Creating Sandbox in " + sandboxDir + " for project " + siProject.getConfigurationPath() );
393 try
394 {
395 Command cmd = new Command( Command.SI, "createsandbox" );
396 cmd.addOption( new Option( "recurse" ) );
397 cmd.addOption( new Option( "nopopulate" ) );
398 cmd.addOption( new Option( "project", siProject.getConfigurationPath() ) );
399 cmd.addOption( new Option( "cwd", sandboxDir ) );
400 api.runCommand( cmd );
401 }
402 catch ( APIException aex )
403 {
404
405 ExceptionHandler eh = new ExceptionHandler( aex );
406 if ( eh.getMessage().indexOf( "There is already a registered entry" ) > 0 )
407 {
408
409 return create();
410 }
411 else
412 {
413 throw aex;
414 }
415 }
416 return true;
417 }
418 }
419
420
421
422
423
424
425
426 public Response resync()
427 throws APIException
428 {
429 api.getLogger().debug(
430 "Resynchronizing Sandbox in " + sandboxDir + " for project " + siProject.getConfigurationPath() );
431 Command cmd = new Command( Command.SI, "resync" );
432 cmd.addOption( new Option( "recurse" ) );
433 cmd.addOption( new Option( "populate" ) );
434 cmd.addOption( new Option( "cwd", sandboxDir ) );
435 return api.runCommand( cmd );
436 }
437
438
439
440
441
442
443
444 public Response makeWriteable()
445 throws APIException
446 {
447 api.getLogger().debug(
448 "Setting files to writeable in " + sandboxDir + " for project " + siProject.getConfigurationPath() );
449 Command cmd = new Command( Command.SI, "makewritable" );
450 cmd.addOption( new Option( "recurse" ) );
451 cmd.addOption( new Option( "cwd", sandboxDir ) );
452 return api.runCommand( cmd );
453 }
454
455
456
457
458
459
460
461 public Response revertMembers()
462 throws APIException
463 {
464 api.getLogger().debug(
465 "Reverting changes in sandbox " + sandboxDir + " for project " + siProject.getConfigurationPath() );
466 Command cmd = new Command( Command.SI, "revert" );
467 cmd.addOption( new Option( "recurse" ) );
468 cmd.addOption( new Option( "cwd", sandboxDir ) );
469 return api.runCommand( cmd );
470 }
471
472
473
474
475
476
477
478
479
480 public List<ScmFile> getNewMembers( String exclude, String include )
481 throws APIException
482 {
483
484 List<ScmFile> filesAdded = new ArrayList<ScmFile>();
485 Command siViewNonMem = new Command( Command.SI, "viewnonmembers" );
486 siViewNonMem.addOption( new Option( "recurse" ) );
487 if ( null != exclude && exclude.length() > 0 )
488 {
489 siViewNonMem.addOption( new Option( "exclude", exclude ) );
490 }
491 if ( null != include && include.length() > 0 )
492 {
493 siViewNonMem.addOption( new Option( "include", include ) );
494 }
495 siViewNonMem.addOption( new Option( "noincludeFormers" ) );
496 siViewNonMem.addOption( new Option( "cwd", sandboxDir ) );
497 Response response = api.runCommand( siViewNonMem );
498 for ( WorkItemIterator wit = response.getWorkItems(); wit.hasNext(); )
499 {
500 filesAdded.add(
501 new ScmFile( wit.next().getField( "absolutepath" ).getValueAsString(), ScmFileStatus.ADDED ) );
502 }
503 return filesAdded;
504
505 }
506
507
508
509
510
511
512
513
514
515 public List<ScmFile> addNonMembers( String exclude, String include, String message )
516 {
517
518 addSuccess = true;
519
520 List<ScmFile> filesAdded = new ArrayList<ScmFile>();
521 api.getLogger().debug( "Looking for new members in sandbox dir: " + sandboxDir );
522 try
523 {
524 List<ScmFile> newFileList = getNewMembers( exclude, include );
525 for ( Iterator<ScmFile> sit = newFileList.iterator(); sit.hasNext(); )
526 {
527 try
528 {
529 ScmFile localFile = sit.next();
530
531 add( new File( localFile.getPath() ), message );
532
533 filesAdded.add( localFile );
534 }
535 catch ( APIException aex )
536 {
537
538 addSuccess = false;
539 ExceptionHandler eh = new ExceptionHandler( aex );
540 api.getLogger().error( "MKS API Exception: " + eh.getMessage() );
541 api.getLogger().debug( eh.getCommand() + " completed with exit Code " + eh.getExitCode() );
542 }
543 }
544 }
545 catch ( APIException aex )
546 {
547
548 addSuccess = false;
549 ExceptionHandler eh = new ExceptionHandler( aex );
550 api.getLogger().error( "MKS API Exception: " + eh.getMessage() );
551 api.getLogger().debug( eh.getCommand() + " completed with exit Code " + eh.getExitCode() );
552 }
553 return filesAdded;
554 }
555
556
557
558
559
560
561 public boolean getOverallAddSuccess()
562 {
563 return addSuccess;
564 }
565
566
567
568
569
570
571
572 public boolean hasWorkingFile( Item wfdelta )
573 {
574
575 return !wfdelta.getField( "noWorkingFile" ).getBoolean().booleanValue();
576 }
577
578
579
580
581
582
583
584 public List<WorkItem> getChangeList()
585 throws APIException
586 {
587
588 List<WorkItem> changedFiles = new ArrayList<WorkItem>();
589
590 Command siViewSandbox = new Command( Command.SI, "viewsandbox" );
591
592 MultiValue mv = new MultiValue( "," );
593 mv.add( "name" );
594 mv.add( "context" );
595 mv.add( "wfdelta" );
596 mv.add( "memberarchive" );
597 siViewSandbox.addOption( new Option( "fields", mv ) );
598 siViewSandbox.addOption( new Option( "recurse" ) );
599 siViewSandbox.addOption( new Option( "noincludeDropped" ) );
600 siViewSandbox.addOption( new Option( "filterSubs" ) );
601 siViewSandbox.addOption( new Option( "cwd", sandboxDir ) );
602
603
604 Response r = api.runCommand( siViewSandbox );
605
606 for ( WorkItemIterator wit = r.getWorkItems(); wit.hasNext(); )
607 {
608 WorkItem wi = wit.next();
609 api.getLogger().debug( "Inspecting file: " + wi.getField( "name" ).getValueAsString() );
610
611 if ( wi.getModelType().equals( SIModelTypeName.MEMBER ) )
612 {
613 Item wfdeltaItem = (Item) wi.getField( "wfdelta" ).getValue();
614
615 if ( isDelta( wfdeltaItem ) )
616 {
617 File memberFile = new File( wi.getField( "name" ).getValueAsString() );
618 if ( hasWorkingFile( wfdeltaItem ) )
619 {
620
621 if ( hasMemberChanged( memberFile, wi.getId() ) )
622 {
623 changedFiles.add( wi );
624 }
625 }
626 else
627 {
628
629 changedFiles.add( wi );
630 }
631 }
632 }
633 }
634 return changedFiles;
635 }
636
637
638
639
640
641
642
643 public List<ScmFile> checkInUpdates( String message )
644 {
645
646 ciSuccess = true;
647
648 List<ScmFile> changedFiles = new ArrayList<ScmFile>();
649 api.getLogger().debug( "Looking for changed and dropped members in sandbox dir: " + sandboxDir );
650
651 try
652 {
653
654 List<WorkItem> changeList = getChangeList();
655
656 for ( Iterator<WorkItem> wit = changeList.iterator(); wit.hasNext(); )
657 {
658 try
659 {
660 WorkItem wi = wit.next();
661 File memberFile = new File( wi.getField( "name" ).getValueAsString() );
662
663 if ( hasWorkingFile( (Item) wi.getField( "wfdelta" ).getValue() ) )
664 {
665
666 lock( memberFile, wi.getId() );
667
668 checkin( memberFile, wi.getId(), message );
669
670 changedFiles.add( new ScmFile( memberFile.getAbsolutePath(), ScmFileStatus.CHECKED_IN ) );
671 }
672 else
673 {
674
675 dropMember( memberFile, wi.getId() );
676
677 changedFiles.add( new ScmFile( memberFile.getAbsolutePath(), ScmFileStatus.DELETED ) );
678 }
679 }
680 catch ( APIException aex )
681 {
682
683 ciSuccess = false;
684 ExceptionHandler eh = new ExceptionHandler( aex );
685 api.getLogger().error( "MKS API Exception: " + eh.getMessage() );
686 api.getLogger().debug( eh.getCommand() + " completed with exit Code " + eh.getExitCode() );
687 }
688 }
689 }
690 catch ( APIException aex )
691 {
692
693 ciSuccess = false;
694 ExceptionHandler eh = new ExceptionHandler( aex );
695 api.getLogger().error( "MKS API Exception: " + eh.getMessage() );
696 api.getLogger().debug( eh.getCommand() + " completed with exit Code " + eh.getExitCode() );
697 }
698
699 return changedFiles;
700 }
701
702
703
704
705
706
707 public boolean getOverallCheckInSuccess()
708 {
709 return ciSuccess;
710 }
711
712
713
714
715
716
717
718
719 public Response createSubproject( String dirPath )
720 throws APIException
721 {
722
723 api.getLogger().debug( "Creating subprojects for: " + dirPath + "/project.pj" );
724 Command siCreateSubproject = new Command( Command.SI, "createsubproject" );
725 siCreateSubproject.addOption( new Option( "cpid", cpid ) );
726 siCreateSubproject.addOption( new Option( "createSubprojects" ) );
727 siCreateSubproject.addOption( new Option( "cwd", sandboxDir ) );
728 siCreateSubproject.addSelection( dirPath + "/project.pj" );
729
730 return api.runCommand( siCreateSubproject );
731 }
732
733
734
735
736
737
738
739
740
741 public ChangeLogSet getChangeLog( Date startDate, Date endDate )
742 throws APIException
743 {
744
745 ChangeLogSet changeLog = new ChangeLogSet( startDate, endDate );
746
747
748 Hashtable<String, ChangeSet> changeSetHash = new Hashtable<String, ChangeSet>();
749
750
751 Command siRlog = new Command( Command.SI, "rlog" );
752 siRlog.addOption( new Option( "recurse" ) );
753 MultiValue rFilter = new MultiValue( ":" );
754 rFilter.add( "daterange" );
755 rFilter.add( "'" + RLOG_DATEFORMAT.format( startDate ) + "'-'" + RLOG_DATEFORMAT.format( endDate ) + "'" );
756 siRlog.addOption( new Option( "rfilter", rFilter ) );
757 siRlog.addOption( new Option( "cwd", sandboxDir ) );
758
759 Response response = api.runCommand( siRlog );
760 for ( WorkItemIterator wit = response.getWorkItems(); wit.hasNext(); )
761 {
762 WorkItem wi = wit.next();
763 String memberName = wi.getContext();
764
765 memberName = memberName.substring( 0, memberName.lastIndexOf( '/' ) );
766 memberName = memberName + '/' + wi.getId();
767 memberName = memberName.replace( '\\', '/' );
768
769 Field revisionsFld = wi.getField( "revisions" );
770 if ( null != revisionsFld && revisionsFld.getDataType().equals( Field.ITEM_LIST_TYPE )
771 && null != revisionsFld.getList() )
772 {
773 @SuppressWarnings( "unchecked" ) List<Item> revList = revisionsFld.getList();
774 for ( Iterator<Item> lit = revList.iterator(); lit.hasNext(); )
775 {
776 Item revisionItem = lit.next();
777 String revision = revisionItem.getId();
778 String author = revisionItem.getField( "author" ).getItem().getId();
779
780 try
781 {
782 author = revisionItem.getField( "author" ).getItem().getField( "fullname" ).getValueAsString();
783 }
784 catch ( NullPointerException npe )
785 { }
786 String cpid = ":none";
787
788 try
789 {
790 cpid = revisionItem.getField( "cpid" ).getItem().getId();
791 }
792 catch ( NullPointerException npe )
793 { }
794
795 String comment = cpid + ": " + revisionItem.getField( "cpsummary" ).getValueAsString();
796
797 Date date = revisionItem.getField( "date" ).getDateTime();
798
799
800 ChangeFile changeFile = new ChangeFile( memberName, revision );
801
802
803 ChangeSet changeSet = changeSetHash.get( cpid );
804 if ( null != changeSet )
805 {
806
807 if ( changeSet.getDate().after( date ) )
808 {
809 changeSet.setDate( date );
810 }
811
812 changeSet.addFile( changeFile );
813
814 changeSetHash.put( cpid, changeSet );
815 }
816 else
817 {
818 List<ChangeFile> changeFileList = new ArrayList<ChangeFile>();
819 changeFileList.add( changeFile );
820 changeSet = new ChangeSet( date, comment, author, changeFileList );
821
822 changeSetHash.put( cpid, changeSet );
823 }
824 }
825 }
826
827 }
828
829
830 List<ChangeSet> changeSetList = new ArrayList<ChangeSet>();
831 changeSetList.addAll( changeSetHash.values() );
832 changeLog.setChangeSets( changeSetList );
833
834 return changeLog;
835 }
836 }