backport concurrent

timeoutSeconds
polish in conversation management system
This commit is contained in:
Keith Donald
2008-03-31 19:12:59 +00:00
parent 7e350051cd
commit 81922cb888
16 changed files with 183 additions and 166 deletions

View File

@@ -6,7 +6,6 @@
<classpathentry kind="src" output="target/test-classes" path="src/test/resources"/>
<classpathentry combineaccessrules="false" kind="src" path="/spring-binding"/>
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER"/>
<classpathentry kind="var" path="IVY_CACHE/edu.oswego.cs/concurrent/concurrent-1.3.4.jar" sourcepath="IVY_CACHE/edu.oswego.cs/concurrent/concurrent-sources-1.3.4.jar"/>
<classpathentry kind="var" path="IVY_CACHE/javax.el/el-api/el-api-1.0.jar" sourcepath="IVY_CACHE/javax.el/el-api/el-api-sources-1.0.jar"/>
<classpathentry kind="var" path="IVY_CACHE/javax.persistence/persistence-api/persistence-api-1.0.0.jar" sourcepath="IVY_CACHE/javax.persistence/persistence-api/persistence-api-sources-1.0.0.jar"/>
<classpathentry kind="var" path="IVY_CACHE/javax.portlet/portlet-api/portlet-api-1.0.jar" sourcepath="IVY_CACHE/javax.portlet/portlet-api/portlet-api-sources-1.0.jar"/>
@@ -39,5 +38,6 @@
<classpathentry kind="var" path="IVY_CACHE/org.springframework/spring-webmvc/spring-webmvc-2.5.2.jar" sourcepath="IVY_CACHE/org.springframework/spring-webmvc/spring-webmvc-sources-2.5.2.jar"/>
<classpathentry kind="var" path="IVY_CACHE/org.springframework/spring-webmvc-portlet/spring-webmvc-portlet-2.5.2.jar" sourcepath="IVY_CACHE/org.springframework/spring-webmvc-portlet/spring-webmvc-portlet-sources-2.5.2.jar"/>
<classpathentry kind="var" path="IVY_CACHE/org.springframework.security/spring-security-core/spring-security-core-2.0.0.m2.jar" sourcepath="IVY_CACHE/org.springframework.security/spring-security-core/spring-security-core-sources-2.0.0.m2.jar"/>
<classpathentry kind="var" path="IVY_CACHE/edu.emory.mathcs/backport-util-concurrent/backport-util-concurrent-3.0.0.jar"/>
<classpathentry kind="output" path="target/classes"/>
</classpath>

View File

@@ -47,7 +47,7 @@
<dependency org="org.springframework" name="spring-tx" rev="2.5.2" conf="compile->default" />
<dependency org="org.springframework.security" name="spring-security-core" rev="2.0.0.m2" conf="compile->default" />
<dependency org="javax.persistence" name="persistence-api" rev="1.0.0" conf="compile->default" />
<dependency org="edu.oswego.cs" name="concurrent" rev="1.3.4" conf="compile->default" />
<dependency org="edu.emory.mathcs" name="backport-util-concurrent" rev="3.0.0" conf="compile->default" />
<!-- test time only dependencies -->
<dependency org="org.hsqldb" name="hsqldb" rev="1.8.0.9" conf="test->default" />

View File

