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