View | Details | Raw Unified | Return to bug 34914
Collapse All | Expand All

(-)src/java/org/apache/lenya/cms/cocoon/acting/ReservedCheckoutTestAction.java (-23 / +22 lines)
Lines 15-21 Link Here
15
 *
15
 *
16
 */
16
 */
17
17
18
/* $Id: ReservedCheckoutTestAction.java,v 1.5 2004/08/16 12:12:43 andreas Exp $  */
18
/* $Id$  */
19
19
20
package org.apache.lenya.cms.cocoon.acting;
20
package org.apache.lenya.cms.cocoon.acting;
21
21
Lines 47-74 Link Here
47
        HashMap actionMap = new HashMap();
47
        HashMap actionMap = new HashMap();
48
48
49
        try {
49
        try {
50
            RCMLEntry entry =getRc().getRCML(getFilename()).getLatestEntry();
50
//            RCMLEntry entry =getRc().getRCML(getFilename()).getLatestEntry();
51
51
//	    if ((entry == null) || (entry.getType() != RCML.co) || !entry.getIdentity().equals(getUsername())) {
52
			if ((entry == null) || (entry.getType() != RCML.co) || !entry.getIdentity().equals(getUsername())) {
52
		//check out only if we can
53
				//check out
53
		getRc().reservedCheckOut(getFilename(),getUsername(), true);
54
	            getRc().reservedCheckOut(getFilename(),getUsername());   
54
//	    }
55
			}
55
	} catch (FileReservedCheckOutException e) {
56
		} catch (FileReservedCheckOutException e) {
56
	    actionMap.put("exception", "fileReservedCheckOutException");
57
			actionMap.put("exception", "fileReservedCheckOutException");
57
	    actionMap.put("filename", getFilename());
58
			actionMap.put("filename", getFilename());
58
	    actionMap.put("user", e.getCheckOutUsername());
59
			actionMap.put("user", e.getCheckOutUsername());
59
	    actionMap.put("date", e.getCheckOutDate());
60
			actionMap.put("date", e.getCheckOutDate());
60
	    getLogger().warn("Document " + getFilename() + " already checked-out by " + e.getCheckOutUsername() + " since " + e.getCheckOutDate());
61
			getLogger().warn("Document " + getFilename() + " already checked-out by " + e.getCheckOutUsername() + " since " + e.getCheckOutDate());
61
	    
62
62
	    return actionMap;
63
			return actionMap;
63
	} catch (Exception e) {
64
		} catch (Exception e) {
64
	    actionMap.put("exception", "genericException");
65
			actionMap.put("exception", "genericException");
65
	    actionMap.put("filename", getFilename());
66
			actionMap.put("filename", getFilename());
66
	    actionMap.put("message", e.getMessage());
67
			actionMap.put("message", e.getMessage());
67
	    getLogger().error(".act(): The document " + getFilename() + " couldn't be checked out: ", e);
68
			getLogger().error(".act(): The document " + getFilename() + " couldn't be checked out: ", e);
68
	    
69
69
	    return actionMap;
70
			return actionMap;
70
	}
71
		}
72
        return null;
71
        return null;
73
    }
72
    }
74
}
73
}
(-)src/java/org/apache/lenya/cms/cocoon/acting/ForceCheckInAction.java (-12 / +10 lines)
Lines 34-39 Link Here
34
import org.apache.lenya.cms.rc.CheckOutEntry;
34
import org.apache.lenya.cms.rc.CheckOutEntry;
35
import org.apache.lenya.cms.rc.CheckInEntry;
35
import org.apache.lenya.cms.rc.CheckInEntry;
36
import org.apache.lenya.cms.rc.RCML;
36
import org.apache.lenya.cms.rc.RCML;
37
import org.apache.lenya.cms.rc.RCMLEntry;
37
38
38
/**
39
/**
39
 * Checkin a document
40
 * Checkin a document
Lines 63-93 Link Here
63
        String step = parameters.getParameter("step");
64
        String step = parameters.getParameter("step");
64
                
65
                
65
        Session session = request.getSession(false);
66
        Session session = request.getSession(false);
66
        RCML rcml = getRc().getRCML(getFilename());
67
//        RCML rcml = getRc().getRCML(getFilename());
67
        Identity identity = (Identity) session.getAttribute(Identity.class
68
        Identity identity = (Identity) session.getAttribute(Identity.class
68
                .getName());
69
                .getName());
69
70
70
        if (step.equals("checkit")) {
71
        if (step.equals("checkit")) {
71
            if (rcml.getLatestEntry().getType() != RCML.ci) {
72
	    RCMLEntry lastentry = getRc().getLatestEntry(getFilename());
72
                CheckOutEntry coe = rcml.getLatestCheckOutEntry();
73
	    actionMap.put("user", lastentry.getIdentity());
73
                actionMap.put("user", coe.getIdentity());
74
	    Date lastDate = new Date(lastentry.getTime());
74
                Date checkOutDate = new Date(coe.getTime());
75
	    actionMap.put("date", lastDate.toString());
75
                actionMap.put("date", checkOutDate.toString());
76
            if (lastentry.getType() != RCML.ci) {
76
                actionMap.put("message", "lenya.rc.checkedoutalready");
77
                actionMap.put("message", "lenya.rc.checkedoutalready");
77
                actionMap.put("state", "co");
78
                actionMap.put("state", "co");
78
            } else {
79
            } else {
79
                CheckInEntry cie = rcml.getLatestCheckInEntry();
80
                actionMap.put("user", cie.getIdentity());
81
                Date checkInDate = new Date(cie.getTime());
82
                actionMap.put("date", checkInDate.toString());
83
                actionMap.put("message", "The resource has already been checked in by");    
80
                actionMap.put("message", "The resource has already been checked in by");    
84
                actionMap.put("state", "ci");
81
                actionMap.put("state", "ci");
85
            }
82
            }
86
            return actionMap;
83
            return actionMap;
87
        }
84
        }
88
85
89
        rcml.checkOutIn(RCML.ci, identity.getUser().getId(), new Date().getTime(),
86
	getRc().forcedCheckIn(getFilename(), identity.getUser().getId());
90
                false);
87
//        rcml.checkOutIn(RCML.ci, identity.getUser().getId(), new Date().getTime(),
88
//                false);
91
89
92
        return null;
90
        return null;
93
    }
91
    }
(-)src/java/org/apache/lenya/cms/rc/RevisionController.java (-174 / +350 lines)
Lines 127-135 Link Here
127
     * @throws IOException if an error occurs
127
     * @throws IOException if an error occurs
128
     * @throws Exception if an error occurs
128
     * @throws Exception if an error occurs
129
     */
