|  | 
| 20 | 20 | import java.nio.charset.StandardCharsets; | 
| 21 | 21 | import java.util.ArrayList; | 
| 22 | 22 | import java.util.Collections; | 
| 23 |  | -import java.util.LinkedHashMap; | 
| 24 | 23 | import java.util.List; | 
| 25 | 24 | import java.util.Map; | 
|  | 25 | +import java.util.concurrent.ConcurrentHashMap; | 
| 26 | 26 | 
 | 
| 27 | 27 | import org.apache.commons.logging.Log; | 
| 28 | 28 | import org.apache.commons.logging.LogFactory; | 
| @@ -211,66 +211,62 @@ public Mono<Void> render(@Nullable Map<String, ?> model, @Nullable MediaType con | 
| 211 | 211 | 	 * <p>The default implementation creates a combined output Map that includes | 
| 212 | 212 | 	 * model as well as static attributes with the former taking precedence. | 
| 213 | 213 | 	 */ | 
| 214 |  | -	protected Mono<Map<String, Object>> getModelAttributes(@Nullable Map<String, ?> model, | 
| 215 |  | -			ServerWebExchange exchange) { | 
|  | 214 | +	protected Mono<Map<String, Object>> getModelAttributes( | 
|  | 215 | +			@Nullable Map<String, ?> model, ServerWebExchange exchange) { | 
| 216 | 216 | 
 | 
| 217 | 217 | 		int size = (model != null ? model.size() : 0); | 
| 218 |  | - | 
| 219 |  | -		Map<String, Object> attributes = new LinkedHashMap<>(size); | 
|  | 218 | +		Map<String, Object> attributes = new ConcurrentHashMap<>(size); | 
| 220 | 219 | 		if (model != null) { | 
| 221 | 220 | 			attributes.putAll(model); | 
| 222 | 221 | 		} | 
| 223 |  | - | 
| 224 |  | -		return resolveAsyncAttributes(attributes).then(Mono.just(attributes)); | 
|  | 222 | +		return resolveAsyncAttributes(attributes).thenReturn(attributes); | 
| 225 | 223 | 	} | 
| 226 | 224 | 
 | 
| 227 | 225 | 	/** | 
| 228 |  | -	 * By default, resolve async attributes supported by the | 
| 229 |  | -	 * {@link ReactiveAdapterRegistry} to their blocking counterparts. | 
| 230 |  | -	 * <p>View implementations capable of taking advantage of reactive types | 
| 231 |  | -	 * can override this method if needed. | 
| 232 |  | -	 * @return {@code Mono} for the completion of async attributes resolution | 
|  | 226 | +	 * Use the configured {@link ReactiveAdapterRegistry} to adapt asynchronous | 
|  | 227 | +	 * model attributes to {@code Mono<T>} or {@code Mono<List<T>>} and resolve | 
|  | 228 | +	 * them to actual values via {@link Mono#zip(Mono, Mono)}, so that when | 
|  | 229 | +	 * the returned result {@code Mono} completes, the model has its asynchronous | 
|  | 230 | +	 * attributes replaced with synchronous values. | 
|  | 231 | +	 * @return result {@code Mono} that completes when the model is ready | 
| 233 | 232 | 	 */ | 
| 234 | 233 | 	protected Mono<Void> resolveAsyncAttributes(Map<String, Object> model) { | 
| 235 |  | -		List<String> names = new ArrayList<>(); | 
| 236 |  | -		List<Mono<?>> valueMonos = new ArrayList<>(); | 
| 237 |  | - | 
|  | 234 | +		List<Mono<?>> asyncAttributes = null; | 
| 238 | 235 | 		for (Map.Entry<String, ?> entry : model.entrySet()) { | 
| 239 | 236 | 			Object value =  entry.getValue(); | 
| 240 | 237 | 			if (value == null) { | 
| 241 | 238 | 				continue; | 
| 242 | 239 | 			} | 
| 243 | 240 | 			ReactiveAdapter adapter = this.adapterRegistry.getAdapter(null, value); | 
| 244 | 241 | 			if (adapter != null) { | 
| 245 |  | -				names.add(entry.getKey()); | 
|  | 242 | +				if (asyncAttributes == null) { | 
|  | 243 | +					asyncAttributes = new ArrayList<>(); | 
|  | 244 | +				} | 
|  | 245 | +				String name = entry.getKey(); | 
| 246 | 246 | 				if (adapter.isMultiValue()) { | 
| 247 |  | -					Flux<Object> fluxValue = Flux.from(adapter.toPublisher(value)); | 
| 248 |  | -					valueMonos.add(fluxValue.collectList().defaultIfEmpty(Collections.emptyList())); | 
|  | 247 | +					asyncAttributes.add( | 
|  | 248 | +							Flux.from(adapter.toPublisher(value)) | 
|  | 249 | +									.collectList() | 
|  | 250 | +									.doOnSuccess(result -> { | 
|  | 251 | +										result = result != null ? result : Collections.emptyList(); | 
|  | 252 | +										model.put(name, result); | 
|  | 253 | +									})); | 
| 249 | 254 | 				} | 
| 250 | 255 | 				else { | 
| 251 |  | -					Mono<Object> monoValue = Mono.from(adapter.toPublisher(value)); | 
| 252 |  | -					valueMonos.add(monoValue.defaultIfEmpty(NO_VALUE)); | 
|  | 256 | +					asyncAttributes.add( | 
|  | 257 | +							Mono.from(adapter.toPublisher(value)) | 
|  | 258 | +									.doOnSuccess(result -> { | 
|  | 259 | +										if (result != null) { | 
|  | 260 | +											model.put(name, result); | 
|  | 261 | +										} | 
|  | 262 | +										else { | 
|  | 263 | +											model.remove(name); | 
|  | 264 | +										} | 
|  | 265 | +									})); | 
| 253 | 266 | 				} | 
| 254 | 267 | 			} | 
| 255 | 268 | 		} | 
| 256 |  | - | 
| 257 |  | -		if (names.isEmpty()) { | 
| 258 |  | -			return Mono.empty(); | 
| 259 |  | -		} | 
| 260 |  | - | 
| 261 |  | -		return Mono.zip(valueMonos, | 
| 262 |  | -				values -> { | 
| 263 |  | -					for (int i=0; i < values.length; i++) { | 
| 264 |  | -						if (values[i] != NO_VALUE) { | 
| 265 |  | -							model.put(names.get(i), values[i]); | 
| 266 |  | -						} | 
| 267 |  | -						else { | 
| 268 |  | -							model.remove(names.get(i)); | 
| 269 |  | -						} | 
| 270 |  | -					} | 
| 271 |  | -					return NO_VALUE; | 
| 272 |  | -				}) | 
| 273 |  | -				.then(); | 
|  | 269 | +		return asyncAttributes != null ? Mono.when(asyncAttributes) : Mono.empty(); | 
| 274 | 270 | 	} | 
| 275 | 271 | 
 | 
| 276 | 272 | 	/** | 
|  | 
0 commit comments