001    package org.hackystat.sensor.ant.svn;
002    
003    import java.util.Collection;
004    import java.util.Date;
005    
006    import org.tmatesoft.svn.core.SVNLogEntry;
007    import org.tmatesoft.svn.core.SVNURL;
008    import org.tmatesoft.svn.core.internal.io.dav.DAVRepositoryFactory;
009    import org.tmatesoft.svn.core.internal.io.dav.http.DefaultHTTPConnectionFactory;
010    import org.tmatesoft.svn.core.internal.io.dav.http.IHTTPConnectionFactory;
011    import org.tmatesoft.svn.core.internal.io.svn.SVNRepositoryFactoryImpl;
012    import org.tmatesoft.svn.core.io.SVNRepository;
013    import org.tmatesoft.svn.core.io.SVNRepositoryFactory;
014    import org.tmatesoft.svn.core.wc.SVNWCUtil;
015    
016    /**
017     * SVN Repository processor to extract commit information.
018     * 
019     * @author (Cedric) Qin ZHANG
020     * @version $Id$
021     */
022    public class SVNCommitProcessor {
023      private SVNRepository svnRepository;
024    
025      /**
026       * Constructs this instance.
027       * 
028       * @param repositoryUrl The svn repository url. It can points to a
029       * subdirectory in the repository.
030       * @param userName The user name. Null is a valid value. If either user name
031       * or password is null, then anonymous credential is used to access the svn
032       * repository.
033       * @param password The password. Null is a valid value.
034       * 
035       * @throws Exception If there is any error.
036       */
037      public SVNCommitProcessor(String repositoryUrl, String userName, String password)
038        throws Exception {
039        if (repositoryUrl.startsWith("http://") || repositoryUrl.startsWith("https://")) {
040          IHTTPConnectionFactory factory =
041            new DefaultHTTPConnectionFactory(null, true, null);
042          DAVRepositoryFactory.setup(factory); 
043        }
044        else if (repositoryUrl.startsWith("svn://")) {
045          SVNRepositoryFactoryImpl.setup();
046        }
047        else {
048          throw new Exception("Repository url must start with http|https|svn.");
049        }
050    
051        this.svnRepository = SVNRepositoryFactory.create(SVNURL.parseURIEncoded(repositoryUrl));
052        if (userName != null && password != null) {
053          this.svnRepository.setAuthenticationManager(SVNWCUtil
054              .createDefaultAuthenticationManager(userName, password));
055        }
056      }
057    
058      /**
059       * Gets the largest revision number at the date specified.
060       * 
061       * @param date The date.
062       * 
063       * @return The revision number.
064       * 
065       * @throws Exception If there is any error.
066       */
067      public long getRevisionNumber(Date date) throws Exception {
068        // If date is too large to too small, such as new Date(Long.MIN_VALUE)
069        // or new Date(Long.MAX_VALUE), then the underlying library JavaSNV
070        // SVNRepository.getDatedRevision() call
071        // will either raise exception or give an erronous version number.
072        // We try to avoid the error here.
073    
074        long startLogTime = Long.MAX_VALUE;
075        long endLogTime = Long.MIN_VALUE;
076        SVNLogEntry startLog = null;
077        SVNLogEntry endLog = null;
078    
079        long latestRevision = this.svnRepository.getLatestRevision();
080        Collection<?> svnLogEntries = this.svnRepository.log(new String[] { "" }, null, 1,
081            latestRevision, true, true);
082        for (Object entry : svnLogEntries) {
083          SVNLogEntry log = (SVNLogEntry) entry;
084          long time = log.getDate().getTime();
085          if (time < startLogTime) {
086            startLogTime = time;
087            startLog = log;
088          }
089          if (time > endLogTime) {
090            endLogTime = time;
091            endLog = log;
092          }
093        }
094    
095        if (startLog == null && endLog == null) {
096          // we cannot determine revision time bounds, give JavaSVN a shot
097          return this.svnRepository.getDatedRevision(date);
098        }
099        else {
100          long targetTime = date.getTime();
101          if (targetTime < startLogTime) {
102            return 0;
103          }
104          else if (endLogTime < targetTime) {
105            return latestRevision;
106          }
107          else {
108            return this.svnRepository.getDatedRevision(date);
109          }
110        }
111      }
112    
113      /**
114       * Gets commit record for a specified revision. Note that if the repository
115       * url supplied in the constructor points to a subdirectory of the repository
116       * root or a file, then there might not be any commits for the revision. In
117       * this case, null is returned.
118       * 
119       * @param revision The revision number.
120       * 
121       * @return The commit record or null.
122       * 
123       * @throws Exception If there is any error.
124       */
125      public CommitRecord getCommitRecord(long revision) throws Exception {
126        Collection<?> svnLogEntries = this.svnRepository.log(new String[] { "" }, null, revision,
127            revision, true, true);
128    
129        // since startRevision and endRevision are the same, we should have at most
130        // 1 svn log entry.
131        if (svnLogEntries == null || svnLogEntries.isEmpty()) {
132          return null;
133        }
134        else if (svnLogEntries.size() > 1) {
135          throw new RuntimeException("Assertion failure. JavaSVN client error?");
136        }
137        else { // exactly 1 svn log entry
138          SVNLogEntry logEntry = (SVNLogEntry) svnLogEntries.iterator().next();
139          return new CommitRecord(this.svnRepository, logEntry);
140        }
141      }
142    }