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         if ( projectType.equalsIgnoreCase( NORMAL_PROJECT ) )
226         {
227             return true;
228         }
229         else
230         {
231             return false;
232         }
233     }
234 
235     /**
236      * Returns true if this is a Variant Project
237      *
238      * @return
239      */
240     public boolean isVariant()
241     {
242         if ( projectType.equalsIgnoreCase( VARIANT_PROJECT ) )
243         {
244             return true;
245         }
246         else
247         {
248             return false;
249         }
250     }
251 
252     /**
253      * Returns true if this is a Build Project
254      *
255      * @return
256      */
257     public boolean isBuild()
258     {
259         if ( projectType.equalsIgnoreCase( BUILD_PROJECT ) )
260         {
261             return true;
262         }
263         else
264         {
265             return false;
266         }
267     }
268 
269     /**
270      * Returns the Full Configuration Path for this Integrity SCM Project
271      *
272      * @return
273      */
274     public String getConfigurationPath()
275     {
276         return fullConfigSyntax;
277     }
278 
279     /**
280      * Returns the date when the last checkpoint was performed on this Project
281      *
282      * @return
283      */
284     public Date getLastCheckpointDate()
285     {
286         return lastCheckpoint;
287     }
288 
289     /**
290      * Parses the output from the si viewproject command to get a list of members
291      *
292      * @param workspaceDir The current workspace directory, which is required for an export
293      * @return The list of Member objects for this project
294      * @throws APIException
295      */
296     public List<Member> listFiles( String workspaceDir )
297         throws APIException
298     {
299         // Re-initialize the member list for this project
300         List<Member> memberList = new ArrayList<Member>();
301         // Initialize the project config hash
302         Hashtable<String, String> pjConfigHash = new Hashtable<String, String>();
303         // Add the mapping for this project
304         pjConfigHash.put( projectName, fullConfigSyntax );
305         // Compute the project root directory
306         String projectRoot = projectName.substring( 0, projectName.lastIndexOf( '/' ) );
307 
308         // Now, lets parse this project
309         Command siViewProjectCmd = new Command( Command.SI, "viewproject" );
310         siViewProjectCmd.addOption( new Option( "recurse" ) );
311         siViewProjectCmd.addOption( new Option( "project", fullConfigSyntax ) );
312         MultiValue mvFields = new MultiValue( "," );
313         mvFields.add( "name" );
314         mvFields.add( "context" );
315         mvFields.add( "memberrev" );
316         mvFields.add( "membertimestamp" );
317         mvFields.add( "memberdescription" );
318         siViewProjectCmd.addOption( new Option( "fields", mvFields ) );
319         api.getLogger().info( "Preparing to execute si viewproject for " + fullConfigSyntax );
320         Response viewRes = api.runCommand( siViewProjectCmd );
321 
322         // Iterate through the list of members returned by the API
323         WorkItemIterator wit = viewRes.getWorkItems();
324         while ( wit.hasNext() )
325         {
326             WorkItem wi = wit.next();
327             if ( wi.getModelType().equals( SIModelTypeName.SI_SUBPROJECT ) )
328             {
329                 // Save the configuration path for the current subproject, using the canonical path name
330                 pjConfigHash.put( wi.getField( "name" ).getValueAsString(), wi.getId() );
331             }
332             else if ( wi.getModelType().equals( SIModelTypeName.MEMBER ) )
333             {
334                 // Figure out this member's parent project's canonical path name
335                 String parentProject = wi.getField( "parent" ).getValueAsString();
336                 // Instantiate our Integrity CM Member object
337                 Member iCMMember = new Member( wi, pjConfigHash.get( parentProject ), projectRoot, workspaceDir );
338                 // Add this to the full list of members in this project
339                 memberList.add( iCMMember );
340             }
341             else
342             {
343                 api.getLogger().warn( "View project output contains an invalid model type: " + wi.getModelType() );
344             }
345         }
346 
347         // Sort the files list...
348         Collections.sort( memberList, FILES_ORDER );
349         return memberList;
350     }
351 
352     /**
353      * Performs a checkpoint on the Integrity SCM Project
354      *
355      * @param message Checkpoint description
356      * @param tag     Checkpoint label
357      * @return MKS API Response object
358      * @throws APIException
359      */
360     public Response checkpoint( String message, String tag )
361         throws APIException
362     {
363         // Setup the checkpoint command
364         api.getLogger().debug( "Checkpointing project " + fullConfigSyntax + " with label '" + tag + "'" );
365         // Construct the checkpoint command
366         Command siCheckpoint = new Command( Command.SI, "checkpoint" );
367         siCheckpoint.addOption( new Option( "recurse" ) );
368         // Set the project name
369         siCheckpoint.addOption( new Option( "project", fullConfigSyntax ) );
370         // Set the label
371         siCheckpoint.addOption( new Option( "label", tag ) );
372         // Set the description, if specified
373         if ( null != message && message.length() > 0 )
374         {
375             siCheckpoint.addOption( new Option( "description", message ) );
376         }
377         // Run the checkpoint command
378         return api.runCommand( siCheckpoint );
379     }
380 
381     /**
382      * Creates a Development Path (project branch) for the MKS Integrity SCM Project
383      *
384      * @param devPath Development Path Name
385      * @return MKS API Response object
386      * @throws APIException
387      */
388     public Response createDevPath( String devPath )
389         throws APIException
390     {
391         // First we need to obtain a checkpoint from the current configuration (normal or variant)
392         String chkpt = projectRevision;
393         if ( !isBuild() )
394         {
395             Response chkptRes = checkpoint( "Pre-checkpoint for development path " + devPath, devPath + " Baseline" );
396             WorkItem wi = chkptRes.getWorkItem( fullConfigSyntax );
397             chkpt = wi.getResult().getField( "resultant" ).getItem().getId();
398         }
399 
400         // Now lets setup the create development path command
401         api.getLogger().debug(
402             "Creating development path '" + devPath + "' for project " + projectName + " at revision '" + chkpt + "'" );
403         Command siCreateDevPath = new Command( Command.SI, "createdevpath" );
404         siCreateDevPath.addOption( new Option( "devpath", devPath ) );
405         // Set the project name
406         siCreateDevPath.addOption( new Option( "project", projectName ) );
407         // Set the checkpoint we want to create the development path from
408         siCreateDevPath.addOption( new Option( "projectRevision", chkpt ) );
409         // Run the create development path command
410         return api.runCommand( siCreateDevPath );
411     }
412 }
413