001 package org.tynamo.conversations.services;
002
003 import java.util.ArrayList;
004 import java.util.Collections;
005 import java.util.HashMap;
006 import java.util.Iterator;
007 import java.util.List;
008 import java.util.Map;
009 import java.util.Map.Entry;
010
011 import javax.servlet.http.HttpServletRequest;
012
013 import org.apache.tapestry5.EventContext;
014 import org.apache.tapestry5.services.ComponentEventRequestParameters;
015 import org.apache.tapestry5.services.Cookies;
016 import org.apache.tapestry5.services.PageRenderRequestParameters;
017 import org.apache.tapestry5.services.Request;
018 import org.tynamo.conversations.ConversationAware;
019
020 public class ConversationManagerImpl implements ConversationManager {
021 // protected so you can override toString() in case user application already uses the same keys
022 // for something else
023 protected enum Keys {
024 _conversationId, conversations
025 };
026
027 private final Request request;
028
029 private final Cookies cookies;
030
031 private ConversationalPersistentFieldStrategy pagePersistentFieldStrategy;
032
033 private HttpServletRequest servletRequest;
034
035 private Map<String, List<ConversationAware>> conversationAwareListeners = Collections.synchronizedMap(new HashMap<String, List<ConversationAware>>());
036
037 public ConversationManagerImpl(Request request, HttpServletRequest servletRequest, Cookies cookies, Map<Class,ConversationAware> listeners) {
038 this.request = request;
039 this.cookies = cookies;
040 this.servletRequest = servletRequest;
041 for (Entry<Class,ConversationAware> entry : listeners.entrySet() ) {
042 String pageName = entry.getKey().getSimpleName();
043 addConversationListener(pageName, entry.getValue());
044 }
045 }
046
047 @SuppressWarnings("unchecked")
048 protected Map<String, Conversation> getConversations() {
049 Map<String, Conversation> conversations = (Map<String, Conversation>) request.getSession(true).getAttribute(Keys.conversations.toString());
050 if (conversations == null) {
051 conversations = Collections.synchronizedMap(new HashMap<String, Conversation>());
052 request.getSession(true).setAttribute(Keys.conversations.toString(), conversations);
053 }
054 return conversations;
055 }
056
057 public boolean activateConversation(Object parameterObject) {
058 if (parameterObject == null) return false;
059 EventContext activationContext = null;
060 String pageName = null;
061 if (parameterObject instanceof PageRenderRequestParameters) {
062 activationContext = ((PageRenderRequestParameters) parameterObject).getActivationContext();
063 pageName = ((PageRenderRequestParameters) parameterObject).getLogicalPageName();
064 } else if (parameterObject instanceof ComponentEventRequestParameters) {
065 activationContext = ((ComponentEventRequestParameters) parameterObject).getPageActivationContext();
066 pageName = ((ComponentEventRequestParameters) parameterObject).getActivePageName();
067 }
068
069 String conversationId = null;
070
071 // Try reading the conversation id from a cookie first
072 try {
073 conversationId = cookies.readCookieValue(pageName.toLowerCase() + ConversationManagerImpl.Keys._conversationId);
074 Conversation conversation = getConversations().get(conversationId);
075 if (conversation == null) conversationId = null;
076 else if (!conversation.isUsingCookie()) conversationId = null;
077 } catch (NumberFormatException e) {
078 // Ignore
079 }
080 // If cookie-based conversation isn't available, try activation context
081 if (conversationId == null) if (activationContext != null) try {
082 conversationId = activationContext.get(String.class, activationContext.getCount() - 1);
083 } catch (RuntimeException e) {
084 // Ignore
085 }
086
087 return activate(endConversationIfIdle(conversationId));
088 }
089
090 private boolean activate(Conversation conversation) {
091 if (conversation == null) return false;
092 request.setAttribute(Keys._conversationId.toString(), conversation.getId());
093 return true;
094 }
095
096 public String createConversation(String pageName, Integer maxIdleSeconds) {
097 return createConversation(pageName, maxIdleSeconds, false);
098 }
099
100 public String createConversation(String pageName, Integer maxIdleSeconds, boolean useCookie) {
101 return createConversation(String.valueOf(System.currentTimeMillis()), pageName, maxIdleSeconds, 0, useCookie);
102 }
103
104 public String createConversation(String pageName, Integer maxIdleSeconds, Integer maxConversationLengthSeconds, boolean useCookie) {
105 return createConversation(String.valueOf(System.currentTimeMillis()), pageName, maxIdleSeconds, maxConversationLengthSeconds, useCookie);
106 }
107
108 public String createConversation(String id, String pageName, Integer maxIdleSeconds, Integer maxConversationLengthSeconds, boolean useCookie) {
109 pageName = pageName == null ? "" : pageName;
110 // Don't use path in a cookie, it's actually relatively difficult to find out from here
111 if (useCookie) cookies.writeCookieValue(pageName.toLowerCase() + Keys._conversationId.toString(), String.valueOf(id));
112 Conversation conversation = new Conversation(servletRequest.getSession(true).getId(), id, pageName, maxIdleSeconds, maxConversationLengthSeconds, useCookie);
113 endIdleConversations();
114 getConversations().put(id, conversation);
115 activate(conversation);
116 if (conversationAwareListeners.containsKey(conversation.getPageName())) {
117 List<ConversationAware> conversationListeners = conversationAwareListeners.get(conversation.getPageName());
118 for (ConversationAware conversationAware : conversationListeners) conversationAware.onConversationCreated(conversation);
119 }
120 return id;
121 }
122
123 public void endIdleConversations() {
124 Iterator<Conversation> iterator = getConversations().values().iterator();
125 while (iterator.hasNext()) {
126 Conversation conversation = iterator.next();
127 if (conversation.isIdle(false)) {
128 discardConversation(conversation, true);
129 iterator.remove();
130 }
131 }
132 }
133
134 protected void discardConversation(Conversation conversation, boolean expired) {
135 if (conversation == null) return;
136 // Notify conversation ending
137 if (conversationAwareListeners.containsKey(conversation.getPageName())) {
138 List<ConversationAware> conversationListeners = conversationAwareListeners.get(conversation.getPageName());
139 for (ConversationAware conversationAware : conversationListeners) conversationAware.onConversationEnded(conversation, expired);
140 }
141 //discardConversation
142 if (conversation.isUsingCookie()) cookies.removeCookieValue(String.valueOf(conversation.getId()));
143 if (pagePersistentFieldStrategy != null) pagePersistentFieldStrategy.discardChanges(conversation.getPageName());
144 }
145
146 public boolean exists(String conversationId) {
147 Conversation conversation = getConversations().get(conversationId);
148 if (conversation == null) return false;
149 else return true;
150 }
151
152 protected Conversation endConversationIfIdle(String conversationId) {
153 Conversation conversation = getConversations().get(conversationId);
154 if (conversation == null) return null;
155 boolean resetTimeout = !("false".equals(request.getParameter(Parameters.keepalive.name())));
156 if (conversation.isIdle(resetTimeout)) {
157 discardConversation(conversation, true);
158 getConversations().remove(conversation.getId());
159 conversationId = null;
160 }
161 return conversation;
162 }
163
164 public String getActiveConversation() {
165 String conversationId = (String) request.getAttribute(Keys._conversationId.toString());
166 if (conversationId == null) return null;
167 return exists(conversationId) ? conversationId : null;
168 }
169
170 public int getSecondsBeforeActiveConversationBecomesIdle() {
171 String conversationId = getActiveConversation();
172 if (conversationId == null) return -1;
173 Conversation conversation = getConversations().get(conversationId);
174 if (conversation == null) return -1;
175 return conversation.getSecondsBeforeBecomesIdle();
176 }
177
178 public void setPagePersistentFieldStrategy(ConversationalPersistentFieldStrategy pagePersistentFieldStrategy) {
179 this.pagePersistentFieldStrategy = pagePersistentFieldStrategy;
180 }
181
182 public boolean isActiveConversation(String conversationId) {
183 if (conversationId == null) return false;
184 return conversationId.equals(getActiveConversation());
185 }
186
187 public String endConversation(String conversationId) {
188 Conversation conversation = getConversations().get(conversationId);
189 if (conversation == null) return null;
190 discardConversation(conversation, false);
191 getConversations().remove(conversation.getId());
192 return null;
193 }
194
195 public void addConversationListener(String pageName, ConversationAware conversationAware) {
196 List<ConversationAware> conversationListenersForPage = conversationAwareListeners.get(pageName);
197 if (conversationListenersForPage == null) {
198 conversationListenersForPage = Collections.synchronizedList(new ArrayList<ConversationAware>() );
199 conversationAwareListeners.put(pageName, conversationListenersForPage);
200 }
201 conversationListenersForPage.add(conversationAware);
202 }
203
204 public void removeConversationListener(String pageName, ConversationAware conversationAware) {
205 List<ConversationAware> conversationListenersForPage = conversationAwareListeners.get(pageName);
206 if (conversationListenersForPage == null) return;
207 conversationListenersForPage.remove(conversationAware);
208 }
209 }