KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > eclipse > team > internal > ccvs > core > connection > CVSRepositoryLocation


1 /*******************************************************************************
2  * Copyright (c) 2000, 2007 IBM Corporation and others.
3  * All rights reserved. This program and the accompanying materials
4  * are made available under the terms of the Eclipse Public License v1.0
5  * which accompanies this distribution, and is available at
6  * http://www.eclipse.org/legal/epl-v10.html
7  *
8  * Contributors:
9  * IBM Corporation - initial API and implementation
10  *******************************************************************************/

11 package org.eclipse.team.internal.ccvs.core.connection;
12
13
14 import java.io.IOException JavaDoc;
15 import java.net.MalformedURLException JavaDoc;
16 import java.net.URL JavaDoc;
17 import java.util.*;
18
19 import org.eclipse.core.resources.ResourcesPlugin;
20 import org.eclipse.core.runtime.*;
21 import org.eclipse.core.runtime.Status;
22 import org.eclipse.core.runtime.jobs.ILock;
23 import org.eclipse.core.runtime.jobs.Job;
24 import org.eclipse.core.runtime.preferences.DefaultScope;
25 import org.eclipse.osgi.util.NLS;
26 import org.eclipse.team.core.TeamException;
27 import org.eclipse.team.internal.ccvs.core.*;
28 import org.eclipse.team.internal.ccvs.core.client.*;
29 import org.eclipse.team.internal.ccvs.core.resources.*;
30 import org.eclipse.team.internal.ccvs.core.util.KnownRepositories;
31 import org.osgi.service.prefs.BackingStoreException;
32 import org.osgi.service.prefs.Preferences;
33
34 /**
35  * This class manages a CVS repository location.
36  *
37  * It provides the mapping between connection method name and the
38  * plugged in ICunnectionMethod.
39  *
40  * It parses location strings into instances.
41  *
42  * It provides a method to open a connection to the server along
43  * with a method to validate that connections can be made.
44  *
45  * It manages its user info using the plugged in IUserAuthenticator
46  * (unless a username and password are provided as part of the creation
47  * string, in which case, no authenticator is used).
48  *
49  * Instances must be disposed of when no longer needed in order to
50  * notify the authenticator so cached properties can be cleared
51  *
52  */

