KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > net > sourceforge > cruisecontrol > sourcecontrols > SVN


1 /********************************************************************************
2  * CruiseControl, a Continuous Integration Toolkit
3  * Copyright (c) 2001, ThoughtWorks, Inc.
4  * 651 W Washington Ave. Suite 600
5  * Chicago, IL 60661 USA
6  * All rights reserved.
7  *
8  * Redistribution and use in source and binary forms, with or without
9  * modification, are permitted provided that the following conditions
10  * are met:
11  *
12  * + Redistributions of source code must retain the above copyright
13  * notice, this list of conditions and the following disclaimer.
14  *
15  * + Redistributions in binary form must reproduce the above
16  * copyright notice, this list of conditions and the following
17  * disclaimer in the documentation and/or other materials provided
18  * with the distribution.
19  *
20  * + Neither the name of ThoughtWorks, Inc., CruiseControl, nor the
21  * names of its contributors may be used to endorse or promote
22  * products derived from this software without specific prior
23  * written permission.
24  *
25  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
26  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
27  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
28  * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR
29  * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
30  * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
31  * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
32  * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
33  * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
34  * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
35  * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
36  ********************************************************************************/

37 package net.sourceforge.cruisecontrol.sourcecontrols;
38
39 import net.sourceforge.cruisecontrol.CruiseControlException;
40 import net.sourceforge.cruisecontrol.Modification;
41 import net.sourceforge.cruisecontrol.SourceControl;
42 import net.sourceforge.cruisecontrol.util.Commandline;
43 import net.sourceforge.cruisecontrol.util.StreamPumper;
44 import net.sourceforge.cruisecontrol.util.ValidationHelper;
45
46 import org.apache.log4j.Logger;
47 import org.jdom.Document;
48 import org.jdom.Element;
49 import org.jdom.JDOMException;
50 import org.jdom.input.SAXBuilder;
51
52 import java.io.File JavaDoc;
53 import java.io.IOException JavaDoc;
54 import java.io.InputStream JavaDoc;
55 import java.io.InputStreamReader JavaDoc;
56 import java.io.PrintWriter JavaDoc;
57 import java.io.Reader JavaDoc;
58 import java.io.UnsupportedEncodingException JavaDoc;
59 import java.text.ParseException JavaDoc;
60 import java.text.SimpleDateFormat JavaDoc;
61 import java.text.DateFormat JavaDoc;
62 import java.util.ArrayList JavaDoc;
63 import java.util.Arrays JavaDoc;
64 import java.util.Date JavaDoc;
65 import java.util.Hashtable JavaDoc;
66 import java.util.Iterator JavaDoc;
67 import java.util.List JavaDoc;
68 import java.util.Map JavaDoc;
69 import java.util.TimeZone JavaDoc;
70
71 /**
72  * This class implements the SourceControl methods for a Subversion repository.
73  * The call to Subversion is assumed to work without any setup. This implies
74  * that either authentication data must be available or the login parameters are
75  * specified in the cc configuration file.
76  *
77  * Note: You can also observe for changes a Subversion repository that you have
78  * not checked out locally.
79  *
80  * @see <a HREF="http://subversion.tigris.org/">subversion.tigris.org</a>
81  * @author <a HREF="etienne.studer@canoo.com">Etienne Studer</a>
82  */

