2
0
Эх сурвалжийг харах

Update Thymeleaf

We can remove PatchThymleeafReactiveView now that it is fixed and released
in Thymeleaf.

Issue gh-4802
Rob Winch 7 жил өмнө
parent
commit
1728e21804

+ 1 - 1
gradle/dependency-management.gradle

@@ -197,7 +197,7 @@ dependencyManagement {
 		dependency 'org.slf4j:slf4j-nop:1.7.25'
 		dependency 'org.sonatype.sisu.inject:cglib:2.2.1-v20090111'
 		dependency 'org.springframework.ldap:spring-ldap-core:2.3.1.RELEASE'
-		dependency 'org.thymeleaf:thymeleaf-spring5:3.0.8.RELEASE'
+		dependency 'org.thymeleaf:thymeleaf-spring5:3.0.9.RELEASE'
 		dependency 'org.unbescape:unbescape:1.1.4.RELEASE'
 		dependency 'org.w3c.css:sac:1.3'
 		dependency 'xalan:serializer:2.7.2'

+ 0 - 2
samples/javaconfig/webflux-form/src/main/java/sample/ThymeleafConfig.java

@@ -26,7 +26,6 @@ import org.thymeleaf.spring5.SpringWebFluxTemplateEngine;
 import org.thymeleaf.spring5.templateresolver.SpringResourceTemplateResolver;
 import org.thymeleaf.spring5.view.reactive.ThymeleafReactiveViewResolver;
 import org.thymeleaf.templatemode.TemplateMode;
-import thymeleaf.PatchThymeleafReactiveView;
 
 /**
  * @author Rob Winch
@@ -68,7 +67,6 @@ public class ThymeleafConfig implements WebFluxConfigurer {
 		viewResolver.setTemplateEngine(thymeleafTemplateEngine());
 		viewResolver.setOrder(1);
 		viewResolver.setResponseMaxChunkSizeBytes(8192); // OUTPUT BUFFER size limit
-		viewResolver.setViewClass(PatchThymeleafReactiveView.class);
 		return viewResolver;
 	}
 

+ 0 - 341
samples/javaconfig/webflux-form/src/main/java/thymeleaf/PatchThymeleafReactiveView.java

@@ -1,341 +0,0 @@
-/*
- * Copyright 2002-2017 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 thymeleaf;
-
-import org.reactivestreams.Publisher;
-import org.springframework.beans.factory.NoSuchBeanDefinitionException;
-import org.springframework.context.ApplicationContext;
-import org.springframework.core.ReactiveAdapterRegistry;
-import org.springframework.core.convert.ConversionService;
-import org.springframework.core.io.buffer.DataBuffer;
-import org.springframework.http.HttpHeaders;
-import org.springframework.http.MediaType;
-import org.springframework.http.server.reactive.ServerHttpResponse;
-import org.springframework.web.reactive.HandlerMapping;
-import org.springframework.web.reactive.result.view.RequestContext;
-import org.springframework.web.server.ServerWebExchange;
-import org.thymeleaf.IEngineConfiguration;
-import org.thymeleaf.exceptions.TemplateProcessingException;
-import org.thymeleaf.spring5.ISpringWebFluxTemplateEngine;
-import org.thymeleaf.spring5.context.webflux.IReactiveDataDriverContextVariable;
-import org.thymeleaf.spring5.context.webflux.SpringWebFluxExpressionContext;
-import org.thymeleaf.spring5.context.webflux.SpringWebFluxThymeleafRequestContext;
-import org.thymeleaf.spring5.expression.ThymeleafEvaluationContext;
-import org.thymeleaf.spring5.naming.SpringContextVariableNames;
-import org.thymeleaf.spring5.view.reactive.ThymeleafReactiveView;
-import org.thymeleaf.standard.expression.FragmentExpression;
-import org.thymeleaf.standard.expression.IStandardExpressionParser;
-import org.thymeleaf.standard.expression.StandardExpressionExecutionContext;
-import org.thymeleaf.standard.expression.StandardExpressions;
-import reactor.core.publisher.Flux;
-import reactor.core.publisher.Mono;
-
-import java.nio.charset.Charset;
-import java.util.Arrays;
-import java.util.HashMap;
-import java.util.Locale;
-import java.util.Map;
-import java.util.Optional;
-import java.util.Set;
-
-/**
- * @author Rob Winch
- * @since 5.0
- */
-public class PatchThymeleafReactiveView extends ThymeleafReactiveView {
-	private static final String WEBFLUX_CONVERSION_SERVICE_NAME = "webFluxConversionService";
-	@Override
-	protected Mono<Void> renderFragmentInternal(
-		Set<String> markupSelectorsToRender, Map<String, Object> renderAttributes,
-		MediaType contentType, ServerWebExchange exchange) {
-		final String viewTemplateName = getTemplateName();
-		final ISpringWebFluxTemplateEngine viewTemplateEngine = getTemplateEngine();
-
-		if (viewTemplateName == null) {
-			return Mono.error(new IllegalArgumentException("Property 'templateName' is required"));
-		}
-		if (getLocale() == null) {
-			return Mono.error(new IllegalArgumentException("Property 'locale' is required"));
-		}
-		if (viewTemplateEngine == null) {
-			return Mono.error(new IllegalArgumentException("Property 'thymeleafTemplateEngine' is required"));
-		}
-
-		final ServerHttpResponse response = exchange.getResponse();
-
-        /*
-         * ----------------------------------------------------------------------------------------------------------
-         * GATHERING OF THE MERGED MODEL
-         * ----------------------------------------------------------------------------------------------------------
-         * - The merged model is the map that will be used for initialising the Thymelef IContext. This context will
-         *   contain all the data accessible by the template during its execution.
-         * - The base of the merged model is the ModelMap created by the Controller, but there are some additional
-         *   things
-         * ----------------------------------------------------------------------------------------------------------
-         */
-
-		final Map<String, Object> mergedModel = new HashMap<>(30);
-		// First of all, set all the static variables into the mergedModel
-		final Map<String, Object> templateStaticVariables = getStaticVariables();
-		if (templateStaticVariables != null) {
-			mergedModel.putAll(templateStaticVariables);
-		}
-		// Add path variables to merged model (if there are any)
-		final Map<String, Object> pathVars =
-			(Map<String, Object>) exchange.getAttributes().get(HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE);
-		if (pathVars != null) {
-			mergedModel.putAll(pathVars);
-		}
-		// Simply dump all the renderAttributes (model coming from the controller) into the merged model
-		if (renderAttributes != null) {
-			mergedModel.putAll(renderAttributes);
-		}
-
-		final ApplicationContext applicationContext = getApplicationContext();
-
-		// Initialize RequestContext (reactive version) and add it to the model as another attribute,
-		// so that it can be retrieved from elsewhere.
-		final RequestContext requestContext = createRequestContext(exchange, mergedModel);
-		final SpringWebFluxThymeleafRequestContext thymeleafRequestContext =
-			new SpringWebFluxThymeleafRequestContext(requestContext, exchange);
-
-		mergedModel.put(SpringContextVariableNames.SPRING_REQUEST_CONTEXT, requestContext);
-		// Add the Thymeleaf RequestContext wrapper that we will be using in this dialect (the bare RequestContext
-		// stays in the context to for compatibility with other dialects)
-		mergedModel.put(SpringContextVariableNames.THYMELEAF_REQUEST_CONTEXT, thymeleafRequestContext);
-
-
-		// Expose Thymeleaf's own evaluation context as a model variable
-		//
-		// Note Spring's EvaluationContexts are NOT THREAD-SAFE (in exchange for SpelExpressions being thread-safe).
-		// That's why we need to create a new EvaluationContext for each request / template execution, even if it is
-		// quite expensive to create because of requiring the initialization of several ConcurrentHashMaps.
-		final ConversionService conversionService =
-			applicationContext.containsBean(WEBFLUX_CONVERSION_SERVICE_NAME)?
-				(ConversionService)applicationContext.getBean(WEBFLUX_CONVERSION_SERVICE_NAME): null;
-		final ThymeleafEvaluationContext evaluationContext =
-			new ThymeleafEvaluationContext(applicationContext, conversionService);
-		mergedModel.put(ThymeleafEvaluationContext.THYMELEAF_EVALUATION_CONTEXT_CONTEXT_VARIABLE_NAME, evaluationContext);
-
-
-		// Determine if we have a data-driver variable, and therefore will need to configure flushing of output chunks
-		final boolean dataDriven = isDataDriven(mergedModel);
-
-
-        /*
-         * ----------------------------------------------------------------------------------------------------------
-         * INSTANTIATION OF THE CONTEXT
-         * ----------------------------------------------------------------------------------------------------------
-         * - Once the model has been merged, we can create the Thymeleaf context object itself.
-         * - The reason it is an ExpressionContext and not a Context is that before executing the template itself,
-         *   we might need to use it for computing the markup selectors (if "template :: selector" was specified).
-         * - The reason it is not a WebExpressionContext is that this class is linked to the Servlet API, which
-         *   might not be present in a Spring WebFlux environment.
-         * ----------------------------------------------------------------------------------------------------------
-         */
-
-		final IEngineConfiguration configuration = viewTemplateEngine.getConfiguration();
-		final SpringWebFluxExpressionContext context =
-			new SpringWebFluxExpressionContext(
-				configuration, exchange, getReactiveAdapterRegistry(), getLocale(), mergedModel);
-
-
-        /*
-         * ----------------------------------------------------------------------------------------------------------
-         * COMPUTATION OF (OPTIONAL) MARKUP SELECTORS
-         * ----------------------------------------------------------------------------------------------------------
-         * - If view name has been specified with a template selector (in order to execute only a fragment of
-         *   the template) like "template :: selector", we will extract it and compute it.
-         * ----------------------------------------------------------------------------------------------------------
-         */
-
-		final String templateName;
-		final Set<String> markupSelectors;
-		if (!viewTemplateName.contains("::")) {
-			// No fragment specified at the template name
-
-			templateName = viewTemplateName;
-			markupSelectors = null;
-
-		} else {
-			// Template name contains a fragment name, so we should parse it as such
-
-			final IStandardExpressionParser parser = StandardExpressions.getExpressionParser(configuration);
-
-			final FragmentExpression fragmentExpression;
-			try {
-				// By parsing it as a standard expression, we might profit from the expression cache
-				fragmentExpression = (FragmentExpression) parser.parseExpression(context, "~{" + viewTemplateName + "}");
-			} catch (final TemplateProcessingException e) {
-				return Mono.error(
-					new IllegalArgumentException("Invalid template name specification: '" + viewTemplateName + "'"));
-			}
-
-			final FragmentExpression.ExecutedFragmentExpression fragment =
-				FragmentExpression.createExecutedFragmentExpression(context, fragmentExpression, StandardExpressionExecutionContext.NORMAL);
-
-			templateName = FragmentExpression.resolveTemplateName(fragment);
-			markupSelectors = FragmentExpression.resolveFragments(fragment);
-			final Map<String,Object> nameFragmentParameters = fragment.getFragmentParameters();
-
-			if (nameFragmentParameters != null) {
-
-				if (fragment.hasSyntheticParameters()) {
-					// We cannot allow synthetic parameters because there is no way to specify them at the template
-					// engine execution!
-					return Mono.error(new IllegalArgumentException(
-						"Parameters in a view specification must be named (non-synthetic): '" + viewTemplateName + "'"));
-				}
-
-				context.setVariables(nameFragmentParameters);
-
-			}
-
-		}
-
-		final Set<String> processMarkupSelectors;
-		if (markupSelectors != null && markupSelectors.size() > 0) {
-			if (markupSelectorsToRender != null && markupSelectorsToRender.size() > 0) {
-				return Mono.error(new IllegalArgumentException(
-					"A markup selector has been specified (" + Arrays.asList(markupSelectors) + ") for a view " +
-						"that was already being executed as a fragment (" + Arrays.asList(markupSelectorsToRender) + "). " +
-						"Only one fragment selection is allowed."));
-			}
-			processMarkupSelectors = markupSelectors;
-		} else {
-			if (markupSelectorsToRender != null && markupSelectorsToRender.size() > 0) {
-				processMarkupSelectors = markupSelectorsToRender;
-			} else {
-				processMarkupSelectors = null;
-			}
-		}
-
-
-        /*
-         * ----------------------------------------------------------------------------------------------------------
-         * COMPUTATION OF TEMPLATE PROCESSING PARAMETERS AND HTTP HEADERS
-         * ----------------------------------------------------------------------------------------------------------
-         * - At this point we will compute the final values of the different parameters needed for processing the
-         *   template (locale, encoding, buffer sizes, etc.)
-         * ----------------------------------------------------------------------------------------------------------
-         */
-
-		final int templateResponseMaxChunkSizeBytes = getResponseMaxChunkSizeBytes();
-
-		final HttpHeaders responseHeaders = exchange.getResponse().getHeaders();
-		final Locale templateLocale = getLocale();
-		if (templateLocale != null) {
-			responseHeaders.setContentLanguage(templateLocale);
-		}
-
-		// Get the charset from the selected content type (or use default)
-		final Charset charset = getCharset(contentType).orElse(getDefaultCharset());
-
-
-        /*
-         * -----------------------------------------------------------------------------------------------------------
-         * SET (AND RETURN) THE TEMPLATE PROCESSING Flux<DataBuffer> OBJECTS
-         * -----------------------------------------------------------------------------------------------------------
-         * - There are three possible processing modes, for each of which a Publisher<DataBuffer> will be created in a
-         *   different way:
-         *
-         *     1. FULL: Output chunks not limited in size (templateResponseMaxChunkSizeBytes == Integer.MAX_VALUE) and
-         *        no data-driven execution (no context variable of type Publisher<X> driving the template engine
-         *        execution): In this case Thymeleaf will be executed unthrottled, in full mode, writing output
-         *        to a single DataBuffer chunk instanced before execution, and which will be passed to the output
-         *        channels in a single onNext(buffer) call (immediately followed by onComplete()).
-         *
-         *     2. CHUNKED: Output chunks limited in size (responseMaxChunkSizeBytes) but no data-driven
-         *        execution (no Publisher<X> driving engine execution). All model attributes are expected to be
-         *        fully resolved (in a non-blocking fashion) by WebFlux before engine execution and the Thymeleaf
-         *        engine will execute in throttled mode, performing a full-stop each time the chunk reaches the
-         *        specified size, sending it to the output channels with onNext(chunk) and then waiting until
-         *        these output channels make the engine resume its work with a new request(n) call. This
-         *        execution mode will request an output flush from the server after producing each chunk.
-         *
-         *     3. DATA-DRIVEN: one of the model attributes is a Publisher<X> wrapped inside an implementation
-         *        of the IReactiveDataDriverContextVariable<?> interface. In this case, the Thymeleaf engine will
-         *        execute as a response to onNext(List<X>) events triggered by this Publisher. The
-         *        "bufferSizeElements" specified at the model attribute will define the amount of elements
-         *        produced by this Publisher that will be buffered into a List<X> before triggering the template
-         *        engine each time (which is why Thymeleaf will react on onNext(List<X>) and not onNext(X)). Thymeleaf
-         *        will expect to find a "th:each" iteration on the data-driven variable inside the processed template,
-         *        and will be executed in throttled mode for the published elements, sending the resulting DataBuffer
-         *        output chunks to the output channels via onNext(chunk) and stopping until a new onNext(List<X>)
-         *        event is triggered. When execution is data-driven, a limit in size can be optionally specified for
-         *        the output chunks (responseMaxChunkSizeBytes) which will make Thymeleaf never send
-         *        to the output channels a chunk bigger than that (thus splitting the output generated for a List<X>
-         *        of published elements into several chunks if required). When executing in DATA-DRIVEN mode,
-         *        Thymeleaf will always request flushing of the output channels after producing each chunk.
-         * ----------------------------------------------------------------------------------------------------------
-         */
-
-
-		final Publisher<DataBuffer> stream =
-			viewTemplateEngine.processStream(
-				templateName, processMarkupSelectors, context, response.bufferFactory(), contentType, charset,
-				templateResponseMaxChunkSizeBytes); // FULL/DATADRIVEN if MAX_VALUE, CHUNKED/DATADRIVEN if other
-
-		if (templateResponseMaxChunkSizeBytes == Integer.MAX_VALUE && !dataDriven) {
-
-			// No size limit for output chunks has been set (FULL mode), so we will let the
-			// server apply its standard behaviour ("writeWith").
-			return response.writeWith(stream);
-
-		}
-
-		// Either we are in DATA-DRIVEN mode or a limit for output chunks has been set (CHUNKED mode), so we will
-		// use "writeAndFlushWith" in order to make sure that output is flushed after each buffer.
-		return response.writeAndFlushWith(Flux.from(stream).window(1));
-	}
-
-
-
-	private static boolean isDataDriven(final Map<String,Object> mergedModel) {
-		if (mergedModel == null || mergedModel.size() == 0) {
-			return false;
-		}
-		for (final Object value : mergedModel.values()) {
-			if (value instanceof IReactiveDataDriverContextVariable) {
-				return true;
-			}
-		}
-		return false;
-	}
-
-	private ReactiveAdapterRegistry getReactiveAdapterRegistry() {
-
-		final ApplicationContext applicationContext = getApplicationContext();
-		if (applicationContext == null) {
-			return null;
-		}
-
-		if (applicationContext != null) {
-			try {
-				return applicationContext.getBean(ReactiveAdapterRegistry.class);
-			} catch (final NoSuchBeanDefinitionException ignored) {
-				// No registry, but note that we can live without it (though limited to Flux and Mono)
-			}
-		}
-		return null;
-
-	}
-
-	private static Optional<Charset> getCharset(final MediaType mediaType) {
-		return mediaType != null ? Optional.ofNullable(mediaType.getCharset()) : Optional.empty();
-	}
-}