KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > apache > lenya > cms > rc > RevisionController


1 /*
2  * Copyright 1999-2004 The Apache Software Foundation
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  *
16  */

17
18 /* $Id: RevisionController.java 43126 2004-07-16 15:43:59Z edith $ */
19
20 package org.apache.lenya.cms.rc;
21
22 import java.io.File JavaDoc;
23 import java.io.FileInputStream JavaDoc;
24 import java.io.FileNotFoundException JavaDoc;
25 import java.io.IOException JavaDoc;
26 import java.io.InputStream JavaDoc;
27 import java.io.OutputStream JavaDoc;
28 import java.util.Date JavaDoc;
29
30 import org.apache.lenya.util.XPSFileOutputStream;
31 import org.apache.log4j.Category;
32
33 /**
34  * Controller for the reserved check-in, check-out, the backup versions and the rollback
35  */

36 public class RevisionController {
37     private static Category log = Category.getInstance(RevisionController.class);
38
39     // System username. This is used for
40
// - creating dummy checkin events in a new RCML file
41
// when it is created on-the-fly
42
// - system override on checkin, i.e. you can force
43
// a checkin into the repository if you use this
44
// username as identity parameter to reservedCheckIn()
45
//
46
public static final String JavaDoc systemUsername = "System";
47
48     private String JavaDoc rcmlDir = null;
49     private String JavaDoc rootDir = null;
50     private String JavaDoc backupDir = null;
51
52     /**
53      * Creates a new RevisionController object.
54      *
55      */

56     public RevisionController() {
57         Configuration conf = new Configuration();
58         rcmlDir = conf.getRcmlDirectory();
59         if (!new File JavaDoc(rcmlDir).exists())
60             log.error("No such directory: " + rcmlDir);
61         backupDir = conf.getBackupDirectory();
62         if (!new File JavaDoc(backupDir).exists())
63             log.error("No such directory: " + backupDir);
64         rootDir = "conf.rootDirectory";
65     }
66
67     /**
68      * Creates a new RevisionController object.
69      *
70      * @param rcmlDirectory The directory for the RCML files
71      * @param backupDirectory The directory for the backup versions
72      * @param rootDirectory The publication directory
73      */

74     public RevisionController(String JavaDoc rcmlDirectory, String JavaDoc backupDirectory, String JavaDoc rootDirectory) {
75         this.rcmlDir = rcmlDirectory;
76         this.backupDir = backupDirectory;
77         this.rootDir = rootDirectory;
78     }
79
80     /**
81      * Creates a new RevisionController object.
82      *
83      * @param rootDir The publication directory
84      */

85     public RevisionController(String JavaDoc rootDir) {
86         this();
87         this.rootDir = rootDir;
88     }
89
90     /**
91      * Shows Configuration
92      *
93      * @return String The rcml directory, the backup directory, the publication directory
94      */

95     public String JavaDoc toString() {
96         return "rcmlDir=" + rcmlDir + " , rcbakDir=" + backupDir + " , rootDir=" + rootDir;
97     }
98
99     /**
100      * Get the RCML File for the file source
101      *
102      * @param source The path of the file from the publication.
103      *
104      * @return RCML The corresponding RCML file.
105      *
106      * @throws FileNotFoundException if an error occurs
107      * @throws IOException if an error occurs
108      * @throws Exception if an error occurs
109      */

110     public RCML getRCML(String JavaDoc source) throws FileNotFoundException JavaDoc, IOException JavaDoc, Exception JavaDoc {
111         return new RCML(rcmlDir, source, rootDir);
112     }
113
114     /**
115      * Try to make a reserved check out of the file source for a user with identity
116      *
117      * @param source The filename of the file to check out
118      * @param identity The identity of the user
119      * @return File File to check out
120      * @throws Exception if an error occurs
121      */

122     public File JavaDoc reservedCheckOut(String JavaDoc source, String JavaDoc identity) throws Exception JavaDoc {
123         
124         File JavaDoc file = new File JavaDoc(rootDir + source);
125         /*
126         if (!file.isFile()) {
127             throw new FileNotFoundException(file.getAbsolutePath());
128         }
129         */

130
131         RCML rcml = new RCML(rcmlDir, source, rootDir);
132
133         RCMLEntry entry = rcml.getLatestEntry();
134
135         // The same user is allowed to check out repeatedly without
136
// having to check back in first.
137
//
138
if (entry != null) {
139             log.debug("entry: " + entry);
140             log.debug("entry.type:" + entry.getType());
141             log.debug("entry.identity" + entry.getIdentity());
142         }
143
144         if ((entry != null)
145             && (entry.getType() != RCML.ci)
146             && !entry.getIdentity().equals(identity)) {
147             throw new FileReservedCheckOutException(rootDir + source, rcml);
148         }
149
150         rcml.checkOutIn(RCML.co, identity, new Date JavaDoc().getTime(), false);
151
152         return file;
153     }
154
155     /**
156      * Checks if a source can be checked out.
157      * @param source The source.
158      * @param identity The identity who requests checking out.
159      * @return A boolean value.
160      * @throws Exception when something went wrong.
161      */

162     public boolean canCheckOut(String JavaDoc source, String JavaDoc identity) throws Exception JavaDoc {
163         RCML rcml = new RCML(rcmlDir, source, rootDir);
164
165         RCMLEntry entry = rcml.getLatestEntry();
166
167         // The same user is allowed to check out repeatedly without
168
// having to check back in first.
169
//
170
if (entry != null) {
171             log.debug("entry: " + entry);
172             log.debug("entry.type:" + entry.getType());
173             log.debug("entry.identity" + entry.getIdentity());
174         }
175
176         boolean checkedOutByOther =
177             entry != null && entry.getType() != RCML.ci && !entry.getIdentity().equals(identity);
178
179         return !checkedOutByOther;
180     }
181
182     /**
183      * Try to make a reserved check in of the file destination for a user with identity. A backup
184      * copy can be made.
185      *
186      * @param destination The file we want to check in
187      * @param identity The identity of the user
188      * @param backup if true, a backup will be created, else no backup will be made.
189      *
190      * @return long The time.
191      *
192      * @exception FileReservedCheckInException if the document couldn't be checked in (for instance
193      * because it is already checked out by someone other ...)
194      * @exception Exception if other problems occur
195      */

196     public long reservedCheckIn(String JavaDoc destination, String JavaDoc identity, boolean backup)
197         throws FileReservedCheckInException, Exception JavaDoc {
198         RCML rcml = new RCML(rcmlDir, destination, rootDir);
199
200         CheckOutEntry coe = rcml.getLatestCheckOutEntry();
201         CheckInEntry cie = rcml.getLatestCheckInEntry();
202
203         // If there has never been a checkout for this object
204
// *or* if the user attempting the checkin right now
205
// is the system itself, we will skip any checks and proceed
206
// right away to the actual checkin.
207
// In all other cases we enforce the revision control
208
// rules inside this if clause:
209
//
210
if (!((coe == null) || identity.equals(RevisionController.systemUsername))) {
211             /*
212              * Possible cases and rules:
213              *
214              * 1.) we were able to read the latest checkin and it is later than latest checkout
215              * (i.e. there is no open checkout to match this checkin, an unusual case)
216              * 1.1.) identity of latest checkin is equal to current user
217              * -> checkin allowed, same user may check in repeatedly
218              * 1.2.) identity of latest checkin is not equal to current user
219              * -> checkin rejected, may not overwrite the revision which
220              * another user checked in previously
221              * 2.) there was no checkin or the latest checkout is later than latest checkin
222              * (i.e. there is an open checkout)
223              * 2.1.) identity of latest checkout is equal to current user
224              * -> checkin allowed, user checked out and may check in again
225              * (the most common case)
226              * 2.2.) identity of latest checkout is not equal to current user
227              * -> checkin rejected, may not check in while another
228              * user is working on this document
229              *
230              */

231             if ((cie != null) && (cie.getTime() > coe.getTime())) {
232                 // We have case 1
233
if (!cie.getIdentity().equals(identity)) {
234                     // Case 1.2., abort...
235
//
236
throw new FileReservedCheckInException(rootDir + destination, rcml);
237                 }
238             } else {
239                 // Case 2
240
if (!coe.getIdentity().equals(identity)) {
241                     // Case 2.2., abort...
242
//
243
throw new FileReservedCheckInException(rootDir + destination, rcml);
244                 }
245             }
246         }
247
248         File JavaDoc originalFile = new File JavaDoc(rootDir, destination);
249         long time = new Date JavaDoc().getTime();
250
251         if (backup && originalFile.isFile()) {
252             File JavaDoc backupFile = new File JavaDoc(backupDir, destination + ".bak." + time);
253             File JavaDoc parent = new File JavaDoc(backupFile.getParent());
254
255             if (!parent.isDirectory()) {
256                 parent.mkdirs();
257             }
258
259             log.info(
260                 "Backup: copy "
261                     + originalFile.getAbsolutePath()
262                     + " to "
263                     + backupFile.getAbsolutePath());
264
265             InputStream JavaDoc in = new FileInputStream JavaDoc(originalFile.getAbsolutePath());
266
267             OutputStream JavaDoc out = new XPSFileOutputStream(backupFile.getAbsolutePath());
268             byte[] buffer = new byte[512];
269             int length;
270
271             while ((length = in.read(buffer)) != -1) {
272                 out.write(buffer, 0, length);
273             }
274
275             out.close();
276         }
277
278         rcml.checkOutIn(RCML.ci, identity, time, backup);
279         rcml.pruneEntries(backupDir);
280         rcml.write();
281
282         // FIXME: If we reuse the observer pattern as implemented in
283
// xps this would be the place to notify the observers,
284
// e.g. like so:
285
// StatusChangeSignalHandler.emitSignal("file:" + originalFile.getAbsolutePath(),
286
// "reservedCheckIn");
287
return time;
288     }
289
290     /**
291      * Get the absolute path of a backup version
292      *
293      * @param time The time of the backup
294      * @param filename The path of the file from the {publication}
295      *
296      * @return String The absolute path of the backup version
297      */

298     public String JavaDoc getBackupFilename(long time, String JavaDoc filename) {
299         File JavaDoc backup = new File JavaDoc(backupDir, filename + ".bak." + time);
300
301         return backup.getAbsolutePath();
302     }
303
304     /**
305      * Get the file of a backup version
306      *
307      * @param time The time of the backup
308      * @param filename The path of the file from the {publication}
309      *
310      * @return File The file of the backup version
311      */

312     public File JavaDoc getBackupFile(long time, String JavaDoc filename) {
313         File JavaDoc backup = new File JavaDoc(backupDir, filename + ".bak." + time);
314
315         return backup;
316     }
317
318     /**
319      * Rolls back to the given point in time.
320      *
321      * @param destination File which will be rolled back
322      * @param identity The identity of the user
323      * @param backupFlag If true, a backup of the current version will be made before the rollback
324      * @param time The time point of the desired version
325      *
326      * @return long The time of the version to roll back to.
327      *
328      * @exception FileReservedCheckInException if the current version couldn't be checked in again
329      * @exception FileReservedCheckOutException if the current version couldn't be checked out
330      * @exception FileNotFoundException if a file couldn't be found
331      * @exception Exception if another problem occurs
332      */

333     public long rollback(String JavaDoc destination, String JavaDoc identity, boolean backupFlag, long time)
334         throws
335             FileReservedCheckInException,
336             FileReservedCheckOutException,
337             FileNotFoundException JavaDoc,
338             Exception JavaDoc {
339         // Make sure the old version exists
340
//
341
File JavaDoc backup = new File JavaDoc(backupDir, destination + ".bak." + time);
342         File JavaDoc current = new File JavaDoc(rootDir, destination);
343
344         if (!backup.isFile()) {
345             throw new FileNotFoundException JavaDoc(backup.getAbsolutePath());
346         }
347
348         if (!current.isFile()) {
349             throw new FileNotFoundException JavaDoc(current.getAbsolutePath());
350         }
351
352         // Try to check out current version
353
//
354
reservedCheckOut(destination, identity);
355
356         // Now roll back to the old state
357
//
358
FileInputStream JavaDoc in = new FileInputStream JavaDoc(backup.getAbsolutePath());
359
360         XPSFileOutputStream out = new XPSFileOutputStream(current.getAbsolutePath());
361         byte[] buffer = new byte[512];
362         int length;
363
364         while ((length = in.read(buffer)) != -1) {
365             out.write(buffer, 0, length);
366         }
367
368         out.close();
369
370         // Try to check back in, this might cause
371
// a backup of the current version to be created if
372
// desired by the user.
373
//
374
long newtime = reservedCheckIn(destination, identity, backupFlag);
375
376         return newtime;
377     }
378
379     /**
380      * Delete the check in and roll back the file to the backup at time
381      *
382      * @param time The time point of the back version we want to retrieve
383      * @param destination The File for which we want undo the check in
384      *
385      * @exception Exception FileNotFoundException if the back version or the current version
386      * couldn't be found
387      */

388     public void undoCheckIn(long time, String JavaDoc destination) throws Exception JavaDoc {
389         File JavaDoc backup = new File JavaDoc(backupDir + "/" + destination + ".bak." + time);
390         File JavaDoc current = new File JavaDoc(rootDir + destination);
391
392         RCML rcml = new RCML(rcmlDir, destination, rootDir);
393
394         if (!backup.isFile()) {
395             throw new FileNotFoundException JavaDoc(backup.getAbsolutePath());
396         }
397
398         if (!current.isFile()) {
399             throw new FileNotFoundException JavaDoc(current.getAbsolutePath());
400         }
401
402         FileInputStream JavaDoc in = new FileInputStream JavaDoc(backup.getAbsolutePath());
403
404         XPSFileOutputStream out = new XPSFileOutputStream(current.getAbsolutePath());
405         byte[] buffer = new byte[512];
406         int length;
407
408         while ((length = in.read(buffer)) != -1) {
409             out.write(buffer, 0, length);
410         }
411
412         log.info("Undo: copy " + backup.getAbsolutePath() + " " + current.getAbsolutePath());
413
414         rcml.deleteFirstCheckIn();
415         out.close();
416     }
417
418     /**
419      * delete the revisions
420      * @param filename of the document
421      * @throws RevisionControlException when somthing went wrong
422      */

423     public void deleteRevisions(String JavaDoc filename) throws RevisionControlException{
424         try {
425             RCML rcml = this.getRCML(filename);
426             String JavaDoc[] times = rcml.getBackupsTime();
427             for (int i=0; i < times.length; i++) {
428                 long time = new Long JavaDoc(times[i]).longValue();
429                 File JavaDoc backup = this.getBackupFile(time, filename);
430                 File JavaDoc parentDirectory = null;
431                 parentDirectory = backup.getParentFile();
432                 boolean deleted = backup.delete();
433                 if (!deleted) {
434                     throw new RevisionControlException("The backup file, "+backup.getCanonicalPath()+" could not be deleted!");
435                 }
436                 if (parentDirectory != null
437                     && parentDirectory.exists()
438                     && parentDirectory.isDirectory()
439                     && parentDirectory.listFiles().length == 0) {
440                         parentDirectory.delete();
441                 }
442             }
443         } catch (Exception JavaDoc e) {
444             throw new RevisionControlException(e);
445         }
446     }
447     
448     /**
449      * delete the rcml file
450      * @param filename of the document
451      * @throws RevisionControlException if something went wrong
452      */

453     public void deleteRCML(String JavaDoc filename) throws RevisionControlException{
454         try {
455             RCML rcml = this.getRCML(filename);
456             boolean deleted = rcml.delete();
457             if (!deleted) {
458                 throw new RevisionControlException("The rcml file could not be deleted!");
459             }
460         } catch (Exception JavaDoc e) {
461             throw new RevisionControlException(e);
462         }
463     }
464     
465 }
466
Popular Tags