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