From bec89dba4c6051eb7e1df4ac46ff0e2d23b07583 Mon Sep 17 00:00:00 2001 From: Juergen Hoeller Date: Fri, 29 May 2020 15:48:19 +0200 Subject: [PATCH 1/6] Consistent MultiValueMap behavior for empty list values This commit extracts MultiValueMapAdapter from CollectionUtils and reuses its implementation as base class of LinkedMultiValueMap. Closes gh-25140 --- .../springframework/util/CollectionUtils.java | 164 ++-------------- .../util/LinkedCaseInsensitiveMap.java | 6 +- .../util/LinkedMultiValueMap.java | 153 +-------------- .../util/MultiValueMapAdapter.java | 178 ++++++++++++++++++ 4 files changed, 202 insertions(+), 299 deletions(-) create mode 100644 spring-core/src/main/java/org/springframework/util/MultiValueMapAdapter.java diff --git a/spring-core/src/main/java/org/springframework/util/CollectionUtils.java b/spring-core/src/main/java/org/springframework/util/CollectionUtils.java index 1cd16243fd..1ec0510467 100644 --- a/spring-core/src/main/java/org/springframework/util/CollectionUtils.java +++ b/spring-core/src/main/java/org/springframework/util/CollectionUtils.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 the original author or authors. + * Copyright 2002-2020 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. @@ -16,7 +16,6 @@ package org.springframework.util; -import java.io.Serializable; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; @@ -24,7 +23,6 @@ import java.util.Collections; import java.util.Enumeration; import java.util.Iterator; import java.util.LinkedHashMap; -import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Properties; @@ -412,25 +410,28 @@ public abstract class CollectionUtils { /** * Adapt a {@code Map>} to an {@code MultiValueMap}. - * @param map the original map - * @return the multi-value map + * @param targetMap the original map + * @return the adapted multi-value map (wrapping the original map) * @since 3.1 */ - public static MultiValueMap toMultiValueMap(Map> map) { - return new MultiValueMapAdapter<>(map); + public static MultiValueMap toMultiValueMap(Map> targetMap) { + Assert.notNull(targetMap, "'targetMap' must not be null"); + return new MultiValueMapAdapter<>(targetMap); } /** * Return an unmodifiable view of the specified multi-value map. - * @param map the map for which an unmodifiable view is to be returned. - * @return an unmodifiable view of the specified multi-value map. + * @param targetMap the map for which an unmodifiable view is to be returned. + * @return an unmodifiable view of the specified multi-value map * @since 3.1 */ @SuppressWarnings("unchecked") - public static MultiValueMap unmodifiableMultiValueMap(MultiValueMap map) { - Assert.notNull(map, "'map' must not be null"); - Map> result = new LinkedHashMap<>(map.size()); - map.forEach((key, value) -> { + public static MultiValueMap unmodifiableMultiValueMap( + MultiValueMap targetMap) { + + Assert.notNull(targetMap, "'targetMap' must not be null"); + Map> result = new LinkedHashMap<>(targetMap.size()); + targetMap.forEach((key, value) -> { List values = Collections.unmodifiableList(value); result.put(key, (List) values); }); @@ -467,141 +468,4 @@ public abstract class CollectionUtils { } - /** - * Adapts a Map to the MultiValueMap contract. - */ - @SuppressWarnings("serial") - private static class MultiValueMapAdapter implements MultiValueMap, Serializable { - - private final Map> map; - - public MultiValueMapAdapter(Map> map) { - Assert.notNull(map, "'map' must not be null"); - this.map = map; - } - - @Override - @Nullable - public V getFirst(K key) { - List values = this.map.get(key); - return (values != null ? values.get(0) : null); - } - - @Override - public void add(K key, @Nullable V value) { - List values = this.map.computeIfAbsent(key, k -> new LinkedList<>()); - values.add(value); - } - - @Override - public void addAll(K key, List values) { - List currentValues = this.map.computeIfAbsent(key, k -> new LinkedList<>()); - currentValues.addAll(values); - } - - @Override - public void addAll(MultiValueMap values) { - for (Entry> entry : values.entrySet()) { - addAll(entry.getKey(), entry.getValue()); - } - } - - @Override - public void set(K key, @Nullable V value) { - List values = new LinkedList<>(); - values.add(value); - this.map.put(key, values); - } - - @Override - public void setAll(Map values) { - values.forEach(this::set); - } - - @Override - public Map toSingleValueMap() { - LinkedHashMap singleValueMap = new LinkedHashMap<>(this.map.size()); - this.map.forEach((key, value) -> singleValueMap.put(key, value.get(0))); - return singleValueMap; - } - - @Override - public int size() { - return this.map.size(); - } - - @Override - public boolean isEmpty() { - return this.map.isEmpty(); - } - - @Override - public boolean containsKey(Object key) { - return this.map.containsKey(key); - } - - @Override - public boolean containsValue(Object value) { - return this.map.containsValue(value); - } - - @Override - public List get(Object key) { - return this.map.get(key); - } - - @Override - public List put(K key, List value) { - return this.map.put(key, value); - } - - @Override - public List remove(Object key) { - return this.map.remove(key); - } - - @Override - public void putAll(Map> map) { - this.map.putAll(map); - } - - @Override - public void clear() { - this.map.clear(); - } - - @Override - public Set keySet() { - return this.map.keySet(); - } - - @Override - public Collection> values() { - return this.map.values(); - } - - @Override - public Set>> entrySet() { - return this.map.entrySet(); - } - - @Override - public boolean equals(@Nullable Object other) { - if (this == other) { - return true; - } - return this.map.equals(other); - } - - @Override - public int hashCode() { - return this.map.hashCode(); - } - - @Override - public String toString() { - return this.map.toString(); - } - } - } diff --git a/spring-core/src/main/java/org/springframework/util/LinkedCaseInsensitiveMap.java b/spring-core/src/main/java/org/springframework/util/LinkedCaseInsensitiveMap.java index 1b70fa5550..a7e24a7620 100644 --- a/spring-core/src/main/java/org/springframework/util/LinkedCaseInsensitiveMap.java +++ b/spring-core/src/main/java/org/springframework/util/LinkedCaseInsensitiveMap.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 the original author or authors. + * Copyright 2002-2020 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. @@ -273,8 +273,8 @@ public class LinkedCaseInsensitiveMap implements Map, Serializable } @Override - public boolean equals(@Nullable Object obj) { - return this.targetMap.equals(obj); + public boolean equals(@Nullable Object other) { + return (this == other || this.targetMap.equals(other)); } @Override diff --git a/spring-core/src/main/java/org/springframework/util/LinkedMultiValueMap.java b/spring-core/src/main/java/org/springframework/util/LinkedMultiValueMap.java index c3edb97971..00ef6b4d6b 100644 --- a/spring-core/src/main/java/org/springframework/util/LinkedMultiValueMap.java +++ b/spring-core/src/main/java/org/springframework/util/LinkedMultiValueMap.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 the original author or authors. + * Copyright 2002-2020 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. @@ -17,14 +17,10 @@ package org.springframework.util; import java.io.Serializable; -import java.util.Collection; import java.util.LinkedHashMap; import java.util.LinkedList; import java.util.List; import java.util.Map; -import java.util.Set; - -import org.springframework.lang.Nullable; /** * Simple implementation of {@link MultiValueMap} that wraps a {@link LinkedHashMap}, @@ -39,18 +35,16 @@ import org.springframework.lang.Nullable; * @param the key type * @param the value element type */ -public class LinkedMultiValueMap implements MultiValueMap, Serializable, Cloneable { +public class LinkedMultiValueMap extends MultiValueMapAdapter implements Serializable, Cloneable { private static final long serialVersionUID = 3801124242820219131L; - private final Map> targetMap; - /** * Create a new LinkedMultiValueMap that wraps a {@link LinkedHashMap}. */ public LinkedMultiValueMap() { - this.targetMap = new LinkedHashMap<>(); + super(new LinkedHashMap<>()); } /** @@ -59,7 +53,7 @@ public class LinkedMultiValueMap implements MultiValueMap, Serializa * @param initialCapacity the initial capacity */ public LinkedMultiValueMap(int initialCapacity) { - this.targetMap = new LinkedHashMap<>(initialCapacity); + super(new LinkedHashMap<>(initialCapacity)); } /** @@ -71,125 +65,7 @@ public class LinkedMultiValueMap implements MultiValueMap, Serializa * @see #deepCopy() */ public LinkedMultiValueMap(Map> otherMap) { - this.targetMap = new LinkedHashMap<>(otherMap); - } - - - // MultiValueMap implementation - - @Override - @Nullable - public V getFirst(K key) { - List values = this.targetMap.get(key); - return (values != null && !values.isEmpty() ? values.get(0) : null); - } - - @Override - public void add(K key, @Nullable V value) { - List values = this.targetMap.computeIfAbsent(key, k -> new LinkedList<>()); - values.add(value); - } - - @Override - public void addAll(K key, List values) { - List currentValues = this.targetMap.computeIfAbsent(key, k -> new LinkedList<>()); - currentValues.addAll(values); - } - - @Override - public void addAll(MultiValueMap values) { - for (Entry> entry : values.entrySet()) { - addAll(entry.getKey(), entry.getValue()); - } - } - - @Override - public void set(K key, @Nullable V value) { - List values = new LinkedList<>(); - values.add(value); - this.targetMap.put(key, values); - } - - @Override - public void setAll(Map values) { - values.forEach(this::set); - } - - @Override - public Map toSingleValueMap() { - LinkedHashMap singleValueMap = new LinkedHashMap<>(this.targetMap.size()); - this.targetMap.forEach((key, values) -> { - if (values != null && !values.isEmpty()) { - singleValueMap.put(key, values.get(0)); - } - }); - return singleValueMap; - } - - - // Map implementation - - @Override - public int size() { - return this.targetMap.size(); - } - - @Override - public boolean isEmpty() { - return this.targetMap.isEmpty(); - } - - @Override - public boolean containsKey(Object key) { - return this.targetMap.containsKey(key); - } - - @Override - public boolean containsValue(Object value) { - return this.targetMap.containsValue(value); - } - - @Override - @Nullable - public List get(Object key) { - return this.targetMap.get(key); - } - - @Override - @Nullable - public List put(K key, List value) { - return this.targetMap.put(key, value); - } - - @Override - @Nullable - public List remove(Object key) { - return this.targetMap.remove(key); - } - - @Override - public void putAll(Map> map) { - this.targetMap.putAll(map); - } - - @Override - public void clear() { - this.targetMap.clear(); - } - - @Override - public Set keySet() { - return this.targetMap.keySet(); - } - - @Override - public Collection> values() { - return this.targetMap.values(); - } - - @Override - public Set>> entrySet() { - return this.targetMap.entrySet(); + super(new LinkedHashMap<>(otherMap)); } @@ -203,8 +79,8 @@ public class LinkedMultiValueMap implements MultiValueMap, Serializa * @see #clone() */ public LinkedMultiValueMap deepCopy() { - LinkedMultiValueMap copy = new LinkedMultiValueMap<>(this.targetMap.size()); - this.targetMap.forEach((key, value) -> copy.put(key, new LinkedList<>(value))); + LinkedMultiValueMap copy = new LinkedMultiValueMap<>(size()); + forEach((key, values) -> copy.put(key, new LinkedList<>(values))); return copy; } @@ -224,19 +100,4 @@ public class LinkedMultiValueMap implements MultiValueMap, Serializa return new LinkedMultiValueMap<>(this); } - @Override - public boolean equals(@Nullable Object obj) { - return this.targetMap.equals(obj); - } - - @Override - public int hashCode() { - return this.targetMap.hashCode(); - } - - @Override - public String toString() { - return this.targetMap.toString(); - } - } diff --git a/spring-core/src/main/java/org/springframework/util/MultiValueMapAdapter.java b/spring-core/src/main/java/org/springframework/util/MultiValueMapAdapter.java new file mode 100644 index 0000000000..cfbf79cb3b --- /dev/null +++ b/spring-core/src/main/java/org/springframework/util/MultiValueMapAdapter.java @@ -0,0 +1,178 @@ +/* + * Copyright 2002-2020 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 + * + * https://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.util; + +import java.io.Serializable; +import java.util.Collection; +import java.util.LinkedHashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import org.springframework.lang.Nullable; + +/** + * Adapts a given {@link Map} to the {@link MultiValueMap} contract. + * + * @author Arjen Poutsma + * @author Juergen Hoeller + * @since 3.1 + * @param the key type + * @param the value element type + * @see CollectionUtils#toMultiValueMap + * @see LinkedMultiValueMap + */ +@SuppressWarnings("serial") +class MultiValueMapAdapter implements MultiValueMap, Serializable { + + private final Map> targetMap; + + + MultiValueMapAdapter(Map> targetMap) { + this.targetMap = targetMap; + } + + + @Override + @Nullable + public V getFirst(K key) { + List values = this.targetMap.get(key); + return (values != null && !values.isEmpty() ? values.get(0) : null); + } + + @Override + public void add(K key, @Nullable V value) { + List values = this.targetMap.computeIfAbsent(key, k -> new LinkedList<>()); + values.add(value); + } + + @Override + public void addAll(K key, List values) { + List currentValues = this.targetMap.computeIfAbsent(key, k -> new LinkedList<>()); + currentValues.addAll(values); + } + + @Override + public void addAll(MultiValueMap values) { + for (Entry> entry : values.entrySet()) { + addAll(entry.getKey(), entry.getValue()); + } + } + + @Override + public void set(K key, @Nullable V value) { + List values = new LinkedList<>(); + values.add(value); + this.targetMap.put(key, values); + } + + @Override + public void setAll(Map values) { + values.forEach(this::set); + } + + @Override + public Map toSingleValueMap() { + Map singleValueMap = new LinkedHashMap<>(this.targetMap.size()); + this.targetMap.forEach((key, values) -> { + if (values != null && !values.isEmpty()) { + singleValueMap.put(key, values.get(0)); + } + }); + return singleValueMap; + } + + @Override + public int size() { + return this.targetMap.size(); + } + + @Override + public boolean isEmpty() { + return this.targetMap.isEmpty(); + } + + @Override + public boolean containsKey(Object key) { + return this.targetMap.containsKey(key); + } + + @Override + public boolean containsValue(Object value) { + return this.targetMap.containsValue(value); + } + + @Override + @Nullable + public List get(Object key) { + return this.targetMap.get(key); + } + + @Override + @Nullable + public List put(K key, List value) { + return this.targetMap.put(key, value); + } + + @Override + @Nullable + public List remove(Object key) { + return this.targetMap.remove(key); + } + + @Override + public void putAll(Map> map) { + this.targetMap.putAll(map); + } + + @Override + public void clear() { + this.targetMap.clear(); + } + + @Override + public Set keySet() { + return this.targetMap.keySet(); + } + + @Override + public Collection> values() { + return this.targetMap.values(); + } + + @Override + public Set>> entrySet() { + return this.targetMap.entrySet(); + } + + @Override + public boolean equals(@Nullable Object other) { + return (this == other || this.targetMap.equals(other)); + } + + @Override + public int hashCode() { + return this.targetMap.hashCode(); + } + + @Override + public String toString() { + return this.targetMap.toString(); + } + +} From ef626e992d05190111a1540d8eb5b8ba082ff96f Mon Sep 17 00:00:00 2001 From: Juergen Hoeller Date: Fri, 29 May 2020 15:49:36 +0200 Subject: [PATCH 2/6] Document that MapPropertySource should not contain null values Closes gh-25142 --- .../springframework/core/env/MapPropertySource.java | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/spring-core/src/main/java/org/springframework/core/env/MapPropertySource.java b/spring-core/src/main/java/org/springframework/core/env/MapPropertySource.java index d08c6fb278..36597a5b24 100644 --- a/spring-core/src/main/java/org/springframework/core/env/MapPropertySource.java +++ b/spring-core/src/main/java/org/springframework/core/env/MapPropertySource.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2014 the original author or authors. + * Copyright 2002-2020 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. @@ -23,6 +23,8 @@ import org.springframework.util.StringUtils; /** * {@link PropertySource} that reads keys and values from a {@code Map} object. + * The underlying map should not contain any {@code null} values in order to + * comply with {@link #getProperty} and {@link #containsProperty} semantics. * * @author Chris Beams * @author Juergen Hoeller @@ -31,6 +33,12 @@ import org.springframework.util.StringUtils; */ public class MapPropertySource extends EnumerablePropertySource> { + /** + * Create a new {@code MapPropertySource} with the given name and {@code Map}. + * @param name the associated name + * @param source the Map source (without {@code null} values in order to get + * consistent {@link #getProperty} and {@link #containsProperty} behavior) + */ public MapPropertySource(String name, Map source) { super(name, source); } From 2ff22510d92986ebabd0f7f4667d7ede3f83e01d Mon Sep 17 00:00:00 2001 From: Juergen Hoeller Date: Fri, 29 May 2020 15:50:10 +0200 Subject: [PATCH 3/6] Avoid earlyApplicationEvents iteration in case of empty Set Closes gh-25161 --- .../context/support/AbstractApplicationContext.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/spring-context/src/main/java/org/springframework/context/support/AbstractApplicationContext.java b/spring-context/src/main/java/org/springframework/context/support/AbstractApplicationContext.java index 6b4e5b6a4f..933fc7359c 100644 --- a/spring-context/src/main/java/org/springframework/context/support/AbstractApplicationContext.java +++ b/spring-context/src/main/java/org/springframework/context/support/AbstractApplicationContext.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 the original author or authors. + * Copyright 2002-2020 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. @@ -79,6 +79,7 @@ import org.springframework.core.io.support.PathMatchingResourcePatternResolver; import org.springframework.core.io.support.ResourcePatternResolver; import org.springframework.lang.Nullable; import org.springframework.util.Assert; +import org.springframework.util.CollectionUtils; import org.springframework.util.ObjectUtils; import org.springframework.util.ReflectionUtils; @@ -836,7 +837,7 @@ public abstract class AbstractApplicationContext extends DefaultResourceLoader // Publish early application events now that we finally have a multicaster... Set earlyEventsToProcess = this.earlyApplicationEvents; this.earlyApplicationEvents = null; - if (earlyEventsToProcess != null) { + if (!CollectionUtils.isEmpty(earlyEventsToProcess)) { for (ApplicationEvent earlyEvent : earlyEventsToProcess) { getApplicationEventMulticaster().multicastEvent(earlyEvent); } From 08474aa921b0c57e34ef25607994fe58bef91e09 Mon Sep 17 00:00:00 2001 From: Juergen Hoeller Date: Fri, 29 May 2020 15:51:19 +0200 Subject: [PATCH 4/6] Clarify JDBC-defined negative values returned from batchUpdate Closes gh-25138 --- .../jdbc/core/JdbcOperations.java | 16 ++++++++++++---- .../namedparam/NamedParameterJdbcOperations.java | 7 ++++++- 2 files changed, 18 insertions(+), 5 deletions(-) diff --git a/spring-jdbc/src/main/java/org/springframework/jdbc/core/JdbcOperations.java b/spring-jdbc/src/main/java/org/springframework/jdbc/core/JdbcOperations.java index 67ff03c8d7..3c3bd254f0 100644 --- a/spring-jdbc/src/main/java/org/springframework/jdbc/core/JdbcOperations.java +++ b/spring-jdbc/src/main/java/org/springframework/jdbc/core/JdbcOperations.java @@ -896,6 +896,8 @@ public interface JdbcOperations { * @param pss object to set parameters on the PreparedStatement * created by this method * @return an array of the number of rows affected by each statement + * (may also contain special JDBC-defined negative values for affected rows such as + * {@link java.sql.Statement#SUCCESS_NO_INFO}/{@link java.sql.Statement#EXECUTE_FAILED}) * @throws DataAccessException if there is any problem issuing the update */ int[] batchUpdate(String sql, BatchPreparedStatementSetter pss) throws DataAccessException; @@ -905,6 +907,8 @@ public interface JdbcOperations { * @param sql the SQL statement to execute * @param batchArgs the List of Object arrays containing the batch of arguments for the query * @return an array containing the numbers of rows affected by each update in the batch + * (may also contain special JDBC-defined negative values for affected rows such as + * {@link java.sql.Statement#SUCCESS_NO_INFO}/{@link java.sql.Statement#EXECUTE_FAILED}) * @throws DataAccessException if there is any problem issuing the update */ int[] batchUpdate(String sql, List batchArgs) throws DataAccessException; @@ -916,20 +920,24 @@ public interface JdbcOperations { * @param argTypes the SQL types of the arguments * (constants from {@code java.sql.Types}) * @return an array containing the numbers of rows affected by each update in the batch + * (may also contain special JDBC-defined negative values for affected rows such as + * {@link java.sql.Statement#SUCCESS_NO_INFO}/{@link java.sql.Statement#EXECUTE_FAILED}) * @throws DataAccessException if there is any problem issuing the update */ int[] batchUpdate(String sql, List batchArgs, int[] argTypes) throws DataAccessException; /** - * Execute multiple batches using the supplied SQL statement with the collect of supplied arguments. - * The arguments' values will be set using the ParameterizedPreparedStatementSetter. + * Execute multiple batches using the supplied SQL statement with the collect of supplied + * arguments. The arguments' values will be set using the ParameterizedPreparedStatementSetter. * Each batch should be of size indicated in 'batchSize'. * @param sql the SQL statement to execute. * @param batchArgs the List of Object arrays containing the batch of arguments for the query * @param batchSize batch size * @param pss the ParameterizedPreparedStatementSetter to use - * @return an array containing for each batch another array containing the numbers of rows affected - * by each update in the batch + * @return an array containing for each batch another array containing the numbers of + * rows affected by each update in the batch + * (may also contain special JDBC-defined negative values for affected rows such as + * {@link java.sql.Statement#SUCCESS_NO_INFO}/{@link java.sql.Statement#EXECUTE_FAILED}) * @throws DataAccessException if there is any problem issuing the update * @since 3.1 */ diff --git a/spring-jdbc/src/main/java/org/springframework/jdbc/core/namedparam/NamedParameterJdbcOperations.java b/spring-jdbc/src/main/java/org/springframework/jdbc/core/namedparam/NamedParameterJdbcOperations.java index ad5c627eeb..62f5934137 100644 --- a/spring-jdbc/src/main/java/org/springframework/jdbc/core/namedparam/NamedParameterJdbcOperations.java +++ b/spring-jdbc/src/main/java/org/springframework/jdbc/core/namedparam/NamedParameterJdbcOperations.java @@ -498,6 +498,8 @@ public interface NamedParameterJdbcOperations { * @param sql the SQL statement to execute * @param batchValues the array of Maps containing the batch of arguments for the query * @return an array containing the numbers of rows affected by each update in the batch + * (may also contain special JDBC-defined negative values for affected rows such as + * {@link java.sql.Statement#SUCCESS_NO_INFO}/{@link java.sql.Statement#EXECUTE_FAILED}) * @throws DataAccessException if there is any problem issuing the update */ int[] batchUpdate(String sql, Map[] batchValues); @@ -505,8 +507,11 @@ public interface NamedParameterJdbcOperations { /** * Execute a batch using the supplied SQL statement with the batch of supplied arguments. * @param sql the SQL statement to execute - * @param batchArgs the array of {@link SqlParameterSource} containing the batch of arguments for the query + * @param batchArgs the array of {@link SqlParameterSource} containing the batch of + * arguments for the query * @return an array containing the numbers of rows affected by each update in the batch + * (may also contain special JDBC-defined negative values for affected rows such as + * {@link java.sql.Statement#SUCCESS_NO_INFO}/{@link java.sql.Statement#EXECUTE_FAILED}) * @throws DataAccessException if there is any problem issuing the update */ int[] batchUpdate(String sql, SqlParameterSource[] batchArgs); From cd4ef6f781a5a4df6e8fef44811a54b410fdeab6 Mon Sep 17 00:00:00 2001 From: Juergen Hoeller Date: Fri, 29 May 2020 15:51:54 +0200 Subject: [PATCH 5/6] Consistently refer to FlushMode.MANUAL instead of outdated NEVER Closes gh-25158 --- .../orm/hibernate5/support/OpenSessionInViewFilter.java | 6 +++--- .../support/TransactionSynchronizationManager.java | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/spring-orm/src/main/java/org/springframework/orm/hibernate5/support/OpenSessionInViewFilter.java b/spring-orm/src/main/java/org/springframework/orm/hibernate5/support/OpenSessionInViewFilter.java index a68af6f778..ec23f51c02 100644 --- a/spring-orm/src/main/java/org/springframework/orm/hibernate5/support/OpenSessionInViewFilter.java +++ b/spring-orm/src/main/java/org/springframework/orm/hibernate5/support/OpenSessionInViewFilter.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2018 the original author or authors. + * Copyright 2002-2020 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. @@ -51,11 +51,11 @@ import org.springframework.web.filter.OncePerRequestFilter; * as well as for non-transactional execution (if configured appropriately). * *

NOTE: This filter will by default not flush the Hibernate Session, - * with the flush mode set to {@code FlushMode.NEVER}. It assumes to be used + * with the flush mode set to {@code FlushMode.MANUAL}. It assumes to be used * in combination with service layer transactions that care for the flushing: The * active transaction manager will temporarily change the flush mode to * {@code FlushMode.AUTO} during a read-write transaction, with the flush - * mode reset to {@code FlushMode.NEVER} at the end of each transaction. + * mode reset to {@code FlushMode.MANUAL} at the end of each transaction. * *

WARNING: Applying this filter to existing logic can cause issues that * have not appeared before, through the use of a single Hibernate Session for the diff --git a/spring-tx/src/main/java/org/springframework/transaction/support/TransactionSynchronizationManager.java b/spring-tx/src/main/java/org/springframework/transaction/support/TransactionSynchronizationManager.java index 19f20f8571..df9132d13d 100644 --- a/spring-tx/src/main/java/org/springframework/transaction/support/TransactionSynchronizationManager.java +++ b/spring-tx/src/main/java/org/springframework/transaction/support/TransactionSynchronizationManager.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 the original author or authors. + * Copyright 2002-2020 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. @@ -383,7 +383,7 @@ public abstract class TransactionSynchronizationManager { * as argument for the {@code beforeCommit} callback, to be able * to suppress change detection on commit. The present method is meant * to be used for earlier read-only checks, for example to set the - * flush mode of a Hibernate Session to "FlushMode.NEVER" upfront. + * flush mode of a Hibernate Session to "FlushMode.MANUAL" upfront. * @see org.springframework.transaction.TransactionDefinition#isReadOnly() * @see TransactionSynchronization#beforeCommit(boolean) */ From 914425eefaee7c80a1654c1598844b69607d14e1 Mon Sep 17 00:00:00 2001 From: Juergen Hoeller Date: Fri, 29 May 2020 15:52:39 +0200 Subject: [PATCH 6/6] Polishing --- .../beans/factory/config/YamlProcessor.java | 4 +--- .../core/env/AbstractPropertyResolver.java | 5 ++++- .../core/env/EnumerablePropertySource.java | 12 +++++++++++- .../springframework/core/env/PropertyResolver.java | 4 +--- .../springframework/core/env/PropertySource.java | 4 +++- .../springframework/util/SystemPropertyUtils.java | 5 ++++- .../web/util/ServletContextPropertyUtils.java | 13 ++++++++----- 7 files changed, 32 insertions(+), 15 deletions(-) diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/config/YamlProcessor.java b/spring-beans/src/main/java/org/springframework/beans/factory/config/YamlProcessor.java index 1b4e2bdeea..6d929f2ba5 100644 --- a/spring-beans/src/main/java/org/springframework/beans/factory/config/YamlProcessor.java +++ b/spring-beans/src/main/java/org/springframework/beans/factory/config/YamlProcessor.java @@ -432,8 +432,7 @@ public abstract class YamlProcessor { /** * {@link Constructor} that supports filtering of unsupported types. *

If an unsupported type is encountered in a YAML document, an - * {@link IllegalStateException} will be thrown from {@link #getClassForName(String)}. - * @since 5.1.16 + * {@link IllegalStateException} will be thrown from {@link #getClassForName}. */ private class FilteringConstructor extends Constructor { @@ -441,7 +440,6 @@ public abstract class YamlProcessor { super(loaderOptions); } - @Override protected Class getClassForName(String name) throws ClassNotFoundException { Assert.state(YamlProcessor.this.supportedTypes.contains(name), diff --git a/spring-core/src/main/java/org/springframework/core/env/AbstractPropertyResolver.java b/spring-core/src/main/java/org/springframework/core/env/AbstractPropertyResolver.java index d5f7fd1110..c3f29e106a 100644 --- a/spring-core/src/main/java/org/springframework/core/env/AbstractPropertyResolver.java +++ b/spring-core/src/main/java/org/springframework/core/env/AbstractPropertyResolver.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2018 the original author or authors. + * Copyright 2002-2020 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. @@ -223,6 +223,9 @@ public abstract class AbstractPropertyResolver implements ConfigurablePropertyRe * @see #setIgnoreUnresolvableNestedPlaceholders */ protected String resolveNestedPlaceholders(String value) { + if (value.isEmpty()) { + return value; + } return (this.ignoreUnresolvableNestedPlaceholders ? resolvePlaceholders(value) : resolveRequiredPlaceholders(value)); } diff --git a/spring-core/src/main/java/org/springframework/core/env/EnumerablePropertySource.java b/spring-core/src/main/java/org/springframework/core/env/EnumerablePropertySource.java index 0ef6ce4a77..2c1386d312 100644 --- a/spring-core/src/main/java/org/springframework/core/env/EnumerablePropertySource.java +++ b/spring-core/src/main/java/org/springframework/core/env/EnumerablePropertySource.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2018 the original author or authors. + * Copyright 2002-2020 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. @@ -44,10 +44,20 @@ import org.springframework.util.ObjectUtils; */ public abstract class EnumerablePropertySource extends PropertySource { + /** + * Create a new {@code EnumerablePropertySource} with the given name and source object. + * @param name the associated name + * @param source the source object + */ public EnumerablePropertySource(String name, T source) { super(name, source); } + /** + * Create a new {@code EnumerablePropertySource} with the given name and with a new + * {@code Object} instance as the underlying source. + * @param name the associated name + */ protected EnumerablePropertySource(String name) { super(name); } diff --git a/spring-core/src/main/java/org/springframework/core/env/PropertyResolver.java b/spring-core/src/main/java/org/springframework/core/env/PropertyResolver.java index 5554463c44..173a1a3378 100644 --- a/spring-core/src/main/java/org/springframework/core/env/PropertyResolver.java +++ b/spring-core/src/main/java/org/springframework/core/env/PropertyResolver.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2016 the original author or authors. + * Copyright 2002-2020 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. @@ -98,7 +98,6 @@ public interface PropertyResolver { * @return the resolved String (never {@code null}) * @throws IllegalArgumentException if given text is {@code null} * @see #resolveRequiredPlaceholders - * @see org.springframework.util.SystemPropertyUtils#resolvePlaceholders(String) */ String resolvePlaceholders(String text); @@ -109,7 +108,6 @@ public interface PropertyResolver { * @return the resolved String (never {@code null}) * @throws IllegalArgumentException if given text is {@code null} * or if any placeholders are unresolvable - * @see org.springframework.util.SystemPropertyUtils#resolvePlaceholders(String, boolean) */ String resolveRequiredPlaceholders(String text) throws IllegalArgumentException; diff --git a/spring-core/src/main/java/org/springframework/core/env/PropertySource.java b/spring-core/src/main/java/org/springframework/core/env/PropertySource.java index 2cb6313064..c8a1da6bb8 100644 --- a/spring-core/src/main/java/org/springframework/core/env/PropertySource.java +++ b/spring-core/src/main/java/org/springframework/core/env/PropertySource.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2018 the original author or authors. + * Copyright 2002-2020 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. @@ -68,6 +68,8 @@ public abstract class PropertySource { /** * Create a new {@code PropertySource} with the given name and source object. + * @param name the associated name + * @param source the source object */ public PropertySource(String name, T source) { Assert.hasText(name, "Property source name must contain at least one character"); diff --git a/spring-core/src/main/java/org/springframework/util/SystemPropertyUtils.java b/spring-core/src/main/java/org/springframework/util/SystemPropertyUtils.java index c1769c6b63..7c5ac6bdf4 100644 --- a/spring-core/src/main/java/org/springframework/util/SystemPropertyUtils.java +++ b/spring-core/src/main/java/org/springframework/util/SystemPropertyUtils.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2018 the original author or authors. + * Copyright 2002-2020 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. @@ -78,6 +78,9 @@ public abstract class SystemPropertyUtils { * and the "ignoreUnresolvablePlaceholders" flag is {@code false} */ public static String resolvePlaceholders(String text, boolean ignoreUnresolvablePlaceholders) { + if (text.isEmpty()) { + return text; + } PropertyPlaceholderHelper helper = (ignoreUnresolvablePlaceholders ? nonStrictHelper : strictHelper); return helper.replacePlaceholders(text, new SystemPropertyPlaceholderResolver(text)); } diff --git a/spring-web/src/main/java/org/springframework/web/util/ServletContextPropertyUtils.java b/spring-web/src/main/java/org/springframework/web/util/ServletContextPropertyUtils.java index 18709788aa..ffaefa75b4 100644 --- a/spring-web/src/main/java/org/springframework/web/util/ServletContextPropertyUtils.java +++ b/spring-web/src/main/java/org/springframework/web/util/ServletContextPropertyUtils.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2018 the original author or authors. + * Copyright 2002-2020 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. @@ -13,6 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + package org.springframework.web.util; import javax.servlet.ServletContext; @@ -73,16 +74,18 @@ public abstract class ServletContextPropertyUtils { * @see SystemPropertyUtils#PLACEHOLDER_SUFFIX * @see SystemPropertyUtils#resolvePlaceholders(String, boolean) */ - public static String resolvePlaceholders(String text, ServletContext servletContext, - boolean ignoreUnresolvablePlaceholders) { + public static String resolvePlaceholders( + String text, ServletContext servletContext, boolean ignoreUnresolvablePlaceholders) { + if (text.isEmpty()) { + return text; + } PropertyPlaceholderHelper helper = (ignoreUnresolvablePlaceholders ? nonStrictHelper : strictHelper); return helper.replacePlaceholders(text, new ServletContextPlaceholderResolver(text, servletContext)); } - private static class ServletContextPlaceholderResolver - implements PropertyPlaceholderHelper.PlaceholderResolver { + private static class ServletContextPlaceholderResolver implements PropertyPlaceholderHelper.PlaceholderResolver { private final String text;