001    // Copyright 2007, 2008, 2010 The Apache Software Foundation
002    //
003    // Licensed under the Apache License, Version 2.0 (the "License");
004    // you may not use this file except in compliance with the License.
005    // You may obtain a copy of the License at
006    //
007    //     http://www.apache.org/licenses/LICENSE-2.0
008    //
009    // Unless required by applicable law or agreed to in writing, software
010    // distributed under the License is distributed on an "AS IS" BASIS,
011    // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
012    // See the License for the specific language governing permissions and
013    // limitations under the License.
014    
015    package org.apache.tapestry5.internal.services;
016    
017    import static org.apache.tapestry5.ioc.internal.util.CollectionFactory.newList;
018    
019    import java.util.Collection;
020    import java.util.Collections;
021    import java.util.List;
022    
023    import org.apache.tapestry5.ioc.internal.util.InternalUtils;
024    import org.apache.tapestry5.services.PersistentFieldChange;
025    import org.apache.tapestry5.services.PersistentFieldStrategy;
026    import org.apache.tapestry5.services.Request;
027    import org.apache.tapestry5.services.Session;
028    
029    /**
030     * Base class for strategies that store their values as keys in the session. Implements a uniform format for the keys,
031     * based on a prefix to identify the particular strategy.
032     */
033    public abstract class AbstractSessionPersistentFieldStrategy implements PersistentFieldStrategy
034    {
035        private final String prefix;
036    
037        private final Request request;
038    
039        protected AbstractSessionPersistentFieldStrategy(String prefix, Request request)
040        {
041            this.prefix = prefix;
042            this.request = request;
043        }
044    
045        public final Collection<PersistentFieldChange> gatherFieldChanges(String pageName)
046        {
047            Session session = request.getSession(false);
048    
049            if (session == null) return Collections.emptyList();
050    
051            List<PersistentFieldChange> result = newList();
052    
053            String fullPrefix = prefix + pageName + ":";
054    
055            for (String name : session.getAttributeNames(fullPrefix))
056            {
057                Object persistedValue = session.getAttribute(name);
058    
059                Object applicationValue = persistedValue == null ? null : convertPersistedToApplicationValue(
060                        persistedValue);
061    
062                PersistentFieldChange change = buildChange(name, applicationValue);
063    
064                result.add(change);
065    
066                didReadChange(session, name);
067            }
068    
069            return result;
070        }
071    
072        public void discardChanges(String pageName)
073        {
074            Session session = request.getSession(false);
075    
076            if (session == null) return;
077    
078            String fullPrefix = prefix + pageName + ":";
079    
080            for (String name : session.getAttributeNames(fullPrefix))
081            {
082                session.setAttribute(name, null);
083            }
084        }
085    
086        /**
087         * Called after each key is read by {@link #gatherFieldChanges(String)}. This implementation does nothing,
088         * subclasses may override.
089         *
090         * @param session       the session from which a value was just read
091         * @param attributeName the name of the attribute used to read a value
092         */
093        protected void didReadChange(Session session, String attributeName)
094        {
095        }
096    
097        private PersistentFieldChange buildChange(String name, Object newValue)
098        {
099            String[] chunks = name.split(":");
100    
101            // Will be empty string for the root component
102            String componentId = chunks[2];
103            String fieldName = chunks[3];
104    
105            return new PersistentFieldChangeImpl(componentId, fieldName, newValue);
106        }
107    
108        public final void postChange(String pageName, String componentId, String fieldName,
109                                     Object newValue)
110        {
111            assert InternalUtils.isNonBlank(pageName);
112            assert InternalUtils.isNonBlank(fieldName);
113            Object persistedValue = newValue == null ? null : convertApplicationValueToPersisted(newValue);
114    
115            StringBuilder builder = new StringBuilder(prefix);
116            builder.append(pageName);
117            builder.append(':');
118    
119            if (componentId != null) builder.append(componentId);
120    
121            builder.append(':');
122            builder.append(fieldName);
123    
124            Session session = request.getSession(persistedValue != null);
125    
126            // TAPESTRY-2308: The session will be false when newValue is null and the session
127            // does not already exist.
128    
129            if (session != null)
130            {
131                session.setAttribute(builder.toString(), persistedValue);
132            }
133        }
134    
135        /**
136         * Hook that allows a value to be converted as it is written to the session. Passed the new value provided by the
137         * application, returns the object to be stored in the session. This implementation simply returns the provided
138         * value.
139         *
140         * @param newValue non-null value
141         * @return persisted value
142         * @see #convertPersistedToApplicationValue(Object)
143         */
144        protected Object convertApplicationValueToPersisted(Object newValue)
145        {
146            return newValue;
147        }
148    
149        /**
150         * Converts a persisted value stored in the session back into an application value.   This implementation returns
151         * the persisted value as is.
152         *
153         * @param persistedValue non-null persisted value
154         * @return application value
155         * @see #convertPersistedToApplicationValue(Object)
156         */
157        protected Object convertPersistedToApplicationValue(Object persistedValue)
158        {
159            return persistedValue;
160        }
161    }