Browse Source
This commit adds a Model implementation based on ConcurrentHashMap for use in Spring Web Reactive. Issue: SPR-14542pull/1224/merge
Rossen Stoyanchev
8 years ago
10 changed files with 237 additions and 27 deletions
@ -0,0 +1,149 @@
@@ -0,0 +1,149 @@
|
||||
/* |
||||
* Copyright 2002-2016 the original author or authors. |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
* you may not use this file except in compliance with the License. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
*/ |
||||
package org.springframework.ui; |
||||
|
||||
import java.util.Collection; |
||||
import java.util.Map; |
||||
import java.util.concurrent.ConcurrentHashMap; |
||||
|
||||
import org.springframework.core.Conventions; |
||||
import org.springframework.util.Assert; |
||||
|
||||
/** |
||||
* Implementation of {@link Model} based on a {@link ConcurrentHashMap} for use |
||||
* in concurrent scenarios. Exposed to handler methods by Spring Web Reactive |
||||
* typically via a declaration of the {@link Model} interface. There is typically |
||||
* no need to create it within user code. If necessary a controller method can |
||||
* return a regular {@code java.util.Map}, or more likely a |
||||
* {@code java.util.ConcurrentMap}. |
||||
* |
||||
* @author Rossen Stoyanchev |
||||
* @since 5.0 |
||||
*/ |
||||
@SuppressWarnings("serial") |
||||
public class ConcurrentModel extends ConcurrentHashMap<String, Object> implements Model { |
||||
|
||||
/** |
||||
* Construct a new, empty {@code ConcurrentModel}. |
||||
*/ |
||||
public ConcurrentModel() { |
||||
} |
||||
|
||||
/** |
||||
* Construct a new {@code ModelMap} containing the supplied attribute |
||||
* under the supplied name. |
||||
* @see #addAttribute(String, Object) |
||||
*/ |
||||
public ConcurrentModel(String attributeName, Object attributeValue) { |
||||
addAttribute(attributeName, attributeValue); |
||||
} |
||||
|
||||
/** |
||||
* Construct a new {@code ModelMap} containing the supplied attribute. |
||||
* Uses attribute name generation to generate the key for the supplied model |
||||
* object. |
||||
* @see #addAttribute(Object) |
||||
*/ |
||||
public ConcurrentModel(Object attributeValue) { |
||||
addAttribute(attributeValue); |
||||
} |
||||
|
||||
|
||||
/** |
||||
* Add the supplied attribute under the supplied name. |
||||
* @param attributeName the name of the model attribute (never {@code null}) |
||||
* @param attributeValue the model attribute value (can be {@code null}) |
||||
*/ |
||||
public ConcurrentModel addAttribute(String attributeName, Object attributeValue) { |
||||
Assert.notNull(attributeName, "Model attribute name must not be null"); |
||||
put(attributeName, attributeValue); |
||||
return this; |
||||
} |
||||
|
||||
/** |
||||
* Add the supplied attribute to this {@code Map} using a |
||||
* {@link org.springframework.core.Conventions#getVariableName generated name}. |
||||
* <p><emphasis>Note: Empty {@link Collection Collections} are not added to |
||||
* the model when using this method because we cannot correctly determine |
||||
* the true convention name. View code should check for {@code null} rather |
||||
* than for empty collections as is already done by JSTL tags.</emphasis> |
||||
* @param attributeValue the model attribute value (never {@code null}) |
||||
*/ |
||||
public ConcurrentModel addAttribute(Object attributeValue) { |
||||
Assert.notNull(attributeValue, "Model object must not be null"); |
||||
if (attributeValue instanceof Collection && ((Collection<?>) attributeValue).isEmpty()) { |
||||
return this; |
||||
} |
||||
return addAttribute(Conventions.getVariableName(attributeValue), attributeValue); |
||||
} |
||||
|
||||
/** |
||||
* Copy all attributes in the supplied {@code Collection} into this |
||||
* {@code Map}, using attribute name generation for each element. |
||||
* @see #addAttribute(Object) |
||||
*/ |
||||
public ConcurrentModel addAllAttributes(Collection<?> attributeValues) { |
||||
if (attributeValues != null) { |
||||
for (Object attributeValue : attributeValues) { |
||||
addAttribute(attributeValue); |
||||
} |
||||
} |
||||
return this; |
||||
} |
||||
|
||||
/** |
||||
* Copy all attributes in the supplied {@code Map} into this {@code Map}. |
||||
* @see #addAttribute(String, Object) |
||||
*/ |
||||
public ConcurrentModel addAllAttributes(Map<String, ?> attributes) { |
||||
if (attributes != null) { |
||||
putAll(attributes); |
||||
} |
||||
return this; |
||||
} |
||||
|
||||
/** |
||||
* Copy all attributes in the supplied {@code Map} into this {@code Map}, |
||||
* with existing objects of the same name taking precedence (i.e. not getting |
||||
* replaced). |
||||
*/ |
||||
public ConcurrentModel mergeAttributes(Map<String, ?> attributes) { |
||||
if (attributes != null) { |
||||
for (Map.Entry<String, ?> entry : attributes.entrySet()) { |
||||
String key = entry.getKey(); |
||||
if (!containsKey(key)) { |
||||
put(key, entry.getValue()); |
||||
} |
||||
} |
||||
} |
||||
return this; |
||||
} |
||||
|
||||
/** |
||||
* Does this model contain an attribute of the given name? |
||||
* @param attributeName the name of the model attribute (never {@code null}) |
||||
* @return whether this model contains a corresponding attribute |
||||
*/ |
||||
public boolean containsAttribute(String attributeName) { |
||||
return containsKey(attributeName); |
||||
} |
||||
|
||||
@Override |
||||
public Map<String, Object> asMap() { |
||||
return this; |
||||
} |
||||
|
||||
} |
@ -0,0 +1,65 @@
@@ -0,0 +1,65 @@
|
||||
/* |
||||
* Copyright 2002-2015 the original author or authors. |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
* you may not use this file except in compliance with the License. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
*/ |
||||
|
||||
package org.springframework.validation.support; |
||||
|
||||
import java.util.Map; |
||||
|
||||
import org.springframework.ui.ConcurrentModel; |
||||
import org.springframework.validation.BindingResult; |
||||
|
||||
/** |
||||
* Sub-class of {@link ConcurrentModel} that automatically removes |
||||
* the {@link BindingResult} object when its corresponding |
||||
* target attribute is replaced through regular {@link Map} operations. |
||||
* |
||||
* <p>This is the class exposed to controller methods by Spring Web Reactive, |
||||
* typically consumed through a declaration of the |
||||
* {@link org.springframework.ui.Model} interface. There is typically |
||||
* no need to create it within user code. If necessary a controller method can |
||||
* return a regular {@code java.util.Map}, or more likely a |
||||
* {@code java.util.ConcurrentMap}. |
||||
* |
||||
* @author Rossen Stoyanchev |
||||
* @since 5.0 |
||||
* @see BindingResult |
||||
*/ |
||||
@SuppressWarnings("serial") |
||||
public class BindingAwareConcurrentModel extends ConcurrentModel { |
||||
|
||||
@Override |
||||
public Object put(String key, Object value) { |
||||
removeBindingResultIfNecessary(key, value); |
||||
return super.put(key, value); |
||||
} |
||||
|
||||
@Override |
||||
public void putAll(Map<? extends String, ?> map) { |
||||
map.entrySet().forEach(e -> removeBindingResultIfNecessary(e.getKey(), e.getValue())); |
||||
super.putAll(map); |
||||
} |
||||
|
||||
private void removeBindingResultIfNecessary(String key, Object value) { |
||||
if (!key.startsWith(BindingResult.MODEL_KEY_PREFIX)) { |
||||
String bindingResultKey = BindingResult.MODEL_KEY_PREFIX + key; |
||||
BindingResult bindingResult = (BindingResult) get(bindingResultKey); |
||||
if (bindingResult != null && bindingResult.getTarget() != value) { |
||||
remove(bindingResultKey); |
||||
} |
||||
} |
||||
} |
||||
|
||||
} |
Loading…
Reference in new issue