001package 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
022import java.io.File;
023import java.util.Date;
024import java.util.List;
025import java.util.Map;
026import java.util.regex.Matcher;
027import java.util.regex.Pattern;
028
029import org.apache.maven.scm.ScmVersion;
030import org.apache.maven.scm.log.ScmLogger;
031import org.apache.maven.scm.provider.ScmProviderRepositoryWithHost;
032import org.apache.maven.scm.provider.accurev.util.WorkspaceUtils;
033import org.codehaus.plexus.util.StringUtils;
034
035public 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}