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