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