package org.wyona.yarep.core.impl.svn; import java.io.File; import java.io.InputStream; import java.io.OutputStream; import java.io.Reader; import java.io.Writer; import java.util.Date; import org.apache.avalon.framework.configuration.Configuration; import org.apache.avalon.framework.configuration.ConfigurationException; import org.apache.log4j.Category; import org.tmatesoft.svn.core.SVNException; import org.tmatesoft.svn.core.SVNURL; import org.tmatesoft.svn.core.wc.SVNRevision; import org.tmatesoft.svn.core.wc.SVNStatusType; import org.wyona.commons.io.FileUtil; import org.wyona.yarep.core.NoSuchNodeException; import org.wyona.yarep.core.Path; import org.wyona.yarep.core.RepositoryException; import org.wyona.yarep.core.Storage; import org.wyona.yarep.core.UID; /** * Subversion based storage implementation. * * Configuration parameters: * - src: URL of subversion repository (including repository path) * - workingdir: directory where the working copy of the repository will be checked out * - username: svn username * - password: svn password * * When the storage is started, the working copy will be updated or checked out. * The working dir will be created automatically in case it does not exist. In * the current implementation, read requests don't do an update before reading for * performance reasons. * Write requests are committed when the streams are being closed. * Locking is not implemented yet. */ public class SVNStorage implements Storage { private static Category log = Category.getInstance(SVNStorage.class); protected SVNClient svnClient; protected SVNURL svnRepoUrl; protected File svnWorkingDir; private boolean offline = false; /** * Reads repository configuration and checks out / updates the local working * copy. * TODO: checkout/update should be moved to a separate init() method. */ public void readConfig(Configuration storageConfig, File repoConfigFile) throws RepositoryException { try { Configuration contentConfig = storageConfig.getChild("content", false); svnRepoUrl = SVNURL.parseURIEncoded(contentConfig.getAttribute("src")); svnWorkingDir = new File(contentConfig.getAttribute("workdir")); if (!svnWorkingDir.isAbsolute()) { svnWorkingDir = FileUtil.file(repoConfigFile.getParent(), svnWorkingDir.toString()); } String username = contentConfig.getAttribute("username"); String password = contentConfig.getAttribute("password"); offline = contentConfig.getAttributeAsBoolean("offline", false); log.debug("SVN host URL: " + svnRepoUrl.toString()); log.debug("SVN working dir: " + svnWorkingDir.getAbsolutePath()); if (!svnWorkingDir.isDirectory()) { svnWorkingDir.mkdirs(); } svnClient = new SVNClient(username, password); // check out or update repository: if (offline) { log.warn("Config is set offline=\"true\" (" + repoConfigFile + ")"); } else { if (svnWorkingDir.listFiles().length == 0) { log.info("checking out repository " + svnRepoUrl + " to " + svnWorkingDir); long rev = svnClient.checkout(svnRepoUrl, svnWorkingDir); log.info("checked out revision " + rev); } else { log.info("updating " + svnWorkingDir); long rev = svnClient.update(svnWorkingDir, SVNRevision.HEAD, true); svnClient.checkStatus(svnWorkingDir); log.info("updated to revison " + rev); } } } catch (ConfigurationException e) { log.error(e); throw new RepositoryException("Could not load repository configuration: " + repoConfigFile + ": " + e.getMessage(), e); } catch (SVNException e) { log.error(e); log.error("Error message: " + e.getErrorMessage()); log.error("Error code: " + e.getErrorMessage().getErrorCode()); throw new RepositoryException("Could not checkout/update svn repository (" + repoConfigFile + "). One might want to set attribute offline=\"true\": " + e.getMessage(), e); } } /** * */ public OutputStream getOutputStream(UID uid, Path path) throws RepositoryException { File file = getFile(uid); return new SVNRepositoryOutputStream(file, svnClient); } /** * */ public InputStream getInputStream(UID uid, Path path) throws RepositoryException { File file = getFile(uid); return new SVNRepositoryInputStream(file); } /** * */ public long getLastModified(UID uid, Path path) throws RepositoryException { File file = getFile(uid); try { SVNStatusType status = svnClient.getStatus(file); if (log.isDebugEnabled()) log.debug("SVN status: " + status); if (status == SVNStatusType.STATUS_UNVERSIONED || status == SVNStatusType.STATUS_ADDED ) { return file.lastModified(); } Date date = svnClient.getCommittedDate(file); return date.getTime(); } catch (SVNException e) { log.error(e); throw new RepositoryException("Could not get committed date of " + file.getAbsolutePath() + ": " + e.getMessage(), e); } } /** * */ public long getSize(UID uid, Path path) throws RepositoryException { File file = getFile(uid); return file.length(); } /** * */ public boolean delete(UID uid, Path path) throws RepositoryException { File file = getFile(uid); try { svnClient.delete(file); svnClient.commit(file, "yarep automated commit"); return true; } catch (SVNException e) { log.error(e); //throw new RepositoryException("Could not delete " + file.getAbsolutePath() // + ": " + e.getMessage(), e); return false; } } /** * */ public String[] getRevisions(UID uid, Path path) throws RepositoryException { File file = getFile(uid); try { //long[] revNumbers = svnClient.getRevisionNumbers(file); //String[] revisions = new String[revNumbers.length]; //for (int i=0; i<revNumbers.length; i++) revisions[i] = String.valueOf(revNumbers[i]); //return revisions; return svnClient.getRevisionStrings(file); } catch (SVNException e) { log.error(e); throw new RepositoryException("Could not get revisions of " + file.getAbsolutePath() + ": " + e.getMessage(), e); } } /** * @deprecated */ public Writer getWriter(UID uid, Path path) { return null; } /** * @deprecated */ public Reader getReader(UID uid, Path path) throws NoSuchNodeException { return null; } protected File getFile(UID uid) { return new File(svnWorkingDir.getAbsolutePath() + File.separator + uid.toString()); } /** * */ public boolean exists(UID uid, Path path) { File file = getFile(uid); try { SVNStatusType status = svnClient.getStatus(file); if (log.isDebugEnabled()) log.debug("SVN status: " + status); return status != SVNStatusType.STATUS_DELETED && status != SVNStatusType.STATUS_IGNORED && status != SVNStatusType.STATUS_NONE && status != SVNStatusType.STATUS_UNVERSIONED ; } catch (SVNException e) { log.error(e); throw new RuntimeException("Could not get status of " + file.getAbsolutePath() + ": " + e.getMessage(), e); } } }