View Javadoc
1   package org.apache.maven.scm.provider.jazz.command.status;
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 org.apache.maven.scm.ScmFile;
23  import org.apache.maven.scm.ScmFileStatus;
24  import org.apache.maven.scm.log.ScmLogger;
25  import org.apache.maven.scm.provider.ScmProviderRepository;
26  import org.apache.maven.scm.provider.jazz.command.consumer.AbstractRepositoryConsumer;
27  import org.apache.maven.scm.provider.jazz.repository.JazzScmProviderRepository;
28  
29  import java.util.ArrayList;
30  import java.util.List;
31  import java.util.regex.Matcher;
32  import java.util.regex.Pattern;
33  
34  /**
35   * Consume the output of the scm command for the "status" operation.
36   * <p/>
37   * It is normally just used to build up a list of ScmFile objects that have
38   * their ScmFileStatus set.
39   * This class has been expanded so that the Workspace, Component and Baseline
40   * are also collected and set back in the JazzScmProviderRepository.
41   * The Workspace and Component names are needed for some other commands (list,
42   * for example), so we can easily get this information here.
43   * <p/>
44   * As this class has expanded over time, it has become more and more of a state
45   * machine, one that needs to parse the output of the "scm status --wide" command.
46   * If there are any issues with this provider, I would suggest this is a good
47   * place to start.
48   * 
49   * @author <a href="mailto:ChrisGWarp@gmail.com">Chris Graham</a>
50   */
51  public class JazzStatusConsumer
52      extends AbstractRepositoryConsumer
53  {
54  // We have have a workspace with no flow targets (it points to itself)
55  //
56  //  Workspace: (1000) "BogusRepositoryWorkspace" <-> (1000) "BogusRepositoryWorkspace"
57  //    Component: (1001) "BogusComponent"
58  //      Baseline: (1128) 27 "BogusTestJazz-3.0.0.40"
59  //      Unresolved:
60  //        d-- /BogusTest/pom.xml.releaseBackup
61  //        d-- /BogusTest/release.properties
62  //
63  // Or, we have have one that does have a flow target (ie a stream or another workspace).
64  //
65  //  Workspace: (1156) "GPDBWorkspace" <-> (1157) "GPDBStream"
66  //    Component: (1158) "GPDB" <-> (1157) "GPDBStream"
67  //      Baseline: (1159) 1 "Initial Baseline"
68  //
69  // Note the (%d) numbers are aliases and are only valid for the machine/instance that made the
70  // remote calls to the server. They are not to be shared across machines (ie don't make them global, public
71  // or persistent).
72  //
73  // We can also have a changeset with a work item associated with it:
74  //
75  //  Workspace: (1156) "GPDBWorkspace" <-> (1157) "GPDBStream"
76  //    Component: (1158) "GPDB"
77  //      Baseline: (2362) 48 "GPDB-1.0.50"
78  //        Outgoing:
79  //          Change sets:
80  //            (2366) *--@  62 "Release the next release of GPDB." - "Man Created Changeset: X.Y.Z" 28-Apr-2015 07:55 PM
81  //
82  // Or not:
83  //
84  //  Workspace: (1156) "GPDBWorkspace" <-> (1157) "GPDBStream"
85  //    Component: (1158) "GPDB"
86  //      Baseline: (2362) 48 "GPDB-1.0.50"
87  //        Outgoing:
88  //          Change sets:
89  //            (2365) ---@  "This is my changeset comment." 26-Apr-2015 09:36 PM
90  //
91  // We can also have a multiple changesets. These will be seen when a JBE is used to perform
92  // the release and has been instructed to create a baseline prior to starting the build.
93  // Multiple changesets will also be seen when a maven release process fails (for whatever reason).
94  //
95  //  Workspace: (1156) "GPDBWorkspace" <-> (1157) "GPDBStream"
96  //    Component: (1158) "GPDB"
97  //      Baseline: (2362) 48 "GPDB-1.0.50"
98  //        Outgoing:
99  //          Change sets:
100 //            (2366) *--@  62 "Release the next release of GPDB." - "Man Created Changeset: X.Y.Z" 28-Apr-2015 07:55 PM
101 //            (2365) ---@  "This is my changeset comment." 26-Apr-2015 09:36 PM
102 //
103 // We can also have Baselines, of which there may be more than one (especially true if an update (accept changes)
104 // has not been done in a while.
105 //
106 // So the most complete/complex example I can find is something like this:
107 //
108 //  Workspace: (1756) "Scott's GPDBWorkspace" <-> (1157) "GPDBStream"
109 //    Component: (1158) "GPDB"
110 //      Baseline: (1718) 25 "GPDB-1.0.25"
111 //      Unresolved:
112 //        -c- /GPDB/pom.xml
113 //      Outgoing:
114 //        Change sets:
115 //          (2389) *--@  "<No comment>" 23-May-2015 07:09 PM
116 //      Incoming:
117 //        Change sets:
118 //          (2385) ---$ Deb 62 "Release the next release of GPDB." - \
119 //             + "[maven-release-plugin] prepare for next development itera..." 02-May-2015 11:01 PM
120 //      Baselines:
121 //        (2386) 52 "GPDB-1.0.53"
122 //        (2387) 51 "GPDB-1.0.52"
123 //        (2388) 50 "GPDB-1.0.51"
124 //        (2369) 49 "GPDB-MAN-1.0.50"
125 //        (2362) 48 "GPDB-1.0.50"
126 //        (2357) 47 "GPDB-1.0.49"
127 //        (2352) 46 "GPDB-1.0.48"
128 //        (2347) 45 "GPDB-1.0.47"
129 //        (2292) 44 "GPDB-1.0.46"
130 //        (2285) 42 "GPDB-1.0.42"
131 //        (2276) 41 "GPDB-1.0.41"
132 //        (2259) 40 "GPDB-1.0.40"
133 //        (2250) 39 "GPDB-1.0.39"
134 //        (2241) 38 "GPDB-1.0.38"
135 //        (2232) 37 "GPDB-1.0.37"
136 //        (2222) 36 "GPDB-1.0.36"
137 //        (2212) 35 "GPDB-1.0.35"
138 //        (2202) 34 "GPDB-1.0.34"
139 //        (2191) 33 "GPDB-1.0.33"
140 //        (2181) 32 "GPDB-1.0.32"
141 //        (2171) 31 "GPDB-1.0.31"
142 //        (2160) 30 "GPDB-1.0.30"
143 //        (2147) 29 "GPDB-1.0.29"
144 //        (2079) 28 "GPDB-1.0.28"
145 //        (1851) 27 "GPDB-1.0.27"
146 //        (1807) 26 "GPDB-1.0.26"
147 //
148 // Because the "Change sets:" line exists by itself, and it is followed by the changeset
149 // lines, we need to implement a state machine... (seenIncomingChangeSets and seenOutgoingChangeSets)
150 //
151 // We can also have collisions:
152 //
153 //  Workspace: (8551) "myNewWorkspace" <-> (8552) "stream19_test_max_results_1256765247692134"
154 //    Component: (8553) "Flux Capacitor"
155 //      Baseline: (8554) 1 "Initial Baseline"
156 //      Outgoing:
157 //        Change sets:
158 //          (8617) -#@ "Update from November planning meeting"
159 //            Changes:
160 //              -#-c /flux.capacitor/requirements.txt
161 //      Incoming:
162 //        Change sets:
163 //          (8616) -#$ "Results of initial trials"
164 //            Changes:
165 //              -#-c /flux.capacitor/requirements.txt
166 
167     //  Workspace: (1000) "BogusRepositoryWorkspace" <-> (1000) "BogusRepositoryWorkspace"
168     //  Workspace: (1156) "GPDBWorkspace" <-> (1157) "GPDBStream"
169     private static final Pattern WORKSPACE_PATTERN =
170         Pattern.compile( "\\((\\d+)\\) \"(.*)\" <-> \\((\\d+)\\) \"(.*)\"" );
171 
172     //  Component: (1001) "BogusComponent"
173     private static final Pattern COMPONENT_PATTERN1 = Pattern.compile( "\\((\\d+)\\) \"(.*)\"" );
174 
175     //  Component: (1158) "GPDB" <-> (1157) "GPDBStream"
176     //  Component: (1002) "FireDragon" <-> (1005) "MavenR3Stream Workspace" (outgoing addition)
177     private static final Pattern COMPONENT_PATTERN2 = Pattern.compile( "\\((\\d+)\\) \"(.*)\" <.*>" );
178 
179     //  Baseline: (1128) 27 "BogusTestJazz-3.0.0.40"
180     private static final Pattern BASELINE_PATTERN = Pattern.compile( "\\((\\d+)\\) (\\d+) \"(.*)\"" );
181 
182     // (2365) ---@  "This is my changeset comment." 26-Apr-2015 09:36 PM
183     private static final Pattern CHANGESET_PATTERN = Pattern.compile( "\\((\\d+)\\) (.*)" );
184 
185     //
186     // Additional data we collect. (eye catchers)
187     //
188     
189     /**
190      * The "Status" command output line that contains the "Workspace" name.
191      */
192     public static final String STATUS_CMD_WORKSPACE = "Workspace:";
193 
194     /**
195      * The "Status" command output line that contains the "Component" name.
196      */
197     public static final String STATUS_CMD_COMPONENT = "Component:";
198 
199     /**
200      * The "Status" command output line that contains the "Baseline" name.
201      */
202     public static final String STATUS_CMD_BASELINE = "Baseline:";
203 
204     /**
205      * The "Status" command output line that contains the "Outgoing" eye catcher.
206      */
207     public static final String STATUS_CMD_OUTGOING = "Outgoing:";
208 
209     /**
210      * The "Status" command output line that contains the "Incoming" eye catcher.
211      */
212     public static final String STATUS_CMD_INCOMING = "Incoming:";
213 
214     /**
215      * The "Status" command output line that contains the line "Change sets:".
216      * This will be followed by the change set lines themselves. 
217      */
218     public static final String STATUS_CMD_CHANGE_SETS = "Change sets:";
219 
220     /**
221      * The "Status" command output line that contains the "Baselines" eye catcher.
222      */
223     public static final String STATUS_CMD_BASELINES = "Baselines:";
224     
225     // File Status Commands (eye catchers)
226 
227     /**
228      * The "Status" command status flag for a resource that has been added.
229      */
230     public static final String STATUS_CMD_ADD_FLAG = "a-";
231 
232     /**
233      * The "Status" command status flag for when the content or properties of
234      * a file have been modified, or the properties of a directory have changed.
235      */
236     public static final String STATUS_CMD_CHANGE_FLAG = "-c";
237 
238     /**
239      * The "Status" command status flag for a resource that has been deleted.
240      */
241     public static final String STATUS_CMD_DELETE_FLAG = "d-";
242 
243     /**
244      * The "Status" command status flag for a resource that has been renamed or moved.
245      */
246     public static final String STATUS_CMD_MOVED_FLAG = "m-";
247 
248     /**
249      * A List of ScmFile objects that have their ScmFileStatus set.
250      */
251     private List<ScmFile> fChangedFiles = new ArrayList<ScmFile>();
252 
253     /**
254      * Implement a simple state machine: Have we seen the "Change sets:" (outgoing) line or not?
255      */
256     private boolean seenOutgoingChangeSets = false;
257 
258     /**
259      * Implement a simple state machine: Have we seen the "Change sets:" (incoming) line or not?
260      */
261     private boolean seenIncomingChangeSets = false;
262 
263     /**
264      * Constructor for our "scm status" consumer.
265      *
266      * @param repo   The JazzScmProviderRepository being used.
267      * @param logger The ScmLogger to use.
268      */
269     public JazzStatusConsumer( ScmProviderRepository repo, ScmLogger logger )
270     {
271         super( repo, logger );
272     }
273 
274     /**
275      * Process one line of output from the execution of the "scm status" command.
276      *
277      * @param line The line of output from the external command that has been pumped to us.
278      * @see org.codehaus.plexus.util.cli.StreamConsumer#consumeLine(java.lang.String)
279      */
280     public void consumeLine( String line )
281     {
282         super.consumeLine( line );
283         if ( containsWorkspace( line ) )
284         {
285             extractWorkspace( line );
286         }
287         if ( containsComponent( line ) )
288         {
289             extractComponent( line );
290         }
291         if ( containsBaseline( line ) )
292         {
293             extractBaseline( line );
294         }
295         if ( containsStatusFlag( line ) )
296         {
297             extractChangedFile( line );
298         }
299         if ( containsOutgoing( line ) )
300         {
301             // Now looking for outgoing, not incoming
302             seenOutgoingChangeSets = true;
303             seenIncomingChangeSets = false;
304         }
305         if ( containsIncoming( line ) )
306         {
307             // Now looking for incoming, not outgoing
308             seenOutgoingChangeSets = false;
309             seenIncomingChangeSets = true;
310         }
311         if ( containsBaselines( line ) )
312         {
313             // Got to baselines, stop looking for all changesets
314             seenOutgoingChangeSets = false;
315             seenIncomingChangeSets = false;
316         }
317         if ( seenOutgoingChangeSets )
318         {
319             Integer changeSetAlias = extractChangeSetAlias( line );
320             if ( changeSetAlias != null )
321             {
322                 // We are now supporting multiple change sets, as this allows
323                 // us to cater for multiple changeset caused by previous failed
324                 // release attempts.
325                 // Our starting point should always be a clean slate of a workspace
326                 // or sandbox, however, if something fails, then we will have some
327                 // changesets already created, so we need to be able to deal with them effectively.
328                 JazzScmProviderRepository jazzRepository = (JazzScmProviderRepository) getRepository();
329                 jazzRepository.getOutgoingChangeSetAliases().add( new Integer( changeSetAlias ) );
330             }
331         }
332         if ( seenIncomingChangeSets )
333         {
334             Integer changeSetAlias = extractChangeSetAlias( line );
335             if ( changeSetAlias != null )
336             {
337                 // We are now supporting multiple change sets, as this allows
338                 // us to cater for multiple changeset caused by previous failed
339                 // release attempts.
340                 // Our starting point should always be a clean slate of a workspace
341                 // or sandbox, however, if something fails, then we will have some
342                 // changesets already created, so we need to be able to deal with them effectively.
343                 JazzScmProviderRepository jazzRepository = (JazzScmProviderRepository) getRepository();
344                 jazzRepository.getIncomingChangeSetAliases().add( new Integer( changeSetAlias ) );
345             }
346         }
347     }
348 
349     private boolean containsWorkspace( String line )
350     {
351         return line.trim().startsWith( STATUS_CMD_WORKSPACE );
352     }
353 
354     private void extractWorkspace( String line )
355     {
356         // With no stream (flow target):
357         //   Workspace: (1000) "BogusRepositoryWorkspace" <-> (1000) "BogusRepositoryWorkspace"
358         // With a stream:
359         //   Workspace: (1156) "GPDBWorkspace" <-> (1157) "GPDBStream"
360 
361         Matcher matcher = WORKSPACE_PATTERN.matcher( line );
362         if ( matcher.find() )
363         {
364             JazzScmProviderRepository jazzRepository = (JazzScmProviderRepository) getRepository();
365 
366             int workspaceAlias = Integer.parseInt( matcher.group( 1 ) );
367             String workspace = matcher.group( 2 );
368             int streamAlias = Integer.parseInt( matcher.group( 3 ) );
369             String stream = matcher.group( 4 );
370             if ( getLogger().isDebugEnabled() )
371             {
372                 getLogger().debug( "Successfully parsed \"Workspace:\" line:" );
373                 getLogger().debug( "  workspaceAlias = " + workspaceAlias );
374                 getLogger().debug( "  workspace      = " + workspace );
375                 getLogger().debug( "  streamAlias    = " + streamAlias );
376                 getLogger().debug( "  stream         = " + stream );
377             }
378             jazzRepository.setWorkspaceAlias( workspaceAlias );
379             jazzRepository.setWorkspace( workspace );
380             jazzRepository.setFlowTargetAlias( streamAlias );
381             jazzRepository.setFlowTarget( stream );
382         }
383     }
384 
385     private boolean containsComponent( String line )
386     {
387         return line.trim().startsWith( STATUS_CMD_COMPONENT );
388     }
389 
390     private void extractComponent( String line )
391     {
392         // With no stream (flow target):
393         //     Component: (1001) "BogusComponent"
394         // With a stream:
395         //     Component: (1158) "GPDB" <-> (1157) "GPDBStream"
396         // With some additional information:
397         //     Component: (1002) "FireDragon" <-> (1005) "MavenR3Stream Workspace" (outgoing addition)
398 
399         Matcher matcher = COMPONENT_PATTERN1.matcher( line );
400         if ( matcher.find() )
401         {
402             //     Component: (1001) "BogusComponent"
403             JazzScmProviderRepository jazzRepository = (JazzScmProviderRepository) getRepository();
404             int componentAlias = Integer.parseInt( matcher.group( 1 ) );
405             String component = matcher.group( 2 );
406             if ( getLogger().isDebugEnabled() )
407             {
408                 getLogger().debug( "Successfully parsed \"Component:\" line:" );
409                 getLogger().debug( "  componentAlias = " + componentAlias );
410                 getLogger().debug( "  component      = " + component );
411             }
412             jazzRepository.setComponent( component );
413         }
414 
415         matcher = COMPONENT_PATTERN2.matcher( line );
416         if ( matcher.find() )
417         {
418             //     Component: (1158) "GPDB" <-> (1157) "GPDBStream"
419             JazzScmProviderRepository jazzRepository = (JazzScmProviderRepository) getRepository();
420             int componentAlias = Integer.parseInt( matcher.group( 1 ) );
421             String component = matcher.group( 2 );
422             if ( getLogger().isDebugEnabled() )
423             {
424                 getLogger().debug( "Successfully parsed \"Component:\" line:" );
425                 getLogger().debug( "  componentAlias = " + componentAlias );
426                 getLogger().debug( "  component      = " + component );
427             }
428             jazzRepository.setComponent( component );
429         }
430     }
431 
432     private boolean containsBaseline( String line )
433     {
434         return line.trim().startsWith( STATUS_CMD_BASELINE );
435     }
436 
437     private void extractBaseline( String line )
438     {
439         // Baseline: (1128) 27 "BogusTestJazz-3.0.0.40"
440 
441         Matcher matcher = BASELINE_PATTERN.matcher( line );
442         if ( matcher.find() )
443         {
444             JazzScmProviderRepository jazzRepository = (JazzScmProviderRepository) getRepository();
445 
446             int baselineAlias = Integer.parseInt( matcher.group( 1 ) );
447             int baselineId = Integer.parseInt( matcher.group( 2 ) );
448             String baseline = matcher.group( 3 );
449             if ( getLogger().isDebugEnabled() )
450             {
451                 getLogger().debug( "Successfully parsed \"Baseline:\" line:" );
452                 getLogger().debug( "  baselineAlias  = " + baselineAlias );
453                 getLogger().debug( "  baselineId     = " + baselineId );
454                 getLogger().debug( "  baseline       = " + baseline );
455             }
456             jazzRepository.setBaseline( baseline );
457         }
458     }
459 
460     private boolean containsStatusFlag( String line )
461     {
462         boolean containsStatusFlag = false;
463 
464         if ( line.trim().length() > 2 )
465         {
466             String flag = line.trim().substring( 0, 2 );
467             if ( STATUS_CMD_ADD_FLAG.equals( flag ) || STATUS_CMD_CHANGE_FLAG.equals( flag )
468                 || STATUS_CMD_DELETE_FLAG.equals( flag ) )
469             {
470                 containsStatusFlag = true;
471             }
472         }
473         return containsStatusFlag;
474     }
475 
476     private void extractChangedFile( String line )
477     {
478         String flag = line.trim().substring( 0, 2 );
479         String filePath = line.trim().substring( 3 ).trim();
480         ScmFileStatus status = ScmFileStatus.UNKNOWN;
481 
482         if ( STATUS_CMD_ADD_FLAG.equals( flag ) )
483         {
484             status = ScmFileStatus.ADDED;
485         }
486 
487         if ( STATUS_CMD_CHANGE_FLAG.equals( flag ) )
488         {
489             status = ScmFileStatus.MODIFIED;
490         }
491 
492         if ( STATUS_CMD_DELETE_FLAG.equals( flag ) )
493         {
494             status = ScmFileStatus.DELETED;
495         }
496 
497         if ( getLogger().isDebugEnabled() )
498         {
499             getLogger().debug( " Extracted filePath  : '" + filePath + "'" );
500             getLogger().debug( " Extracted     flag  : '" + flag + "'" );
501             getLogger().debug( " Extracted   status  : '" + status + "'" );
502         }
503 
504         fChangedFiles.add( new ScmFile( filePath, status ) );
505     }
506 
507     public List<ScmFile> getChangedFiles()
508     {
509         return fChangedFiles;
510     }
511 
512     private boolean containsOutgoing( String line )
513     {
514         return line.trim().startsWith( STATUS_CMD_OUTGOING );
515     }
516 
517     private boolean containsIncoming( String line )
518     {
519         return line.trim().startsWith( STATUS_CMD_INCOMING );
520     }
521 
522     private boolean containsBaselines( String line )
523     {
524         return line.trim().startsWith( STATUS_CMD_BASELINES );
525     }
526 
527     /**
528      * Extract and return an Integer of a change set alias, from both
529      * incoming and outgoing changesets.
530      * @param line The line to extract the change sets from.
531      * @return A parsed Integer value, or null if not able to parse.
532      */
533     private Integer extractChangeSetAlias( String line )
534     {
535         // (2365) ---@  "This is my changeset comment." 26-Apr-2015 09:36 PM
536 
537         Matcher matcher = CHANGESET_PATTERN.matcher( line );
538         if ( matcher.find() )
539         {
540             int changeSetAlias = Integer.parseInt( matcher.group( 1 ) );
541             if ( getLogger().isDebugEnabled() )
542             {
543                 getLogger().debug( "Successfully parsed post \"Change sets:\" line:" );
544                 getLogger().debug( "  changeSetAlias = " + changeSetAlias );
545             }
546             return new Integer( changeSetAlias );
547         }
548         else
549         {
550             return null;
551         }
552     }
553 }