001/*
002 * Copyright 2002-2018 the original author or authors.
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 *      https://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016
017package org.springframework.web.method.annotation;
018
019import java.util.Arrays;
020import java.util.Collections;
021import java.util.HashMap;
022import java.util.HashSet;
023import java.util.Map;
024import java.util.Set;
025import java.util.concurrent.ConcurrentHashMap;
026
027import org.springframework.core.annotation.AnnotatedElementUtils;
028import org.springframework.util.Assert;
029import org.springframework.web.bind.annotation.SessionAttributes;
030import org.springframework.web.bind.support.SessionAttributeStore;
031import org.springframework.web.bind.support.SessionStatus;
032import org.springframework.web.context.request.WebRequest;
033
034/**
035 * Manages controller-specific session attributes declared via
036 * {@link SessionAttributes @SessionAttributes}. Actual storage is
037 * delegated to a {@link SessionAttributeStore} instance.
038 *
039 * <p>When a controller annotated with {@code @SessionAttributes} adds
040 * attributes to its model, those attributes are checked against names and
041 * types specified via {@code @SessionAttributes}. Matching model attributes
042 * are saved in the HTTP session and remain there until the controller calls
043 * {@link SessionStatus#setComplete()}.
044 *
045 * @author Rossen Stoyanchev
046 * @author Juergen Hoeller
047 * @since 3.1
048 */
049public class SessionAttributesHandler {
050
051        private final Set<String> attributeNames = new HashSet<String>();
052
053        private final Set<Class<?>> attributeTypes = new HashSet<Class<?>>();
054
055        private final Set<String> knownAttributeNames =
056                        Collections.newSetFromMap(new ConcurrentHashMap<String, Boolean>(4));
057
058        private final SessionAttributeStore sessionAttributeStore;
059
060
061        /**
062         * Create a new session attributes handler. Session attribute names and types
063         * are extracted from the {@code @SessionAttributes} annotation, if present,
064         * on the given type.
065         * @param handlerType the controller type
066         * @param sessionAttributeStore used for session access
067         */
068        public SessionAttributesHandler(Class<?> handlerType, SessionAttributeStore sessionAttributeStore) {
069                Assert.notNull(sessionAttributeStore, "SessionAttributeStore may not be null");
070                this.sessionAttributeStore = sessionAttributeStore;
071
072                SessionAttributes ann = AnnotatedElementUtils.findMergedAnnotation(handlerType, SessionAttributes.class);
073                if (ann != null) {
074                        this.attributeNames.addAll(Arrays.asList(ann.names()));
075                        this.attributeTypes.addAll(Arrays.asList(ann.types()));
076                }
077                this.knownAttributeNames.addAll(this.attributeNames);
078        }
079
080
081        /**
082         * Whether the controller represented by this instance has declared any
083         * session attributes through an {@link SessionAttributes} annotation.
084         */
085        public boolean hasSessionAttributes() {
086                return (!this.attributeNames.isEmpty() || !this.attributeTypes.isEmpty());
087        }
088
089        /**
090         * Whether the attribute name or type match the names and types specified
091         * via {@code @SessionAttributes} on the underlying controller.
092         * <p>Attributes successfully resolved through this method are "remembered"
093         * and subsequently used in {@link #retrieveAttributes(WebRequest)} and
094         * {@link #cleanupAttributes(WebRequest)}.
095         * @param attributeName the attribute name to check
096         * @param attributeType the type for the attribute
097         */
098        public boolean isHandlerSessionAttribute(String attributeName, Class<?> attributeType) {
099                Assert.notNull(attributeName, "Attribute name must not be null");
100                if (this.attributeNames.contains(attributeName) || this.attributeTypes.contains(attributeType)) {
101                        this.knownAttributeNames.add(attributeName);
102                        return true;
103                }
104                else {
105                        return false;
106                }
107        }
108
109        /**
110         * Store a subset of the given attributes in the session. Attributes not
111         * declared as session attributes via {@code @SessionAttributes} are ignored.
112         * @param request the current request
113         * @param attributes candidate attributes for session storage
114         */
115        public void storeAttributes(WebRequest request, Map<String, ?> attributes) {
116                for (String name : attributes.keySet()) {
117                        Object value = attributes.get(name);
118                        Class<?> attrType = (value != null ? value.getClass() : null);
119                        if (isHandlerSessionAttribute(name, attrType)) {
120                                this.sessionAttributeStore.storeAttribute(request, name, value);
121                        }
122                }
123        }
124
125        /**
126         * Retrieve "known" attributes from the session, i.e. attributes listed
127         * by name in {@code @SessionAttributes} or attributes previously stored
128         * in the model that matched by type.
129         * @param request the current request
130         * @return a map with handler session attributes, possibly empty
131         */
132        public Map<String, Object> retrieveAttributes(WebRequest request) {
133                Map<String, Object> attributes = new HashMap<String, Object>();
134                for (String name : this.knownAttributeNames) {
135                        Object value = this.sessionAttributeStore.retrieveAttribute(request, name);
136                        if (value != null) {
137                                attributes.put(name, value);
138                        }
139                }
140                return attributes;
141        }
142
143        /**
144         * Remove "known" attributes from the session, i.e. attributes listed
145         * by name in {@code @SessionAttributes} or attributes previously stored
146         * in the model that matched by type.
147         * @param request the current request
148         */
149        public void cleanupAttributes(WebRequest request) {
150                for (String attributeName : this.knownAttributeNames) {
151                        this.sessionAttributeStore.cleanupAttribute(request, attributeName);
152                }
153        }
154
155        /**
156         * A pass-through call to the underlying {@link SessionAttributeStore}.
157         * @param request the current request
158         * @param attributeName the name of the attribute of interest
159         * @return the attribute value, or {@code null} if none
160         */
161        Object retrieveAttribute(WebRequest request, String attributeName) {
162                return this.sessionAttributeStore.retrieveAttribute(request, attributeName);
163        }
164
165}