package org.jboss.seam.core; import java.io.Serializable; import java.util.Date; import java.util.List; import java.util.concurrent.TimeUnit; import java.util.concurrent.locks.ReentrantLock; import org.jboss.seam.Component; import org.jboss.seam.ScopeType; import org.jboss.seam.contexts.Context; import org.jboss.seam.contexts.Contexts; /** * Metadata about an active conversation. Also used * by the conversation list and breadcrumbs. * * @author Gavin King * */ public final class ConversationEntry implements Serializable, Comparable<ConversationEntry> { private static final long serialVersionUID = 3624635335271963568L; private long lastRequestTime; private String description; private String id; private Date startDatetime; private Date lastDatetime; private String viewId; private List<String> conversationIdStack; private Integer timeout; private Integer concurrentRequestTimeout; private boolean removeAfterRedirect; private boolean ended; private ConversationEntries entries; private ReentrantLock lock; public ConversationEntry(String id, List<String> stack, ConversationEntries entries) { this.id = id; if (stack==null) throw new IllegalArgumentException("Stack must not be null"); if (id==null) throw new IllegalArgumentException("id must not be null"); this.conversationIdStack = stack; this.startDatetime = new Date(); this.entries = entries; if ( conversationIdStack.size()>1 ) { // get the root conversation entry lock (we want to share the same lock // among all nested conversations in the same conversation stack) lock = entries.getConversationEntry( conversationIdStack.get( conversationIdStack.size()-1 ) ).lock; } else { lock = new ReentrantLock(true); } touch(); } public String getDescription() { return description; } void setDescription(String description) { entries.setDirty(this.description, description); this.description = description; } public synchronized long getLastRequestTime() { return lastRequestTime; } synchronized void touch() { entries.setDirty(); lastRequestTime = System.currentTimeMillis(); lastDatetime = new Date(); } public String getId() { return id; } public Date getStartDatetime() { return startDatetime; } public void destroy() { boolean success = Manager.instance().switchConversation( getId() ); if (success) Manager.instance().endConversation(false); } public void select() { redirect(); } public boolean redirect() { String viewId = getViewId(); if (viewId==null) { return false; } else { Manager.instance().redirect( viewId, getId() ); return true; } } void setViewId(String viewId) { entries.setDirty(this.viewId, viewId); this.viewId = viewId; } public String getViewId() { return viewId; } public synchronized Date getLastDatetime() { return lastDatetime; } public List<String> getConversationIdStack() { return conversationIdStack; } public boolean isDisplayable() { return !isEnded() && !isRemoveAfterRedirect() && getDescription()!=null; } public boolean isCurrent() { Manager manager = Manager.instance(); if ( manager.isLongRunningConversation() ) { return id.equals( manager.getCurrentConversationId() ); } else if ( manager.isNestedConversation() ) { return id.equals( manager.getParentConversationId() ); } else { return false; } } public int compareTo(ConversationEntry entry) { int result = new Long ( getLastRequestTime() ).compareTo( entry.getLastRequestTime() ); return - ( result==0 ? getId().compareTo( entry.getId() ) : result ); } public int getTimeout() { return timeout==null ? Manager.instance().getConversationTimeout() : timeout; } void setTimeout(int conversationTimeout) { entries.setDirty(this.timeout, timeout); this.timeout = conversationTimeout; } public Integer getConcurrentRequestTimeout() { return concurrentRequestTimeout == null ? Manager.instance().getConcurrentRequestTimeout() : concurrentRequestTimeout; } void setConcurrentRequestTimeout(Integer concurrentRequestTimeout) { entries.setDirty(this.concurrentRequestTimeout, concurrentRequestTimeout); this.concurrentRequestTimeout = concurrentRequestTimeout; } public boolean isRemoveAfterRedirect() { return removeAfterRedirect; } public void setRemoveAfterRedirect(boolean removeAfterRedirect) { entries.setDirty(); this.removeAfterRedirect = removeAfterRedirect; } void setId(String id) { this.id = id; } public boolean lockNoWait() //not synchronized! { return lock.tryLock(); } public boolean lock() //not synchronized! { try { return lock.tryLock( getConcurrentRequestTimeout(), TimeUnit.MILLISECONDS ); } catch (InterruptedException ie) { throw new RuntimeException(ie); } } public void unlock() //not synchronized! { lock.unlock(); } public boolean isLockedByCurrentThread() { return lock.isHeldByCurrentThread(); } public void end() { ended = true; } public boolean isEnded() { return ended; } public boolean isNested() { return conversationIdStack.size()>1; } /** * Determines which conversation in the stack is holding the instance of this * component. A nested conversation can see context variables in all ancestor * conversations. In this case, we are interesting in knowing where that * instance was found. We are assuming that if the reference is not in an * ancestor conversation, then it must be in the current conversation. The * goal here is not to locate the instance, but rather to determine which * conversation is contributing the instance that we already know exists. * * The low-level interaction with the session context should be refactored * out. The problem is that it is defined in private areas of * ServerConversationContext and cannot be reused. Actually, what we really * need is a general purpose utility for analyzing the contents of each * conversation in the stack (at least the keys). */ public String findPositionInConversationStack(Component component) { if (component.isPerNestedConversation()) { return id; } String name = component.getName(); Context session = Contexts.getSessionContext(); String location = id; for (int i = 1, len = conversationIdStack.size(); i < len; i++) { String cid = conversationIdStack.get(i); String key = ScopeType.CONVERSATION.getPrefix() + '#' + cid + '$' + name; if (session.get(key) != null) { location = cid; break; } } return location; } @Override public String toString() { return "ConversationEntry(" + id + ")"; } }