View Javadoc

1   package org.appfuse.tool;
2   
3   import org.tmatesoft.svn.core.SVNCommitInfo;
4   import org.tmatesoft.svn.core.SVNErrorCode;
5   import org.tmatesoft.svn.core.SVNErrorMessage;
6   import org.tmatesoft.svn.core.SVNException;
7   import org.tmatesoft.svn.core.SVNNodeKind;
8   import org.tmatesoft.svn.core.SVNURL;
9   import org.tmatesoft.svn.core.auth.ISVNAuthenticationManager;
10  import org.tmatesoft.svn.core.internal.io.dav.DAVRepositoryFactory;
11  import org.tmatesoft.svn.core.internal.io.fs.FSRepositoryFactory;
12  import org.tmatesoft.svn.core.internal.io.svn.SVNRepositoryFactoryImpl;
13  import org.tmatesoft.svn.core.io.ISVNEditor;
14  import org.tmatesoft.svn.core.io.ISVNReporter;
15  import org.tmatesoft.svn.core.io.ISVNReporterBaton;
16  import org.tmatesoft.svn.core.io.SVNRepository;
17  import org.tmatesoft.svn.core.io.SVNRepositoryFactory;
18  import org.tmatesoft.svn.core.io.diff.SVNDeltaProcessor;
19  import org.tmatesoft.svn.core.io.diff.SVNDiffWindow;
20  import org.tmatesoft.svn.core.wc.SVNWCUtil;
21  
22  import java.io.File;
23  import java.io.IOException;
24  import java.io.OutputStream;
25  
26  /*
27   * This example program export contents of the repository directory into file system using
28   * SVNKit library low level API.
29   *
30   * In general, approach we are using in this example is the same that is used for operations
31   * like 'update', 'remote status', 'diff' or 'checkout'. The export operation is the most
32   * simple one and allows to demonstrate this approach without going too much into the details.
33   *
34   * You may find and an article describing this (update) technique at
35   * http://svnkit.com/kb/dev-guide-update-operation.html
36   *
37   * To perform any update-like operation one have to do the following:
38   *
39   * 1. Report the state of the client's working copy to the Subversion server. Of course, it could be
40   *    'virtual' working copy, not necessary stored in the Subversion wc format or, in case of export or
41   *    diff operation there could be no working copy at all, which is reflected in report.
42   *
43   *    Report is performed with the help ISVNReporter instance that is passed to the client's ISVNReporterBaton
44   *    object at the moment report have to be sent.
45   *
46   * 2. Process instructions received from the server. These instructions describes how to modify working copy
47   *    to make it be at the desirable revision. Amount of instructions depends on the report sent by the client.
48   *    Different operations process received instructions in different manner. For instance, update operation
49   *    updates working copy in the filsystem, remote status operation merely logs files and directories that
50   *    have to be updated and displays this information.
51   *
52   *    With SVNKit API you may implement your own processing code, e.g. repository replication or custom merging code.
53   *    ISVNEditor is the interface which implementations process update instructions sent by the server and in
54   *    this example ISVNEditor implementation (ExportEditor) creates files and directories corresponding to those
55   *    in the repository.
56   *
57   */
58  public class SubversionUtils {
59      private String url;
60      private String destinationDirectory;
61  
62      public SubversionUtils(String url, String destinationDirectory) {
63          /*
64           * Initialize the library. It must be done before calling any
65           * method of the library.
66           */
67          setupLibrary();
68          this.url = url;
69          this.destinationDirectory = destinationDirectory;
70      }
71  
72      public void export() throws SVNException {
73          SVNURL url = SVNURL.parseURIEncoded(this.url);
74          String userName = "guest";
75          String userPassword = "guest";
76  
77          /*
78           * Prepare filesystem directory (export destination).
79           */
80          File exportDir = new File(this.destinationDirectory);
81          /*if (exportDir.exists()) {
82              SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.IO_ERROR, "Path ''{0}'' already exists", exportDir);
83              throw new SVNException(err);
84          }
85          exportDir.mkdirs();*/
86  
87          /*
88           * Create an instance of SVNRepository class. This class is the main entry point
89           * for all "low-level" Subversion operations supported by Subversion protocol.
90           *
91           * These operations includes browsing, update and commit operations. See
92           * SVNRepository methods javadoc for more details.
93           */
94          SVNRepository repository = SVNRepositoryFactory.create(url);
95  
96          /*
97           * User's authentication information (name/password) is provided via  an
98           * ISVNAuthenticationManager  instance.  SVNWCUtil  creates  a   default
99           * authentication manager given user's name and password.
100          *
101          * Default authentication manager first attempts to use provided user name
102          * and password and then falls back to the credentials stored in the
103          * default Subversion credentials storage that is located in Subversion
104          * configuration area. If you'd like to use provided user name and password
105          * only you may use BasicAuthenticationManager class instead of default
106          * authentication manager:
107          *
108          *  authManager = new BasicAuthenticationsManager(userName, userPassword);
109          *
110          * You may also skip this point - anonymous access will be used.
111          */
112         ISVNAuthenticationManager authManager = SVNWCUtil.createDefaultAuthenticationManager(userName, userPassword);
113         repository.setAuthenticationManager(authManager);
114 
115         /*
116          * Get type of the node located at URL we used to create SVNRepository.
117          *
118          * "" (empty string) is path relative to that URL,
119          * -1 is value that may be used to specify HEAD (latest) revision.
120          */
121         SVNNodeKind nodeKind = repository.checkPath("", -1);
122         if (nodeKind == SVNNodeKind.NONE) {
123             SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.UNKNOWN, "No entry at URL ''{0}''", url);
124             throw new SVNException(err);
125         } else if (nodeKind == SVNNodeKind.FILE) {
126             SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.UNKNOWN, "Entry at URL ''{0}'' is a file while directory was expected", url);
127             throw new SVNException(err);
128         }
129 
130         /*
131          * Get latest repository revision. We will export repository contents at this very revision.
132          */
133         long latestRevision = repository.getLatestRevision();
134 
135         /*
136          * Create reporterBaton. This class is responsible for reporting 'wc state' to the server.
137          *
138          * In this example it will always report that working copy is empty to receive update
139          * instructions that are sufficient to create complete directories hierarchy and get full
140          * files contents.
141          */
142         ISVNReporterBaton reporterBaton = new ExportReporterBaton(latestRevision);
143 
144         /*
145          * Create editor. This class will process update instructions received from the server and
146          * will create directories and files accordingly.
147          *
148          * As we've reported 'emtpy working copy', server will only send 'addDir/addFile' instructions
149          * and will never ask our editor implementation to modify a file or directory properties.
150          */
151         ISVNEditor exportEditor = new ExportEditor(exportDir);
152 
153         /*
154          * Now ask SVNKit to perform generic 'update' operation using our reporter and editor.
155          *
156          * We are passing:
157          *
158          * - revision from which we would like to export
159          * - null as "target" name, to perform export from the URL SVNRepository was created for,
160          *   not from some child directory.
161          * - reporterBaton
162          * - exportEditor.
163          */
164         repository.update(latestRevision, null, true, reporterBaton, exportEditor);
165 
166         //System.out.println("Exported revision: " + latestRevision);
167     }
168 
169     /*
170      * ReporterBaton implementation that always reports 'empty wc' state.
171      */
172     private static class ExportReporterBaton implements ISVNReporterBaton {
173 
174         private long exportRevision;
175 
176         public ExportReporterBaton(long revision){
177             exportRevision = revision;
178         }
179 
180         public void report(ISVNReporter reporter) throws SVNException {
181             try {
182                 /*
183                  * Here empty working copy is reported.
184                  *
185                  * ISVNReporter includes methods that allows to report mixed-rev working copy
186                  * and even let server know that some files or directories are locally missing or
187                  * locked.
188                  */
189                 reporter.setPath("", null, exportRevision, true);
190 
191                 /*
192                  * Don't forget to finish the report!
193                  */
194                 reporter.finishReport();
195             } catch (SVNException svne) {
196                 reporter.abortReport();
197                 System.out.println("Report failed.");
198             }
199         }
200     }
201 
202     /*
203      * ISVNEditor implementation that will add directories and files into the target directory
204      * accordingly to update instructions sent by the server.
205      */
206     private static class ExportEditor implements ISVNEditor {
207 
208         private File myRootDirectory;
209         private SVNDeltaProcessor myDeltaProcessor;
210 
211         /*
212          * root - the local directory where the node tree is to be exported into.
213          */
214         public ExportEditor(File root) {
215             myRootDirectory = root;
216             /*
217              * Utility class that will help us to transform 'deltas' sent by the
218              * server to the new file contents.
219              */
220             myDeltaProcessor = new SVNDeltaProcessor();
221         }
222 
223         /*
224          * Server reports revision to which application of the further
225          * instructions will update working copy to.
226          */
227         public void targetRevision(long revision) throws SVNException {
228         }
229 
230         /*
231          * Called before sending other instructions.
232          */
233         public void openRoot(long revision) throws SVNException {
234         }
235 
236         /*
237          * Called when a new directory has to be added.
238          *
239          * For each 'addDir' call server will call 'closeDir' method after
240          * all children of the added directory are added.
241          *
242          * This implementation creates corresponding directory below root directory.
243          */
244         public void addDir(String path, String copyFromPath, long copyFromRevision) throws SVNException {
245             File newDir = new File(myRootDirectory, path);
246             if (!newDir.exists()) {
247                 if (!newDir.mkdirs()) {
248                     //SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.IO_ERROR, "error: failed to add the directory ''{0}''.", newDir);
249                     //throw new SVNException(err);
250                     //System.err.println(err.getMessage() + "Ignoring and not overriding.");
251                 }
252             }
253             //System.out.println("dir added: " + path);
254         }
255 
256         /*
257          * Called when there is an existing directory that has to be 'opened' either
258          * to modify this directory properties or to process other files and directories
259          * inside this directory.
260          *
261          * In case of export this method will never be called because we reported
262          * that our 'working copy' is empty and so server knows that there are
263          * no 'existing' directories.
264          */
265         public void openDir(String path, long revision) throws SVNException {
266         }
267 
268         /*
269          * Instructs to change opened or added directory property.
270          *
271          * This method is called to update properties set by the user as well
272          * as those created automatically, like "svn:committed-rev".
273          * See SVNProperty class for default property names.
274          *
275          * When property has to be deleted value will be 'null'.
276          */
277         public void changeDirProperty(String name, String value) throws SVNException {
278         }
279 
280         /*
281          * Called when a new file has to be created.
282          *
283          * For each 'addFile' call server will call 'closeFile' method after
284          * sending file properties and contents.
285          *
286          * This implementation creates empty file below root directory, file contents
287          * will be updated later, and for empty files may not be sent at all.
288          */
289         public void addFile(String path, String copyFromPath, long copyFromRevision) throws SVNException {
290             File file = new File(myRootDirectory, path);
291             if (file.exists()) {
292                 //SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.IO_ERROR, "error: exported file ''{0}'' already exists!", file);
293                 //System.err.println(err.getMessage() + "Ignoring and not overriding.");
294                 //throw new SVNException(err);
295             } else {
296                 try {
297                     file.createNewFile();
298                 } catch (IOException e) {
299                     SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.IO_ERROR, "error: cannot create new  file ''{0}''", file);
300                     throw new SVNException(err);
301                 }
302             }
303         }
304 
305         /*
306          * Called when there is an existing files that has to be 'opened' either
307          * to modify file contents or properties.
308          *
309          * In case of export this method will never be called because we reported
310          * that our 'working copy' is empty and so server knows that there are
311          * no 'existing' files.
312          */
313         public void openFile(String path, long revision) throws SVNException {
314         }
315 
316         /*
317          * Instructs to add, modify or delete file property.
318          * In this example we skip this instruction, but 'real' export operation
319          * may inspect 'svn:eol-style' or 'svn:mime-type' property values to
320          * transfor file contents propertly after receiving.
321          */
322         public void changeFileProperty(String path, String name, String value) throws SVNException {
323         }
324 
325         /*
326          * Called before sending 'delta' for a file. Delta may include instructions
327          * on how to create a file or how to modify existing file. In this example
328          * delta will always contain instructions on how to create a new file and so
329          * we set up deltaProcessor with 'null' base file and target file to which we would
330          * like to store the result of delta application.
331          */
332         public void applyTextDelta(String path, String baseChecksum) throws SVNException {
333             myDeltaProcessor.applyTextDelta(null, new File(myRootDirectory, path), false);
334         }
335 
336         /*
337          * Server sends deltas in form of 'diff windows'. Depending on the file size
338          * there may be several diff windows. Utility class SVNDeltaProcessor processes
339          * these windows for us.
340          */
341         public OutputStream textDeltaChunk(String path, SVNDiffWindow diffWindow)   throws SVNException {
342             return myDeltaProcessor.textDeltaChunk(diffWindow);
343         }
344 
345         /*
346          * Called when all diff windows (delta) is transferred.
347          */
348         public void textDeltaEnd(String path) throws SVNException {
349             myDeltaProcessor.textDeltaEnd();
350         }
351 
352         /*
353          * Called when file update is completed.
354          * This call always matches addFile or openFile call.
355          */
356         public void closeFile(String path, String textChecksum) throws SVNException {
357             //System.out.println("file added: " + path);
358         }
359 
360         /*
361          * Called when all child files and directories are processed.
362          * This call always matches addDir, openDir or openRoot call.
363          */
364         public void closeDir() throws SVNException {
365         }
366 
367         /*
368          * Insturcts to delete an entry in the 'working copy'. Of course will not be
369          * called during export operation.
370          */
371         public void deleteEntry(String path, long revision) throws SVNException {
372         }
373 
374         /*
375          * Called when directory at 'path' should be somehow processed,
376          * but authenticated user (or anonymous user) doesn't have enough
377          * access rights to get information on this directory (properties, children).
378          */
379         public void absentDir(String path) throws SVNException {
380         }
381 
382         /*
383          * Called when file at 'path' should be somehow processed,
384          * but authenticated user (or anonymous user) doesn't have enough
385          * access rights to get information on this file (contents, properties).
386          */
387         public void absentFile(String path) throws SVNException {
388         }
389 
390         /*
391          * Called when update is completed.
392          */
393         public SVNCommitInfo closeEdit() throws SVNException {
394             return null;
395         }
396 
397         /*
398          * Called when update is completed with an error or server
399          * requests client to abort update operation.
400          */
401         public void abortEdit() throws SVNException {
402         }
403     }
404 
405     /*
406      * Initializes the library to work with a repository via
407      * different protocols.
408      */
409     private static void setupLibrary() {
410         /*
411          * For using over http:// and https://
412          */
413         DAVRepositoryFactory.setup();
414         /*
415          * For using over svn:// and svn+xxx://
416          */
417         SVNRepositoryFactoryImpl.setup();
418 
419         /*
420          * For using over file:///
421          */
422         FSRepositoryFactory.setup();
423     }
424 }