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