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
015package org.apache.tapestry5.internal.services;
016
017import static org.apache.tapestry5.ioc.internal.util.CollectionFactory.newList;
018
019import java.util.Collection;
020import java.util.Collections;
021import java.util.List;
022
023import org.apache.tapestry5.ioc.internal.util.InternalUtils;
024import org.apache.tapestry5.services.PersistentFieldChange;
025import org.apache.tapestry5.services.PersistentFieldStrategy;
026import org.apache.tapestry5.services.Request;
027import 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 */
033public 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}