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    }