001 package 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
022 import com.mks.api.Command;
023 import com.mks.api.MultiValue;
024 import com.mks.api.Option;
025 import com.mks.api.response.APIException;
026 import com.mks.api.response.Field;
027 import com.mks.api.response.Response;
028 import com.mks.api.response.WorkItem;
029 import com.mks.api.response.WorkItemIterator;
030 import com.mks.api.si.SIModelTypeName;
031
032 import java.util.ArrayList;
033 import java.util.Calendar;
034 import java.util.Collections;
035 import java.util.Comparator;
036 import java.util.Date;
037 import java.util.Hashtable;
038 import java.util.List;
039 import 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 */
049 public 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