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}