001package org.apache.maven.scm.provider.integrity; 002 003/* 004 * Licensed to the Apache Software Foundation (ASF) under one 005 * or more contributor license agreements. See the NOTICE file 006 * distributed with this work for additional information 007 * regarding copyright ownership. The ASF licenses this file 008 * to you under the Apache License, Version 2.0 (the 009 * "License"); you may not use this file except in compliance 010 * with the License. You may obtain a copy of the License at 011 * 012 * http://www.apache.org/licenses/LICENSE-2.0 013 * 014 * Unless required by applicable law or agreed to in writing, 015 * software distributed under the License is distributed on an 016 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 017 * KIND, either express or implied. See the License for the 018 * specific language governing permissions and limitations 019 * under the License. 020 */ 021 022import com.mks.api.Command; 023import com.mks.api.MultiValue; 024import com.mks.api.Option; 025import com.mks.api.response.APIException; 026import com.mks.api.response.Field; 027import com.mks.api.response.Response; 028import com.mks.api.response.WorkItem; 029import com.mks.api.response.WorkItemIterator; 030import com.mks.api.si.SIModelTypeName; 031 032import java.util.ArrayList; 033import java.util.Calendar; 034import java.util.Collections; 035import java.util.Comparator; 036import java.util.Date; 037import java.util.Hashtable; 038import java.util.List; 039import java.util.NoSuchElementException; 040 041/** 042 * This class represents a MKS Integrity Configuration Management Project 043 * <br>Provides metadata information about a Project 044 * 045 * @author <a href="mailto:cletus@mks.com">Cletus D'Souza</a> 046 * @since 1.6 047 */ 048public class Project 049{ 050 public static final String NORMAL_PROJECT = "Normal"; 051 052 public static final String VARIANT_PROJECT = "Variant"; 053 054 public static final String BUILD_PROJECT = "Build"; 055 056 private String projectName; 057 058 private String projectType; 059 060 private String projectRevision; 061 062 private String fullConfigSyntax; 063 064 private Date lastCheckpoint; 065 066 private APISession api; 067 068 // Create a custom comparator to compare project members 069 public static final Comparator<Member> FILES_ORDER = new Comparator<Member>() 070 { 071 public int compare( Member cmm1, Member cmm2 ) 072 { 073 return cmm1.getMemberName().compareToIgnoreCase( cmm2.getMemberName() ); 074 } 075 }; 076 077 /** 078 * Checks if the given value is a valid MKS Integrity Label. 079 * <br>If it's invalid, this method throws an exception providing the reason as string. 080 * 081 * @param tagName The checkpoint label name 082 * @return the error message, or null if label is valid 083 */ 084 public static void validateTag( String tagName ) 085 throws Exception 086 { 087 if ( tagName == null || tagName.length() == 0 ) 088 { 089 throw new Exception( "The checkpoint label string is empty!" ); 090 } 091 092 char ch = tagName.charAt( 0 ); 093 if ( !( ( 'A' <= ch && ch <= 'Z' ) || ( 'a' <= ch && ch <= 'z' ) ) ) 094 { 095 throw new Exception( "The checkpoint label must start with an alpha character!" ); 096 } 097 098 for ( char invalid : "$,.:;/\\@".toCharArray() ) 099 { 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