129
     */
130
    /* this should never be allowed to be used because it breaks synch -- MM
130
    public RCML getRCML(String source) throws FileNotFoundException, IOException, Exception {
131
    public RCML getRCML(String source) throws FileNotFoundException, IOException, Exception {
131
        return new RCML(rcmlDir, source, rootDir);
132
        return new RCML(rcmlDir, source, rootDir);
132
    }
133
    }
134
    */
133
135
134
    /**
136
    /**
135
     * Try to make a reserved check out of the file source for a user with identity
137
     * Try to make a reserved check out of the file source for a user with identity
Lines 185-215 Link Here
185
        }
187
        }
186
        */
188
        */
187
189
188
        RCML rcml = new RCML(rcmlDir, source, rootDir);
190
	CacheEntryReference entryRef = CacheEntryReference.get("revctlr:"+source);
191
	synchronized (entryRef) { // disallow any simultaneous revctl on the same file
192
	    try { // snag exceptions so we can cleanup the synch ref and rethrow them
193
		RCML rcml = new RCML(rcmlDir, source, rootDir);
189
194
190
        RCMLEntry entry = rcml.getLatestEntry();
195
		RCMLEntry entry = rcml.getLatestEntry();
191
196
192
        // The same user is allowed to check out repeatedly without
197
		// The same user is allowed to check out repeatedly without
193
        // having to check back in first.
198
		// having to check back in first.
194
        //
199
		//
195
        if (entry != null) {
200
		if (entry != null) {
196
            log.debug("entry: " + entry);
201
		    log.debug("entry: " + entry);
197
            log.debug("entry.type:" + entry.getType());
202
		    log.debug("entry.type:" + entry.getType());
198
            log.debug("entry.identity" + entry.getIdentity());
203
		    log.debug("entry.identity" + entry.getIdentity());
199
        }
204
		}
200
205
201
        if ((entry != null)
206
		if ((entry != null)
202
            && (entry.getType() != RCML.ci)
207
		    && (entry.getType() != RCML.ci)
203
            && !entry.getIdentity().equals(identity)) {
208
		    && !entry.getIdentity().equals(identity)) {
204
            throw new FileReservedCheckOutException(rootDir + source, rcml);
209
		    throw new FileReservedCheckOutException(rootDir + source, rcml);
205
        }
210
		}
206
211
207
        rcml.checkOutIn(RCML.co, identity, new Date().getTime(), false);
212
		rcml.checkOutIn(RCML.co, identity, new Date().getTime(), false);
213
	    }
214
	    catch (Exception e) {
215
		entryRef.finish();
216
		throw(e);
217
	    }
218
	}
219
	entryRef.finish();
208
220
209
        return file;
221
        return file;
210
    }
222
    }
211
223
212
    /**
224
    /**
225
     * Try to make a reserved check out of the file source for a user with identity with a possible "check if they can first"
226
     *
227
     * @param source The filename of the file to check out
228
     * @param identity The identity of the user
229
     * @param checkIfICan Whether or not it should check if they are allowed to check out the file first
230
     * @return File File to check out
231
     * @throws Exception if an error occurs
232
     */
