package hudson.scm;
import hudson.XmlFile;
import hudson.model.AbstractProject;
import hudson.model.Saveable;
import hudson.remoting.Channel;
import hudson.scm.SubversionSCM.DescriptorImpl.Credential;
import hudson.scm.SubversionSCM.DescriptorImpl.RemotableSVNAuthenticationProvider;
import jenkins.model.Jenkins;
import org.tmatesoft.svn.core.SVNURL;
import java.io.File;
import java.io.IOException;
import java.util.Hashtable;
import java.util.Map;
import java.util.logging.Logger;
import static java.util.logging.Level.INFO;
/**
* Persists the credential per job. This object is remotable.
*
* @author Kohsuke Kawaguchi
*/
final class PerJobCredentialStore implements Saveable, RemotableSVNAuthenticationProvider {
private static final long serialVersionUID = 8509067873170016632L;
private final transient AbstractProject<?,?> project;
/**
* SVN authentication realm to its associated credentials, scoped to this project.
*/
private final Map<String,Credential> credentials = new Hashtable<String,Credential>();
PerJobCredentialStore(AbstractProject<?,?> project) {
this.project = project;
// read existing credential
XmlFile xml = getXmlFile();
try {
if (xml.exists())
xml.unmarshal(this);
} catch (IOException e) {
// ignore the failure to unmarshal, or else we'll never get through beyond this point.
LOGGER.log(INFO,"Failed to retrieve Subversion credentials from "+xml,e);
}
}
public synchronized Credential get(String realm) {
return credentials.get(realm);
}
public Credential getCredential(SVNURL url, String realm) {
return get(realm);
}
public void acknowledgeAuthentication(String realm, Credential cred) {
try {
acknowledge(realm, cred);
} catch (IOException e) {
LOGGER.log(INFO,"Failed to persist the credentials",e);
}
}
public synchronized void acknowledge(String realm, Credential cred) throws IOException {
Credential old = cred==null ? credentials.remove(realm) : credentials.put(realm, cred);
// save only if there was a change
if (old==null && cred==null) return;
if (old==null || cred==null || !old.equals(cred))
save();
}
public synchronized void save() throws IOException {
IS_SAVING.set(Boolean.TRUE);
try {
getXmlFile().write(this);
} finally {
IS_SAVING.remove();
}
}
private XmlFile getXmlFile() {
return new XmlFile(new File(project.getRootDir(),"subversion.credentials"));
}
/*package*/ synchronized boolean isEmpty() {
return credentials.isEmpty();
}
/**
* When sent to the remote node, send a proxy.
*/
private Object writeReplace() {
if (IS_SAVING.get()!=null) return this;
Channel c = Channel.current();
return c==null ? this : c.export(RemotableSVNAuthenticationProvider.class, this);
}
private static final Logger LOGGER = Logger.getLogger(PerJobCredentialStore.class.getName());
/**
* Used to remember the context. If we are persisting, we don't want to persist a proxy,
* even if that happens in the context of a remote call.
*/
private static final ThreadLocal<Boolean> IS_SAVING = new ThreadLocal<Boolean>();
/*package*/ void migrateCredentials(SubversionSCM.DescriptorImpl descriptor) throws IOException {
for (Map.Entry<String, Credential> e : credentials.entrySet()) {
descriptor.migrateCredentials(project, e.getKey(), e.getValue());
}
}
}