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