233
    public File reservedCheckOut(String source, String identity, boolean checkIfICan) throws Exception {
234
	// It's not worth synching here although we could to avoid race exceptions but the only code that calls this already handles that case (ReservedCheckoutTestAction)
235
	if (checkIfICan) {
236
	    if (!canCheckOut(source, identity)) {
237
		return(null);
238
	    }
239
	}
240
	return(reservedCheckOut(source, identity));
241
    }
242
243
    /**
213
     * Checks if a source can be checked out.
244
     * Checks if a source can be checked out.
214
     * @param source The source.
245
     * @param source The source.
215
     * @param identity The identity who requests checking out.
246
     * @param identity The identity who requests checking out.
Lines 248-270 Link Here
248
     * @throws Exception when something went wrong.
279
     * @throws Exception when something went wrong.
249
     */
280
     */
250
    public boolean canCheckOut(String source, String identity) throws Exception {
281
    public boolean canCheckOut(String source, String identity) throws Exception {
251
        RCML rcml = new RCML(rcmlDir, source, rootDir);
282
	boolean checkedOutByOther = true;
252
283
253
        RCMLEntry entry = rcml.getLatestEntry();
284
	CacheEntryReference entryRef = CacheEntryReference.get("revctlr:"+source);
285
	synchronized (entryRef) { // disallow any simultaneous revctl on the same file
286
	    try { // snag exceptions so we can cleanup the synch ref and rethrow them
287
		RCML rcml = new RCML(rcmlDir, source, rootDir);
254
288
255
        // The same user is allowed to check out repeatedly without
289
		RCMLEntry entry = rcml.getLatestEntry();
256
        // having to check back in first.
257
        //
258
        if (entry != null) {
259
            log.debug("entry: " + entry);
260
            log.debug("entry.type:" + entry.getType());
261
            log.debug("entry.identity" + entry.getIdentity());
262
        }
263
290
264
        boolean checkedOutByOther =
291
		// The same user is allowed to check out repeatedly without
265
            entry != null && entry.getType() != RCML.ci && !entry.getIdentity().equals(identity);
292
		// having to check back in first.
293
		//
294
		if (entry != null) {
295
		    log.debug("entry: " + entry);
296
		    log.debug("entry.type:" + entry.getType());
297
		    log.debug("entry.identity" + entry.getIdentity());
298
		}
266
299
267
        return !checkedOutByOther;
300
		checkedOutByOther =
301
		    entry != null && entry.getType() != RCML.ci && !entry.getIdentity().equals(identity);
302
	    }
303
	    catch (Exception e) {
304
		entryRef.finish();
305
		throw(e);
306
	    }
307
	}
308
	entryRef.finish();
309
310
	return !checkedOutByOther;
268
    }
311
    }
269
312
270
    /**
313
    /**
Lines 375-464 Link Here
375
     */
418
     */
376
    public long reservedCheckIn(String destination, String identity, boolean backup)
419
    public long reservedCheckIn(String destination, String identity, boolean backup)