83 public class SVN implements SourceControl {
84
85     private static final Logger LOG = Logger.getLogger(SVN.class);
86
87     /** Date format expected by Subversion */
88     private static final String JavaDoc SVN_DATE_FORMAT_IN = "yyyy-MM-dd'T'HH:mm:ss'Z'";
89
90     /** Date format returned by Subversion in XML output */
91     private static final String JavaDoc SVN_DATE_FORMAT_OUT = "yyyy-MM-dd'T'HH:mm:ss.SSS";
92
93     private Hashtable JavaDoc properties = new Hashtable JavaDoc();
94     private String JavaDoc property;
95     private String JavaDoc propertyOnDelete;
96
97     /** Configuration parameters */
98     private String JavaDoc repositoryLocation;
99     private String JavaDoc localWorkingCopy;
100     private String JavaDoc userName;
101     private String JavaDoc password;
102
103     public Map JavaDoc getProperties() {
104         return properties;
105     }
106
107     public void setProperty(String JavaDoc property) {
108         this.property = property;
109     }
110
111     public void setPropertyOnDelete(String JavaDoc propertyOnDelete) {
112         this.propertyOnDelete = propertyOnDelete;
113     }
114
115     /**
116      * Sets the repository location to use when making calls to Subversion.
117      *
118      * @param repositoryLocation String indicating the url to the Subversion
119      * repository on which to find the log history.
120      */

121     public void setRepositoryLocation(String JavaDoc repositoryLocation) {
122         this.repositoryLocation = repositoryLocation;
123     }
124
125     /**
126      * Sets the local working copy to use when making calls to Subversion.
127      *
128      * @param localWorkingCopy String indicating the relative or absolute path
129      * to the local working copy of the Subversion
130      * repository of which to find the log history.
131      */

132     public void setLocalWorkingCopy(String JavaDoc localWorkingCopy) {
133         this.localWorkingCopy = localWorkingCopy;
134     }
135
136     /**
137      * Sets the username for authentication.
138      */

139     public void setUsername(String JavaDoc userName) {
140         this.userName = userName;
141     }
142
143     /**
144      * Sets the password for authentication.
145      */

146     public void setPassword(String JavaDoc password) {
147         this.password = password;
148     }
149
150     /**
151      * This method validates that at least the repository location or the local
152      * working copy location has been specified.
153      *
154      * @throws CruiseControlException Thrown when the repository location and
155      * the local working copy location are both
156      * null
157      */

158     public void validate() throws CruiseControlException {
159         ValidationHelper.assertTrue(repositoryLocation != null || localWorkingCopy != null,
160                 "At least 'repositoryLocation'or 'localWorkingCopy' is a required attribute on the Subversion task ");
161
162         if (localWorkingCopy != null) {
163             File JavaDoc workingDir = new File JavaDoc(localWorkingCopy);
164             ValidationHelper.assertTrue(workingDir.exists() && workingDir.isDirectory(),
165                     "'localWorkingCopy' must be an existing directory. Was "
166                     + workingDir.getAbsolutePath());
167         }
168     }
169
170     /**
171      * Returns a list of modifications detailing all the changes between
172      * the last build and the latest revision in the repository.
173      * @return the list of modifications, or an empty list if we failed
174      * to retrieve the changes.
175      */

176     public List JavaDoc getModifications(Date JavaDoc lastBuild, Date JavaDoc now) {
177         List JavaDoc modifications = new ArrayList JavaDoc();
178         Commandline command;
179         try {
180             command = buildHistoryCommand(lastBuild, now);
181         } catch (CruiseControlException e) {
182             LOG.error("Error building history command", e);
183             return modifications;
184         }
185         try {
186             modifications = execHistoryCommand(command, lastBuild);
187         } catch (Exception JavaDoc e) {
188             LOG.error("Error executing svn log command " + command, e);
189         }
190         fillPropertiesIfNeeded(modifications);
191         return modifications;
192     }
193
194
195     /**
196      * Generates the command line for the svn log command.
197      *
198      * For example:
199      *
200      * 'svn log --non-interactive --xml -v -r {lastbuildTime}:headRevision repositoryLocation'
201      */

202     Commandline buildHistoryCommand(Date JavaDoc lastBuild, Date JavaDoc checkTime) throws CruiseControlException {
203         Commandline command = new Commandline();
204         command.setExecutable("svn");
205
206         if (localWorkingCopy != null) {
207             command.setWorkingDirectory(localWorkingCopy);
208         }
209
210         command.createArgument().setValue("log");
211         command.createArgument().setValue("--non-interactive");
212         command.createArgument().setValue("--xml");
213         command.createArgument().setValue("-v");
214         command.createArgument().setValue("-r");
215         command.createArgument().setValue("{" + formatSVNDate(lastBuild) + "}" + ":"
216                 + "{" + formatSVNDate(checkTime) + "}");
217
218         if (userName != null) {
219             command.createArgument().setValue("--username");
220             command.createArgument().setValue(userName);
221         }
222         if (password != null) {
223             command.createArgument().setValue("--password");
224             command.createArgument().setValue(password);
225         }
226         if (repositoryLocation != null) {
227             command.createArgument().setValue(repositoryLocation);
228         }
229
230         LOG.debug("Executing command: " + command);
231
232         return command;
233     }
234
235
236     static String JavaDoc formatSVNDate(Date JavaDoc lastBuild) {
237         DateFormat JavaDoc f = new SimpleDateFormat JavaDoc(SVN_DATE_FORMAT_IN);
238         f.setTimeZone(TimeZone.getTimeZone("GMT"));
239         return f.format(lastBuild);
240     }
241
242
243     private List JavaDoc execHistoryCommand(Commandline command, Date JavaDoc lastBuild)
244         throws InterruptedException JavaDoc, IOException JavaDoc, ParseException JavaDoc, JDOMException {
245
246         Process JavaDoc p = command.execute();
247
248         logErrorStream(p);
249         InputStream JavaDoc svnStream = p.getInputStream();
250         List JavaDoc modifications = parseStream(svnStream, lastBuild);
251
252         p.waitFor();
253         p.getInputStream().close();
254         p.getOutputStream().close();
255         p.getErrorStream().close();
256
257         return modifications;
258     }
259
260     private void logErrorStream(Process JavaDoc p) {
261         StreamPumper errorPumper =
262             new StreamPumper(p.getErrorStream(), new PrintWriter JavaDoc(System.err, true));
263         new Thread JavaDoc(errorPumper).start();
264     }
265
266     private List JavaDoc parseStream(InputStream JavaDoc svnStream, Date JavaDoc lastBuild)
267         throws JDOMException, IOException JavaDoc, ParseException JavaDoc, UnsupportedEncodingException JavaDoc {
268
269         InputStreamReader JavaDoc reader = new InputStreamReader JavaDoc(svnStream, "UTF-8");
270         return SVNLogXMLParser.parseAndFilter(reader, lastBuild);
271     }
272
273     void fillPropertiesIfNeeded(List JavaDoc modifications) {
274         if (property != null) {
275             if (modifications.size() > 0) {
276                 properties.put(property, "true");
277             }
278         }
279
280         if (propertyOnDelete != null) {
281             for (int i = 0; i < modifications.size(); i++) {
282                 Modification modification = (Modification) modifications.get(i);
283                 Modification.ModifiedFile file = (Modification.ModifiedFile) modification.files.get(0);
284                 if (file.action.equals("deleted")) {
285                     properties.put(propertyOnDelete, "true");
286                     break;
287                 }
288             }
289         }
290     }
291
292     public static DateFormat JavaDoc getOutDateFormatter() {
293         DateFormat JavaDoc f = new SimpleDateFormat JavaDoc(SVN_DATE_FORMAT_OUT);
294         f.setTimeZone(TimeZone.getTimeZone("GMT"));
295         return f;
296     }
297
298     static final class SVNLogXMLParser {
299
300         private SVNLogXMLParser() {
301         }
302
303
304         static List JavaDoc parseAndFilter(Reader JavaDoc reader, Date JavaDoc lastBuild)
305                 throws ParseException JavaDoc, JDOMException, IOException JavaDoc {
306
307             Modification[] modifications = parse(reader);
308             return filterModifications(modifications, lastBuild);
309         }
310
311
312         static Modification[] parse(Reader JavaDoc reader)
313                 throws ParseException JavaDoc, JDOMException, IOException JavaDoc {
314
315             SAXBuilder builder = new SAXBuilder(false);
316             Document document = builder.build(reader);
317             return parseDOMTree(document);
318         }
319
320         static Modification[] parseDOMTree(Document document) throws ParseException JavaDoc {
321             List JavaDoc modifications = new ArrayList JavaDoc();
322
323             Element rootElement = document.getRootElement();
324             List JavaDoc logEntries = rootElement.getChildren("logentry");
325             for (Iterator JavaDoc iterator = logEntries.iterator(); iterator.hasNext();) {
326                 Element logEntry = (Element) iterator.next();
327
328                 Modification[] modificationsOfRevision = parseLogEntry(logEntry);
329                 modifications.addAll(Arrays.asList(modificationsOfRevision));
330             }
331
332             return (Modification[]) modifications.toArray(new Modification[modifications.size()]);
333         }
334
335         static Modification[] parseLogEntry(Element logEntry) throws ParseException JavaDoc {
336             List JavaDoc modifications = new ArrayList JavaDoc();
337
338             Element logEntryPaths = logEntry.getChild("paths");
339             if (logEntryPaths != null) {
340                 List JavaDoc paths = logEntryPaths.getChildren("path");
341                 for (Iterator JavaDoc iterator = paths.iterator(); iterator.hasNext();) {
342                     Element path = (Element) iterator.next();
343
344                     Modification modification = new Modification("svn");
345
346                     modification.modifiedTime = convertDate(logEntry.getChildText("date"));
347                     modification.userName = logEntry.getChildText("author");
348                     modification.comment = logEntry.getChildText("msg");
349                     modification.revision = logEntry.getAttributeValue("revision");
350
351                     Modification.ModifiedFile modfile = modification.createModifiedFile(path.getText(), null);
352                     modfile.action = convertAction(path.getAttributeValue("action"));
353                     modfile.revision = modification.revision;
354
355                     modifications.add(modification);
356                 }
357             }
358
359             return (Modification[]) modifications.toArray(new Modification[modifications.size()]);
360         }
361
362         /**
363          * Converts the specified SVN date string into a Date.
364          * @param date with format "yyyy-MM-dd'T'HH:mm:ss.SSS" + "...Z"
365          * @return converted date
366          * @throws ParseException if specified date doesn't match the expected format
367          */

368         static Date JavaDoc convertDate(String JavaDoc date) throws ParseException JavaDoc {
369             final int zIndex = date.indexOf('Z');
370             if (zIndex - 3 < 0) {
371                 throw new ParseException JavaDoc(date
372                         + " doesn't match the expected subversion date format", date.length());
373             }
374             String JavaDoc withoutMicroSeconds = date.substring(0, zIndex - 3);
375
376             return getOutDateFormatter().parse(withoutMicroSeconds);
377         }
378
379         static String JavaDoc convertAction(String JavaDoc action) {
380             if (action.equals("A")) {
381                 return "added";
382             }
383             if (action.equals("M")) {
384                 return "modified";
385             }
386             if (action.equals("D")) {
387                 return "deleted";
388             }
389             return "unknown";
390         }
391
392         /**
393          * Unlike CVS, Subversion maps dates to revisions which leads to a
394          * different behavior when using the svn log command in conjunction with
395          * dates, e.g., a date maps to a revision but the revision may have been
396          * created earlier than the specified date. Therefore, if we are only
397          * interested in changes that took place after the last build date, we
398          * have to filter the modifications returned from the log command and
399          * omit modifications that are older than the last build date.
400          *
401          * @see <a HREF="http://subversion.tigris.org/">subversion.tigris.org</a>
402          */

403         static List JavaDoc filterModifications(Modification[] modifications, Date JavaDoc lastBuild) {
404             List JavaDoc filtered = new ArrayList JavaDoc();
405             for (int i = 0; i < modifications.length; i++) {
406                 Modification modification = modifications[i];
407                 if (modification.modifiedTime.getTime() > lastBuild.getTime()) {
408                     filtered.add(modification);
409                 }
410             }
411             return filtered;
412         }
413     }
414 }
415
Popular Tags