53 public class CVSRepositoryLocation extends PlatformObject implements ICVSRepositoryLocation, IUserInfo {
54
55     /**
56      * The name of the preferences node in the CVS preferences that contains
57      * the known repositories as its children.
58      */

59     public static final String JavaDoc PREF_REPOSITORIES_NODE = "repositories"; //$NON-NLS-1$
60

61     /*
62      * The name of the node in the default scope that has the default settings
63      * for a repository.
64      */

65     private static final String JavaDoc DEFAULT_REPOSITORY_SETTINGS_NODE = "default_repository_settings"; //$NON-NLS-1$
66

67     // Preference keys used to persist the state of the location
68
public static final String JavaDoc PREF_LOCATION = "location"; //$NON-NLS-1$
69
public static final String JavaDoc PREF_SERVER_ENCODING = "encoding"; //$NON-NLS-1$
70

71     // server platform constants
72
public static final int UNDETERMINED_PLATFORM = 0;
73     public static final int CVS_SERVER = 1;
74     public static final int CVSNT_SERVER = 2;
75     public static final int UNSUPPORTED_SERVER = 3;
76     public static final int UNKNOWN_SERVER = 4;
77     
78     // static variables for extension points
79
private static IUserAuthenticator authenticator;
80     private static IConnectionMethod[] pluggedInConnectionMethods = null;
81     
82     // Locks for ensuring that authentication to a host is serialized
83
// so that invalid passwords do not result in account lockout
84
private static Map hostLocks = new HashMap();
85
86     private IConnectionMethod method;
87     private String JavaDoc user;
88     private String JavaDoc password;
89     private String JavaDoc host;
90     private int port;
91     private String JavaDoc root;
92     private boolean userFixed;
93     private boolean passwordFixed;
94     private boolean allowCaching;
95     
96     private int serverPlatform = UNDETERMINED_PLATFORM;
97     
98     public static final char COLON = ':';
99     public static final char SEMICOLON = ';';
100     public static final char HOST_SEPARATOR = '@';
101     public static final char PORT_SEPARATOR = '#';
102     public static final boolean STANDALONE_MODE = (System.getProperty("eclipse.cvs.standalone")==null) ? //$NON-NLS-1$
103
false :(Boolean.valueOf(System.getProperty("eclipse.cvs.standalone")).booleanValue()); //$NON-NLS-1$
104

105     // command to start remote cvs in server mode
106
private static final String JavaDoc INVOKE_SVR_CMD = "server"; //$NON-NLS-1$
107

108     // fields needed for caching the password
109
public static final String JavaDoc INFO_PASSWORD = "org.eclipse.team.cvs.core.password";//$NON-NLS-1$
110
public static final String JavaDoc INFO_USERNAME = "org.eclipse.team.cvs.core.username";//$NON-NLS-1$
111
public static final String JavaDoc AUTH_SCHEME = "";//$NON-NLS-1$
112
public static final URL JavaDoc FAKE_URL;
113
114     /*
115      * Fields used to create the EXT command invocation
116      */

117     public static final String JavaDoc USER_VARIABLE = "{user}"; //$NON-NLS-1$
118
public static final String JavaDoc PASSWORD_VARIABLE = "{password}"; //$NON-NLS-1$
119
public static final String JavaDoc HOST_VARIABLE = "{host}"; //$NON-NLS-1$
120
public static final String JavaDoc PORT_VARIABLE = "{port}"; //$NON-NLS-1$
121

122     /*
123      * Field that indicates which connection method is to be used for
124      * locations that use the EXT connection method.
125      */

126     private static String JavaDoc extProxy;
127     
128     /*
129      * Field that indicates that the last connection attempt made to
130      * this repository location failed due to an authentication failure.
131      * When this is set, subsequent attempts should prompt before attempting to connect
132      */

133     private boolean previousAuthenticationFailed = false;
134     
135     static {
136         URL JavaDoc temp = null;
137         try {
138             temp = new URL JavaDoc("http://org.eclipse.team.cvs.core");//$NON-NLS-1$
139
} catch (MalformedURLException JavaDoc e) {
140             // Should never fail
141
}
142         FAKE_URL = temp;
143     }
144     
145     /**
146      * Return the preferences node whose child nodes are teh know repositories
147      * @return a preferences node
148      */

149     public static Preferences getParentPreferences() {
150         return CVSProviderPlugin.getPlugin().getInstancePreferences().node(PREF_REPOSITORIES_NODE);
151     }
152     
153     /**
154      * Return a preferences node that contains suitabel defaults for a
155      * repository location.
156      * @return a preferences node
157      */

158     public static Preferences getDefaultPreferences() {
159         Preferences defaults = new DefaultScope().getNode(CVSProviderPlugin.ID).node(DEFAULT_REPOSITORY_SETTINGS_NODE);
160         defaults.put(PREF_SERVER_ENCODING, getDefaultEncoding());
161         return defaults;
162     }
163     
164     private static String JavaDoc getDefaultEncoding() {
165         return System.getProperty("file.encoding", "UTF-8"); //$NON-NLS-1$ //$NON-NLS-2$
166
}
167     
168     /**
169      * Set the proxy connection method that is to be used when a
170      * repository location has the ext connection method. This is
171      * usefull with the extssh connection method as it can be used to
172      * kepp the sandbox compatible with the command line client.
173      * @param string
174      */

175     public static void setExtConnectionMethodProxy(String JavaDoc string) {
176         extProxy = string;
177     }
178     
179     /**
180      * Create a repository location instance from the given properties.
181      * The supported properties are:
182      *
183      * connection The connection method to be used
184      * user The username for the connection (optional)
185      * password The password used for the connection (optional)
186      * host The host where the repository resides
187      * port The port to connect to (optional)
188      * root The server directory where the repository is located
189      * encoding The file system encoding of the server
190      */

191     public static CVSRepositoryLocation fromProperties(Properties configuration) throws CVSException {
192         // We build a string to allow validation of the components that are provided to us
193
String JavaDoc connection = configuration.getProperty("connection");//$NON-NLS-1$
194
if (connection == null)
195             connection = "pserver";//$NON-NLS-1$
196
IConnectionMethod method = getPluggedInConnectionMethod(connection);
197         if (method == null)
198             throw new CVSException(new Status(IStatus.ERROR, CVSProviderPlugin.ID, TeamException.UNABLE, NLS.bind(CVSMessages.CVSRepositoryLocation_methods, (new Object JavaDoc[] {getPluggedInConnectionMethodNames()})), null));//
199
String JavaDoc user = configuration.getProperty("user");//$NON-NLS-1$
200
if (user.length() == 0)
201             user = null;
202         String JavaDoc password = configuration.getProperty("password");//$NON-NLS-1$
203
if (user == null)
204             password = null;
205         String JavaDoc host = configuration.getProperty("host");//$NON-NLS-1$
206
if (host == null)
207             throw new CVSException(new Status(IStatus.ERROR, CVSProviderPlugin.ID, TeamException.UNABLE, CVSMessages.CVSRepositoryLocation_hostRequired, null));//
208
String JavaDoc portString = configuration.getProperty("port");//$NON-NLS-1$
209
int port;
210         if (portString == null)
211             port = ICVSRepositoryLocation.USE_DEFAULT_PORT;
212         else
213             port = Integer.parseInt(portString);
214         String JavaDoc root = configuration.getProperty("root");//$NON-NLS-1$
215
if (root == null)
216             throw new CVSException(new Status(IStatus.ERROR, CVSProviderPlugin.ID, TeamException.UNABLE, CVSMessages.CVSRepositoryLocation_rootRequired, null));//
217

218         String JavaDoc encoding = configuration.getProperty("encoding"); //$NON-NLS-1$
219

220         return new CVSRepositoryLocation(method, user, password, host, port, root, encoding, user != null, false);
221     }
222     
223     /**
224      * Parse a location string and return a CVSRepositoryLocation.
225      *
226      * On failure, the status of the exception will be a MultiStatus
227      * that includes the original parsing error and a general status
228      * displaying the passed location and proper form. This form is
229      * better for logging, etc.
230      */

231     public static CVSRepositoryLocation fromString(String JavaDoc location) throws CVSException {
232         try {
233             return fromString(location, false);
234         } catch (CVSException e) {
235             // Parsing failed. Include a status that
236
// shows the passed location and the proper form
237
MultiStatus error = new MultiStatus(CVSProviderPlugin.ID, IStatus.ERROR, NLS.bind(CVSMessages.CVSRepositoryLocation_invalidFormat, (new Object JavaDoc[] {location})), null);//
238
error.merge(new CVSStatus(IStatus.ERROR, CVSMessages.CVSRepositoryLocation_locationForm));//
239
error.merge(e.getStatus());
240             throw new CVSException(error);
241         }
242     }
243     
244     /**
245      * Parse a location string and return a CVSRepositoryLocation.
246      *
247      * The valid format (from the cederqvist) is:
248      *
249      * :method:[[user][:password]@]hostname[:[port]]/path/to/repository
250      *
251      * However, this does not work with CVS on NT so we use the format
252      *
253      * :method:[user[:password]@]hostname[#port]:/path/to/repository
254      *
255      * Some differences to note:
256      * The : after the host/port is not optional because of NT naming including device
257      * e.g. :pserver:username:password@hostname#port:D:\cvsroot
258      *
259      * Also parse alternative format from WinCVS, which stores connection
260      * parameters such as username and hostname in method options:
261      *
262      * :method[;option=arg...]:other_connection_data
263      *
264      * e.g. :pserver;username=anonymous;hostname=localhost:/path/to/repository
265      *
266      * If validateOnly is true, this method will always throw an exception.
267      * The status of the exception indicates success or failure. The status
268      * of the exception contains a specific message suitable for displaying
269      * to a user who has knowledge of the provided location string.
270      * @see CVSRepositoryLocation.fromString(String)
271      */

272     public static CVSRepositoryLocation fromString(String JavaDoc location, boolean validateOnly) throws CVSException {
273         String JavaDoc errorMessage = null;
274         try {
275             // Get the connection method
276
errorMessage = CVSMessages.CVSRepositoryLocation_parsingMethod;
277             int start = location.indexOf(COLON);
278             String JavaDoc methodName;
279             int end;
280             // For parsing alternative location format
281
int optionStart = location.indexOf(SEMICOLON);
282             HashMap hmOptions = new HashMap();
283
284             if (start == 0) {
285                 end = location.indexOf(COLON, start + 1);
286                 
287                 // Check for alternative location syntax
288
if (optionStart != -1) {
289                     // errorMessage = CVSMessages.CVSRepositoryLocation_parsingMethodOptions;
290
methodName = location.substring(start + 1, optionStart);
291                     // Save options in hash table
292
StringTokenizer stOpt = new StringTokenizer(
293                         location.substring(optionStart+1, end),
294                                 "=;" //$NON-NLS-1$
295
);
296                     while (stOpt.hasMoreTokens()) {
297                         hmOptions.put(stOpt.nextToken(), stOpt.nextToken());
298                     }
299                     start = end + 1;
300                 } else {
301                     methodName = location.substring(start + 1, end);
302                     start = end + 1;
303                 }
304             } else {
305                 // this could be an alternate format for ext: username:password@host:path
306
methodName = "ext"; //$NON-NLS-1$
307
start = 0;
308             }
309             
310             IConnectionMethod method = getPluggedInConnectionMethod(methodName);
311             if (method == null)
312                 throw new CVSException(new CVSStatus(IStatus.ERROR, NLS.bind(CVSMessages.CVSRepositoryLocation_methods, (new Object JavaDoc[] {getPluggedInConnectionMethodNames()}))));//
313

314             // Get the user name and password (if provided)
315
errorMessage = CVSMessages.CVSRepositoryLocation_parsingUser;
316             
317             end = location.indexOf(HOST_SEPARATOR, start);
318             String JavaDoc user = null;
319             String JavaDoc password = null;
320             // if end is -1 then there is no host separator meaning that the username is not present
321
// or set in options of alternative-style location string
322
if (end != -1) {
323                 // Get the optional user and password
324
user = location.substring(start, end);
325                 // Separate the user and password (if there is a password)
326
start = user.indexOf(COLON);
327                 if (start != -1) {
328                     errorMessage = CVSMessages.CVSRepositoryLocation_parsingPassword;
329                     password = user.substring(start+1);
330                     user = user.substring(0, start);
331                 }
332                 // Set start to point after the host separator
333
start = end + 1;
334             } else if (optionStart != -1) {
335                 // alternative location string data
336
// errorMessage = CVSMessages.CVSRepositoryLocation_parsingOptionsUsername;
337
if (hmOptions.containsKey("username")) user = hmOptions.get("username").toString(); //$NON-NLS-1$ //$NON-NLS-2$
338
// errorMessage = CVSMessages.CVSRepositoryLocation_parsingOptionsPassword;
339
if (hmOptions.containsKey("password")) password = hmOptions.get("password").toString(); //$NON-NLS-1$ //$NON-NLS-2$
340
}
341             
342             // Get the host (and port)
343
errorMessage = CVSMessages.CVSRepositoryLocation_parsingHost;
344             end= location.indexOf(COLON, start);
345             int hostEnd = end;
346             if (end == -1) {
347                 // The last colon is optional so look for the slash that starts the path
348
end = location.indexOf('/', start);
349                 hostEnd = end;
350                 // Decrement the end since the slash is part of the path
351
if (end != -1) end--;
352             }
353             String JavaDoc host = (optionStart != -1) ? hmOptions.get("hostname").toString() : location.substring(start, hostEnd); //$NON-NLS-1$
354
int port = USE_DEFAULT_PORT;
355             boolean havePort = false;
356             if (hmOptions.containsKey("port")) { //$NON-NLS-1$
357
port = Integer.parseInt(hmOptions.get("port").toString()); //$NON-NLS-1$
358
havePort = true;
359             }
360             // Separate the port and host if there is a port
361
start = host.indexOf(PORT_SEPARATOR);
362             if (start != -1) {
363                 try {
364                     // Initially, we used a # between the host and port
365
errorMessage = CVSMessages.CVSRepositoryLocation_parsingPort;
366                     port = Integer.parseInt(host.substring(start+1));
367                     host = host.substring(0, start);
368                     havePort = true;
369                 } catch (NumberFormatException JavaDoc e) {
370                     // Ignore this as the #1234 port could be part of a proxy host string
371
}
372             }
373             if (!havePort) {
374                 // In the correct CVS format, the port follows the COLON
375
errorMessage = CVSMessages.CVSRepositoryLocation_parsingPort;
376                 int index = end;
377                 char c = location.charAt(++index);
378                 String JavaDoc portString = new String JavaDoc();
379                 while (Character.isDigit(c)) {
380                     portString += c;
381                     c = location.charAt(++index);
382                 }
383                 if (portString.length() > 0) {
384                     end = index - 1;
385                     port = Integer.parseInt(portString);
386                 }
387             }
388             
389             // Get the repository path (translating backslashes to slashes)
390
errorMessage = CVSMessages.CVSRepositoryLocation_parsingRoot;
391             start = end + 1;
392             String JavaDoc root = location.substring(start);
393             
394             if (validateOnly)
395                 throw new CVSException(new CVSStatus(IStatus.OK, CVSMessages.ok));//
396
return new CVSRepositoryLocation(method, user, password, host, port, root, null /* encoding */, (user != null), (password != null));
397         }
398         catch (IndexOutOfBoundsException JavaDoc e) {
399             // We'll get here if anything funny happened while extracting substrings
400
IStatus status = new CVSStatus(IStatus.ERROR, errorMessage);
401             throw new CVSException(status);
402         }
403         catch (NumberFormatException JavaDoc e) {
404             IStatus status = new CVSStatus(IStatus.ERROR, errorMessage);
405             // We'll get here if we couldn't parse a number
406
throw new CVSException(status);
407         }
408     }
409     
410     /**
411      * Get the plugged-in user authenticator if there is one.
412      * @return the plugged-in user authenticator or <code>null</code>
413      */

414     public static IUserAuthenticator getAuthenticator() {
415         if (authenticator == null) {
416             authenticator = getPluggedInAuthenticator();
417         }
418         return authenticator;
419     }
420     
421     /**
422      * Return the list of plugged-in connection methods.
423      * @return the list of plugged-in connection methods
424      */

425     public static IConnectionMethod[] getPluggedInConnectionMethods() {
426         if(pluggedInConnectionMethods==null) {
427             List connectionMethods = new ArrayList();
428             
429             if (STANDALONE_MODE) {
430                 connectionMethods.add(new PServerConnectionMethod());
431             } else {
432                 IExtension[] extensions = Platform.getExtensionRegistry().getExtensionPoint(CVSProviderPlugin.ID, CVSProviderPlugin.PT_CONNECTIONMETHODS).getExtensions();
433                 for(int i=0; i<extensions.length; i++) {
434                     IExtension extension = extensions[i];
435                     IConfigurationElement[] configs = extension.getConfigurationElements();
436                     if (configs.length == 0) {
437                         CVSProviderPlugin.log(IStatus.ERROR, NLS.bind("Connection method {0} is missing required fields", new Object JavaDoc[] {extension.getUniqueIdentifier()}), null);//$NON-NLS-1$
438
continue;
439                     }
440                     try {
441                         IConfigurationElement config = configs[0];
442                         connectionMethods.add(config.createExecutableExtension("run"));//$NON-NLS-1$
443
} catch (CoreException ex) {
444                         CVSProviderPlugin.log(IStatus.ERROR, NLS.bind("Could not instantiate connection method for {0}", new Object JavaDoc[] {extension.getUniqueIdentifier()}), ex);//$NON-NLS-1$
445
}
446                 }
447             }
448             pluggedInConnectionMethods = (IConnectionMethod[])connectionMethods.toArray(new IConnectionMethod[0]);
449         }
450         return pluggedInConnectionMethods;
451     }
452     
453     /*
454      * Return the connection method registered for the given name
455      * or <code>null</code> if none is registered with the given name.
456      */

457     private static IConnectionMethod getPluggedInConnectionMethod(String JavaDoc methodName) {
458         Assert.isNotNull(methodName);
459         IConnectionMethod[] methods = getPluggedInConnectionMethods();
460         for(int i=0; i<methods.length; i++) {
461             if(methodName.equals(methods[i].getName()))
462                 return methods[i];
463         }
464         return null;
465     }
466     
467     /*
468      * Return a string containing a list of all connection methods
469      * that is suitable for inclusion in an error message.
470      */

471     private static String JavaDoc getPluggedInConnectionMethodNames() {
472         IConnectionMethod[] methods = getPluggedInConnectionMethods();
473         StringBuffer JavaDoc methodNames = new StringBuffer JavaDoc();
474         for(int i=0; i<methods.length; i++) {
475             String JavaDoc name = methods[i].getName();
476             if (i>0)
477                 methodNames.append(", ");//$NON-NLS-1$
478
methodNames.append(name);
479         }
480         return methodNames.toString();
481     }
482     
483     /*
484      * Get the pluged-in authenticator from the plugin manifest.
485      */

486     private static IUserAuthenticator getPluggedInAuthenticator() {
487         IExtension[] extensions = Platform.getExtensionRegistry().getExtensionPoint(CVSProviderPlugin.ID, CVSProviderPlugin.PT_AUTHENTICATOR).getExtensions();
488         if (extensions.length == 0)
489             return null;
490         IExtension extension = extensions[0];
491         IConfigurationElement[] configs = extension.getConfigurationElements();
492         if (configs.length == 0) {
493             CVSProviderPlugin.log(IStatus.ERROR, NLS.bind("User autheticator {0} is missing required fields", (new Object JavaDoc[] {extension.getUniqueIdentifier()})), null);//$NON-NLS-1$
494
return null;
495         }
496         try {
497             IConfigurationElement config = configs[0];
498             return (IUserAuthenticator) config.createExecutableExtension("run");//$NON-NLS-1$
499
} catch (CoreException ex) {
500             CVSProviderPlugin.log(IStatus.ERROR, NLS.bind("Unable to instantiate user authenticator {0}", (new Object JavaDoc[] {extension.getUniqueIdentifier()})), ex);//$NON-NLS-1$
501
return null;
502         }
503     }
504     
505     /*
506      * Create a CVSRepositoryLocation from its composite parts.
507      */

508     private CVSRepositoryLocation(IConnectionMethod method, String JavaDoc user, String JavaDoc password, String JavaDoc host, int port, String JavaDoc root, String JavaDoc encoding, boolean userFixed, boolean passwordFixed) {
509         this.method = method;
510         this.user = user;
511         this.password = password;
512         this.host = host;
513         this.port = port;
514         this.root = root;
515         // The username can be fixed only if one is provided
516
if (userFixed && (user != null))
517             this.userFixed = true;
518         // The password can only be fixed if the username is and a password is provided
519
if (userFixed && passwordFixed && (password != null))
520             this.passwordFixed = true;
521         if (encoding != null) {
522             setEncoding(encoding);
523         }
524     }
525     
526     /*
527      * Create the connection to the remote server.
528      * If anything fails, an exception will be thrown and must
529      * be handled by the caller.
530      */

531     private Connection createConnection(String JavaDoc password, IProgressMonitor monitor) throws CVSException {
532         IConnectionMethod methodToUse = method;
533         if (method.getName().equals("ext") && extProxy != null && !extProxy.equals(method.getName())) { //$NON-NLS-1$
534
methodToUse = getPluggedInConnectionMethod(extProxy);
535         }
536         Connection connection = new Connection(this, methodToUse.createConnection(this, password));
537         connection.open(monitor);
538         return connection;
539     }
540     
541     /*
542      * Dispose of the receiver by clearing any cached authorization information.
543      * This method shold only be invoked when the corresponding adapter is shut
544      * down or a connection is being validated.
545      */

546     public void dispose() {
547         flushCache();
548         try {
549             if (hasPreferences()) {
550                 internalGetPreferences().removeNode();
551                 getParentPreferences().flush();
552             }
553         } catch (BackingStoreException e) {
554             CVSProviderPlugin.log(IStatus.ERROR, NLS.bind(CVSMessages.CVSRepositoryLocation_73, new String JavaDoc[] { getLocation(true) }), e);
555         }
556     }
557     
558     /*
559      * Flush the keyring entry associated with the receiver
560      */

561     private void flushCache() {
562         try {
563             Platform.flushAuthorizationInfo(FAKE_URL, getLocation(), AUTH_SCHEME);
564         } catch (CoreException e) {
565             // No need to report this since the location is
566
// most likely being disposed.
567
// Just fail silently and continue
568
CVSProviderPlugin.log(e);
569         }
570     }
571     
572     /*
573      * @see ICVSRepositoryLocation#getHost()
574      */

575     public String JavaDoc getHost() {
576         return host;
577     }
578
579     /*
580      * @see IRepositoryLocation#getLocation()
581      *
582      * The username is included if it is fixed.
583      * The password is never included even if it is fixed.
584      * The port is included if it is not the default port.
585      */

586     public String JavaDoc getLocation() {
587         return getLocation(false);
588     }
589     
590     public String JavaDoc getLocation(boolean forDisplay) {
591         return COLON + method.getName() + COLON +
592             (userFixed?(user +
593                 ((passwordFixed && !forDisplay)?(COLON + password):"")//$NON-NLS-1$
594
+ HOST_SEPARATOR):"") +//$NON-NLS-1$
595
host + COLON +
596             ((port == USE_DEFAULT_PORT)?"":(new Integer JavaDoc(port).toString())) + //$NON-NLS-1$
597
root;
598     }
599     
600     /*
601      * @see ICVSRepositoryLocation#getMethod()
602      */

603     public IConnectionMethod getMethod() {
604         return method;
605     }
606
607     /*
608      * @see ICVSRepositoryLocation#getPort()
609      */

610     public int getPort() {
611         return port;
612     }
613     
614     /*
615      * @see ICVSRepositoryLocation#getEncoding()
616      */

617     public String JavaDoc getEncoding() {
618         if (hasPreferences()) {
619             return internalGetPreferences().get(PREF_SERVER_ENCODING, getDefaultEncoding());
620         } else {
621             return getDefaultEncoding();
622         }
623     }
624
625     /*
626      * @see ICVSRepositoryLocation#setEncoding()
627      */

628     public void setEncoding(String JavaDoc encoding) {
629         if (encoding == null || encoding == getDefaultEncoding()) {
630             if (hasPreferences()) {
631                 internalGetPreferences().remove(PREF_SERVER_ENCODING);
632             }
633         } else {
634             ensurePreferencesStored();
635             internalGetPreferences().put(PREF_SERVER_ENCODING, encoding);
636             flushPreferences();
637         }
638     }
639
640     /*
641      * @see ICVSRepositoryLocation#members(CVSTag, boolean, IProgressMonitor)
642      */

643     public ICVSRemoteResource[] members(CVSTag tag, boolean modules, IProgressMonitor progress) throws CVSException {
644         try {
645             if (modules) {
646                 return RemoteModule.getRemoteModules(this, tag, progress);
647             } else {
648                 RemoteFolder root = new RemoteFolder(null, this, ICVSRemoteFolder.REPOSITORY_ROOT_FOLDER_NAME, tag);
649                 ICVSRemoteResource[] resources = root.members(progress);
650                 // There is the off chance that there is a file in the root of the repository.
651
// This is not supported by cvs so we need to make sure there are no files
652
List folders = new ArrayList(resources.length);
653                 for (int i = 0; i < resources.length; i++) {
654                     ICVSRemoteResource remoteResource = resources[i];
655                     if (remoteResource.isContainer()) {
656                         folders.add(remoteResource);
657                     }
658                 }
659                 return (ICVSRemoteResource[]) folders.toArray(new ICVSRemoteResource[folders.size()]);
660             }
661         } catch (CVSException e){
662             // keep current CVSException
663
throw e;
664         } catch(TeamException e1) {
665             throw new CVSException(e1.getStatus());
666         }
667     }
668     
669     /*
670      * @see ICVSRepositoryLocation#getRemoteFolder(String, CVSTag)
671      */

672     public ICVSRemoteFolder getRemoteFolder(String JavaDoc remotePath, CVSTag tag) {
673         return new RemoteFolder(null, this, remotePath, tag);
674     }
675     
676     /*
677      * @see ICVSRepositoryLocation#getRemoteFile(String, CVSTag)
678      */

679     public ICVSRemoteFile getRemoteFile(String JavaDoc remotePath, CVSTag tag) {
680         IPath path = new Path(null, remotePath);
681         RemoteFolderTree remoteFolder = new RemoteFolderTree(null, this, path.removeLastSegments(1).toString(), tag);
682         RemoteFile remoteFile = new RemoteFile(remoteFolder, Update.STATE_ADDED_LOCAL, path.lastSegment(), null, null, tag);
683         remoteFolder.setChildren(new ICVSRemoteResource[] { remoteFile });
684         return remoteFile;
685     }
686     
687     /*
688      * @see ICVSRepositoryLocation#getRootDirectory()
689      */

690     public String JavaDoc getRootDirectory() {
691         return root;
692     }
693     
694     /*
695      * @see ICVSRepositoryLocation#getTimeout()
696      *
697      * For the time being, the timeout value is a system wide value
698      * associated with the CVSPlugin singleton.
699      */

700     public int getTimeout() {
701         return CVSProviderPlugin.getPlugin().getTimeout();
702     }
703     
704     /*
705      * @see ICVSRepositoryLocation#getUserInfo()
706      */

707     public IUserInfo getUserInfo(boolean makeUsernameMutable) {
708         return new UserInfo(getUsername(), password, makeUsernameMutable ? true : isUsernameMutable());
709     }
710     
711     /*
712      * @see ICVSRepositoryLocation#getUsername()
713      * @see IUserInfo#getUsername()
714      */

715     public String JavaDoc getUsername() {
716         // If the username is mutable, get it from the cache if it's there
717
if (user == null && isUsernameMutable()) {
718             retrievePassword();
719         }
720         return user == null ? "" : user; //$NON-NLS-1$
721
}
722     
723     /*
724      * @see IUserInfo#isUsernameMutable()
725      */

726     public boolean isUsernameMutable() {
727         return !userFixed;
728     }
729
730     /*
731      * Open a connection to the repository represented by the receiver.
732      * If the username or password are not fixed, openConnection will
733      * use the plugged-in authenticator to prompt for the username and/or
734      * password if one has not previously been provided or if the previously
735      * supplied username and password are invalid.
736      *
737      * This method is synchronized to ensure that authentication with the
738      * remote server is serialized. This is needed to avoid the situation where
739      * multiple failed authentications occur and result in the remote account
740      * being locked. The CVSProviderPlugin enforces that there is one instance
741      * of a CVSRepositoryLocation per remote location thus this method is called
742      * for any connection made to this remote location.
743      */

744     public Connection openConnection(IProgressMonitor monitor) throws CVSException {
745         // Get the lock for the host to ensure that we are not connecting to the same host concurrently.
746
Policy.checkCanceled(monitor);
747         ILock hostLock;
748         synchronized(hostLocks) {
749             hostLock = (ILock)hostLocks.get(getHost());
750             if (hostLock == null) {
751                 hostLock = Job.getJobManager().newLock();
752                 hostLocks.put(getHost(), hostLock);
753             }
754         }
755         try {
756             hostLock.acquire();
757             // Allow two ticks in case of a retry
758
monitor.beginTask(NLS.bind(CVSMessages.CVSRepositoryLocation_openingConnection, new String JavaDoc[] { getHost() }), 2);
759             ensureLocationCached();
760             boolean cacheNeedsUpdate = false;
761             // If the previous connection failed, prompt before attempting to connect
762
if (previousAuthenticationFailed) {
763                 promptForUserInfo(null);
764                 // The authentication information has been change so update the cache
765
cacheNeedsUpdate = true;
766             }
767             while (true) {
768                 try {
769                     // The following will throw an exception if authentication fails
770
String JavaDoc password = this.password;
771                     if (password == null) {
772                         // If the instance has no password, obtain it from the cache
773
password = retrievePassword();
774                     }
775                     if (user == null) {
776                         // This is possible if the cache was cleared somehow for a location with a mutable username
777
throw new CVSAuthenticationException(CVSMessages.CVSRepositoryLocation_usernameRequired, CVSAuthenticationException.RETRY, this, null);
778                     }
779                     //if (password == null)
780
// password = "";//$NON-NLS-1$
781
Connection connection = createConnection(password, monitor);
782                     if (cacheNeedsUpdate)
783                         updateCachedLocation();
784                     previousAuthenticationFailed = false;
785                     return connection;
786                 } catch (CVSAuthenticationException ex) {
787                     previousAuthenticationFailed = true;
788                     if (ex.getRetryStatus() == CVSAuthenticationException.RETRY) {
789                         String JavaDoc message = ex.getMessage();
790                         promptForUserInfo(message);
791                         // The authentication information has been change so update the cache
792
cacheNeedsUpdate = true;
793                     } else {
794                         throw ex;
795                     }
796                 }
797             }
798         } finally {
799             hostLock.release();
800             monitor.done();
801         }
802     }
803
804     /*
805      * Prompt for the user authentication information (i.e. user name and password).
806      */

807     private void promptForUserInfo(String JavaDoc message) throws CVSException {
808         IUserAuthenticator authenticator = getAuthenticator();
809         if (authenticator == null) {
810             throw new CVSAuthenticationException(CVSMessages.CVSRepositoryLocation_noAuthenticator, CVSAuthenticationException.NO_RETRY,this);//
811
}
812         authenticator.promptForUserInfo(this, this, message);
813     }
814
815     /*
816      * Ensure that this location is in the known repositories list
817      * and that the authentication information matches what is in the
818      * cache, if this instance is not the instance in the cache.
819      */

820     private void ensureLocationCached() {
821         String JavaDoc location = getLocation();
822         KnownRepositories repositories = KnownRepositories.getInstance();
823         if (repositories.isKnownRepository(location)) {
824             try {
825                 // The repository is already known.
826
// Ensure that the authentication information of this
827
// location matches that of the known location
828
setAuthenticationInformation((CVSRepositoryLocation)repositories.getRepository(location));
829             } catch (CVSException e) {
830                 // Log the exception and continue
831
CVSProviderPlugin.log(e);
832             }
833         } else {
834             // The repository is not known so record it so any authentication
835
// information the user may provide is remembered
836
repositories.addRepository(this, true /* broadcast */);
837         }
838     }
839
840     /*
841      * Set the authentication information of this instance such that it matches the
842      * provided instances.
843      */

844     private void setAuthenticationInformation(CVSRepositoryLocation other) {
845         if (other != this) {
846             // The instances differ so copy from the other location to this one
847
if (other.getUserInfoCached()) {
848                 // The user info is cached for the other instance
849
// so null all the values in this instance so the
850
// information is obtained from the cache
851
this.allowCaching = true;
852                 if (!userFixed) this.user = null;
853                 if (!passwordFixed) this.password = null;
854             } else {
855                 // The user info is not cached for the other instance so
856
// copy the authentication information into this instance
857
setAllowCaching(false); /* this will clear any cahced values */
858                 // Only copy the username and password if they are not fixed.
859
// (If they are fixed, they would be included in the location
860
// identifier and therefore must already match)
861
if (!other.userFixed)
862                     this.user = other.user;
863                 if (!other.passwordFixed)
864                     this.password = other.password;
865             }
866         }
867     }
868
869     /*
870      * The connection was sucessfully made. Update the cached
871      * repository location if it is a differnet instance than
872      * this location.
873      */

874     private void updateCachedLocation() {
875         try {
876             CVSRepositoryLocation known = (CVSRepositoryLocation)KnownRepositories.getInstance().getRepository(getLocation());
877             known.setAuthenticationInformation(this);
878         } catch (CVSException e) {
879             // Log the exception and continue
880
CVSProviderPlugin.log(e);
881         }
882     }
883     
884     /*
885      * Implementation of inherited toString()
886      */

887     public String JavaDoc toString() {
888         return getLocation(true);
889     }
890     
891     public boolean equals(Object JavaDoc o) {
892         if (this == o) return true;
893         if (!(o instanceof CVSRepositoryLocation)) return false;
894         return getLocation().equals(((CVSRepositoryLocation)o).getLocation());
895     }
896     public int hashCode() {
897         return getLocation().hashCode();
898     }
899     
900     /*
901      * Return the cached password from the keyring.
902      * Also, set the username of the receiver if the username is mutable
903      */

904     private String JavaDoc retrievePassword() {
905         Map map = Platform.getAuthorizationInfo(FAKE_URL, getLocation(), AUTH_SCHEME);
906         if (map != null) {
907             String JavaDoc username = (String JavaDoc) map.get(INFO_USERNAME);
908             if (username != null && isUsernameMutable())
909                 setUsername(username);
910             String JavaDoc password = (String JavaDoc) map.get(INFO_PASSWORD);
911             if (password != null) {
912                 return password;
913             }
914         }
915         return null;
916     }
917     /*
918      * @see IUserInfo#setPassword(String)
919      */

920     public void setPassword(String JavaDoc password) {
921         if (passwordFixed)
922             throw new UnsupportedOperationException JavaDoc();
923         // We set the password here but it will be cleared
924
// if the user info is cached using updateCache()
925
this.password = password;
926     }
927     
928     /*
929      * @see IUserInfo#setUsername(String)
930      */

931     public void setUsername(String JavaDoc user) {
932         if (userFixed)
933             throw new UnsupportedOperationException JavaDoc();
934         this.user = user;
935     }
936     
937     public void setUserMuteable(boolean muteable) {
938         userFixed = !muteable;
939     }
940     
941     public void setAllowCaching(boolean value) {
942         allowCaching = value;
943         if (allowCaching)
944             updateCache();
945         else
946             flushCache();
947     }
948     
949     public void updateCache() {
950         // Nothing to cache if the password is fixed
951
if (passwordFixed || ! allowCaching) return;
952         // Nothing to cache if the password is null and the user is fixed
953
if (password == null && userFixed) return;
954         if (updateCache(user, password)) {
955             // If the cache was updated, null the password field
956
// so we will obtain the password from the cache when needed
957
password = null;
958         }
959         ensurePreferencesStored();
960     }
961
962     /*
963      * Cache the user info in the keyring. Return true if the operation
964      * succeeded and false otherwise. If an error occurs, it will be logged.
965      */

966     private boolean updateCache(String JavaDoc username, String JavaDoc password) {
967         // put the password into the Platform map
968
Map map = Platform.getAuthorizationInfo(FAKE_URL, getLocation(), AUTH_SCHEME);
969         if (map == null) {
970             map = new java.util.HashMap JavaDoc(10);
971         }
972         if (username != null)
973             map.put(INFO_USERNAME, username);
974         if (password != null)
975             map.put(INFO_PASSWORD, password);
976         try {
977             Platform.addAuthorizationInfo(FAKE_URL, getLocation(), AUTH_SCHEME, map);
978         } catch (CoreException e) {
979             // We should probably wrap the CoreException here!
980
CVSProviderPlugin.log(e);
981             return false;
982         }
983         return true;
984     }
985     
986     /*
987      * Validate that the receiver contains valid information for
988      * making a connection. If the receiver contains valid
989      * information, the method returns. Otherwise, an exception
990      * indicating the problem is throw.
991      */

992     public void validateConnection(IProgressMonitor monitor) throws CVSException {
993         try {
994             monitor = Policy.monitorFor(monitor);
995             monitor.beginTask(null, 100);
996             ICVSFolder root = CVSWorkspaceRoot.getCVSFolderFor(ResourcesPlugin.getWorkspace().getRoot());
997             Session session = new Session(this, root, false /* output to console */);
998             session.open(Policy.subMonitorFor(monitor, 50), false /* read-only */);
999             try {
1000                IStatus status = Command.VERSION.execute(session, this, Policy.subMonitorFor(monitor, 50));
1001                // Log any non-ok status
1002
if (! status.isOK()) {
1003                    CVSProviderPlugin.log(status);
1004                }
1005            } finally {
1006                session.close();
1007                monitor.done();
1008            }
1009        } catch (CVSException e) {
1010            // If the validation failed, dispose of any cached info
1011
dispose();
1012            throw e;
1013        }
1014    }
1015    
1016    /**
1017     * Return the server platform type. It will be one of the following:
1018     * UNDETERMINED_PLATFORM: The platform has not been determined
1019     * CVS_SERVER: The platform is regular CVS server
1020     * CVSNT_SERVER: The platform in CVSNT
1021     * If UNDETERMINED_PLATFORM is returned, the platform can be determined
1022     * using the Command.VERSION command.
1023     */

1024    public int getServerPlatform() {
1025        return serverPlatform;
1026    }
1027    
1028    /**
1029     * This method is called from Command.VERSION to set the platform type.
1030     */

1031    public void setServerPlaform(int serverType) {
1032        // Second, check the code of the status itself to see if it is NT
1033
switch (serverType) {
1034            case CVS_SERVER:
1035            case CVSNT_SERVER:
1036            case UNKNOWN_SERVER:
1037            case UNSUPPORTED_SERVER:
1038                serverPlatform = serverType;
1039                break;
1040            default:
1041                // We had an error status with no info about the server.
1042
// Mark it as undetermined.
1043
serverPlatform = UNDETERMINED_PLATFORM;
1044        }
1045    }
1046    
1047    /**
1048     * @see ICVSRepositoryLocation#flushUserInfo()
1049     */

1050    public void flushUserInfo() {
1051        flushCache();
1052    }
1053    
1054    /*
1055     * Return the command string that is to be used by the EXT connection method.
1056     */

1057    String JavaDoc[] getExtCommand(String JavaDoc password) throws IOException JavaDoc {
1058        // Get the user specified connection parameters
1059
String JavaDoc CVS_RSH = CVSProviderPlugin.getPlugin().getCvsRshCommand();
1060        String JavaDoc CVS_RSH_PARAMETERS = CVSProviderPlugin.getPlugin().getCvsRshParameters();
1061        String JavaDoc CVS_SERVER = CVSProviderPlugin.getPlugin().getCvsServer();
1062        if(CVS_RSH == null || CVS_SERVER == null) {
1063            throw new IOException JavaDoc(CVSMessages.EXTServerConnection_varsNotSet);
1064        }
1065        
1066        // If there is only one token, assume it is the command and use the default parameters and order
1067
if (CVS_RSH_PARAMETERS == null || CVS_RSH_PARAMETERS.length() == 0) {
1068            if (port != USE_DEFAULT_PORT)
1069                throw new IOException JavaDoc(CVSMessages.EXTServerConnection_invalidPort);
1070            return new String JavaDoc[] {CVS_RSH, host, "-l", user, CVS_SERVER, INVOKE_SVR_CMD}; //$NON-NLS-1$
1071
}
1072
1073        // Substitute any variables for their appropriate values
1074
CVS_RSH_PARAMETERS = stringReplace(CVS_RSH_PARAMETERS, USER_VARIABLE, user);
1075        CVS_RSH_PARAMETERS = stringReplace(CVS_RSH_PARAMETERS, PASSWORD_VARIABLE, password);
1076        CVS_RSH_PARAMETERS = stringReplace(CVS_RSH_PARAMETERS, HOST_VARIABLE, host);
1077        CVS_RSH_PARAMETERS = stringReplace(CVS_RSH_PARAMETERS, PORT_VARIABLE, new Integer JavaDoc(port).toString());
1078
1079        // Build the command list to be sent to the OS.
1080
List commands = new ArrayList();
1081        commands.add(CVS_RSH);
1082        StringTokenizer tokenizer = new StringTokenizer(CVS_RSH_PARAMETERS);
1083        while (tokenizer.hasMoreTokens()) {
1084            String JavaDoc next = tokenizer.nextToken();
1085            commands.add(next);
1086        }
1087        commands.add(CVS_SERVER);
1088        commands.add(INVOKE_SVR_CMD);
1089        return (String JavaDoc[]) commands.toArray(new String JavaDoc[commands.size()]);
1090    }
1091
1092    /*
1093     * Replace all occurances of oldString with newString
1094     */

1095    private String JavaDoc stringReplace(String JavaDoc string, String JavaDoc oldString, String JavaDoc newString) {
1096        int index = string.toLowerCase().indexOf(oldString);
1097        if (index == -1) return string;
1098        return stringReplace(
1099            string.substring(0, index) + newString + string.substring(index + oldString.length()),
1100            oldString, newString);
1101    }
1102
1103    /**
1104     * Return the server message with the prefix removed.
1105     * Server aborted messages typically start with
1106     * "cvs server: ..."
1107     * "cvs [server aborted]: ..."
1108     * "cvs rtag: ..."
1109     */

1110    public String JavaDoc getServerMessageWithoutPrefix(String JavaDoc errorLine, String JavaDoc prefix) {
1111        String JavaDoc message = errorLine;
1112        int firstSpace = message.indexOf(' ');
1113        if(firstSpace != -1) {
1114            // remove the program name and the space
1115
message = message.substring(firstSpace + 1);
1116            // Quick fix to handle changes in server message format (see Bug 45138)
1117
if (prefix.startsWith("[")) { //$NON-NLS-1$
1118
// This is the server aborted message
1119
// Remove the pattern "[command_name aborted]: "
1120
int closingBracket = message.indexOf("]: "); //$NON-NLS-1$
1121
if (closingBracket == -1) return null;
1122                // get what is inside the brackets
1123
String JavaDoc realPrefix = message.substring(1, closingBracket);
1124                // check that there is two words and the second word is "aborted"
1125
int space = realPrefix.indexOf(' ');
1126                if (space == -1) return null;
1127                if (realPrefix.indexOf(' ', space +1) != -1) return null;
1128                if (!realPrefix.substring(space +1).equals("aborted")) return null; //$NON-NLS-1$
1129
// It's a match, return the rest of the line
1130
message = message.substring(closingBracket + 2);
1131                if (message.charAt(0) == ' ') {
1132                    message = message.substring(1);
1133                }
1134                return message;
1135            } else {
1136                // This is the server command message
1137
// Remove the pattern "command_name: "
1138
int colon = message.indexOf(": "); //$NON-NLS-1$
1139
if (colon == -1) return null;
1140                // get what is before the colon
1141
String JavaDoc realPrefix = message.substring(0, colon);
1142                // ensure that it is a single word
1143
if (realPrefix.indexOf(' ') != -1) return null;
1144                message = message.substring(colon + 1);
1145                if (message.charAt(0) == ' ') {
1146                    message = message.substring(1);
1147                }
1148                return message;
1149            }
1150        }
1151        // This is not a server message with the desired prefix
1152
return null;
1153    }
1154
1155    /* (non-Javadoc)
1156     * @see org.eclipse.team.internal.ccvs.core.ICVSRepositoryLocation#getUserAuthenticator()
1157     */

1158    public IUserAuthenticator getUserAuthenticator() {
1159        return getAuthenticator();
1160    }
1161    
1162    /* (non-Javadoc)
1163     * @see org.eclipse.team.internal.ccvs.core.ICVSRepositoryLocation#setUserAuthenticator()
1164     */

1165    public void setUserAuthenticator(IUserAuthenticator authenticator) {
1166        CVSRepositoryLocation.authenticator = authenticator;
1167    }
1168    
1169    /*
1170     * Return the preferences node for this repository
1171     */

1172    public Preferences getPreferences() {
1173        if (!hasPreferences()) {
1174            ensurePreferencesStored();
1175        }
1176        return internalGetPreferences();
1177    }
1178    
1179    private Preferences internalGetPreferences() {
1180        return getParentPreferences().node(getPreferenceName());
1181    }
1182    
1183    private boolean hasPreferences() {
1184        try {
1185            return getParentPreferences().nodeExists(getPreferenceName());
1186        } catch (BackingStoreException e) {
1187            CVSProviderPlugin.log(IStatus.ERROR, NLS.bind(CVSMessages.CVSRepositoryLocation_74, new String JavaDoc[] { getLocation(true) }), e);
1188            return false;
1189        }
1190    }
1191    
1192    /**
1193     * Return a unique name that identifies this location but
1194     * does not contain any slashes (/). Also, do not use ':'.
1195     * Although a valid path character, the initial core implementation
1196     * didn't handle it well.
1197     */

1198    private String JavaDoc getPreferenceName() {
1199        return getLocation().replace('/', '%').replace(':', '%');
1200    }
1201
1202    public void storePreferences() {
1203        Preferences prefs = internalGetPreferences();
1204        // Must store at least one preference in the node
1205
prefs.put(PREF_LOCATION, getLocation());
1206        flushPreferences();
1207    }
1208    
1209    private void flushPreferences() {
1210        try {
1211            internalGetPreferences().flush();
1212        } catch (BackingStoreException e) {
1213            CVSProviderPlugin.log(IStatus.ERROR, NLS.bind(CVSMessages.CVSRepositoryLocation_75, new String JavaDoc[] { getLocation(true) }), e);
1214        }
1215    }
1216
1217    private void ensurePreferencesStored() {
1218        if (!hasPreferences()) {
1219            storePreferences();
1220        }
1221    }
1222
1223    /* (non-Javadoc)
1224     * @see org.eclipse.team.internal.ccvs.core.ICVSRepositoryLocation#getUserInfoCached()
1225     */

1226    public boolean getUserInfoCached() {
1227        Map map = Platform.getAuthorizationInfo(FAKE_URL, getLocation(), AUTH_SCHEME);
1228        if (map != null) {
1229            String JavaDoc password = (String JavaDoc) map.get(INFO_PASSWORD);
1230            return (password != null);
1231        }
1232        return false;
1233    }
1234}
1235
Popular Tags