001/* 002 * Copyright 2002-2019 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.ui; 018 019import java.util.Collection; 020import java.util.Map; 021import java.util.concurrent.ConcurrentHashMap; 022 023import org.springframework.core.Conventions; 024import org.springframework.lang.Nullable; 025import org.springframework.util.Assert; 026 027/** 028 * Implementation of the {@link Model} interface based on a {@link ConcurrentHashMap} 029 * for use in concurrent scenarios. 030 * 031 * <p>Exposed to handler methods by Spring WebFlux, typically via a declaration of the 032 * {@link Model} interface. There is typically no need to create it within user code. 033 * If necessary a handler method can return a regular {@code java.util.Map}, 034 * likely a {@code java.util.ConcurrentMap}, for a pre-determined model. 035 * 036 * @author Rossen Stoyanchev 037 * @since 5.0 038 */ 039@SuppressWarnings("serial") 040public class ConcurrentModel extends ConcurrentHashMap<String, Object> implements Model { 041 042 /** 043 * Construct a new, empty {@code ConcurrentModel}. 044 */ 045 public ConcurrentModel() { 046 } 047 048 /** 049 * Construct a new {@code ModelMap} containing the supplied attribute 050 * under the supplied name. 051 * @see #addAttribute(String, Object) 052 */ 053 public ConcurrentModel(String attributeName, Object attributeValue) { 054 addAttribute(attributeName, attributeValue); 055 } 056 057 /** 058 * Construct a new {@code ModelMap} containing the supplied attribute. 059 * Uses attribute name generation to generate the key for the supplied model 060 * object. 061 * @see #addAttribute(Object) 062 */ 063 public ConcurrentModel(Object attributeValue) { 064 addAttribute(attributeValue); 065 } 066 067 068 @Override 069 public Object put(String key, Object value) { 070 if (value != null) { 071 return super.put(key, value); 072 } 073 else { 074 return remove(key); 075 } 076 } 077 078 @Override 079 public void putAll(Map<? extends String, ?> map) { 080 for (Map.Entry<? extends String, ?> entry : map.entrySet()) { 081 put(entry.getKey(), entry.getValue()); 082 } 083 } 084 085 /** 086 * Add the supplied attribute under the supplied name. 087 * @param attributeName the name of the model attribute (never {@code null}) 088 * @param attributeValue the model attribute value (ignored if {@code null}, 089 * just removing an existing entry if any) 090 */ 091 @Override 092 public ConcurrentModel addAttribute(String attributeName, @Nullable Object attributeValue) { 093 Assert.notNull(attributeName, "Model attribute name must not be null"); 094 put(attributeName, attributeValue); 095 return this; 096 } 097 098 /** 099 * Add the supplied attribute to this {@code Map} using a 100 * {@link org.springframework.core.Conventions#getVariableName generated name}. 101 * <p><i>Note: Empty {@link Collection Collections} are not added to 102 * the model when using this method because we cannot correctly determine 103 * the true convention name. View code should check for {@code null} rather 104 * than for empty collections as is already done by JSTL tags.</i> 105 * @param attributeValue the model attribute value (never {@code null}) 106 */ 107 @Override 108 public ConcurrentModel addAttribute(Object attributeValue) { 109 Assert.notNull(attributeValue, "Model attribute value must not be null"); 110 if (attributeValue instanceof Collection && ((Collection<?>) attributeValue).isEmpty()) { 111 return this; 112 } 113 return addAttribute(Conventions.getVariableName(attributeValue), attributeValue); 114 } 115 116 /** 117 * Copy all attributes in the supplied {@code Collection} into this 118 * {@code Map}, using attribute name generation for each element. 119 * @see #addAttribute(Object) 120 */ 121 @Override 122 public ConcurrentModel addAllAttributes(@Nullable Collection<?> attributeValues) { 123 if (attributeValues != null) { 124 for (Object attributeValue : attributeValues) { 125 addAttribute(attributeValue); 126 } 127 } 128 return this; 129 } 130 131 /** 132 * Copy all attributes in the supplied {@code Map} into this {@code Map}. 133 * @see #addAttribute(String, Object) 134 */ 135 @Override 136 public ConcurrentModel addAllAttributes(@Nullable Map<String, ?> attributes) { 137 if (attributes != null) { 138 putAll(attributes); 139 } 140 return this; 141 } 142 143 /** 144 * Copy all attributes in the supplied {@code Map} into this {@code Map}, 145 * with existing objects of the same name taking precedence (i.e. not getting 146 * replaced). 147 */ 148 @Override 149 public ConcurrentModel mergeAttributes(@Nullable Map<String, ?> attributes) { 150 if (attributes != null) { 151 attributes.forEach((key, value) -> { 152 if (!containsKey(key)) { 153 put(key, value); 154 } 155 }); 156 } 157 return this; 158 } 159 160 /** 161 * Does this model contain an attribute of the given name? 162 * @param attributeName the name of the model attribute (never {@code null}) 163 * @return whether this model contains a corresponding attribute 164 */ 165 @Override 166 public boolean containsAttribute(String attributeName) { 167 return containsKey(attributeName); 168 } 169 170 @Override 171 @Nullable 172 public Object getAttribute(String attributeName) { 173 return get(attributeName); 174 } 175 176 @Override 177 public Map<String, Object> asMap() { 178 return this; 179 } 180 181}