All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.thymeleaf.spring5.context.reactive.ReactiveLazyContextVariable Maven / Gradle / Ivy

Go to download

Modern server-side Java template engine for both web and standalone environments

There is a newer version: 3.1.3.RELEASE
Show newest version
/*
 * =============================================================================
 * 
 *   Copyright (c) 2011-2016, The THYMELEAF team (http://www.thymeleaf.org)
 * 
 *   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.thymeleaf.spring5.context.reactive;

import java.util.Collections;

import org.reactivestreams.Publisher;
import org.springframework.core.ReactiveAdapterRegistry;
import org.thymeleaf.context.ILazyContextVariable;
import org.thymeleaf.context.LazyContextVariable;
import org.thymeleaf.util.Validate;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;

/**
 * 

* Implementation of the {@link org.thymeleaf.context.ILazyContextVariable} interface meant to contain * a reactive data stream that should not be resolved until view is rendered. *

*

* By being added to the context/model wrapped by an object of this class, reactive asynchronous objects will * reach the execution phase of the view layer unresolved, and will only be resolved if they are really * needed. So asynchronous variables that the template does not really need in the end (because template * logic resolves in a way that doesn't make use of them) will never be consumed at all. *

*

* Note that resolving this kind of objects means actually blocking and collecting * its values. *

*

* The reactive async object wrapped by this class will usually have the shape of an implementation of the * {@link Publisher} interface, such as {@link reactor.core.publisher.Flux}. But other types of reactive * artifacts are supported thanks to Spring's {@link org.springframework.core.ReactiveAdapterRegistry} * mechanism. *

*

* When lazily resolving these variables, this class mirrors the mechanism used by Spring for resolving * asynchronous variables at the model of views: *

*
    *
  • Flux<T> or other multi-valued streams are resolved as * List<T> so that they are iterable.
  • *
  • Mono<T> or other single-valued streams are resolved as * T so that they are directly referenceable just like any other object.
  • *
*

* Being multi-valued does not mean to necessarily return more than one value, * but simply to have the capability to do so. E.g. a {@link reactor.core.publisher.Flux} object will * be considered multi-valued even if it publishes none or just one result, whereas a * {@link reactor.core.publisher.Mono} object will be considered single-valued. *

*

* Example use: *

*

 * @RequestMapping("/something")
 * public String doSomething(final Model model) {
 *
 *     final Publisher<Item> async = ...;
 *
 *     // If 'async' is multi-valued, 'someData' will be usable as if it were of type List<Item>
 *     // If 'async' is single-valued, 'someData' will be usable as if it were of type Item
 *     model.addAttribute("someData", new ReactiveLazyContextVariable(async));
 *
 *     return "view";
 *
 * }
 * 
*

* This class is NOT thread-safe. Thread-safety is not a requirement for context variables. *

* * @see ILazyContextVariable * * @author Daniel Fernández * * @since 3.0.3 * */ public class ReactiveLazyContextVariable extends LazyContextVariable implements ILazyContextVariable { private final Object asyncObject; private ReactiveAdapterRegistry adapterRegistry; /** *

* Creates a new lazy context variable, wrapping a reactive asynchronous object. *

*

* The specified asyncObject must be adaptable to a Reactive Streams * {@link Publisher} by means of Spring's {@link ReactiveAdapterRegistry} mechanism. If no * adapter has been registered for the type of the asynchronous object, and exception will be * thrown during lazy resolution. *

*

* Examples of supported implementations are Reactor's {@link Flux} and {@link Mono}, and also * RxJava's Observable and Single. *

* * @param asyncObject the asynchronous object, which must be convertible to a {@link Publisher} by * means of Spring's {@link ReactiveAdapterRegistry}. */ public ReactiveLazyContextVariable(final Object asyncObject) { super(); Validate.notNull(asyncObject, "Lazy context variable value cannot be null"); this.asyncObject = asyncObject; this.adapterRegistry = null; // optional, but set transparently by ThymeleafReactiveView before rendering } /** *

* Sets the {@link ReactiveAdapterRegistry} used for converting (if necessary) the wrapped asynchronous * object into a {@link Publisher}. *

*

* This method is transparently called before template execution in order * to initialize lazy context variables. It can also be called programmatically, but there is normally * no reason to do this. If not called at all, only {@link Flux} and {@link Mono} will be allowed as valid types * for the wrapped asynchronous object. *

* * @param reactiveAdapterRegistry the reactive adapter registry. */ public final void setReactiveAdapterRegistry(final ReactiveAdapterRegistry reactiveAdapterRegistry) { // Note the presence of the ReactiveAdapterRegistry is optional, so this method might never be // called. We can only be sure that it will be called if this context variable is part of a model // used for rendering a ThymeleafReactiveView (which, anyway, will be most of the cases). this.adapterRegistry = reactiveAdapterRegistry; } @Override protected final Object loadValue() { /* * First the async object will be 'normalized' into a Publisher (which will ALWAYS be a Flux or a Mono * object), and then we will block in order to collect its values and return, as if the variable was never * async. */ final Publisher publisher = ReactiveContextVariableUtils.computePublisherValue(this.asyncObject, this.adapterRegistry); if (publisher instanceof Flux) { // Data stream is multi-valued return ((Flux)publisher).collectList().defaultIfEmpty(Collections.emptyList()).block(); } // Data stream is single-valued return ((Mono)publisher).block(); // Will return null if empty, which is OK } }