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
55 public class Sandbox
56 {
57
58 public static final SimpleDateFormat RLOG_DATEFORMAT = new SimpleDateFormat( "MMMMM d, yyyy - h:mm:ss a" );
59
60
61 private String fs = System.getProperty( "file.separator" );
62
63
64 private APISession api;
65
66
67 private Project siProject;
68
69 private String sandboxDir;
70
71 private String cpid;
72
73
74 private boolean addSuccess;
75
76
77 private boolean ciSuccess;
78
79
80
81
82
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
111
112
113
114
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
129
130
131
132
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
160
161
162
163
164 private boolean isDelta( Item wfdelta )
165 {
166
167 if ( wfdelta.getField( "isDelta" ).getBoolean().booleanValue() )
168 {
169 return true;
170 }
171 else
172 {
173 return false;
174 }
175 }
176
177
178
179
180
181
182
183
184
185 private Response add( File memberFile, String message )
186 throws APIException
187 {
188
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
204
205
206
207
208
209
210
211 private Response checkin( File memberFile, String relativeName, String message )
212 throws APIException
213 {
214
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
229
230
231
232
233
234
235 private Response dropMember( File memberFile, String relativeName )
236 throws APIException
237 {
238
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
251
252
253
254
255
256
257 private boolean hasMemberChanged( File memberFile, String relativeName )
258 {
259
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
266 Response res = api.runCommand( siDiff );
267 try
268 {
269
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
294
295
296
297 public String getSandboxDir()
298 {
299 return sandboxDir;
300 }
301
302
303
304
305
306
307
308
309
310 public Response lock( File memberFile, String relativeName )
311 throws APIException
312 {
313
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
321 return api.runCommand( siLock );
322 }
323
324
325
326
327
328
329
330
331
332 public Response unlock( File memberFile, String relativeName )
333 throws APIException
334 {
335
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
343 return api.runCommand( siUnlock );
344 }
345
346
347
348
349
350
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
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
369
370
371
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
380 api.getLogger().debug( "Sandbox Project File: " + sandboxpj.getAbsolutePath() );
381 if ( sandboxpj.isFile() )
382 {
383
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
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
413 ExceptionHandler eh = new ExceptionHandler( aex );
414 if ( eh.getMessage().indexOf( "There is already a registered entry" ) > 0 )
415 {
416
417 return create();
418 }
419 else
420 {
421 throw aex;
422 }
423 }
424 return true;
425 }
426 }
427
428
429
430
431
432
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
448
449
450
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
465
466
467
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
482
483
484
485
486
487
488 public List<ScmFile> getNewMembers( String exclude, String include )
489 throws APIException
490 {
491
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
517
518
519
520
521
522
523 public List<ScmFile> addNonMembers( String exclude, String include, String message )
524 {
525
526 addSuccess = true;
527
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
539 add( new File( localFile.getPath() ), message );
540
541 filesAdded.add( localFile );
542 }
543 catch ( APIException aex )
544 {
545
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
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
566
567
568
569 public boolean getOverallAddSuccess()
570 {
571 return addSuccess;
572 }
573
574
575
576
577
578
579
580 public boolean hasWorkingFile( Item wfdelta )
581 {
582
583 if ( wfdelta.getField( "noWorkingFile" ).getBoolean().booleanValue() )
584 {
585 return false;
586 }
587 else
588 {
589 return true;
590 }
591 }
592
593
594
595
596
597
598
599 public List<WorkItem> getChangeList()
600 throws APIException
601 {
602
603 List<WorkItem> changedFiles = new ArrayList<WorkItem>();
604
605 Command siViewSandbox = new Command( Command.SI, "viewsandbox" );
606
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
619 Response r = api.runCommand( siViewSandbox );
620
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
630 if ( isDelta( wfdeltaItem ) )
631 {
632 File memberFile = new File( wi.getField( "name" ).getValueAsString() );
633 if ( hasWorkingFile( wfdeltaItem ) )
634 {
635
636 if ( hasMemberChanged( memberFile, wi.getId() ) )
637 {
638 changedFiles.add( wi );
639 }
640 }
641 else
642 {
643
644 changedFiles.add( wi );
645 }
646 }
647 }
648 }
649 return changedFiles;
650 }
651
652
653
654
655
656
657
658 public List<ScmFile> checkInUpdates( String message )
659 {
660
661 ciSuccess = true;
662
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
669 List<WorkItem> changeList = getChangeList();
670
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
678 if ( hasWorkingFile( (Item) wi.getField( "wfdelta" ).getValue() ) )
679 {
680
681 lock( memberFile, wi.getId() );
682
683 checkin( memberFile, wi.getId(), message );
684
685 changedFiles.add( new ScmFile( memberFile.getAbsolutePath(), ScmFileStatus.CHECKED_IN ) );
686 }
687 else
688 {
689
690 dropMember( memberFile, wi.getId() );
691
692 changedFiles.add( new ScmFile( memberFile.getAbsolutePath(), ScmFileStatus.DELETED ) );
693 }
694 }
695 catch ( APIException aex )
696 {
697
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
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
719
720
721
722 public boolean getOverallCheckInSuccess()
723 {
724 return ciSuccess;
725 }
726
727
728
729
730
731
732
733
734 public Response createSubproject( String dirPath )
735 throws APIException
736 {
737
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
745 return api.runCommand( siCreateSubproject );
746 }
747
748
749
750
751
752
753
754
755
756 public ChangeLogSet getChangeLog( Date startDate, Date endDate )
757 throws APIException
758 {
759
760 ChangeLogSet changeLog = new ChangeLogSet( startDate, endDate );
761
762
763 Hashtable<String, ChangeSet> changeSetHash = new Hashtable<String, ChangeSet>();
764
765
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
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
780 memberName = memberName.substring( 0, memberName.lastIndexOf( '/' ) );
781 memberName = memberName + '/' + wi.getId();
782 memberName = memberName.replace( '\\', '/' );
783
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
795 try
796 {
797 author = revisionItem.getField( "author" ).getItem().getField( "fullname" ).getValueAsString();
798 }
799 catch ( NullPointerException npe )
800 { }
801 String cpid = ":none";
802
803 try
804 {
805 cpid = revisionItem.getField( "cpid" ).getItem().getId();
806 }
807 catch ( NullPointerException npe )
808 { }
809
810 String comment = cpid + ": " + revisionItem.getField( "cpsummary" ).getValueAsString();
811
812 Date date = revisionItem.getField( "date" ).getDateTime();
813
814
815 ChangeFile changeFile = new ChangeFile( memberName, revision );
816
817
818 ChangeSet changeSet = changeSetHash.get( cpid );
819 if ( null != changeSet )
820 {
821
822 if ( changeSet.getDate().after( date ) )
823 {
824 changeSet.setDate( date );
825 }
826
827 changeSet.addFile( changeFile );
828
829 changeSetHash.put( cpid, changeSet );
830 }
831 else
832 {
833 List<ChangeFile> changeFileList = new ArrayList<ChangeFile>();
834 changeFileList.add( changeFile );
835 changeSet = new ChangeSet( date, comment, author, changeFileList );
836
837 changeSetHash.put( cpid, changeSet );
838 }
839 }
840 }
841
842 }
843
844
845 List<ChangeSet> changeSetList = new ArrayList<ChangeSet>();
846 changeSetList.addAll( changeSetHash.values() );
847 changeLog.setChangeSets( changeSetList );
848
849 return changeLog;
850 }
851 }