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.Response;
28  import com.mks.api.response.WorkItem;
29  import com.mks.api.response.WorkItemIterator;
30  import com.mks.api.si.SIModelTypeName;
31  
32  import java.util.ArrayList;
33  import java.util.Calendar;
34  import java.util.Collections;
35  import java.util.Comparator;
36  import java.util.Date;
37  import java.util.Hashtable;
38  import java.util.List;
39  import java.util.NoSuchElementException;
40  
41  /**
42   * This class represents a MKS Integrity Configuration Management Project
43   * <br>Provides metadata information about a Project
44   *
45   * @author <a href="mailto:cletus@mks.com">Cletus D'Souza</a>
46   * @since 1.6
47   */
48  public class Project
49  {
50      public static final String NORMAL_PROJECT = "Normal";
51  
52      public static final String VARIANT_PROJECT = "Variant";
53  
54      public static final String BUILD_PROJECT = "Build";
55  
56      private String projectName;
57  
58      private String projectType;
59  
60      private String projectRevision;
61  
62      private String fullConfigSyntax;
63  
64      private Date lastCheckpoint;
65  
66      private APISession api;
67  
68      // Create a custom comparator to compare project members
69      public static final Comparator<Member> FILES_ORDER = new Comparator<Member>()
70      {
71          public int compare( Member cmm1, Member cmm2 )
72          {
73              return cmm1.getMemberName().compareToIgnoreCase( cmm2.getMemberName() );
74          }
75      };
76  
77      /**
78       * Checks if the given value is a valid MKS Integrity Label.
79       * <br>If it's invalid, this method throws an exception providing the reason as string.
80       *
81       * @param tagName The checkpoint label name
82       * @return the error message, or null if label is valid
83       */
84      public static void validateTag( String tagName )
85          throws Exception
86      {
87          if ( tagName == null || tagName.length() == 0 )
88          {
89              throw new Exception( "The checkpoint label string is empty!" );
90          }
91  
92          char ch = tagName.charAt( 0 );
93          if ( !( ( 'A' <= ch && ch <= 'Z' ) || ( 'a' <= ch && ch <= 'z' ) ) )
94          {
95              throw new Exception( "The checkpoint label must start with an alpha character!" );
96          }
97  
98          for ( char invalid : "$,.:;/\\@".toCharArray() )
99          {
100             if ( tagName.indexOf( invalid ) >= 0 )
101             {
102                 throw new Exception(
103                     "The checkpoint label may cannot contain one of the following characters: $ , . : ; / \\ @" );
104             }
105         }
106     }
107 
108     /**
109      * Creates an instance of an Integrity SCM Project
110      *
111      * @param api        MKS API Session object
112      * @param configPath Configuration path for the MKS Integrity SCM Project
113      * @throws APIException
114      */
115     public Project( APISession api, String configPath )
116         throws APIException
117     {
118         // Initialize our local APISession
119         this.api = api;
120         try
121         {
122             // Get the project information for this project
123             Command siProjectInfoCmd = new Command( Command.SI, "projectinfo" );
124             siProjectInfoCmd.addOption( new Option( "project", configPath ) );
125             api.getLogger().info( "Preparing to execute si projectinfo for " + configPath );
126             Response infoRes = api.runCommand( siProjectInfoCmd );
127             // Get the only work item from the response
128             WorkItem wi = infoRes.getWorkItems().next();
129             // Get the metadata information about the project
130             Field pjNameFld = wi.getField( "projectName" );
131             Field pjTypeFld = wi.getField( "projectType" );
132             Field pjCfgPathFld = wi.getField( "fullConfigSyntax" );
133             Field pjChkptFld = wi.getField( "lastCheckpoint" );
134 
135             // Convert to our class fields
136             // First obtain the project name field
137             if ( null != pjNameFld && null != pjNameFld.getValueAsString() )
138             {
139                 projectName = pjNameFld.getValueAsString();
140             }
141             else
142             {
143                 api.getLogger().warn( "Project info did not provide a value for the 'projectName' field!" );
144                 projectName = "";
145             }
146             // Next, we'll need to know the project type
147             if ( null != pjTypeFld && null != pjTypeFld.getValueAsString() )
148             {
149                 projectType = pjTypeFld.getValueAsString();
150                 if ( isBuild() )
151                 {
152                     // Next, we'll need to know the current build checkpoint for this configuration
153                     Field pjRevFld = wi.getField( "revision" );
154                     if ( null != pjRevFld && null != pjRevFld.getItem() )
155                     {
156                         projectRevision = pjRevFld.getItem().getId();
157                     }
158                     else
159                     {
160                         projectRevision = "";
161                         api.getLogger().warn( "Project info did not provide a vale for the 'revision' field!" );
162                     }
163                 }
164             }
165             else
166             {
167                 api.getLogger().warn( "Project info did not provide a value for the 'projectType' field!" );
168                 projectType = "";
169             }
170             // Most important is the configuration path
171             if ( null != pjCfgPathFld && null != pjCfgPathFld.getValueAsString() )
172             {
173                 fullConfigSyntax = pjCfgPathFld.getValueAsString();
174             }
175             else
176             {
177                 api.getLogger().error( "Project info did not provide a value for the 'fullConfigSyntax' field!" );
178                 fullConfigSyntax = "";
179             }
180             // Finally, we'll need to store the last checkpoint to figure out differences, etc.
181             if ( null != pjChkptFld && null != pjChkptFld.getDateTime() )
182             {
183                 lastCheckpoint = pjChkptFld.getDateTime();
184             }
185             else
186             {
187                 api.getLogger().warn( "Project info did not provide a value for the 'lastCheckpoint' field!" );
188                 lastCheckpoint = Calendar.getInstance().getTime();
189             }
190         }
191         catch ( NoSuchElementException nsee )
192         {
193             api.getLogger().error( "Project info did not provide a value for field " + nsee.getMessage() );
194         }
195     }
196 
197     /**
198      * Returns the project path for this Integrity SCM Project
199      *
200      * @return
201      */
202     public String getProjectName()
203     {
204         return projectName;
205     }
206 
207     /**
208      * Returns the project revision for this Integrity SCM Project
209      *
210      * @return
211      */
212     public String getProjectRevision()
213     {
214         return projectRevision;
215     }
216 
217     /**
218      * Returns true is this is a Normal Project
219      *
220      * @return
221      */
222     public boolean isNormal()
223     {
224         return projectType.equalsIgnoreCase( NORMAL_PROJECT );
225     }
226 
227     /**
228      * Returns true if this is a Variant Project
229      *
230      * @return
231      */
232     public boolean isVariant()
233     {
234         return projectType.equalsIgnoreCase( VARIANT_PROJECT );
235     }
236 
237     /**
238      * Returns true if this is a Build Project
239      *
240      * @return
241      */
242     public boolean isBuild()
243     {
244         return projectType.equalsIgnoreCase( BUILD_PROJECT );
245     }
246 
247     /**
248      * Returns the Full Configuration Path for this Integrity SCM Project
249      *
250      * @return
251      */
252     public String getConfigurationPath()
253     {
254         return fullConfigSyntax;
255     }
256 
257     /**
258      * Returns the date when the last checkpoint was performed on this Project
259      *
260      * @return
261      */
262     public Date getLastCheckpointDate()
263     {
264         return lastCheckpoint;
265     }
266 
267     /**
268      * Parses the output from the si viewproject command to get a list of members
269      *
270      * @param workspaceDir The current workspace directory, which is required for an export
271      * @return The list of Member objects for this project
272      * @throws APIException
273      */
274     public List<Member> listFiles( String workspaceDir )
275         throws APIException
276     {
277         // Re-initialize the member list for this project
278         List<Member> memberList = new ArrayList<Member>();
279         // Initialize the project config hash
280         Hashtable<String, String> pjConfigHash = new Hashtable<String, String>();
281         // Add the mapping for this project
282         pjConfigHash.put( projectName, fullConfigSyntax );
283         // Compute the project root directory
284         String projectRoot = projectName.substring( 0, projectName.lastIndexOf( '/' ) );
285 
286         // Now, lets parse this project
287         Command siViewProjectCmd = new Command( Command.SI, "viewproject" );
288         siViewProjectCmd.addOption( new Option( "recurse" ) );
289         siViewProjectCmd.addOption( new Option( "project", fullConfigSyntax ) );
290         MultiValue mvFields = new MultiValue( "," );
291         mvFields.add( "name" );
292         mvFields.add( "context" );
293         mvFields.add( "memberrev" );
294         mvFields.add( "membertimestamp" );
295         mvFields.add( "memberdescription" );
296         siViewProjectCmd.addOption( new Option( "fields", mvFields ) );
297         api.getLogger().info( "Preparing to execute si viewproject for " + fullConfigSyntax );
298         Response viewRes = api.runCommand( siViewProjectCmd );
299 
300         // Iterate through the list of members returned by the API
301         WorkItemIterator wit = viewRes.getWorkItems();
302         while ( wit.hasNext() )
303         {
304             WorkItem wi = wit.next();
305             if ( wi.getModelType().equals( SIModelTypeName.SI_SUBPROJECT ) )
306             {
307                 // Save the configuration path for the current subproject, using the canonical path name
308                 pjConfigHash.put( wi.getField( "name" ).getValueAsString(), wi.getId() );
309             }
310             else if ( wi.getModelType().equals( SIModelTypeName.MEMBER ) )
311             {
312                 // Figure out this member's parent project's canonical path name
313                 String parentProject = wi.getField( "parent" ).getValueAsString();
314                 // Instantiate our Integrity CM Member object
315                 Member iCMMember = new Member( wi, pjConfigHash.get( parentProject ), projectRoot, workspaceDir );
316                 // Add this to the full list of members in this project
317                 memberList.add( iCMMember );
318             }
319             else
320             {
321                 api.getLogger().warn( "View project output contains an invalid model type: " + wi.getModelType() );
322             }
323         }
324 
325         // Sort the files list...
326         Collections.sort( memberList, FILES_ORDER );
327         return memberList;
328     }
329 
330     /**
331      * Performs a checkpoint on the Integrity SCM Project
332      *
333      * @param message Checkpoint description
334      * @param tag     Checkpoint label
335      * @return MKS API Response object
336      * @throws APIException
337      */
338     public Response checkpoint( String message, String tag )
339         throws APIException
340     {
341         // Setup the checkpoint command
342         api.getLogger().debug( "Checkpointing project " + fullConfigSyntax + " with label '" + tag + "'" );
343         // Construct the checkpoint command
344         Command siCheckpoint = new Command( Command.SI, "checkpoint" );
345         siCheckpoint.addOption( new Option( "recurse" ) );
346         // Set the project name
347         siCheckpoint.addOption( new Option( "project", fullConfigSyntax ) );
348         // Set the label
349         siCheckpoint.addOption( new Option( "label", tag ) );
350         // Set the description, if specified
351         if ( null != message && message.length() > 0 )
352         {
353             siCheckpoint.addOption( new Option( "description", message ) );
354         }
355         // Run the checkpoint command
356         return api.runCommand( siCheckpoint );
357     }
358 
359     /**
360      * Creates a Development Path (project branch) for the MKS Integrity SCM Project
361      *
362      * @param devPath Development Path Name
363      * @return MKS API Response object
364      * @throws APIException
365      */
366     public Response createDevPath( String devPath )
367         throws APIException
368     {
369         // First we need to obtain a checkpoint from the current configuration (normal or variant)
370         String chkpt = projectRevision;
371         if ( !isBuild() )
372         {
373             Response chkptRes = checkpoint( "Pre-checkpoint for development path " + devPath, devPath + " Baseline" );
374             WorkItem wi = chkptRes.getWorkItem( fullConfigSyntax );
375             chkpt = wi.getResult().getField( "resultant" ).getItem().getId();
376         }
377 
378         // Now lets setup the create development path command
379         api.getLogger().debug(
380             "Creating development path '" + devPath + "' for project " + projectName + " at revision '" + chkpt + "'" );
381         Command siCreateDevPath = new Command( Command.SI, "createdevpath" );
382         siCreateDevPath.addOption( new Option( "devpath", devPath ) );
383         // Set the project name
384         siCreateDevPath.addOption( new Option( "project", projectName ) );
385         // Set the checkpoint we want to create the development path from
386         siCreateDevPath.addOption( new Option( "projectRevision", chkpt ) );
387         // Run the create development path command
388         return api.runCommand( siCreateDevPath );
389     }
390 }
391