377
        throws FileReservedCheckInException, Exception {
420
        throws FileReservedCheckInException, Exception {
378
        RCML rcml = new RCML(rcmlDir, destination, rootDir);
379
421
380
        CheckOutEntry coe = rcml.getLatestCheckOutEntry();
422
	long time = new Date().getTime();
381
        CheckInEntry cie = rcml.getLatestCheckInEntry();
382
423
383
        // If there has never been a checkout for this object
424
	CacheEntryReference entryRef = CacheEntryReference.get("revctlr:"+destination);
384
        // *or* if the user attempting the checkin right now
425
	synchronized (entryRef) { // disallow any simultaneous revctl on the same file
385
        // is the system itself, we will skip any checks and proceed
426
	    try { // snag exceptions so we can cleanup the synch ref and rethrow them
386
        // right away to the actual checkin.
427
		RCML rcml = new RCML(rcmlDir, destination, rootDir);
387
        // In all other cases we enforce the revision control
388
        // rules inside this if clause:
389
        //
390
        if (!((coe == null) || identity.equals(RevisionController.systemUsername))) {
391
            /*
392
             * Possible cases and rules:
393
             *
394
             * 1.) we were able to read the latest checkin and it is later than latest checkout
395
             *     (i.e. there is no open checkout to match this checkin, an unusual case)
396
             *     1.1.) identity of latest checkin is equal to current user
397
             *           -> checkin allowed, same user may check in repeatedly
398
             *     1.2.) identity of latest checkin is not equal to current user
399
             *           -> checkin rejected, may not overwrite the revision which
400
             *              another user checked in previously
401
             * 2.) there was no checkin or the latest checkout is later than latest checkin
402
             *     (i.e. there is an open checkout)
403
             *     2.1.) identity of latest checkout is equal to current user
404
             *           -> checkin allowed, user checked out and may check in again
405
             *              (the most common case)
406
             *     2.2.) identity of latest checkout is not equal to current user
407
             *           -> checkin rejected, may not check in while another
408
             *              user is working on this document
409
             *
410
             */
411
            if ((cie != null) && (cie.getTime() > coe.getTime())) {
412
                // We have case 1
413
                if (!cie.getIdentity().equals(identity)) {
414
                    // Case 1.2., abort...
415
                    //
416
                    throw new FileReservedCheckInException(rootDir + destination, rcml);
417
                }
418
            } else {
419
                // Case 2
420
                if (!coe.getIdentity().equals(identity)) {
421
                    // Case 2.2., abort...
422
                    //
423
                    throw new FileReservedCheckInException(rootDir + destination, rcml);
424
                }
425
            }
426
        }
427
428
428
        File originalFile = new File(rootDir, destination);
429
		CheckOutEntry coe = rcml.getLatestCheckOutEntry();
429
        long time = new Date().getTime();
430
		CheckInEntry cie = rcml.getLatestCheckInEntry();
430
431
431
        if (backup && originalFile.isFile()) {
432
		// If there has never been a checkout for this object
432
            File backupFile = new File(backupDir, destination + ".bak." + time);
433
		// *or* if the user attempting the checkin right now
433
            File parent = new File(backupFile.getParent());
434
		// is the system itself, we will skip any checks and proceed
435
		// right away to the actual checkin.
436
		// In all other cases we enforce the revision control
437
		// rules inside this if clause:
438
		//
439
		if (!((coe == null) || identity.equals(RevisionController.systemUsername))) {
440
		    /*
441
		     * Possible cases and rules:
442
		     *
443
		     * 1.) we were able to read the latest checkin and it is later than latest checkout
444
		     *     (i.e. there is no open checkout to match this checkin, an unusual case)
445
		     *     1.1.) identity of latest checkin is equal to current user
446
		     *           -> checkin allowed, same user may check in repeatedly
447
		     *     1.2.) identity of latest checkin is not equal to current user
448
		     *           -> checkin rejected, may not overwrite the revision which
449
		     *              another user checked in previously
450
		     * 2.) there was no checkin or the latest checkout is later than latest checkin
451
		     *     (i.e. there is an open checkout)
452
		     *     2.1.) identity of latest checkout is equal to current user
453
		     *           -> checkin allowed, user checked out and may check in again
454
		     *              (the most common case)
455
		     *     2.2.) identity of latest checkout is not equal to current user
456
		     *           -> checkin rejected, may not check in while another
457
		     *              user is working on this document
458
		     *
459
		     */
460
		    if ((cie != null) && (cie.getTime() > coe.getTime())) {
461
			// We have case 1
462
			if (!cie.getIdentity().equals(identity)) {
463
			    // Case 1.2., abort...
464
			    //
465
			    throw new FileReservedCheckInException(rootDir + destination, rcml);
466
			}
467
		    } else {
468
			// Case 2
469
			if (!coe.getIdentity().equals(identity)) {
470
			    // Case 2.2., abort...
471
			    //
472
			    throw new FileReservedCheckInException(rootDir + destination, rcml);
473
			}
474
		    }
475
		}
434
476
435
            if (!parent.isDirectory()) {
477
		File originalFile = new File(rootDir, destination);
436
                parent.mkdirs();
437
            }
438
478
439
            log.info(
479
		if (backup && originalFile.isFile()) {
440
                "Backup: copy "
480
		    File backupFile = new File(backupDir, destination + ".bak." + time);
441
                    + originalFile.getAbsolutePath()
481
		    File parent = new File(backupFile.getParent());
442
                    + " to "
443
                    + backupFile.getAbsolutePath());
444
482
445
            InputStream in = new FileInputStream(originalFile.getAbsolutePath());
483
		    if (!parent.isDirectory()) {
484
			parent.mkdirs();
485
		    }
446
486
447
            OutputStream out = new XPSFileOutputStream(backupFile.getAbsolutePath());
487
		    log.info(
448
            byte[] buffer = new byte[512];
488
			     "Backup: copy "
449
            int length;
489
			     + originalFile.getAbsolutePath()
490
			     + " to "
491
			     + backupFile.getAbsolutePath());
492
		    
493
		    InputStream in = new FileInputStream(originalFile.getAbsolutePath());
494
		    
495
		    OutputStream out = new XPSFileOutputStream(backupFile.getAbsolutePath());
496
		    byte[] buffer = new byte[512];
497
		    int length;
498
		    
499
		    while ((length = in.read(buffer)) != -1) {
500
			out.write(buffer, 0, length);
501
		    }
502
		    
503
		    out.close();
504
		}
505
		
506
		rcml.checkOutIn(RCML.ci, identity, time, backup);
507
		rcml.pruneEntries(backupDir);
508
		rcml.write();
509
	    }
510
	    catch (Exception e) {
511
		entryRef.finish();
512
		throw(e);
513
	    }
514
	}
515
	entryRef.finish();
450
516
451
            while ((length = in.read(buffer)) != -1) {
452
                out.write(buffer, 0, length);
453
            }
454
455
            out.close();
456
        }
457
458
        rcml.checkOutIn(RCML.ci, identity, time, backup);
459
        rcml.pruneEntries(backupDir);
460
        rcml.write();
461
462
        // FIXME: If we reuse the observer pattern as implemented in
517
        // FIXME: If we reuse the observer pattern as implemented in
463
        // xps this would be the place to notify the observers,
518
        // xps this would be the place to notify the observers,
464
        // e.g. like so:
519
        // e.g. like so:
Lines 475-481 Link Here
475
     *
530
     *
476
     * @return String The absolute path of the backup version
531
     * @return String The absolute path of the backup version
477
     */
532
     */
478
    public String getBackupFilename(long time, String filename) {
533
    private String getBackupFilename(long time, String filename) {
479
        File backup = new File(backupDir, filename + ".bak." + time);
534
        File backup = new File(backupDir, filename + ".bak." + time);
480
535
481
        return backup.getAbsolutePath();
536
        return backup.getAbsolutePath();
Lines 505-511 Link Here
505
     *
560
     *
506
     * @return File The file of the backup version
561
     * @return File The file of the backup version
507
     */
562
     */
508
    public File getBackupFile(long time, String filename) {
563
    private File getBackupFile(long time, String filename) {
509
        File backup = new File(backupDir, filename + ".bak." + time);
564
        File backup = new File(backupDir, filename + ".bak." + time);
510
565
511
        return backup;
566
        return backup;
Lines 597-602 Link Here
597
            FileReservedCheckOutException,
652
            FileReservedCheckOutException,
598
            FileNotFoundException,
653
            FileNotFoundException,
599
            Exception {
654
            Exception {
655
656
	long newtime = time;
657
600
        // Make sure the old version exists
658
        // Make sure the old version exists
601
        //
659
        //
602
        File backup = new File(backupDir, destination + ".bak." + time);
660
        File backup = new File(backupDir, destination + ".bak." + time);
Lines 610-638 Link Here
610
            throw new FileNotFoundException(current.getAbsolutePath());
668
            throw new FileNotFoundException(current.getAbsolutePath());
611
        }
669
        }
612
670
613
        // Try to check out current version
671
	CacheEntryReference entryRef = CacheEntryReference.get("revctlr:"+destination);
614
        //
672
	synchronized (entryRef) { // disallow any simultaneous revctl on the same file
615
        reservedCheckOut(destination, identity);
673
	    try { // snag exceptions so we can cleanup the synch ref and rethrow them
674
		// Try to check out current version
675
		//
676
		reservedCheckOut(destination, identity);
616
677
617
        // Now roll back to the old state
678
		// Now roll back to the old state
618
        //
679
		//
619
        FileInputStream in = new FileInputStream(backup.getAbsolutePath());
680
		FileInputStream in = new FileInputStream(backup.getAbsolutePath());
620
681
621
        XPSFileOutputStream out = new XPSFileOutputStream(current.getAbsolutePath());
682
		XPSFileOutputStream out = new XPSFileOutputStream(current.getAbsolutePath());
622
        byte[] buffer = new byte[512];
683
		byte[] buffer = new byte[512];
623
        int length;
684
		int length;
624
685
625
        while ((length = in.read(buffer)) != -1) {
686
		while ((length = in.read(buffer)) != -1) {
626
            out.write(buffer, 0, length);
687
		    out.write(buffer, 0, length);
627
        }
688
		}
628
689
629
        out.close();
690
		out.close();
630
691
631
        // Try to check back in, this might cause
692
		// Try to check back in, this might cause
632
        // a backup of the current version to be created if
693
		// a backup of the current version to be created if
633
        // desired by the user.
694
		// desired by the user.
634
        //
695
		//
635
        long newtime = reservedCheckIn(destination, identity, backupFlag);
696
		newtime = reservedCheckIn(destination, identity, backupFlag);
697
	    }
698
	    catch (Exception e) {
699
		entryRef.finish();
700
		throw(e);
701
	    }
702
	}
703
	entryRef.finish();
636
704
637
        return newtime;
705
        return newtime;
638
    }
706
    }
Lines 650-657 Link Here
650
        File backup = new File(backupDir + "/" + destination + ".bak." + time);
718
        File backup = new File(backupDir + "/" + destination + ".bak." + time);
651
        File current = new File(rootDir + destination);
719
        File current = new File(rootDir + destination);
652
720
653
        RCML rcml = new RCML(rcmlDir, destination, rootDir);
654
655
        if (!backup.isFile()) {
721
        if (!backup.isFile()) {
656
            throw new FileNotFoundException(backup.getAbsolutePath());
722
            throw new FileNotFoundException(backup.getAbsolutePath());
657
        }
723
        }
Lines 660-679 Link Here
660
            throw new FileNotFoundException(current.getAbsolutePath());
726
            throw new FileNotFoundException(current.getAbsolutePath());
661
        }
727
        }
662
728
663
        FileInputStream in = new FileInputStream(backup.getAbsolutePath());
729
	CacheEntryReference entryRef = CacheEntryReference.get("revctlr:"+destination);
730
	synchronized (entryRef) { // disallow any simultaneous revctl on the same file
731
	    try { // snag exceptions so we can cleanup the synch ref and rethrow them
732
		RCML rcml = new RCML(rcmlDir, destination, rootDir);
664
733
665
        XPSFileOutputStream out = new XPSFileOutputStream(current.getAbsolutePath());
734
		FileInputStream in = new FileInputStream(backup.getAbsolutePath());
666
        byte[] buffer = new byte[512];
667
        int length;
668
735
669
        while ((length = in.read(buffer)) != -1) {
736
		XPSFileOutputStream out = new XPSFileOutputStream(current.getAbsolutePath());
670
            out.write(buffer, 0, length);
737
		byte[] buffer = new byte[512];
671
        }
738
		int length;
672
739
673
        log.info("Undo: copy " + backup.getAbsolutePath() + " " + current.getAbsolutePath());
740
		while ((length = in.read(buffer)) != -1) {
741
		    out.write(buffer, 0, length);
742
		}
674
743
675
        rcml.deleteFirstCheckIn();
744
		log.info("Undo: copy " + backup.getAbsolutePath() + " " + current.getAbsolutePath());
676
        out.close();
745
746
		rcml.deleteFirstCheckIn();
747
		out.close();
748
	    }
749
	    catch (Exception e) {
750
		entryRef.finish();
751
		throw(e);
752
	    }
753
	}
754
	entryRef.finish();
677
    }
755
    }
