View Javadoc

1   package org.apache.maven.cvslib;
2   
3   /* ====================================================================
4    *   Licensed to the Apache Software Foundation (ASF) under one or more
5    *   contributor license agreements.  See the NOTICE file distributed with
6    *   this work for additional information regarding copyright ownership.
7    *   The ASF licenses this file to You under the Apache License, Version 2.0
8    *   (the "License"); you may not use this file except in compliance with
9    *   the License.  You may obtain a copy of the License at
10   *
11   *       http://www.apache.org/licenses/LICENSE-2.0
12   *
13   *   Unless required by applicable law or agreed to in writing, software
14   *   distributed under the License is distributed on an "AS IS" BASIS,
15   *   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16   *   See the License for the specific language governing permissions and
17   *   limitations under the License.
18   * ====================================================================
19   */
20  
21  import java.io.BufferedReader;
22  import java.io.File;
23  import java.io.FileReader;
24  import java.io.IOException;
25  
26  import java.util.Vector;
27  
28  import org.apache.commons.logging.Log;
29  import org.apache.commons.logging.LogFactory;
30  import org.apache.tools.ant.util.StringUtils;
31  import org.netbeans.lib.cvsclient.CVSRoot;
32  import org.netbeans.lib.cvsclient.Client;
33  import org.netbeans.lib.cvsclient.admin.StandardAdminHandler;
34  import org.netbeans.lib.cvsclient.command.Command;
35  import org.netbeans.lib.cvsclient.command.CommandAbortedException;
36  import org.netbeans.lib.cvsclient.command.CommandException;
37  import org.netbeans.lib.cvsclient.command.GlobalOptions;
38  import org.netbeans.lib.cvsclient.commandLine.CommandFactory;
39  import org.netbeans.lib.cvsclient.commandLine.GetOpt;
40  import org.netbeans.lib.cvsclient.connection.AuthenticationException;
41  import org.netbeans.lib.cvsclient.connection.Connection;
42  import org.netbeans.lib.cvsclient.connection.ConnectionFactory;
43  import org.netbeans.lib.cvsclient.connection.PServerConnection;
44  import org.netbeans.lib.cvsclient.connection.StandardScrambler;
45  import org.netbeans.lib.cvsclient.event.CVSListener;
46  
47  
48  /**
49   * A Cvs connection that simulates a command line interface.
50   *
51   * @author <a href="mailto:epugh@upstate.com">Eric Pugh</a>
52   */
53  public class CvsConnection
54  {
55      /** Log */
56      private static final Log LOG = LogFactory.getLog( CvsConnection.class );
57  
58      /**
59       * The path to the repository on the server
60       */
61      private String repository;
62  
63      /**
64       * The local path to use to perform operations (the top level)
65       */
66      private String localPath;
67  
68      /**
69       * The connection to the server
70       */
71      private Connection connection;
72  
73      /**
74       * The client that manages interactions with the server
75       */
76      private Client client;
77  
78      /**
79       * The global options being used. GlobalOptions are only global for a
80       * particular command.
81       */
82      private GlobalOptions globalOptions;
83  
84      /**
85       * Execute a configured CVS command
86       *
87       * @param command the command to execute
88       * @throws CommandException if there is an error running the command
89       */
90      public void executeCommand( Command command )
91          throws CommandException, AuthenticationException
92      {
93          client.executeCommand( command, globalOptions );
94      }
95  
96      public void setRepository( String repository )
97      {
98          this.repository = repository;
99      }
100 
101     public void setLocalPath( String localPath )
102     {
103         this.localPath = localPath;
104     }
105 
106     public void setGlobalOptions( GlobalOptions globalOptions )
107     {
108         this.globalOptions = globalOptions;
109     }
110 
111     /**
112      * Creates the connection and the client and connects.
113      */
114     private void connect( CVSRoot root, String password )
115         throws IllegalArgumentException, AuthenticationException, 
116             CommandAbortedException
117     {
118         connection = ConnectionFactory.getConnection( root );
119 
120         if ( CVSRoot.METHOD_PSERVER.equals( root.getMethod() ) )
121         {
122             ( (PServerConnection) connection ).setEncodedPassword( password );
123         }
124 
125         connection.open();
126 
127         client = new Client( connection, new StandardAdminHandler() );
128         client.setLocalPath( localPath );
129     }
130 
131     private void addListener( CVSListener listener )
132     {
133         if ( client != null )
134         {
135             // add a listener to the client
136             client.getEventManager().addCVSListener( listener );
137         }
138     }
139 
140     /**
141      * Obtain the CVS root, either from the -D option cvs.root or from the CVS
142      * directory
143      *
144      * @return the CVSRoot string
145      */
146     private static String getCVSRoot( String workingDir )
147     {
148         String root = null;
149         BufferedReader r = null;
150 
151         if ( workingDir == null )
152         {
153             workingDir = System.getProperty( "user.dir" );
154         }
155 
156         try
157         {
158             File f = new File( workingDir );
159             File rootFile = new File( f, "CVS/Root" );
160 
161             if ( rootFile.exists() )
162             {
163                 r = new BufferedReader( new FileReader( rootFile ) );
164                 root = r.readLine();
165             }
166         }
167         catch ( IOException e )
168         {
169             // ignore
170         }
171         finally
172         {
173             try
174             {
175                 if ( r != null )
176                 {
177                     r.close();
178                 }
179             }
180             catch ( IOException e )
181             {
182                 System.err.println( "Warning: could not close CVS/Root file!" );
183             }
184         }
185 
186         if ( root == null )
187         {
188             root = System.getProperty( "cvs.root" );
189         }
190 
191         return root;
192     }
193 
194     /**
195      * Process global options passed into the application
196      *
197      * @param args the argument list, complete
198      * @param globalOptions the global options structure that will be passed to
199      *            the command
200      */
201     private static int processGlobalOptions( String[] args,
202         GlobalOptions globalOptions )
203     {
204         final String getOptString = globalOptions.getOptString();
205         GetOpt go = new GetOpt( args, getOptString );
206         int ch = -1;
207 
208         while ( ( ch = go.getopt() ) != GetOpt.optEOF )
209         {
210             //System.out.println("Global option '"+((char) ch)+"',
211             // '"+go.optArgGet()+"'");
212             String arg = go.optArgGet();
213             boolean success = globalOptions.setCVSCommand( (char) ch, arg );
214 
215             if ( !success )
216             {
217                 throw new IllegalArgumentException( 
218                     "Failed to set CVS Command: -" + ch + " = " + arg );
219             }
220         }
221 
222         return go.optIndexGet();
223     }
224 
225     /**
226      * Lookup the password in the .cvspass file. This file is looked for in the
227      * user.home directory if the option cvs.passfile is not set
228      *
229      * @param cvsRoot the CVS root for which the password is being searched
230      * @return the password, scrambled
231      */
232     private static String lookupPassword( String cvsRoot )
233     {
234         File passFile =
235             new File( System.getProperty( "cvs.passfile",
236                     System.getProperty( "user.home" ) + "/.cvspass" ) );
237 
238         BufferedReader reader = null;
239         String password = null;
240 
241         try
242         {
243             reader = new BufferedReader( new FileReader( passFile ) );
244             password = processCvspass( cvsRoot, reader );
245         }
246         catch ( IOException e )
247         {
248             LOG.warn( "Could not read password for '" + cvsRoot + "' from '"
249                 + passFile + "'", e );
250 
251             return null;
252         }
253         finally
254         {
255             if ( reader != null )
256             {
257                 try
258                 {
259                     reader.close();
260                 }
261                 catch ( IOException e )
262                 {
263                     LOG.error( "Warning: could not close password file." );
264                 }
265             }
266         }
267 
268         if ( password == null )
269         {
270             LOG.error( "Didn't find password for CVSROOT '" + cvsRoot + "'." );
271         }
272 
273         return password;
274     }
275 
276     /**
277      * Read in a list of return delimited lines from .cvspass and retreive
278      * the password.  Return null if the cvsRoot can't be found.
279      *
280      * @param cvsRoot the CVS root for which the password is being searched
281      * @param reader A buffered reader of lines of cvspass information
282      * @return  The password, or null if it can't be found.
283      * @throws IOException
284      */
285     static String processCvspass( String cvsRoot, BufferedReader reader )
286         throws IOException
287     {
288         String line;
289         String password = null;
290 
291         while ( ( line = reader.readLine() ) != null )
292         {
293             if ( line.startsWith( "/" ) )
294             {
295                 Vector cvspass = StringUtils.split( line, ' ' );
296                 String cvspassRoot = (String) cvspass.get( 1 );
297 
298                 if ( compareCvsRoot( cvsRoot, cvspassRoot ) )
299                 {
300                     int index =
301                         line.indexOf( cvspassRoot ) + cvspassRoot.length()
302                         + 1;
303 
304                     password = line.substring( index );
305 
306                     break;
307                 }
308             }
309             else if ( line.startsWith( cvsRoot ) )
310             {
311                 password = line.substring( cvsRoot.length() + 1 );
312 
313                 break;
314             }
315         }
316 
317         return password;
318     }
319 
320     static boolean compareCvsRoot( String cvsRoot, String target )
321     {
322         String s1 = completeCvsRootPort( cvsRoot );
323         String s2 = completeCvsRootPort( target );
324 
325         if ( ( s1 != null ) && s1.equals( s2 ) )
326         {
327             return true;
328         }
329 
330         return false;
331     }
332 
333     private static String completeCvsRootPort( String cvsRoot )
334     {
335         String result = cvsRoot;
336         int idx = cvsRoot.indexOf( ':' );
337 
338         for ( int i = 0; ( i < 2 ) && ( idx != -1 ); i++ )
339         {
340             idx = cvsRoot.indexOf( ':', idx + 1 );
341         }
342 
343         if ( ( idx != -1 ) && ( cvsRoot.charAt( idx + 1 ) == '/' ) )
344         {
345             StringBuffer sb = new StringBuffer();
346 
347             sb.append( cvsRoot.substring( 0, idx + 1 ) );
348             sb.append( "2401" );
349             sb.append( cvsRoot.substring( idx + 1 ) );
350             result = sb.toString();
351         }
352 
353         return result;
354     }
355 
356     /**
357      * Process the CVS command passed in args[] array with all necessary
358      * options. The only difference from main() method is, that this method
359      * does not exit the JVM and provides command output.
360      *
361      * @param args The command with options
362      */
363     public static boolean processCommand( String[] args, String localPath,
364         CVSListener listener )
365         throws Exception
366     {
367         // Set up the CVSRoot. Note that it might still be null after this
368         // call if the user has decided to set it with the -d command line
369         // global option
370         GlobalOptions globalOptions = new GlobalOptions();
371 
372         globalOptions.setCVSRoot( getCVSRoot( localPath ) );
373 
374         // Set up any global options specified. These occur before the
375         // name of the command to run
376         int commandIndex = -1;
377 
378         try
379         {
380             commandIndex = processGlobalOptions( args, globalOptions );
381         }
382         catch ( IllegalArgumentException e )
383         {
384             LOG.error( "Invalid argument: " + e );
385 
386             return false;
387         }
388 
389         // if we don't have a CVS root by now, the user has messed up
390         if ( globalOptions.getCVSRoot() == null )
391         {
392             LOG.error( 
393                 "No CVS root is set. Check your <repository> information in the POM." );
394 
395             return false;
396         }
397 
398         // parse the CVS root into its constituent parts
399         CVSRoot root = null;
400         final String cvsRoot = globalOptions.getCVSRoot();
401 
402         try
403         {
404             root = CVSRoot.parse( cvsRoot );
405         }
406         catch ( IllegalArgumentException e )
407         {
408             LOG.error( "Incorrect format for CVSRoot: " + cvsRoot
409                 + "\nThe correct format is: "
410                 + "[:method:][[user][:password]@][hostname:[port]]/path/to/repository"
411                 + "\nwhere \"method\" is pserver." );
412 
413             return false;
414         }
415 
416         final String command = args[commandIndex];
417 
418         // this is not login, but a 'real' cvs command, so construct it,
419         // set the options, and then connect to the server and execute it
420         Command c = null;
421 
422         try
423         {
424             c = CommandFactory.getDefault().createCommand( command, args,
425                     ++commandIndex, globalOptions, localPath );
426         }
427         catch ( IllegalArgumentException e )
428         {
429             LOG.error( "Illegal argument: " + e.getMessage() );
430 
431             return false;
432         }
433 
434         String password = null;
435 
436         if ( CVSRoot.METHOD_PSERVER.equals( root.getMethod() ) )
437         {
438             password = root.getPassword();
439 
440             if ( password != null )
441             {
442                 password =
443                     StandardScrambler.getInstance().scramble( password );
444             }
445             else
446             {
447                 password = lookupPassword( cvsRoot );
448 
449                 if ( password == null )
450                 {
451                     password = StandardScrambler.getInstance().scramble( "" );
452 
453                     // an empty password
454                 }
455             }
456         }
457 
458         CvsConnection cvsCommand = new CvsConnection();
459 
460         cvsCommand.setGlobalOptions( globalOptions );
461         cvsCommand.setRepository( root.getRepository() );
462 
463         // the local path is just the path where we executed the
464         // command. This is the case for command-line CVS but not
465         // usually for GUI front-ends
466         cvsCommand.setLocalPath( localPath );
467 
468         cvsCommand.connect( root, password );
469         cvsCommand.addListener( listener );
470         LOG.debug( "Executing CVS command: " + c.getCVSCommand() );
471         cvsCommand.executeCommand( c );
472 
473         return true;
474     }
475 }