Index: src/java/org/apache/lenya/cms/cocoon/acting/ReservedCheckoutTestAction.java =================================================================== --- src/java/org/apache/lenya/cms/cocoon/acting/ReservedCheckoutTestAction.java (revision 370504) +++ src/java/org/apache/lenya/cms/cocoon/acting/ReservedCheckoutTestAction.java (working copy) @@ -15,7 +15,7 @@ * */ -/* $Id: ReservedCheckoutTestAction.java,v 1.5 2004/08/16 12:12:43 andreas Exp $ */ +/* $Id$ */ package org.apache.lenya.cms.cocoon.acting; @@ -47,28 +47,27 @@ HashMap actionMap = new HashMap(); try { - RCMLEntry entry =getRc().getRCML(getFilename()).getLatestEntry(); - - if ((entry == null) || (entry.getType() != RCML.co) || !entry.getIdentity().equals(getUsername())) { - //check out - getRc().reservedCheckOut(getFilename(),getUsername()); - } - } catch (FileReservedCheckOutException e) { - actionMap.put("exception", "fileReservedCheckOutException"); - actionMap.put("filename", getFilename()); - actionMap.put("user", e.getCheckOutUsername()); - actionMap.put("date", e.getCheckOutDate()); - getLogger().warn("Document " + getFilename() + " already checked-out by " + e.getCheckOutUsername() + " since " + e.getCheckOutDate()); - - return actionMap; - } catch (Exception e) { - actionMap.put("exception", "genericException"); - actionMap.put("filename", getFilename()); - actionMap.put("message", e.getMessage()); - getLogger().error(".act(): The document " + getFilename() + " couldn't be checked out: ", e); - - return actionMap; - } +// RCMLEntry entry =getRc().getRCML(getFilename()).getLatestEntry(); +// if ((entry == null) || (entry.getType() != RCML.co) || !entry.getIdentity().equals(getUsername())) { + //check out only if we can + getRc().reservedCheckOut(getFilename(),getUsername(), true); +// } + } catch (FileReservedCheckOutException e) { + actionMap.put("exception", "fileReservedCheckOutException"); + actionMap.put("filename", getFilename()); + actionMap.put("user", e.getCheckOutUsername()); + actionMap.put("date", e.getCheckOutDate()); + getLogger().warn("Document " + getFilename() + " already checked-out by " + e.getCheckOutUsername() + " since " + e.getCheckOutDate()); + + return actionMap; + } catch (Exception e) { + actionMap.put("exception", "genericException"); + actionMap.put("filename", getFilename()); + actionMap.put("message", e.getMessage()); + getLogger().error(".act(): The document " + getFilename() + " couldn't be checked out: ", e); + + return actionMap; + } return null; } } Index: src/java/org/apache/lenya/cms/cocoon/acting/ForceCheckInAction.java =================================================================== --- src/java/org/apache/lenya/cms/cocoon/acting/ForceCheckInAction.java (revision 370504) +++ src/java/org/apache/lenya/cms/cocoon/acting/ForceCheckInAction.java (working copy) @@ -34,6 +34,7 @@ import org.apache.lenya.cms.rc.CheckOutEntry; import org.apache.lenya.cms.rc.CheckInEntry; import org.apache.lenya.cms.rc.RCML; +import org.apache.lenya.cms.rc.RCMLEntry; /** * Checkin a document @@ -63,31 +64,28 @@ String step = parameters.getParameter("step"); Session session = request.getSession(false); - RCML rcml = getRc().getRCML(getFilename()); +// RCML rcml = getRc().getRCML(getFilename()); Identity identity = (Identity) session.getAttribute(Identity.class .getName()); if (step.equals("checkit")) { - if (rcml.getLatestEntry().getType() != RCML.ci) { - CheckOutEntry coe = rcml.getLatestCheckOutEntry(); - actionMap.put("user", coe.getIdentity()); - Date checkOutDate = new Date(coe.getTime()); - actionMap.put("date", checkOutDate.toString()); + RCMLEntry lastentry = getRc().getLatestEntry(getFilename()); + actionMap.put("user", lastentry.getIdentity()); + Date lastDate = new Date(lastentry.getTime()); + actionMap.put("date", lastDate.toString()); + if (lastentry.getType() != RCML.ci) { actionMap.put("message", "lenya.rc.checkedoutalready"); actionMap.put("state", "co"); } else { - CheckInEntry cie = rcml.getLatestCheckInEntry(); - actionMap.put("user", cie.getIdentity()); - Date checkInDate = new Date(cie.getTime()); - actionMap.put("date", checkInDate.toString()); actionMap.put("message", "The resource has already been checked in by"); actionMap.put("state", "ci"); } return actionMap; } - rcml.checkOutIn(RCML.ci, identity.getUser().getId(), new Date().getTime(), - false); + getRc().forcedCheckIn(getFilename(), identity.getUser().getId()); +// rcml.checkOutIn(RCML.ci, identity.getUser().getId(), new Date().getTime(), +// false); return null; } Index: src/java/org/apache/lenya/cms/rc/RevisionController.java =================================================================== --- src/java/org/apache/lenya/cms/rc/RevisionController.java (revision 370504) +++ src/java/org/apache/lenya/cms/rc/RevisionController.java (working copy) @@ -127,9 +127,11 @@ * @throws IOException if an error occurs * @throws Exception if an error occurs */ + /* this should never be allowed to be used because it breaks synch -- MM public RCML getRCML(String source) throws FileNotFoundException, IOException, Exception { return new RCML(rcmlDir, source, rootDir); } + */ /** * Try to make a reserved check out of the file source for a user with identity @@ -185,31 +187,60 @@ } */ - RCML rcml = new RCML(rcmlDir, source, rootDir); + CacheEntryReference entryRef = CacheEntryReference.get("revctlr:"+source); + synchronized (entryRef) { // disallow any simultaneous revctl on the same file + try { // snag exceptions so we can cleanup the synch ref and rethrow them + RCML rcml = new RCML(rcmlDir, source, rootDir); - RCMLEntry entry = rcml.getLatestEntry(); + RCMLEntry entry = rcml.getLatestEntry(); - // The same user is allowed to check out repeatedly without - // having to check back in first. - // - if (entry != null) { - log.debug("entry: " + entry); - log.debug("entry.type:" + entry.getType()); - log.debug("entry.identity" + entry.getIdentity()); - } + // The same user is allowed to check out repeatedly without + // having to check back in first. + // + if (entry != null) { + log.debug("entry: " + entry); + log.debug("entry.type:" + entry.getType()); + log.debug("entry.identity" + entry.getIdentity()); + } - if ((entry != null) - && (entry.getType() != RCML.ci) - && !entry.getIdentity().equals(identity)) { - throw new FileReservedCheckOutException(rootDir + source, rcml); - } + if ((entry != null) + && (entry.getType() != RCML.ci) + && !entry.getIdentity().equals(identity)) { + throw new FileReservedCheckOutException(rootDir + source, rcml); + } - rcml.checkOutIn(RCML.co, identity, new Date().getTime(), false); + rcml.checkOutIn(RCML.co, identity, new Date().getTime(), false); + } + catch (Exception e) { + entryRef.finish(); + throw(e); + } + } + entryRef.finish(); return file; } /** + * Try to make a reserved check out of the file source for a user with identity with a possible "check if they can first" + * + * @param source The filename of the file to check out + * @param identity The identity of the user + * @param checkIfICan Whether or not it should check if they are allowed to check out the file first + * @return File File to check out + * @throws Exception if an error occurs + */ + public File reservedCheckOut(String source, String identity, boolean checkIfICan) throws Exception { + // 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) + if (checkIfICan) { + if (!canCheckOut(source, identity)) { + return(null); + } + } + return(reservedCheckOut(source, identity)); + } + + /** * Checks if a source can be checked out. * @param source The source. * @param identity The identity who requests checking out. @@ -248,23 +279,35 @@ * @throws Exception when something went wrong. */ public boolean canCheckOut(String source, String identity) throws Exception { - RCML rcml = new RCML(rcmlDir, source, rootDir); + boolean checkedOutByOther = true; - RCMLEntry entry = rcml.getLatestEntry(); + CacheEntryReference entryRef = CacheEntryReference.get("revctlr:"+source); + synchronized (entryRef) { // disallow any simultaneous revctl on the same file + try { // snag exceptions so we can cleanup the synch ref and rethrow them + RCML rcml = new RCML(rcmlDir, source, rootDir); - // The same user is allowed to check out repeatedly without - // having to check back in first. - // - if (entry != null) { - log.debug("entry: " + entry); - log.debug("entry.type:" + entry.getType()); - log.debug("entry.identity" + entry.getIdentity()); - } + RCMLEntry entry = rcml.getLatestEntry(); - boolean checkedOutByOther = - entry != null && entry.getType() != RCML.ci && !entry.getIdentity().equals(identity); + // The same user is allowed to check out repeatedly without + // having to check back in first. + // + if (entry != null) { + log.debug("entry: " + entry); + log.debug("entry.type:" + entry.getType()); + log.debug("entry.identity" + entry.getIdentity()); + } - return !checkedOutByOther; + checkedOutByOther = + entry != null && entry.getType() != RCML.ci && !entry.getIdentity().equals(identity); + } + catch (Exception e) { + entryRef.finish(); + throw(e); + } + } + entryRef.finish(); + + return !checkedOutByOther; } /** @@ -375,90 +418,102 @@ */ public long reservedCheckIn(String destination, String identity, boolean backup) throws FileReservedCheckInException, Exception { - RCML rcml = new RCML(rcmlDir, destination, rootDir); - CheckOutEntry coe = rcml.getLatestCheckOutEntry(); - CheckInEntry cie = rcml.getLatestCheckInEntry(); + long time = new Date().getTime(); - // If there has never been a checkout for this object - // *or* if the user attempting the checkin right now - // is the system itself, we will skip any checks and proceed - // right away to the actual checkin. - // In all other cases we enforce the revision control - // rules inside this if clause: - // - if (!((coe == null) || identity.equals(RevisionController.systemUsername))) { - /* - * Possible cases and rules: - * - * 1.) we were able to read the latest checkin and it is later than latest checkout - * (i.e. there is no open checkout to match this checkin, an unusual case) - * 1.1.) identity of latest checkin is equal to current user - * -> checkin allowed, same user may check in repeatedly - * 1.2.) identity of latest checkin is not equal to current user - * -> checkin rejected, may not overwrite the revision which - * another user checked in previously - * 2.) there was no checkin or the latest checkout is later than latest checkin - * (i.e. there is an open checkout) - * 2.1.) identity of latest checkout is equal to current user - * -> checkin allowed, user checked out and may check in again - * (the most common case) - * 2.2.) identity of latest checkout is not equal to current user - * -> checkin rejected, may not check in while another - * user is working on this document - * - */ - if ((cie != null) && (cie.getTime() > coe.getTime())) { - // We have case 1 - if (!cie.getIdentity().equals(identity)) { - // Case 1.2., abort... - // - throw new FileReservedCheckInException(rootDir + destination, rcml); - } - } else { - // Case 2 - if (!coe.getIdentity().equals(identity)) { - // Case 2.2., abort... - // - throw new FileReservedCheckInException(rootDir + destination, rcml); - } - } - } + CacheEntryReference entryRef = CacheEntryReference.get("revctlr:"+destination); + synchronized (entryRef) { // disallow any simultaneous revctl on the same file + try { // snag exceptions so we can cleanup the synch ref and rethrow them + RCML rcml = new RCML(rcmlDir, destination, rootDir); - File originalFile = new File(rootDir, destination); - long time = new Date().getTime(); + CheckOutEntry coe = rcml.getLatestCheckOutEntry(); + CheckInEntry cie = rcml.getLatestCheckInEntry(); - if (backup && originalFile.isFile()) { - File backupFile = new File(backupDir, destination + ".bak." + time); - File parent = new File(backupFile.getParent()); + // If there has never been a checkout for this object + // *or* if the user attempting the checkin right now + // is the system itself, we will skip any checks and proceed + // right away to the actual checkin. + // In all other cases we enforce the revision control + // rules inside this if clause: + // + if (!((coe == null) || identity.equals(RevisionController.systemUsername))) { + /* + * Possible cases and rules: + * + * 1.) we were able to read the latest checkin and it is later than latest checkout + * (i.e. there is no open checkout to match this checkin, an unusual case) + * 1.1.) identity of latest checkin is equal to current user + * -> checkin allowed, same user may check in repeatedly + * 1.2.) identity of latest checkin is not equal to current user + * -> checkin rejected, may not overwrite the revision which + * another user checked in previously + * 2.) there was no checkin or the latest checkout is later than latest checkin + * (i.e. there is an open checkout) + * 2.1.) identity of latest checkout is equal to current user + * -> checkin allowed, user checked out and may check in again + * (the most common case) + * 2.2.) identity of latest checkout is not equal to current user + * -> checkin rejected, may not check in while another + * user is working on this document + * + */ + if ((cie != null) && (cie.getTime() > coe.getTime())) { + // We have case 1 + if (!cie.getIdentity().equals(identity)) { + // Case 1.2., abort... + // + throw new FileReservedCheckInException(rootDir + destination, rcml); + } + } else { + // Case 2 + if (!coe.getIdentity().equals(identity)) { + // Case 2.2., abort... + // + throw new FileReservedCheckInException(rootDir + destination, rcml); + } + } + } - if (!parent.isDirectory()) { - parent.mkdirs(); - } + File originalFile = new File(rootDir, destination); - log.info( - "Backup: copy " - + originalFile.getAbsolutePath() - + " to " - + backupFile.getAbsolutePath()); + if (backup && originalFile.isFile()) { + File backupFile = new File(backupDir, destination + ".bak." + time); + File parent = new File(backupFile.getParent()); - InputStream in = new FileInputStream(originalFile.getAbsolutePath()); + if (!parent.isDirectory()) { + parent.mkdirs(); + } - OutputStream out = new XPSFileOutputStream(backupFile.getAbsolutePath()); - byte[] buffer = new byte[512]; - int length; + log.info( + "Backup: copy " + + originalFile.getAbsolutePath() + + " to " + + backupFile.getAbsolutePath()); + + InputStream in = new FileInputStream(originalFile.getAbsolutePath()); + + OutputStream out = new XPSFileOutputStream(backupFile.getAbsolutePath()); + byte[] buffer = new byte[512]; + int length; + + while ((length = in.read(buffer)) != -1) { + out.write(buffer, 0, length); + } + + out.close(); + } + + rcml.checkOutIn(RCML.ci, identity, time, backup); + rcml.pruneEntries(backupDir); + rcml.write(); + } + catch (Exception e) { + entryRef.finish(); + throw(e); + } + } + entryRef.finish(); - while ((length = in.read(buffer)) != -1) { - out.write(buffer, 0, length); - } - - out.close(); - } - - rcml.checkOutIn(RCML.ci, identity, time, backup); - rcml.pruneEntries(backupDir); - rcml.write(); - // FIXME: If we reuse the observer pattern as implemented in // xps this would be the place to notify the observers, // e.g. like so: @@ -475,7 +530,7 @@ * * @return String The absolute path of the backup version */ - public String getBackupFilename(long time, String filename) { + private String getBackupFilename(long time, String filename) { File backup = new File(backupDir, filename + ".bak." + time); return backup.getAbsolutePath(); @@ -505,7 +560,7 @@ * * @return File The file of the backup version */ - public File getBackupFile(long time, String filename) { + private File getBackupFile(long time, String filename) { File backup = new File(backupDir, filename + ".bak." + time); return backup; @@ -597,6 +652,9 @@ FileReservedCheckOutException, FileNotFoundException, Exception { + + long newtime = time; + // Make sure the old version exists // File backup = new File(backupDir, destination + ".bak." + time); @@ -610,29 +668,39 @@ throw new FileNotFoundException(current.getAbsolutePath()); } - // Try to check out current version - // - reservedCheckOut(destination, identity); + CacheEntryReference entryRef = CacheEntryReference.get("revctlr:"+destination); + synchronized (entryRef) { // disallow any simultaneous revctl on the same file + try { // snag exceptions so we can cleanup the synch ref and rethrow them + // Try to check out current version + // + reservedCheckOut(destination, identity); - // Now roll back to the old state - // - FileInputStream in = new FileInputStream(backup.getAbsolutePath()); + // Now roll back to the old state + // + FileInputStream in = new FileInputStream(backup.getAbsolutePath()); - XPSFileOutputStream out = new XPSFileOutputStream(current.getAbsolutePath()); - byte[] buffer = new byte[512]; - int length; + XPSFileOutputStream out = new XPSFileOutputStream(current.getAbsolutePath()); + byte[] buffer = new byte[512]; + int length; - while ((length = in.read(buffer)) != -1) { - out.write(buffer, 0, length); - } + while ((length = in.read(buffer)) != -1) { + out.write(buffer, 0, length); + } - out.close(); + out.close(); - // Try to check back in, this might cause - // a backup of the current version to be created if - // desired by the user. - // - long newtime = reservedCheckIn(destination, identity, backupFlag); + // Try to check back in, this might cause + // a backup of the current version to be created if + // desired by the user. + // + newtime = reservedCheckIn(destination, identity, backupFlag); + } + catch (Exception e) { + entryRef.finish(); + throw(e); + } + } + entryRef.finish(); return newtime; } @@ -650,8 +718,6 @@ File backup = new File(backupDir + "/" + destination + ".bak." + time); File current = new File(rootDir + destination); - RCML rcml = new RCML(rcmlDir, destination, rootDir); - if (!backup.isFile()) { throw new FileNotFoundException(backup.getAbsolutePath()); } @@ -660,20 +726,32 @@ throw new FileNotFoundException(current.getAbsolutePath()); } - FileInputStream in = new FileInputStream(backup.getAbsolutePath()); + CacheEntryReference entryRef = CacheEntryReference.get("revctlr:"+destination); + synchronized (entryRef) { // disallow any simultaneous revctl on the same file + try { // snag exceptions so we can cleanup the synch ref and rethrow them + RCML rcml = new RCML(rcmlDir, destination, rootDir); - XPSFileOutputStream out = new XPSFileOutputStream(current.getAbsolutePath()); - byte[] buffer = new byte[512]; - int length; + FileInputStream in = new FileInputStream(backup.getAbsolutePath()); - while ((length = in.read(buffer)) != -1) { - out.write(buffer, 0, length); - } + XPSFileOutputStream out = new XPSFileOutputStream(current.getAbsolutePath()); + byte[] buffer = new byte[512]; + int length; - log.info("Undo: copy " + backup.getAbsolutePath() + " " + current.getAbsolutePath()); + while ((length = in.read(buffer)) != -1) { + out.write(buffer, 0, length); + } - rcml.deleteFirstCheckIn(); - out.close(); + log.info("Undo: copy " + backup.getAbsolutePath() + " " + current.getAbsolutePath()); + + rcml.deleteFirstCheckIn(); + out.close(); + } + catch (Exception e) { + entryRef.finish(); + throw(e); + } + } + entryRef.finish(); } /** @@ -711,32 +789,37 @@ /** * @deprecated please use deleteRevisions(Document) * delete the revisions - * @param filename of the document - * @throws RevisionControlException when somthing went wrong - */ - public void deleteRevisions(String filename) throws RevisionControlException{ - try { - RCML rcml = this.getRCML(filename); - String[] times = rcml.getBackupsTime(); - for (int i=0; i < times.length; i++) { - long time = new Long(times[i]).longValue(); - File backup = this.getBackupFile(time, filename); - File parentDirectory = null; - parentDirectory = backup.getParentFile(); - boolean deleted = backup.delete(); - if (!deleted) { - throw new RevisionControlException("The backup file, "+backup.getCanonicalPath()+" could not be deleted!"); - } - if (parentDirectory != null - && parentDirectory.exists() - && parentDirectory.isDirectory() - && parentDirectory.listFiles().length == 0) { - parentDirectory.delete(); - } - } - } catch (Exception e) { - throw new RevisionControlException(e); + * @param filename of the document + * @throws RevisionControlException when somthing went wrong + */ + public void deleteRevisions(String filename) throws RevisionControlException{ + CacheEntryReference entryRef = CacheEntryReference.get("revctlr:"+filename); + synchronized (entryRef) { // disallow any simultaneous revctl on the same file + try { + RCML rcml = new RCML(rcmlDir, filename, rootDir); + String[] times = rcml.getBackupsTime(); + for (int i=0; i < times.length; i++) { + long time = new Long(times[i]).longValue(); + File backup = this.getBackupFile(time, filename); + File parentDirectory = null; + parentDirectory = backup.getParentFile(); + boolean deleted = backup.delete(); + if (!deleted) { + throw new RevisionControlException("The backup file, "+backup.getCanonicalPath()+" could not be deleted!"); + } + if (parentDirectory != null + && parentDirectory.exists() + && parentDirectory.isDirectory() + && parentDirectory.listFiles().length == 0) { + parentDirectory.delete(); + } } + } catch (Exception e) { + entryRef.finish(); + throw new RevisionControlException(e); // this is kinda stupid coz it wraps the one we threw ourselves too but oh well + } + } + entryRef.finish(); } /** @@ -761,19 +844,112 @@ /** * @deprecated please use deleteRCML(Document) * delete the rcml file - * @param filename of the document - * @throws RevisionControlException if something went wrong - */ - public void deleteRCML(String filename) throws RevisionControlException{ - try { - RCML rcml = this.getRCML(filename); - boolean deleted = rcml.delete(); - if (!deleted) { - throw new RevisionControlException("The rcml file could not be deleted!"); - } - } catch (Exception e) { - throw new RevisionControlException(e); - } + * @param filename of the document + * @throws RevisionControlException if something went wrong + */ + public void deleteRCML(String filename) throws RevisionControlException{ + CacheEntryReference entryRef = CacheEntryReference.get("revctlr:"+filename); + synchronized (entryRef) { // disallow any simultaneous revctl on the same file + try { + RCML rcml = new RCML(rcmlDir, filename, rootDir); + boolean deleted = rcml.delete(); + if (!deleted) { + throw new RevisionControlException("The rcml file could not be deleted!"); + } + } catch (Exception e) { + entryRef.finish(); + throw new RevisionControlException(e); // this is kinda stupid coz it wraps the one we threw ourselves too but oh well + } + } + entryRef.finish(); } + /** + * 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) + * + * @param destination The file we want to check in + * + * @return RCMLEntry the latest entry + * + * @exception Exception if other problems occur + */ + public org.w3c.dom.Document getRCMLTree(String destination) + throws Exception { + + org.w3c.dom.Document rcmlDoc = null; + CacheEntryReference entryRef = CacheEntryReference.get("revctlr:"+destination); + synchronized (entryRef) { // disallow any simultaneous revctl on the same file + try { // snag exceptions so we can cleanup the synch ref and rethrow them + RCML rcml = new RCML(rcmlDir, destination, rootDir); + rcmlDoc = rcml.getDOMDocumentClone(); + } + catch (Exception e) { + entryRef.finish(); + throw(e); + } + } + entryRef.finish(); + + return(rcmlDoc); + } + + + /** + * Try to force a check in of the file destination for a user with identity. + * + * @param destination The file we want to check in + * @param identity The identity of the user + * + * @exception Exception if other problems occur + */ + public void forcedCheckIn(String destination, String identity) + throws Exception { + + CacheEntryReference entryRef = CacheEntryReference.get("revctlr:"+destination); + synchronized (entryRef) { // disallow any simultaneous revctl on the same file + try { // snag exceptions so we can cleanup the synch ref and rethrow them + RCML rcml = new RCML(rcmlDir, destination, rootDir); + rcml.checkOutIn(RCML.ci, identity, new Date().getTime(), false); + } + catch (Exception e) { + entryRef.finish(); + throw(e); + } + } + entryRef.finish(); + } + + /** + * Get the latest entry for one-time display purposes only (it's not protected from synch issues if you use it for anything else) + * + * @param destination The file we want to get info for + * + * @return RCMLEntry the latest entry + * + * @exception Exception if other problems occur + */ + public RCMLEntry getLatestEntry(String destination) + throws Exception { + + RCMLEntry entry = null; + CacheEntryReference entryRef = CacheEntryReference.get("revctlr:"+destination); + synchronized (entryRef) { // disallow any simultaneous revctl on the same file + try { // snag exceptions so we can cleanup the synch ref and rethrow them + RCML rcml = new RCML(rcmlDir, destination, rootDir); + if (rcml.getLatestEntry().getType() != RCML.ci) { + entry = rcml.getLatestCheckOutEntry(); + } + else { + entry = rcml.getLatestCheckInEntry(); + } + } + catch (Exception e) { + entryRef.finish(); + throw(e); + } + } + entryRef.finish(); + + return(entry); + } } Index: src/java/org/apache/lenya/cms/rc/CacheEntryReference.java =================================================================== --- src/java/org/apache/lenya/cms/rc/CacheEntryReference.java (revision 0) +++ src/java/org/apache/lenya/cms/rc/CacheEntryReference.java (revision 0) @@ -0,0 +1,83 @@ +/* + * Copyright 1999-2004 The Apache Software Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.apache.lenya.cms.rc; + +// Imports for the cache stuff +import java.util.Hashtable; + +import org.apache.log4j.*; + +/* + * This class helps deal with multiple requests for the same resource (key) by acting somewhat like a semaphore on that resource key + * + * All requests for the same key would be synchronized on an object of CacheEntryReference based on that key + * + * 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) + * 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) + * 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) + * + * Usage: + * CacheEntryReference entryRef = CacheEntryReference.get("typeofref:"+refname); + * // where typeofref is something like "RevisionController" and refname is the indiv keyname, like the sourcefile name + * // wrap your code that needs to be synched on this resource with + * synchronized (entryRef) { + * } + * entryRef.finish(); // this doesn't need to be in the synch block + */ +public class CacheEntryReference { + private int _refCount; + private String _id; + private static Hashtable _refs = new Hashtable(); + private static Logger log = Logger.getLogger(CacheEntryReference.class); + + public static CacheEntryReference get(String id) { +// log.setLevel((Level)Level.DEBUG); + synchronized (_refs) { + log.debug("get: START " + id); + CacheEntryReference entry = (CacheEntryReference)_refs.get(id); + if (entry == null) { + log.debug("get: not in hash " + id); + entry = new CacheEntryReference(id); + _refs.put(id, entry); + } + entry._refCount++; + log.debug("get: new refcount=" + entry._refCount + " for " + id); + log.debug("get: END " + id); + return(entry); + } + } + + public void finish() { + synchronized (_refs) { + log.debug("finish: START " + _id); + _refCount--; + log.debug("finish: new refcount=" + _refCount + " for " + _id); + if (_refCount <= 0) { + log.debug("finish: removing " + _id); + _refs.remove(_id); + _refCount = 0; + } + log.debug("finish: END " + _id); + } + } + + private CacheEntryReference(String id) { + _id = id; + _refCount = 0; + } +} Index: src/webapp/lenya/content/rc/versions-screen.xsp =================================================================== --- src/webapp/lenya/content/rc/versions-screen.xsp (revision 370504) +++ src/webapp/lenya/content/rc/versions-screen.xsp (working copy) @@ -15,7 +15,7 @@ limitations under the License. --> - + rollback: Unable to get DOM doc for rcml file, caught exception: e.toString() } Index: src/webapp/lenya/content/info/revisions.xsp =================================================================== --- src/webapp/lenya/content/info/revisions.xsp (revision 370504) +++ src/webapp/lenya/content/info/revisions.xsp (working copy) @@ -15,7 +15,7 @@ limitations under the License. --> - + rollback: Unable to get DOM doc for rcml file, caught exception: e.toString() }