678
756
679
    /**
757
    /**
Lines 711-742 Link Here
711
    /**
789
    /**
712
     * @deprecated please use deleteRevisions(Document)
790
     * @deprecated please use deleteRevisions(Document)
713
     * delete the revisions
791
     * delete the revisions
714
	 * @param filename of the document
792
     * @param filename of the document
715
	 * @throws RevisionControlException when somthing went wrong
793
     * @throws RevisionControlException when somthing went wrong
716
	 */
794
     */
717
	public void deleteRevisions(String filename) throws RevisionControlException{
795
    public void deleteRevisions(String filename) throws RevisionControlException{
718
        try {
796
	CacheEntryReference entryRef = CacheEntryReference.get("revctlr:"+filename);
719
			RCML rcml = this.getRCML(filename);
797
	synchronized (entryRef) { // disallow any simultaneous revctl on the same file
720
            String[] times = rcml.getBackupsTime();
798
	    try {
721
            for (int i=0; i < times.length; i++) {
799
		RCML rcml = new RCML(rcmlDir, filename, rootDir);
722
                long time = new Long(times[i]).longValue();
800
		String[] times = rcml.getBackupsTime();
723
                File backup = this.getBackupFile(time, filename);
801
		for (int i=0; i < times.length; i++) {
724
                File parentDirectory = null; 
802
		    long time = new Long(times[i]).longValue();
725
                parentDirectory = backup.getParentFile(); 
803
		    File backup = this.getBackupFile(time, filename);
726
                boolean deleted = backup.delete();
804
		    File parentDirectory = null; 
727
                if (!deleted) {
805
		    parentDirectory = backup.getParentFile(); 
728
                    throw new RevisionControlException("The backup file, "+backup.getCanonicalPath()+" could not be deleted!");
806
		    boolean deleted = backup.delete();
729
                }
807
		    if (!deleted) {
730
                if (parentDirectory != null 
808
			throw new RevisionControlException("The backup file, "+backup.getCanonicalPath()+" could not be deleted!");
731
                    && parentDirectory.exists()
809
		    }
732
                    && parentDirectory.isDirectory()
810
		    if (parentDirectory != null 
733
                    && parentDirectory.listFiles().length == 0) {
811
			&& parentDirectory.exists()
734
                        parentDirectory.delete();
812
			&& parentDirectory.isDirectory()
735
                }
813
			&& parentDirectory.listFiles().length == 0) {
736
            }
814
                            parentDirectory.delete();
737
		} catch (Exception e) {
815
		    }
738
            throw new RevisionControlException(e);
739
		}
816
		}
817
	    } catch (Exception e) {
818
		entryRef.finish();
819
		throw new RevisionControlException(e); // this is kinda stupid coz it wraps the one we threw ourselves too but oh well
820
	    }
821
	}
822
	entryRef.finish();
740
    }
823
    }