@@ -57,8 +57,9 @@ public interface Conversation {
/**
* Lock this conversation. May block until the lock is available, if someone else has acquired the lock.
* @throws ConversationLockException if the lock could not be acquired
*/
public void lock();
public void lock() throws ConversationLockException;
/**
* Returns the conversation attribute with the specified name. You need to aquire the lock on this conversation

View File

@@ -15,14 +15,12 @@
*/
package org.springframework.webflow.conversation;
import org.springframework.webflow.core.FlowException;
/**
* The root of the conversation service exception hierarchy.
*
* @author Keith Donald
*/
public class ConversationException extends FlowException {
public abstract class ConversationException extends RuntimeException {
/**
* Creates a conversation service exception.

View File

@@ -0,0 +1,27 @@
package org.springframework.webflow.conversation;
/**
* An exception occurred within the conversation locking system.
*
* @author Keith Donald
*/
public abstract class ConversationLockException extends ConversationException {
/**
* Creates a new lock exception.
* @param msg the <code>Exception</code> message
*/
public ConversationLockException(String msg) {
super(msg);
}
/**
* Creates a new lock exception.
* @param msg the <code>Exception</code> message
* @param cause the root cause of this <code>Exception</code>
*/
public ConversationLockException(String msg, Throwable cause) {
super(msg, cause);
}
}

View File

@@ -15,9 +15,6 @@
*/
package org.springframework.webflow.conversation.impl;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.util.HashMap;
import java.util.Map;
@@ -44,7 +41,7 @@ class ContainedConversation implements Conversation, Serializable {
private ConversationId id;
private transient ConversationLock lock;
private ConversationLock lock;
private Map attributes;
@@ -52,11 +49,12 @@ class ContainedConversation implements Conversation, Serializable {
* Create a new contained conversation.
* @param container the container containing the conversation
* @param id the unique id assigned to the conversation
* @param lock the conversation lock
*/
public ContainedConversation(ConversationContainer container, ConversationId id) {
public ContainedConversation(ConversationContainer container, ConversationId id, ConversationLock lock) {
this.container = container;
this.id = id;
this.lock = ConversationLockFactory.createLock();
this.lock = lock;
this.attributes = new HashMap();
}
@@ -129,14 +127,4 @@ class ContainedConversation implements Conversation, Serializable {
return id.hashCode();
}
// custom serialization
private void writeObject(ObjectOutputStream out) throws IOException {
out.defaultWriteObject();
}
private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
in.defaultReadObject();
lock = ConversationLockFactory.createLock();
}
}

View File

@@ -82,11 +82,13 @@ class ConversationContainer implements Serializable {
/**
* Create a new conversation based on given parameters and add it to the container.
* @param parameters descriptive parameters
* @param parameters descriptive conversation parameters
* @param lockFactory the lock factory to use to create the conversation lock
* @return the created conversation
*/
public synchronized Conversation createConversation(ConversationParameters parameters) {
ContainedConversation conversation = new ContainedConversation(this, nextId());
public synchronized Conversation createConversation(ConversationParameters parameters,
ConversationLockFactory lockFactory) {
ContainedConversation conversation = new ContainedConversation(this, nextId(), lockFactory.createLock());
conversations.add(conversation);
if (maxExceeded()) {
// end oldest conversation

View File

@@ -15,17 +15,22 @@
*/
package org.springframework.webflow.conversation.impl;
import java.io.Serializable;
import org.springframework.webflow.conversation.ConversationLockException;
/**
* A normalized interface for conversation locks, used to obtain exclusive access to a conversation.
*
* @author Keith Donald
*/
public interface ConversationLock {
public interface ConversationLock extends Serializable {
/**
* Acquire the conversation lock.
* @throws ConversationLockException if an exception is thrown attempting to acquire this lock
*/
public void lock();
public void lock() throws ConversationLockException;
/**
* Release the conversation lock.

View File

@@ -25,34 +25,52 @@ import org.springframework.core.JdkVersion;
* @author Keith Donald
* @author Rob Harrop
*/
public class ConversationLockFactory {
class ConversationLockFactory {
private static final Log logger = LogFactory.getLog(ConversationLockFactory.class);
private static boolean utilConcurrentPresent;
private static boolean backportConcurrentPresent;
static {
try {
Class.forName("EDU.oswego.cs.dl.util.concurrent.ReentrantLock");
utilConcurrentPresent = true;
Class.forName("edu.emory.mathcs.backport.java.util.concurrent.locks.ReentrantLock");
backportConcurrentPresent = true;
} catch (ClassNotFoundException ex) {
utilConcurrentPresent = false;
backportConcurrentPresent = false;
}
}
private int timeoutSeconds = 30;
/**
* When running on Java 1.5+, returns a jdk5 concurrent lock. When running on older JDKs with the 'util.concurrent'
* package available, returns a util concurrent lock. In all other cases a "no-op" lock is returned.
* Returns the period of time that can elapse before a lock attempt times out for locks created by this factory.
*/
public static ConversationLock createLock() {
public int getTimeoutSeconds() {
return timeoutSeconds;
}
/**
* Sets the period of time that can elapse before a lock attempt times out for locks created by this factory.
* @param timeoutSeconds the timeout period in seconds
*/
public void setTimeoutSeconds(int timeoutSeconds) {
this.timeoutSeconds = timeoutSeconds;
}
/**
* When running on Java 1.5+, returns a jdk5 concurrent lock. When running on older JDKs with the
* 'backport-util-concurrent' package available, returns a backport concurrent lock. In all other cases a "no-op"
* lock is returned.
*/
public ConversationLock createLock() {
if (JdkVersion.getMajorJavaVersion() >= JdkVersion.JAVA_15) {
return new JdkConcurrentConversationLock();
} else if (utilConcurrentPresent) {
return new UtilConcurrentConversationLock();
return new JdkConcurrentConversationLock(timeoutSeconds);
} else if (backportConcurrentPresent) {
return new JdkBackportConcurrentConversationLock(timeoutSeconds);
} else {
logger.warn("Unable to enable conversation locking. Switch to Java 5 or above, "
+ "or put the 'util.concurrent' package on the classpath "
+ "to enable locking in your environment.");
+ "or put the 'backport-util-concurrent' package on the classpath "
+ "to enable locking in your Java 1.4 environment.");
return NoOpConversationLock.INSTANCE;
}
}

View File

@@ -0,0 +1,57 @@
/*
* Copyright 2004-2007 the original author or authors.
*
* 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.springframework.webflow.conversation.impl;
import org.springframework.webflow.conversation.ConversationLockException;
import edu.emory.mathcs.backport.java.util.concurrent.TimeUnit;
import edu.emory.mathcs.backport.java.util.concurrent.locks.Lock;
import edu.emory.mathcs.backport.java.util.concurrent.locks.ReentrantLock;
/**
* A conversation lock that relies on backport-concurrent. For use on JDK 1.4
*
* @author Keith Donald
* @author Rob Harrop
*/
class JdkBackportConcurrentConversationLock implements ConversationLock {
private Lock lock = new ReentrantLock();
private int timeoutSeconds;
public JdkBackportConcurrentConversationLock(int timeoutSeconds) {
this.timeoutSeconds = timeoutSeconds;
}
public void lock() throws ConversationLockException {
try {
boolean acquired = lock.tryLock(timeoutSeconds, TimeUnit.SECONDS);
if (!acquired) {
throw new LockTimeoutException(timeoutSeconds);
}
} catch (InterruptedException e) {
throw new LockInterruptedException(e);
}
}
/**
* Releases the lock.
*/
public void unlock() {
lock.unlock();
}
}

View File

@@ -15,38 +15,36 @@
*/
package org.springframework.webflow.conversation.impl;
import java.io.Serializable;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import org.springframework.webflow.conversation.ConversationLockException;
/**
* A conversation lock that relies on a {@link ReentrantLock} within Java 5's <code>util.concurrent.locks</code>
* package.
*
* @author Keith Donald
*/
class JdkConcurrentConversationLock implements ConversationLock, Serializable {
class JdkConcurrentConversationLock implements ConversationLock {
/**
* The lock.
*/
private Lock lock = new ReentrantLock();
private int timeoutSeconds = 30;
private int timeoutSeconds;
public void setTimeoutSeconds(int timeoutSeconds) {
public JdkConcurrentConversationLock(int timeoutSeconds) {
this.timeoutSeconds = timeoutSeconds;
}
public void lock() {
public void lock() throws ConversationLockException {
try {
boolean acquired = lock.tryLock(timeoutSeconds, TimeUnit.SECONDS);
if (!acquired) {
throw new LockException("Unable to acquire conversation lock after " + timeoutSeconds + " seconds");
throw new LockTimeoutException(timeoutSeconds);
}
} catch (InterruptedException e) {
throw new IllegalStateException("Unable to acquire conversation lock - thread interrupted", e);
throw new LockInterruptedException(e);
}
}

View File

@@ -1,9 +0,0 @@
package org.springframework.webflow.conversation.impl;
import org.springframework.webflow.conversation.ConversationException;
public class LockException extends ConversationException {
public LockException(String message) {
super(message);
}
}

View File

@@ -16,7 +16,6 @@
package org.springframework.webflow.conversation.impl;
import java.io.ObjectStreamException;
import java.io.Serializable;
/**
* A singleton lock that doesn't do anything. For use when conversations don't require or choose not to implement
@@ -24,7 +23,7 @@ import java.io.Serializable;
*
* @author Keith Donald
*/
class NoOpConversationLock implements ConversationLock, Serializable {
class NoOpConversationLock implements ConversationLock {
/**
* The singleton instance.

View File

@@ -53,18 +53,9 @@ public class SessionBindingConversationManager implements ConversationManager {
private int maxConversations = 5;
/**
* Returns the maximum number of allowed concurrent conversations. The default is 5.
* The factory for creating conversation lock objects.
*/
public int getMaxConversations() {
return maxConversations;
}
/**
* Set the maximum number of allowed concurrent conversations. Set to -1 for no limit. The default is 5.
*/
public void setMaxConversations(int maxConversations) {
this.maxConversations = maxConversations;
}
private ConversationLockFactory conversationLockFactory = new ConversationLockFactory();
/**
* Returns the key this conversation manager uses to store conversation data in the session.
@@ -84,8 +75,41 @@ public class SessionBindingConversationManager implements ConversationManager {
this.sessionKey = sessionKey;
}
/**
* Returns the maximum number of allowed concurrent conversations. The default is 5.
*/
public int getMaxConversations() {
return maxConversations;
}
/**
* Set the maximum number of allowed concurrent conversations. Set to -1 for no limit. The default is 5.
*/
public void setMaxConversations(int maxConversations) {
this.maxConversations = maxConversations;
}
/**
* Returns the time period that can elapse before a timeout occurs on an attempt to acquire a conversation lock. The
* default is 30 seconds.
*/
public int getLockTimeoutSeconds() {
return conversationLockFactory.getTimeoutSeconds();
}
/**
* Sets the time period that can elapse before a timeout occurs on an attempt to acquire a conversation lock. The
* default is 30 seconds.
* @param timeoutSeconds the timeout period in seconds
*/
public void setLockTimeoutSeconds(int timeoutSeconds) {
conversationLockFactory.setTimeoutSeconds(timeoutSeconds);
}
// implementing conversation manager
public Conversation beginConversation(ConversationParameters conversationParameters) throws ConversationException {
return getConversationContainer().createConversation(conversationParameters);
return getConversationContainer().createConversation(conversationParameters, conversationLockFactory);
}
public Conversation getConversation(ConversationId id) throws ConversationException {
@@ -96,7 +120,7 @@ public class SessionBindingConversationManager implements ConversationManager {
try {
return new SimpleConversationId(Integer.valueOf(encodedId));
} catch (NumberFormatException e) {
throw new ConversationException("Unable to parse string-encoded conversationId + '" + encodedId + "'", e);
throw new BadlyFormattedConversationIdException(encodedId, e);
}
}

View File

@@ -21,8 +21,7 @@ import org.springframework.webflow.conversation.ConversationId;
import org.springframework.webflow.conversation.ConversationManager;
/**
* An id that uniquely identifies a conversation managed by a {@link ConversationManager}. This key consists of a
* unique string that is typically a GUID.
* An id that uniquely identifies a conversation managed by a {@link ConversationManager}.
*
* @author Ben Hale
*/

View File

@@ -1,90 +0,0 @@
/*
* Copyright 2004-2007 the original author or authors.
*
* 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.springframework.webflow.conversation.impl;
import org.springframework.core.NestedRuntimeException;
import EDU.oswego.cs.dl.util.concurrent.ReentrantLock;
/**
* A conversation lock that relies on a {@link ReentrantLock} within Doug Lea's <a
* href="http://gee.cs.oswego.edu/dl/classes/EDU/oswego/cs/dl/util/concurrent/intro.html">util.concurrent</a> package.
* For use on JDK 1.3 and 1.4.
*
* @author Keith Donald
* @author Rob Harrop
*/
class UtilConcurrentConversationLock implements ConversationLock {
/**
* The {@link ReentrantLock} instance.
*/
private final ReentrantLock lock = new ReentrantLock();
private int timeoutMills;
public void setTimeoutSeconds(int timeoutSeconds) {
this.timeoutMills = timeoutSeconds * 1000;
}
/**
* Acquires the lock.
* @throws SystemInterruptedException if the lock cannot be acquired due to interruption
*/
public void lock() {
try {
boolean acquired = lock.attempt(timeoutMills);
if (!acquired) {
throw new LockException("Unable to acquire conversation lock after " + timeoutMills / 1000 + " seconds");
}
} catch (InterruptedException e) {
throw new SystemInterruptedException("Unable to acquire conversation lock - thread interrupted", e);
}
}
/**
* Releases the lock.
*/
public void unlock() {
lock.release();
}
/**
* <code>Exception</code> indicating that some {@link Thread} was {@link Thread#interrupt() interrupted} during
* processing and as such processing was halted.
* <p>
* Only used to wrap the checked {@link InterruptedException java.lang.InterruptedException}.
*/
public static class SystemInterruptedException extends NestedRuntimeException {
/**
* Creates a new <code>SystemInterruptedException</code>.
* @param msg the <code>Exception</code> message
*/
public SystemInterruptedException(String msg) {
super(msg);
}
/**
* Creates a new <code>SystemInterruptedException</code>.
* @param msg the <code>Exception</code> message
* @param cause the root cause of this <code>Exception</code>
*/
public SystemInterruptedException(String msg, Throwable cause) {
super(msg, cause);
}
}
}