+package org.argeo.slc.web.ajaxplorer.svn;\r
+\r
+import java.io.File;\r
+\r
+import org.argeo.slc.web.ajaxplorer.AjxpDriverException;\r
+import org.argeo.slc.web.ajaxplorer.file.FileDriver;\r
+import org.springframework.beans.factory.BeanNameAware;\r
+import org.tmatesoft.svn.core.SVNException;\r
+import org.tmatesoft.svn.core.SVNURL;\r
+import org.tmatesoft.svn.core.internal.io.fs.FSRepositoryFactory;\r
+import org.tmatesoft.svn.core.io.SVNRepository;\r
+import org.tmatesoft.svn.core.wc.SVNClientManager;\r
+import org.tmatesoft.svn.core.wc.SVNInfo;\r
+import org.tmatesoft.svn.core.wc.SVNRevision;\r
+import org.tmatesoft.svn.core.wc.admin.SVNAdminClient;\r
+\r
+public class SvnDriver extends FileDriver implements BeanNameAware {\r
+ private final static String DEFAULT_DATA_PATH = System\r
+ .getProperty("user.home")\r
+ + File.separator + "AjaXplorerArchiver" + File.separator + "data";\r
+\r
+ private final static long WRITE_ACTION_TIMEOUT = 10 * 60 * 1000;\r
+\r
+ private SVNURL baseUrl;\r
+ private SVNClientManager manager;\r
+\r
+ private String beanName;\r
+\r
+ private boolean isInWriteAction = false;\r
+\r
+ public void init() {\r
+ FSRepositoryFactory.setup();\r
+ manager = SVNClientManager.newInstance();\r
+\r
+ String basePath = getBasePath();\r
+ if (basePath != null) {\r
+ File baseDir = new File(basePath);\r
+ if (baseDir.exists()) {// base dir exists\r
+ boolean shouldCheckOut = baseDirChecks(baseDir);\r
+ if (shouldCheckOut) {\r
+ checkOut(baseDir);\r
+ }\r
+ } else {\r
+ checkOut(baseDir);\r
+ }\r
+ } else {\r
+ String defaultBasePath = DEFAULT_DATA_PATH + File.separator\r
+ + "svnwc" + File.separator + beanName;\r
+ log.warn("No base path provided, use " + defaultBasePath);\r
+ setBasePath(defaultBasePath);\r
+\r
+ File baseDir = new File(getBasePath());\r
+ if (!baseDir.exists()) {\r
+ baseDir.mkdirs();\r
+ }\r
+\r
+ if (baseDirChecks(baseDir)) {\r
+ if (getBaseUrl() == null) {\r
+ String defaultRepoPath = DEFAULT_DATA_PATH + File.separator\r
+ + "svnrepos" + File.separator + beanName;\r
+ log.warn("No base URL found, create repository at "\r
+ + defaultRepoPath);\r
+ baseUrl = createRepository(new File(defaultRepoPath));\r
+ }\r
+ checkOut(new File(getBasePath()));\r
+ }\r
+ }\r
+ log.info("SVN driver initialized with base url " + getBaseUrl()\r
+ + " and base path " + getBasePath());\r
+ }\r
+\r
+ /** Builds a SVN URL. */\r
+ public SVNURL getSVNURL(String relativePath) {\r
+ try {\r
+ return baseUrl.appendPath(relativePath, false);\r
+ } catch (SVNException e) {\r
+ throw new AjxpDriverException(\r
+ "Cannot build URL from relative path " + relativePath\r
+ + " and base url " + baseUrl);\r
+ }\r
+ }\r
+\r
+ public SVNRepository getRepository() {\r
+ try {\r
+ return manager.createRepository(baseUrl, true);\r
+ } catch (SVNException e) {\r
+ throw new AjxpDriverException("Cannot create repository for "\r
+ + baseUrl, e);\r
+ }\r
+ }\r
+\r
+ /**\r
+ * Verifies that the provided existing base dir is ok and whether one should\r
+ * check out. Set the base url from the working copy.\r
+ * \r
+ * @return whether one should check out.\r
+ */\r
+ protected boolean baseDirChecks(File baseDir) {\r
+ if (!baseDir.isDirectory()) {\r
+ throw new AjxpDriverException("Base path " + baseDir\r
+ + " is not a directory.");\r
+ }\r
+\r
+ try {// retrieves SVN infos\r
+ SVNInfo info = manager.getWCClient().doInfo(baseDir,\r
+ SVNRevision.WORKING);\r
+ SVNURL baseUrlTemp = info.getURL();\r
+ if (baseUrl != null) {\r
+ if (!baseUrl.equals(baseUrlTemp)) {\r
+ throw new AjxpDriverException(\r
+ "SVN URL of the working copy "\r
+ + baseUrlTemp\r
+ + " is not compatible with provided baseUrl "\r
+ + baseUrl);\r
+ }\r
+ } else {\r
+ this.baseUrl = baseUrlTemp;\r
+ }\r
+ return false;\r
+ } catch (SVNException e) {// no info retrieved\r
+ log\r
+ .warn("Could not retrieve SVN info from "\r
+ + baseDir\r
+ + "("\r
+ + e.getMessage()\r
+ + "). Guess that it is and empty dir and try to check out from provided URL.");\r
+ if (baseDir.listFiles().length != 0) {\r
+ throw new AjxpDriverException("Base dir " + baseDir\r
+ + " is not a working copy and not an empty dir.");\r
+ }\r
+ return true;\r
+ }\r
+ }\r
+\r
+ protected void checkOut(File baseDir) {\r
+ if (getBaseUrl() == null) {\r
+ throw new AjxpDriverException(\r
+ "No SVN URL provided, cannot check out.");\r
+ }\r
+\r
+ // Make sure directory exists\r
+ baseDir.mkdirs();\r
+\r
+ try {\r
+ long revision = manager.getUpdateClient().doCheckout(getBaseUrl(),\r
+ baseDir, SVNRevision.UNDEFINED, SVNRevision.HEAD, true);\r
+ log.info("Checked out from " + baseUrl + " to " + baseDir\r
+ + " at revision " + revision);\r
+ } catch (SVNException e) {\r
+ throw new AjxpDriverException("Cannot check out from " + baseUrl\r
+ + " to " + baseDir, e);\r
+ }\r
+ }\r
+\r
+ protected SVNURL createRepository(File repoDir) {\r
+ try {\r
+ SVNAdminClient adminClient = manager.getAdminClient();\r
+ return adminClient.doCreateRepository(repoDir, null, true, false);\r
+ } catch (SVNException e) {\r
+ throw new AjxpDriverException("Cannot create repository at "\r
+ + repoDir, e);\r
+ }\r
+ }\r
+\r
+ private void updateIfRequired(File dir) {\r
+ try {\r
+ SVNInfo wcInfo = manager.getWCClient().doInfo(getBaseDir(),\r
+ SVNRevision.WORKING);\r
+ SVNRevision wcRev = wcInfo.getRevision();\r
+ SVNInfo repoInfo = manager.getWCClient().doInfo(getBaseUrl(),\r
+ null, SVNRevision.HEAD);\r
+ SVNRevision repoRev = repoInfo.getRevision();\r
+\r
+ if (log.isTraceEnabled())\r
+ log\r
+ .trace("WC Revision=" + wcRev + ", Repo Revision="\r
+ + repoRev);\r
+\r
+ if (!wcRev.equals(repoRev)) {\r
+ log.debug("Update working copy from revision " + wcRev\r
+ + " to revision " + repoRev);\r
+ manager.getUpdateClient().doUpdate(getBaseDir(),\r
+ SVNRevision.HEAD, true);\r
+ }\r
+ } catch (SVNException e) {\r
+ throw new AjxpDriverException("Cannot update working copy "\r
+ + getBaseDir(),e);\r
+ }\r
+ }\r
+\r
+ public synchronized void beginWriteAction(File dir) {\r
+ if (isInWriteAction) {\r
+ try {\r
+ wait(WRITE_ACTION_TIMEOUT);\r
+ } catch (InterruptedException e) {\r
+ // silent\r
+ }\r
+ if (isInWriteAction) {\r
+ throw new AjxpDriverException(\r
+ "Still in write action after timeout "\r
+ + WRITE_ACTION_TIMEOUT + " ms.");\r
+ }\r
+ }\r
+\r
+ isInWriteAction = true;\r
+ updateIfRequired(dir);\r
+ }\r
+\r
+ public synchronized void completeWriteAction(File dir) {\r
+ isInWriteAction = false;\r
+ notifyAll();\r
+ }\r
+\r
+ public synchronized void rollbackWriteAction(File dir) {\r
+ // TODO: revert?\r
+ isInWriteAction = false;\r
+ notifyAll();\r
+ }\r
+ \r
+ public void commitAll(String message) throws SVNException{\r
+ if(log.isTraceEnabled())\r
+ log.trace("SVN Commit: " + getBaseDir());\r
+ manager.getCommitClient().doCommit(new File[] { getBaseDir() }, true,\r
+ message, true, true);\r
+\r
+ }\r
+\r
+ /** Spring bean name, set at initialization. */\r
+ public void setBeanName(String beanName) {\r
+ this.beanName = beanName;\r
+ }\r
+\r
+ public void setBaseUrl(String baseUrl) {\r
+ try {\r
+ this.baseUrl = SVNURL.parseURIDecoded(baseUrl);\r
+ } catch (SVNException e) {\r
+ throw new AjxpDriverException("Cannot parse SVN URL " + baseUrl, e);\r
+ }\r
+ }\r
+\r
+ public SVNURL getBaseUrl() {\r
+ return baseUrl;\r
+ }\r
+\r
+ public SVNClientManager getManager() {\r
+ return manager;\r
+ }\r
+\r
+}\r