741
    
824
    
742
    /**
825
    /**
Lines 761-779 Link Here
761
    /**
844
    /**
762
     * @deprecated please use deleteRCML(Document)
845
     * @deprecated please use deleteRCML(Document)
763
     * delete the rcml file
846
     * delete the rcml file
764
	 * @param filename of the document
847
     * @param filename of the document
765
	 * @throws RevisionControlException if something went wrong
848
     * @throws RevisionControlException if something went wrong
766
	 */
849
     */
767
	public void deleteRCML(String filename) throws RevisionControlException{
850
    public void deleteRCML(String filename) throws RevisionControlException{
768
        try {
851
	CacheEntryReference entryRef = CacheEntryReference.get("revctlr:"+filename);
769
            RCML rcml = this.getRCML(filename);
852
	synchronized (entryRef) { // disallow any simultaneous revctl on the same file
770
            boolean deleted = rcml.delete();
853
	    try {
771
            if (!deleted) {
854
		RCML rcml = new RCML(rcmlDir, filename, rootDir);
772
                throw new RevisionControlException("The rcml file could not be deleted!");
855
		boolean deleted = rcml.delete();
773
            }
856
		if (!deleted) {
774
        } catch (Exception e) {
857
		    throw new RevisionControlException("The rcml file could not be deleted!");
775
            throw new RevisionControlException(e);
858
		}
776
        }
859
	    } catch (Exception e) {
860
		entryRef.finish();
861
		throw new RevisionControlException(e); // this is kinda stupid coz it wraps the one we threw ourselves too but oh well
862
	    }
863
	}
864
	entryRef.finish();
777
    }
865
    }
