001    package org.apache.maven.scm.provider.accurev;
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 java.io.File;
023    import java.util.Date;
024    import java.util.List;
025    import java.util.Map;
026    import java.util.regex.Matcher;
027    import java.util.regex.Pattern;
028    
029    import org.apache.maven.scm.ScmVersion;
030    import org.apache.maven.scm.log.ScmLogger;
031    import org.apache.maven.scm.provider.ScmProviderRepositoryWithHost;
032    import org.apache.maven.scm.provider.accurev.util.WorkspaceUtils;
033    import org.codehaus.plexus.util.StringUtils;
034    
035    public class AccuRevScmProviderRepository
036        extends ScmProviderRepositoryWithHost
037    {
038        public static final String DEFAULT_TAG_FORMAT = "%s";
039    
040        private AccuRev accurev;
041    
042        private String streamName;
043    
044        private String projectPath;
045    
046        private String tagFormat = DEFAULT_TAG_FORMAT;
047    
048        private ScmLogger logger;
049    
050        public AccuRevScmProviderRepository()
051        {
052            super();
053            // True is a more sensible default (ie for tck tests)
054            // TODO raise jira so tck tests properly handle setPersist
055            setPersistCheckout( true );
056    
057            setShouldUseExportForNonPersistentCheckout( true );
058        }
059    
060        public String getTagFormat()
061        {
062            return tagFormat;
063        }
064    
065        public void setTagFormat( String tagFormat )
066        {
067            if ( tagFormat == null || !tagFormat.contains( "%s" ) )
068            {
069                throw new IllegalArgumentException( "tagFormat must contain '%s' to be replaced" );
070            }
071            this.tagFormat = tagFormat;
072        }
073    
074        public String getStreamName()
075        {
076            return streamName;
077        }
078    
079        public void setStreamName( String streamName )
080        {
081            this.streamName = streamName;
082        }
083    
084        public String getProjectPath()
085        {
086            return projectPath;
087        }
088    
089        public void setProjectPath( String projectPath )
090        {
091            this.projectPath = projectPath;
092            setCheckoutRelativePath( projectPath );
093        }
094    
095        public AccuRev getAccuRev()
096        {
097            return this.accurev;
098        }
099    
100        public void setAccuRev( AccuRev accurev )
101        {
102            this.accurev = accurev;
103        }
104    
105        /**
106         * @param info
107         * @return true if info indicates a root of the workspace.
108         */
109        public boolean isWorkSpaceRoot( AccuRevInfo info )
110        {
111            return ( ( getProjectPath() != null && WorkspaceUtils.isSameFile(info.getBasedir(), new File( info.getTop(), getProjectPath() ) ) ) || isWorkSpaceTop( info ) );
112        }
113    
114        public boolean isWorkSpaceTop( AccuRevInfo info )
115        {
116            return info.isWorkSpaceTop();      
117    
118        }
119    
120       
121        String tagToStream( String tagName )
122        {
123            return String.format( getTagFormat(), tagName );
124        }
125    
126        String streamToTag( String streamName )
127        {
128            tagFormat = getTagFormat();
129            // TODO - strictly we should quote either side of the %s
130            String tagPatternString = tagToStream( "(.*)" );
131            Pattern tagPattern = Pattern.compile( tagPatternString );
132    
133            Matcher tagMatcher = tagPattern.matcher( streamName );
134            if ( tagMatcher.matches() )
135            {
136                return tagMatcher.group( 1 );
137            }
138            else
139            {
140                return streamName;
141            }
142    
143        }
144    
145        public void setLogger( ScmLogger logger )
146        {
147            this.logger = logger;
148        }
149    
150        // TODO raise JIRA to pull up these methods to ScmProviderRepository
151    
152        private String checkoutRelativePath;
153    
154        private boolean shouldUseExportForNonPersistentCheckout = true;
155    
156        /**
157         * The relative path of the directory of the checked out project in comparison to the checkout directory, or an
158         * empty String in case the checkout directory equals the project directory.
159         * <p/>
160         * With most SCMs, this is just an empty String, meaning that the checkout directory equals the project directory.
161         * But there are cases (e.g. ClearCase) where within the checkout directory, the directory structure of the SCM
162         * system is repeated. E.g. if you check out the project "my/project" to "/some/dir", the project sources are
163         * actually checked out to "some/dir/my/project". In this example, relativePathProjectDirectory would contain
164         * "my/project".
165         */
166        public String getCheckoutRelativePath()
167        {
168            if ( this.checkoutRelativePath == null )
169            {
170                return "";
171            }
172            return this.checkoutRelativePath;
173        }
174    
175        public void setCheckoutRelativePath( String checkoutRelativePath )
176        {
177            this.checkoutRelativePath = checkoutRelativePath;
178        }
179    
180        /**
181         * Relative project path for export
182         * 
183         * @return default same as {@link #getCheckoutRelativePath()}
184         */
185        public String getExportRelativePath()
186        {
187            return getCheckoutRelativePath();
188        }
189    
190        /**
191         * When checkout is not expected to be refreshed or committed, should export be used instead? Perforce, Clearcase
192         * and AccuRev store their meta-data about file status within the server rather than files in the source tree. This
193         * makes checkouts within checkouts (eg release:perform) difficult. Typically there is a way to do a lightweight
194         * export instead which can be implemented as the "export" command. This is a hint to downstream applications that
195         * "export" is available and should be used in preference to "checkout" in cases where "update" and "commit" are not
196         * intended to be used. (ie release:perform)
197         * 
198         * @return false by default
199         */
200        public boolean shouldUseExportForNonPersistentCheckout()
201        {
202            return this.shouldUseExportForNonPersistentCheckout;
203        }
204    
205        public void setShouldUseExportForNonPersistentCheckout( boolean shouldUseExportForNonPersistentCheckout )
206        {
207            this.shouldUseExportForNonPersistentCheckout = shouldUseExportForNonPersistentCheckout;
208        }
209    
210        public String getDepotRelativeProjectPath()
211        {
212            return "/./" + ( projectPath == null ? "" : projectPath );
213        }
214    
215        public AccuRevVersion getAccuRevVersion( ScmVersion scmVersion )
216        {
217    
218            String tran = null;
219            String basisStream = null;
220    
221            if ( scmVersion == null )
222            {
223                basisStream = getStreamName();
224            }
225            else
226            {
227                String name = StringUtils.clean( scmVersion.getName() );
228    
229                String[] versionComponents = name.split( "[/\\\\]", 2 );
230                basisStream = versionComponents[0];
231                if ( basisStream.length() == 0 )
232                {
233                    // Use the default stream from the URL
234                    basisStream = getStreamName();
235                }
236                else
237                {
238                    // name is a tag name - convert to a stream.
239                    basisStream = tagToStream( basisStream );
240                }
241    
242                if ( versionComponents.length == 2 && versionComponents[1].length() > 0 )
243                {
244                    tran = versionComponents[1];
245                }
246            }
247    
248            return new AccuRevVersion( basisStream, tran );
249        }
250    
251        public String getSnapshotName( String tagName )
252        {
253            return tagToStream( tagName );
254        }
255    
256        public String getRevision( String streamName, Date date )
257        {
258            return getRevision( streamName, AccuRev.ACCUREV_TIME_SPEC.format( date == null ? new Date() : date ) );
259        }
260    
261        public String getRevision( String stream, long fromTranId )
262        {
263            return getRevision( stream, Long.toString( fromTranId ) );
264        }
265    
266        public String getRevision( String streamName, String transaction )
267        {
268            return streamToTag( streamName ) + "/" + transaction;
269        }
270    
271        public String getWorkSpaceRevision( String workspace )
272            throws AccuRevException
273        {
274            return getRevision( workspace, Long.toString( getCurrentTransactionId( workspace ) ) );
275        }
276    
277        public Transaction getDepotTransaction( String stream, String tranSpec )
278            throws AccuRevException
279        {
280    
281            if ( tranSpec == null )
282            {
283                tranSpec = "now";
284            }
285    
286            List<Transaction> transactions = getAccuRev().history( stream, tranSpec, null, 1, true, true );
287    
288            if ( transactions == null || transactions.isEmpty() )
289            {
290                logger.warn( "Unable to find transaction for tranSpec=" + tranSpec );
291                return null;
292            }
293            else
294            {
295                return transactions.get( 0 );
296            }
297    
298        }
299    
300        public String getDepotTransactionId( String stream, String tranSpec )
301            throws AccuRevException
302        {
303            Transaction t = getDepotTransaction( stream, tranSpec );
304    
305            return t == null ? tranSpec : Long.toString( t.getTranId() );
306        }
307    
308        private long getCurrentTransactionId( String workSpaceName )
309            throws AccuRevException
310        {
311            // AccuRev does not have a way to get at this workspace info by name.
312            // So we have to do it the hard way...
313    
314            AccuRev accuRev = getAccuRev();
315    
316            Map<String, WorkSpace> workSpaces = accuRev.showWorkSpaces();
317    
318            WorkSpace workspace = workSpaces.get( workSpaceName );
319    
320            if ( workspace == null )
321            {
322                // Must be a reftree
323                workSpaces = accuRev.showRefTrees();
324                workspace = workSpaces.get( workSpaceName );
325            }
326    
327            if ( workspace == null )
328            {
329                throw new AccuRevException( "Can't find workspace " + workSpaceName );
330            }
331            return workspace.getTransactionId();
332        }
333    
334        public String toString()
335        {
336            StringBuilder buff = new StringBuilder( "AccuRevScmProviderRepository" );
337            buff.append( " user=" );
338            buff.append( getUser() );
339            buff.append( " pass=" );
340            buff.append( getPassword() == null ? "null" : StringUtils.repeat( "*", getPassword().length() ) );
341            buff.append( " host=" );
342            buff.append( getHost() );
343            buff.append( " port=" );
344            buff.append( getPort() );
345            buff.append( " stream=" );
346            buff.append( getStreamName() );
347            buff.append( " projectPath=" );
348            buff.append( getProjectPath() );
349    
350            return buff.toString();
351        }
352    
353        public static String formatTimeSpec( Date when )
354        {
355    
356            if ( when == null )
357            {
358                return "now";
359            }
360    
361            return AccuRev.ACCUREV_TIME_SPEC.format( when );
362    
363        }
364    
365    }