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