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