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