/*******************************************************************************
* Copyright (c) 2006-2013, Cloudsmith Inc.
* The code, documentation and other materials contained herein have been
* licensed under the Eclipse Public License - v 1.0 by the copyright holder
* listed above, as the Initial Contributor under such license. The text of
* such license is available at www.eclipse.org.
******************************************************************************/
package org.eclipse.buckminster.subversive.internal;
import java.io.FileNotFoundException;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.Collection;
import java.util.Date;
import java.util.Map;
import java.util.UUID;
import org.eclipse.buckminster.core.RMContext;
import org.eclipse.buckminster.core.version.VersionSelector;
import org.eclipse.buckminster.runtime.BuckminsterException;
import org.eclipse.buckminster.subversion.GenericSession;
import org.eclipse.buckminster.subversion.ISubversionCache;
import org.eclipse.buckminster.subversion.ISvnEntryHelper;
import org.eclipse.buckminster.subversion.RepositoryAccess;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.NullProgressMonitor;
import org.eclipse.team.svn.core.SVNTeamPlugin;
import org.eclipse.team.svn.core.connector.ISVNConnector;
import org.eclipse.team.svn.core.connector.ISVNCredentialsPrompt;
import org.eclipse.team.svn.core.connector.ISVNProgressMonitor;
import org.eclipse.team.svn.core.connector.SVNDepth;
import org.eclipse.team.svn.core.connector.SVNEntry;
import org.eclipse.team.svn.core.connector.SVNEntryRevisionReference;
import org.eclipse.team.svn.core.connector.SVNRevision;
import org.eclipse.team.svn.core.extension.CoreExtensionsManager;
import org.eclipse.team.svn.core.extension.options.IOptionProvider;
import org.eclipse.team.svn.core.resource.IRepositoryLocation;
import org.eclipse.team.svn.core.svnstorage.SVNRemoteStorage;
import org.eclipse.team.svn.core.svnstorage.SVNRepositoryLocation.BaseCredentialsPromptWrapper;
import org.eclipse.team.svn.core.utility.SVNUtility;
/**
* <p>
* The Subversive repository will be able to use reader checks if a repository
* contains the three recommended directories <code>trunk</code>,
* <code>tags</code>, and <code>branches</code>. A missing <code>tags</code>
* directory is interpreted as no <code>tags</code>. A missing
* <code>branches</code> directory is interpreted as no branches. In order to
* use <code>trunk</code>, <code>tags</code>, and <code>branches</code>
* repository identifier must contain the path element <code>trunk</code>.
* Anything that follows the <code>trunk</code> element in the path will be
* considered a <code>module</code> path. If no <code>trunk</code> element is
* present in the path, the last element will be considered the
* <code>module</code>
* </p>
* <p>
* The repository URL may also contain a query part that in turn may have four
* different flags:
* <dl>
* <dt>moduleBeforeTag</dt>
* <dd>When resolving a tag, put the module name between the <code>tags</code>
* directory and the actual tag</dd>
* <dt>moduleAfterTag</dt>
* <dd>When resolving a tag, append the module name after the actual tag</dd>
* <dt>moduleBeforeBranch</dt>
* <dd>When resolving a branch, put the module name between the
* <code>branches</code> directory and the actual branch</dd>
* <dt>moduleAfterBranch</dt>
* <dd>When resolving a branch, append the module name after the actual branch</dd>
* </dl>
* </p>
* A fragment in the repository URL will be treated as a sub-module. It will be
* appended at the end of the resolved URL.
*
* @author Thomas Hallgren
* @author Guillaume Chatelet
* @author Lorenzo Bettini -
* https://bugs.eclipse.org/bugs/show_bug.cgi?id=428301
*/
public class SubversiveSession extends GenericSession<IRepositoryLocation, SVNEntry, SVNRevision> {
private class UnattendedPromptUserPassword implements ISVNCredentialsPrompt {
private int promptLimit = 3;
@Override
public Answer askTrustSSLServer(Object location, String info, boolean allowPermanently) {
return ISVNCredentialsPrompt.DEFAULT_PROMPT.askTrustSSLServer(location, info, allowPermanently);
}
@Override
public String getPassword() {
return password;
}
@Override
public String getProxyHost() {
return ISVNCredentialsPrompt.DEFAULT_PROMPT.getProxyHost();
}
@Override
public String getProxyPassword() {
return ISVNCredentialsPrompt.DEFAULT_PROMPT.getProxyPassword();
}
@Override
public int getProxyPort() {
return ISVNCredentialsPrompt.DEFAULT_PROMPT.getProxyPort();
}
@Override
public String getProxyUserName() {
return ISVNCredentialsPrompt.DEFAULT_PROMPT.getProxyUserName();
}
@Override
public String getRealmToSave() {
return ISVNCredentialsPrompt.DEFAULT_PROMPT.getRealmToSave();
}
@Override
public int getSSHPort() {
return ISVNCredentialsPrompt.DEFAULT_PROMPT.getSSHPort();
}
@Override
public String getSSHPrivateKeyPassphrase() {
return ISVNCredentialsPrompt.DEFAULT_PROMPT.getSSHPrivateKeyPassphrase();
}
@Override
public String getSSHPrivateKeyPath() {
return ISVNCredentialsPrompt.DEFAULT_PROMPT.getSSHPrivateKeyPath();
}
@Override
public String getSSLClientCertPassword() {
return ISVNCredentialsPrompt.DEFAULT_PROMPT.getSSLClientCertPassword();
}
@Override
public String getSSLClientCertPath() {
return ISVNCredentialsPrompt.DEFAULT_PROMPT.getSSLClientCertPath();
}
@Override
public String getUsername() {
return username;
}
@Override
public boolean isProxyAuthenticationEnabled() {
return ISVNCredentialsPrompt.DEFAULT_PROMPT.isProxyAuthenticationEnabled();
}
@Override
public boolean isProxyEnabled() {
return ISVNCredentialsPrompt.DEFAULT_PROMPT.isProxyEnabled();
}
@Override
public boolean isSaveCredentialsEnabled() {
return ISVNCredentialsPrompt.DEFAULT_PROMPT.isSaveCredentialsEnabled();
}
@Override
public boolean isSaveProxyPassword() {
return ISVNCredentialsPrompt.DEFAULT_PROMPT.isSaveProxyPassword();
}
@Override
public boolean isSSHPrivateKeyPassphraseSaved() {
return ISVNCredentialsPrompt.DEFAULT_PROMPT.isSSHPrivateKeyPassphraseSaved();
}
@Override
public boolean isSSHPublicKeySelected() {
return ISVNCredentialsPrompt.DEFAULT_PROMPT.isSSHPublicKeySelected();
}
@Override
public boolean isSSLAuthenticationEnabled() {
return ISVNCredentialsPrompt.DEFAULT_PROMPT.isSSLAuthenticationEnabled();
}
@Override
public boolean isSSLSavePassphrase() {
return ISVNCredentialsPrompt.DEFAULT_PROMPT.isSSLSavePassphrase();
}
@Override
public boolean prompt(Object arg0, String arg1) {
// We support the password prompt only if we actually know the
// password
// and only a limited number of times
//
return password != null && --promptLimit >= 0;
}
@Override
public boolean promptProxy(Object location) {
return ISVNCredentialsPrompt.DEFAULT_PROMPT.promptProxy(location);
}
@Override
public boolean promptSSH(Object location, String realm) {
return ISVNCredentialsPrompt.DEFAULT_PROMPT.promptSSH(location, realm);
}
@Override
public boolean promptSSL(Object location, String realm) {
return ISVNCredentialsPrompt.DEFAULT_PROMPT.promptSSL(location, realm);
}
}
private static final SubversiveEntryHelper HELPER = new SubversiveEntryHelper();
private static final SVNEntry[] emptyFolder = new SVNEntry[0];
private static final String UNKNOWN_ROOT_PREFIX = SubversiveSession.class.getPackage().getName() + ".root."; //$NON-NLS-1$
public static URI getURIParent(URI uri) {
if (uri == null)
return null;
String path = uri.toString();
if (path == null)
return null;
int lastSlash = path.lastIndexOf('/');
if (lastSlash == path.length() - 1 && lastSlash > 0) {
path = path.substring(0, path.length() - 1);
lastSlash = path.lastIndexOf('/');
}
if (lastSlash < 0)
return null;
String parentPath = path.substring(0, lastSlash);
try {
return new URI(parentPath);
} catch (URISyntaxException e) {
return null;
}
}
private ISVNConnector proxy;
/**
* @param repositoryURI
* The string representation of the URI that appoints the trunk
* of repository module. No branch or tag information must be
* included.
* @param branch
* The desired branch or <code>null</code> if not applicable.
* @param tag
* The desired tag or <code>null</code> if not applicable.
* @param revision
* The desired revision or <code>-1</code> of not applicable
* @param timestamp
* The desired timestamp or <code>null</code> if not applicable
* @param context
* The context used for the resolution/materialization operation
* @throws CoreException
*/
public SubversiveSession(String repositoryURI, VersionSelector branchOrTag, long revision, Date timestamp, RMContext context)
throws CoreException {
super(repositoryURI, branchOrTag, revision, timestamp, context);
}
@Override
public void close() {
proxy.dispose();
}
@Override
protected void createRoots(Collection<RepositoryAccess> sourceRoots) throws CoreException {
final SVNRemoteStorage storage = SVNRemoteStorage.instance();
for (RepositoryAccess root : sourceRoots) {
IRepositoryLocation location = storage.newRepositoryLocation();
location.setUrl(root.getSvnURL().toString());
location.setPassword(root.getPassword());
location.setUsername(root.getUser());
storage.addRepositoryLocation(location);
}
try {
storage.saveConfiguration();
} catch (Exception e) {
throw BuckminsterException.wrap(e);
}
}
@Override
protected ISubversionCache<SVNEntry> getCache(Map<UUID, Object> userCache) {
assert (cache == null);
final SubversiveCache svnCache = new SubversiveCache();
svnCache.initialize(userCache);
return svnCache;
}
public SVNEntry getDirEntry(URI uri, SVNRevision revision, IProgressMonitor monitor) throws CoreException {
final URI parent = getURIParent(uri);
if (parent == null)
return null;
final String path = uri.getPath();
final String entryPath = path.substring(path.lastIndexOf('/') + 1);
final SVNEntry[] entries = listFolder(parent, monitor);
for (SVNEntry entry : entries)
if (entryPath.equals(entry.path))
return entry;
return null;
}
@Override
protected SVNEntry[] getEmptyEntryList() {
return emptyFolder;
}
@Override
protected IRepositoryLocation[] getKnownRepositories() {
return SVNRemoteStorage.instance().getRepositoryLocations();
}
@Override
public long getLastChangeNumber() throws CoreException {
try {
URI svnURL = getSVNUrl(null);
SVNEntry root = getDirEntry(svnURL, getRevision(), null);
if (root == null)
throw new FileNotFoundException(svnURL.toString());
return root.revision;
} catch (Exception e) {
throw BuckminsterException.wrap(e);
}
}
@Override
public Date getLastTimestamp() throws CoreException {
try {
URI svnURL = getSVNUrl(null);
SVNEntry root = getDirEntry(svnURL, getRevision(), new NullProgressMonitor());
if (root == null)
throw new FileNotFoundException(svnURL.toString());
return new Date(root.date);
} catch (Exception e) {
throw BuckminsterException.wrap(e);
}
}
@Override
public SVNEntry getRootEntry(IProgressMonitor monitor) throws CoreException {
return getDirEntry(getSVNUrl(null), getRevision(), monitor);
}
@Override
protected String getRootUrl(IRepositoryLocation location) {
return location.getRoot().getUrl();
}
@Override
public ISvnEntryHelper<SVNEntry> getSvnEntryHelper() {
return HELPER;
}
ISVNConnector getSVNProxy() {
if (proxy == null)
proxy = CoreExtensionsManager.instance().getSVNConnectorFactory().createConnector();
return proxy;
}
@Override
public SVNRevision getSVNRevision(long revision, Date timestamp) {
if (revision == -1) {
if (timestamp == null)
return SVNRevision.HEAD;
return SVNRevision.fromDate(timestamp.getTime());
}
if (timestamp != null)
throw new IllegalArgumentException(org.eclipse.buckminster.subversion.Messages.svn_session_cannot_use_both_timestamp_and_revision_number);
return SVNRevision.fromNumber(revision);
}
@Override
protected String getUnknownRootPrefix() {
return UNKNOWN_ROOT_PREFIX;
}
@Override
protected void initializeSvn(RMContext context, URI ourRoot, IRepositoryLocation bestMatch) {
final ISVNConnector svnProxy = getSVNProxy();
svnProxy.setCommitMissingFiles(false);
svnProxy.setSSLCertificateCacheEnabled(true);
svnProxy.setUsername(username);
svnProxy.setPassword(password);
svnProxy.setCredentialsCacheEnabled(true);
if (bestMatch == null) {
addUnknownRoot(context.getBindingProperties(), new RepositoryAccess(ourRoot, username, password));
} else {
SVNUtility.configureProxy(svnProxy, repositoryLocation);
IOptionProvider optionProvider = SVNTeamPlugin.instance().getOptionProvider();
ISVNCredentialsPrompt externalPrompt = optionProvider.getCredentialsPrompt();
if (externalPrompt != null)
svnProxy.setPrompt(new BaseCredentialsPromptWrapper(externalPrompt, repositoryLocation));
}
// Add the UnattendedPromptUserPassword callback only in case
// the authentication data (at least the username) is actually
// specified in the URL
//
if (username != null)
svnProxy.setPrompt(new UnattendedPromptUserPassword());
}
@Override
protected SVNEntry[] innerListFolder(URI url, IProgressMonitor monitor) throws Exception {
ISVNProgressMonitor svnMon = SimpleMonitorWrapper.beginTask(monitor, 100);
return SVNUtility.list(proxy, new SVNEntryRevisionReference(url.toString(), getRevision(), getRevision()), SVNDepth.IMMEDIATES,
SVNEntry.Fields.ALL, ISVNConnector.Options.NONE, svnMon);
}
@Override
public String toString() {
try {
return getSVNUrl(null).toString();
} catch (CoreException e) {
return super.toString();
}
}
}