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