001    package org.tynamo.conversations.services;
002    
003    import static org.apache.tapestry5.ioc.internal.util.CollectionFactory.newList;
004    import static org.apache.tapestry5.ioc.internal.util.Defense.notBlank;
005    
006    import java.util.Collection;
007    import java.util.Collections;
008    import java.util.List;
009    
010    import org.apache.tapestry5.internal.services.PersistentFieldChangeImpl;
011    import org.apache.tapestry5.services.PersistentFieldChange;
012    import org.apache.tapestry5.services.PersistentFieldStrategy;
013    import org.apache.tapestry5.services.Request;
014    import org.apache.tapestry5.services.Session;
015    
016    public class ConversationalPersistentFieldStrategy implements PersistentFieldStrategy {
017            /**
018             * Prefix used to identify keys stored in the session that are being used to store persistent field data.
019             */
020            static final String PREFIX = "state:";
021    
022            private final ConversationManager conversationManager;
023    
024            public ConversationalPersistentFieldStrategy(Request request, ConversationManager conversationManager) {
025                    this.prefix = PREFIX;
026                    this.request = request;
027                    conversationManager.setPagePersistentFieldStrategy(this);
028                    this.conversationManager = conversationManager;
029            }
030    
031            private final String prefix;
032    
033            private final Request request;
034    
035            private String buildPrefix(String pageName) {
036                    // System.out.println("Conversation id is: " + conversationManager.getActiveConversation());
037                    return prefix + pageName + "_conversation_" + conversationManager.getActiveConversation() + ":";
038            }
039    
040            public final Collection<PersistentFieldChange> gatherFieldChanges(String pageName) {
041                    Session session = request.getSession(false);
042    
043                    if (session == null) return Collections.emptyList();
044    
045                    List<PersistentFieldChange> result = newList();
046    
047                    // if conversation is not active don't gather any changes
048                    if (conversationManager.getActiveConversation() == null) return result;
049    
050                    String fullPrefix = buildPrefix(pageName);
051    
052                    for (String name : session.getAttributeNames(fullPrefix)) {
053                            Object persistedValue = session.getAttribute(name);
054    
055                            Object applicationValue = persistedValue == null ? null : convertPersistedToApplicationValue(persistedValue);
056    
057                            PersistentFieldChange change = buildChange(name, applicationValue);
058    
059                            result.add(change);
060    
061                            didReadChange(session, name);
062                    }
063    
064                    return result;
065            }
066    
067            public void discardChanges(String pageName) {
068                    Session session = request.getSession(false);
069    
070                    if (session == null || conversationManager.getActiveConversation() == null) return;
071    
072                    String fullPrefix = buildPrefix(pageName);
073    
074                    for (String name : session.getAttributeNames(fullPrefix)) {
075                            session.setAttribute(name, null);
076                    }
077            }
078    
079            /**
080             * Called after each key is read by {@link #gatherFieldChanges(String)}. This implementation does nothing, subclasses
081             * may override.
082             * 
083             * @param session
084             *          the session from which a value was just read
085             * @param attributeName
086             *          the name of the attribute used to read a value
087             */
088            protected void didReadChange(Session session, String attributeName) {
089            }
090    
091            private PersistentFieldChange buildChange(String name, Object newValue) {
092                    String[] chunks = name.split(":");
093    
094                    // Will be empty string for the root component
095                    String componentId = chunks[2];
096                    String fieldName = chunks[3];
097    
098                    return new PersistentFieldChangeImpl(componentId, fieldName, newValue);
099            }
100    
101            public final void postChange(String pageName, String componentId, String fieldName, Object newValue) {
102                    notBlank(pageName, "pageName");
103                    notBlank(fieldName, "fieldName");
104    
105                    // If no active conversation, no changes to post
106                    if (conversationManager.getActiveConversation() == null) return;
107    
108                    Object persistedValue = newValue == null ? null : convertApplicationValueToPersisted(newValue);
109    
110                    StringBuilder builder = new StringBuilder(buildPrefix(pageName));
111    
112                    if (componentId != null) builder.append(componentId);
113    
114                    builder.append(':');
115                    builder.append(fieldName);
116    
117                    Session session = request.getSession(persistedValue != null);
118    
119                    // TAPESTRY-2308: The session will be false when newValue is null and the session
120                    // does not already exist.
121    
122                    if (session != null) {
123                            session.setAttribute(builder.toString(), persistedValue);
124                    }
125            }
126    
127            /**
128             * Hook that allows a value to be converted as it is written to the session. Passed the new value provided by the
129             * application, returns the object to be stored in the session. This implementation simply returns the provided value.
130             * 
131             * @param newValue
132             *          non-null value
133             * @return persisted value
134             * @see #convertPersistedToApplicationValue(Object)
135             */
136            protected Object convertApplicationValueToPersisted(Object newValue) {
137                    return newValue;
138            }
139    
140            /**
141             * Converts a persisted value stored in the session back into an application value. This implementation returns the
142             * persisted value as is.
143             * 
144             * @param persistedValue
145             *          non-null persisted value
146             * @return application value
147             * @see #convertPersistedToApplicationValue(Object)
148             */
149            protected Object convertPersistedToApplicationValue(Object persistedValue) {
150                    return persistedValue;
151            }
152    }