778
    
866
    
867
    /**
868
     * Get a copy of the rcml tree for one-time display only (it's not protected from synch issues if you use it for anything else)
869
     *
870
     * @param destination The file we want to check in
871
     *
872
     * @return RCMLEntry the latest entry
873
     *
874
     * @exception Exception if other problems occur
875
     */
876
    public org.w3c.dom.Document getRCMLTree(String destination)
877
        throws Exception {
878
879
	org.w3c.dom.Document rcmlDoc = null;
880
	CacheEntryReference entryRef = CacheEntryReference.get("revctlr:"+destination);
881
	synchronized (entryRef) { // disallow any simultaneous revctl on the same file
882
	    try { // snag exceptions so we can cleanup the synch ref and rethrow them
883
		RCML rcml = new RCML(rcmlDir, destination, rootDir);
884
		rcmlDoc = rcml.getDOMDocumentClone();
885
	    }
886
	    catch (Exception e) {
887
		entryRef.finish();
888
		throw(e);
889
	    }
890
	}
891
	entryRef.finish();
892
893
	return(rcmlDoc);
894
    }
895
896
897
    /**
898
     * Try to force a check in of the file destination for a user with identity.
899
     *
900
     * @param destination The file we want to check in
901
     * @param identity The identity of the user
902
     *
903
     * @exception Exception if other problems occur
904
     */
905
    public void forcedCheckIn(String destination, String identity)
906
        throws Exception {
907
908
	CacheEntryReference entryRef = CacheEntryReference.get("revctlr:"+destination);
909
	synchronized (entryRef) { // disallow any simultaneous revctl on the same file
910
	    try { // snag exceptions so we can cleanup the synch ref and rethrow them
911
		RCML rcml = new RCML(rcmlDir, destination, rootDir);
912
		rcml.checkOutIn(RCML.ci, identity, new Date().getTime(), false);
913
	    }
914
	    catch (Exception e) {
915
		entryRef.finish();
916
		throw(e);
917
	    }
918
	}
919
	entryRef.finish();
920
    }
921
922
    /**
923
     * Get the latest entry for one-time display purposes only (it's not protected from synch issues if you use it for anything else)
924
     *
925
     * @param destination The file we want to get info for
926
     *
927
     * @return RCMLEntry the latest entry
928
     *
929
     * @exception Exception if other problems occur
930
     */
931
    public RCMLEntry getLatestEntry(String destination)
932
        throws Exception {
933
934
	RCMLEntry entry = null;
935
	CacheEntryReference entryRef = CacheEntryReference.get("revctlr:"+destination);
936
	synchronized (entryRef) { // disallow any simultaneous revctl on the same file
937
	    try { // snag exceptions so we can cleanup the synch ref and rethrow them
938
		RCML rcml = new RCML(rcmlDir, destination, rootDir);
939
		if (rcml.getLatestEntry().getType() != RCML.ci) {
940
		    entry = rcml.getLatestCheckOutEntry();
941
		}
942
		else {
943
		    entry = rcml.getLatestCheckInEntry();
944
		}
945
	    }
946
	    catch (Exception e) {
947
		entryRef.finish();
948
		throw(e);
949
	    }
950
	}
951
	entryRef.finish();
952
953
	return(entry);
954
    }
779
}
955
}
(-)src/java/org/apache/lenya/cms/rc/CacheEntryReference.java (+83 lines)
Line 0 Link Here
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
package org.apache.lenya.cms.rc;
19
20
// Imports for the cache stuff
21
import java.util.Hashtable;
22
23
import org.apache.log4j.*;
24
25
/*
26
 * This class helps deal with multiple requests for the same resource (key) by acting somewhat like a semaphore on that resource key
27
 *
28
 * All requests for the same key would be synchronized on an object of CacheEntryReference based on that key
29
 *
30
 * When you need to synchronize access to a resource, you call get() passing a key string made up of the "type" and a unique resource id (e.g. filename) (type would be something like "RevisionController", something that identifies how your app wants to use it without stepping on other uses of the resource by the same type)
31
 * get will return either a new CER object or a cached one (and because every call to get returns the same object for a specific key, until every thread that's accessing it simultaneously is done with it, you can use this object to synchronize stuff that needs to be done to the resource)
32
 * By using the keyed object for synch, requests and processing for different keys can all happen simultaneously without interfering with each other (a finer grain of synch)
33
 *
34
 * Usage:
35
 * CacheEntryReference entryRef = CacheEntryReference.get("typeofref:"+refname);
36
 * // where typeofref is something like "RevisionController" and refname is the indiv keyname, like the sourcefile name
37
 * // wrap your code that needs to be synched on this resource with
38
 * synchronized (entryRef) {
39
 * }
40
 * entryRef.finish(); // this doesn't need to be in the synch block
41
 */
