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 */ 083 public static void validateTag( String tagName ) 084 throws Exception 085 { 086 if ( tagName == null || tagName.length() == 0 ) 087 { 088 throw new Exception( "The checkpoint label string is empty!" ); 089 } 090 091 char ch = tagName.charAt( 0 ); 092 if ( !( ( 'A' <= ch && ch <= 'Z' ) || ( 'a' <= ch && ch <= 'z' ) ) ) 093 { 094 throw new Exception( "The checkpoint label must start with an alpha character!" ); 095 } 096 097 for ( char invalid : "$,.:;/\\@".toCharArray() ) 098 { 099 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