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 * @version $Id: Project.java 1.6 2011/08/22 13:06:48EDT Cletus D'Souza (dsouza) Exp $ 047 * @since 1.6 048 */ 049public class Project 050{ 051 public static final String NORMAL_PROJECT = "Normal"; 052 053 public static final String VARIANT_PROJECT = "Variant"; 054 055 public static final String BUILD_PROJECT = "Build"; 056 057 private String projectName; 058 059 private String projectType; 060 061 private String projectRevision; 062 063 private String fullConfigSyntax; 064 065 private Date lastCheckpoint; 066 067 private APISession api; 068 069 // Create a custom comparator to compare project members 070 public static final Comparator<Member> FILES_ORDER = new Comparator<Member>() 071 { 072 public int compare( Member cmm1, Member cmm2 ) 073 { 074 return cmm1.getMemberName().compareToIgnoreCase( cmm2.getMemberName() ); 075 } 076 }; 077 078 /** 079 * Checks if the given value is a valid MKS Integrity Label. 080 * <br>If it's invalid, this method throws an exception providing the reason as string. 081 * 082 * @param tagName The checkpoint label name 083 * @return the error message, or null if label is valid 084 */ 085 public static void validateTag( String tagName ) 086 throws Exception 087 { 088 if ( tagName == null || tagName.length() == 0 ) 089 { 090 throw new Exception( "The checkpoint label string is empty!" ); 091 } 092 093 char ch = tagName.charAt( 0 ); 094 if ( !( ( 'A' <= ch && ch <= 'Z' ) || ( 'a' <= ch && ch <= 'z' ) ) ) 095 { 096 throw new Exception( "The checkpoint label must start with an alpha character!" ); 097 } 098 099 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