42
public class CacheEntryReference {
43
    private int _refCount;
44
    private String _id;
45
    private static Hashtable _refs = new Hashtable();
46
    private static Logger log = Logger.getLogger(CacheEntryReference.class);
47
48
    public static CacheEntryReference get(String id) {
49
//	log.setLevel((Level)Level.DEBUG);
50
	synchronized (_refs) {
51
	    log.debug("get: START " + id);
52
	    CacheEntryReference entry = (CacheEntryReference)_refs.get(id);
53
	    if (entry == null) {
54
		log.debug("get: not in hash " + id);
55
		entry = new CacheEntryReference(id);
56
		_refs.put(id, entry);
57
	    }
58
	    entry._refCount++;
59
	    log.debug("get: new refcount=" + entry._refCount + " for " + id);
60
	    log.debug("get: END " + id);
61
	    return(entry);
62
	}
63
    }
64
65
    public void finish() {
66
	synchronized (_refs) {
67
	    log.debug("finish: START " + _id);
68
	    _refCount--;
69
	    log.debug("finish: new refcount=" + _refCount + " for " + _id);
70
	    if (_refCount <= 0) {
71
		log.debug("finish: removing " + _id);
72
		_refs.remove(_id);
73
		_refCount = 0;
74
	    }
75
	    log.debug("finish: END " + _id);
76
	}
77
    }
78
79
    private CacheEntryReference(String id) {
80
	_id = id;
81
	_refCount = 0;
82
    }
83
}
(-)src/webapp/lenya/content/rc/versions-screen.xsp (-2 / +3 lines)
Lines 15-21 Link Here
15
  limitations under the License.
15
  limitations under the License.
16
-->
16
-->
17
17
18
<!-- $Id: versions-screen.xsp,v 1.14 2004/03/13 12:31:31 gregor Exp $ -->
18
<!-- $Id$ -->
19
19
20
<xsp:page 
20
<xsp:page 
21
       language="java" 
21
       language="java" 
Lines 58-64 Link Here
58
58
59
      org.w3c.dom.Document rcmlDoc=null;
59
      org.w3c.dom.Document rcmlDoc=null;
60
      try {
60
      try {
61
        rcmlDoc=rc.getRCML(filename).getDOMDocumentClone();
61
//        rcmlDoc=rc.getRCML(filename).getDOMDocumentClone();
62
        rcmlDoc = rc.getRCMLTree(filename);
62
      } catch (Exception e) {
63
      } catch (Exception e) {
63
        <rc:exception>rollback: Unable to get DOM doc for rcml file, caught exception: <xsp:expr>e.toString()</xsp:expr></rc:exception>
64
        <rc:exception>rollback: Unable to get DOM doc for rcml file, caught exception: <xsp:expr>e.toString()</xsp:expr></rc:exception>
64
      }                                                                                                                                    
65
      }                                                                                                                                    
(-)src/webapp/lenya/content/info/revisions.xsp (-2 / +3 lines)
Lines 15-21 Link Here
15
  limitations under the License.
15
  limitations under the License.
16
-->
16
-->
17
17
18
<!-- $Id: revisions.xsp,v 1.5 2004/03/13 12:31:30 gregor Exp $ -->
18
<!-- $Id$ -->
19
19
20
<xsp:page 
20
<xsp:page 
21
  language="java" 
21
  language="java" 
Lines 61-67 Link Here
61
61
62
      org.w3c.dom.Document rcmlDoc=null;
62
      org.w3c.dom.Document rcmlDoc=null;
63
      try {
63
      try {
64
        rcmlDoc=rc.getRCML(filename).getDOMDocumentClone();
64
//        rcmlDoc=rc.getRCML(filename).getDOMDocumentClone();
65
        rcmlDoc = rc.getRCMLTree(filename);
65
      } catch (Exception e) {
66
      } catch (Exception e) {
66
        <rc:exception>rollback: Unable to get DOM doc for rcml file, caught exception: <xsp:expr>e.toString()</xsp:expr></rc:exception>
67
        <rc:exception>rollback: Unable to get DOM doc for rcml file, caught exception: <xsp:expr>e.toString()</xsp:expr></rc:exception>
67
      }                                                                                                                                    
68
      }                                                                                                                                    

Return to bug 34914