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

org.zodiac.template.velocity.spring.view.reactive.ReactiveVelocityView Maven / Gradle / Ivy

The newest version!
package org.zodiac.template.velocity.spring.view.reactive;

import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.io.Writer;
import java.nio.charset.Charset;
import java.util.LinkedHashMap;
import java.util.Locale;
import java.util.Map;
import java.util.Optional;

import org.apache.velocity.Template;
import org.apache.velocity.app.VelocityEngine;
import org.apache.velocity.context.Context;
import org.apache.velocity.exception.ParseErrorException;
import org.apache.velocity.exception.VelocityException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanFactoryUtils;
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextException;
import org.springframework.context.i18n.LocaleContextHolder;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.core.io.buffer.DataBufferUtils;
import org.springframework.core.io.buffer.PooledDataBuffer;
import org.springframework.http.MediaType;
import org.springframework.lang.Nullable;
import org.springframework.util.MimeType;
import org.springframework.web.reactive.result.view.AbstractUrlBasedView;
import org.springframework.web.reactive.result.view.RequestContext;
import org.springframework.web.reactive.result.view.RequestDataValueProcessor;
import org.springframework.web.server.ServerWebExchange;
import org.zodiac.sdk.toolkit.util.AssertUtil;
import org.zodiac.sdk.toolkit.util.collection.CollUtil;
import org.zodiac.template.velocity.constants.VelocityTemplateConstants;
import org.zodiac.template.velocity.platform.PlatformVelocityContext;
import org.zodiac.template.velocity.spring.view.VelocityConfig;
import org.zodiac.template.velocity.spring.view.VelocityConfigurer;

import reactor.core.publisher.Mono;

public class ReactiveVelocityView extends AbstractUrlBasedView {

    protected final Logger log = LoggerFactory.getLogger(getClass());

    private Map> toolAttributes;

    private String contentType = VelocityTemplateConstants.DEFAULT_CONTENT_TYPE;

    private boolean exposeRequestAttributes = false;

    private boolean allowRequestOverride = false;

    private boolean exposeSessionAttributes = false;

    private boolean allowSessionOverride = false;

    private boolean exposeSpringMacroHelpers = true;

    private String dateToolAttribute;

    private String numberToolAttribute;

    private String encoding;

    private boolean cacheTemplate = false;

    private VelocityEngine velocityEngine;

    private Template template;

    public ReactiveVelocityView() {
        super();
    }

    public ReactiveVelocityView(String url) {
        super(url);
    }

    @Override
    public void afterPropertiesSet() throws Exception {
        super.afterPropertiesSet();
        if (null == getVelocityEngine()) {
            VelocityConfig config = autodetectVelocityEngine();
            this.velocityEngine = config.getVelocityEngine();
        }
    }

    @Override
    public boolean checkResourceExists(Locale locale) throws Exception {
        try {
            /*Check that we can get the template, even if we might subsequently get it again.*/
            getTemplate(locale);
            return true;
        } catch (FileNotFoundException ex) {
            /*Allow for ViewResolver chaining.*/
            return false;
        } catch (ParseErrorException ex) {
            throw new ApplicationContextException("Failed to parse FreeMarker template for URL [" + getUrl() + "]", ex);
        } catch (IOException ex) {
            throw new ApplicationContextException("Could not load FreeMarker template for URL [" + getUrl() + "]", ex);
        }
    }

    public Map> getToolAttributes() {
        return toolAttributes;
    }

    public void setToolAttributes(Map> toolAttributes) {
        this.toolAttributes = toolAttributes;
    }

    public String getContentType() {
        return contentType;
    }

    public void setContentType(String contentType) {
        this.contentType = contentType;
    }

    public boolean isExposeRequestAttributes() {
        return exposeRequestAttributes;
    }

    public void setExposeRequestAttributes(boolean exposeRequestAttributes) {
        this.exposeRequestAttributes = exposeRequestAttributes;
    }

    public boolean isAllowRequestOverride() {
        return allowRequestOverride;
    }

    public void setAllowRequestOverride(boolean allowRequestOverride) {
        this.allowRequestOverride = allowRequestOverride;
    }

    public boolean isExposeSessionAttributes() {
        return exposeSessionAttributes;
    }

    public void setExposeSessionAttributes(boolean exposeSessionAttributes) {
        this.exposeSessionAttributes = exposeSessionAttributes;
    }

    public boolean isAllowSessionOverride() {
        return allowSessionOverride;
    }

    public void setAllowSessionOverride(boolean allowSessionOverride) {
        this.allowSessionOverride = allowSessionOverride;
    }

    public boolean isExposeSpringMacroHelpers() {
        return exposeSpringMacroHelpers;
    }

    public void setExposeSpringMacroHelpers(boolean exposeSpringMacroHelpers) {
        this.exposeSpringMacroHelpers = exposeSpringMacroHelpers;
    }

    public String getDateToolAttribute() {
        return dateToolAttribute;
    }

    public void setDateToolAttribute(String dateToolAttribute) {
        this.dateToolAttribute = dateToolAttribute;
    }

    public String getNumberToolAttribute() {
        return numberToolAttribute;
    }

    public void setNumberToolAttribute(String numberToolAttribute) {
        this.numberToolAttribute = numberToolAttribute;
    }

    public String getEncoding() {
        return encoding;
    }

    public boolean isCacheTemplate() {
        return cacheTemplate;
    }

    public void setCacheTemplate(boolean cacheTemplate) {
        this.cacheTemplate = cacheTemplate;
    }

    public void setVelocityEngine(VelocityEngine velocityEngine) {
        this.velocityEngine = velocityEngine;
    }

    protected void setEncoding(String encoding) {
        this.encoding = encoding;
    }

    protected VelocityEngine getVelocityEngine() {
        return velocityEngine;
    }

    protected VelocityEngine obtainVelocityEngine() {
        VelocityEngine velocityEngine = getVelocityEngine();
        AssertUtil.state(velocityEngine != null, "No velocityEngine set");
        return velocityEngine;
    }

    @Override
    protected Mono renderInternal(Map renderAttributes, MediaType contentType,
        ServerWebExchange exchange) {
        MediaType mediaType = contentType == null && null != getContentType() ? new MediaType(getContentType()) : contentType;

        if (this.exposeSessionAttributes) {
            Map exposed = new LinkedHashMap<>();
            Map attributes = exchange.getSession().block().getAttributes();
            if (CollUtil.isNotEmptyMap(attributes)) {
                attributes.forEach((k, v) -> {
                    if (renderAttributes.containsKey(k) && !this.allowRequestOverride) {
                        throw new VelocityException(String.format("Cannot expose request attribute '%s' because of an existing model object of the same name", k));
                    }
                    if (log.isDebugEnabled()) {
                        exposed.put(k, v);
                    }
                    renderAttributes.put(k, v);
                });
                if (log.isTraceEnabled() && exposed != null) {
                    log.trace("Exposed request attributes to model: {}", exposed);
                }
            }
        }

        if (this.exposeSpringMacroHelpers) {
            if (renderAttributes.containsKey(VelocityTemplateConstants.SPRING_MACRO_REQUEST_CONTEXT_ATTRIBUTE)) {
                throw new VelocityException(
                    String.format("Cannot expose bind macro helper '%s' because of an existing model object of the same name", VelocityTemplateConstants.SPRING_MACRO_REQUEST_CONTEXT_ATTRIBUTE));
            }
            /*Expose RequestContext instance for Spring macros.*/
            renderAttributes.put(VelocityTemplateConstants.SPRING_MACRO_REQUEST_CONTEXT_ATTRIBUTE,
                    new RequestContext(exchange, renderAttributes, obtainApplicationContext(), getRequestDataValueProcessor()));
        }

        return exchange.getResponse().writeWith(Mono.fromCallable(() -> {
            /*Expose all standard FreeMarker hash models.*/
            Context velocityContext = getTemplateContext(renderAttributes, exchange);
            if (logger.isDebugEnabled()) {
                log.debug("{} Rendering [{}]", exchange.getLogPrefix(), getUrl());
            }

            Locale locale = LocaleContextHolder.getLocale(exchange.getLocaleContext());
            DataBuffer dataBuffer = exchange.getResponse().bufferFactory().allocateBuffer();
            try {
                Charset charset = getCharset(mediaType);
                Writer writer = new OutputStreamWriter(dataBuffer.asOutputStream(), charset);
                getTemplate(locale).merge(velocityContext, writer);
                return dataBuffer;
            } catch (IOException ex) {
                DataBufferUtils.release(dataBuffer);
                String message = String.format("Could not load FreeMarker template for URL [%s]", getUrl());
                throw new IllegalStateException(message, ex);
            } catch (Throwable ex) {
                DataBufferUtils.release(dataBuffer);
                throw ex;
            }
        }).doOnDiscard(PooledDataBuffer.class, DataBufferUtils::release));
    }

    protected RequestDataValueProcessor getRequestDataValueProcessor() {
        ApplicationContext context = getApplicationContext();
        if (context != null && context.containsBean(REQUEST_DATA_VALUE_PROCESSOR_BEAN_NAME)) {
            return context.getBean(REQUEST_DATA_VALUE_PROCESSOR_BEAN_NAME, RequestDataValueProcessor.class);
        }
        return null;
    }

    protected VelocityConfig autodetectVelocityEngine() throws BeansException {
        try {
            return BeanFactoryUtils.beanOfTypeIncludingAncestors(obtainApplicationContext(), VelocityConfig.class, true,
                false);
        } catch (NoSuchBeanDefinitionException ex) {
            StringBuilder error =
                new StringBuilder("Must define a single ").append(VelocityConfig.class.getSimpleName())
                    .append(" bean in this web application context (may be inherited): ")
                    .append(VelocityConfigurer.class.getSimpleName())
                    .append(" is the usual implementation. This bean may be given any name.");
            throw new ApplicationContextException(error.toString(), ex);
        }
    }

    protected Template getTemplate(Locale locale) throws IOException {
        obtainVelocityEngine().getTemplate(getUrl(), getEncoding());
        return (getEncoding() != null ? obtainVelocityEngine().getTemplate(getUrl(), getEncoding())
            : obtainVelocityEngine().getTemplate(getUrl()));
    }

    protected Template getTemplate(String name) throws Exception {
        return (getEncoding() != null ? getVelocityEngine().getTemplate(name, getEncoding())
            : getVelocityEngine().getTemplate(name));
    }

    protected Template getTemplate() throws Exception {
        /*
         * We already hold a reference to the template, but we might want to load it
         * if not caching. Velocity itself caches templates, so our ability to
         * cache templates in this class is a minor optimization only.
         * */
        if (isCacheTemplate() && this.template != null) {
            return this.template;
        } else {
            return getTemplate(getUrl());
        }
    }

    protected Context getTemplateContext(Map model, ServerWebExchange exchange) {
        //Context velocityContext = new VelocityContext(model);
        Context velocityContext = new PlatformVelocityContext(model);
        return velocityContext;
    }

    private Charset getCharset(@Nullable MediaType mediaType) {
        return Optional.ofNullable(mediaType).map(MimeType::getCharset).orElse(getDefaultCharset());
    }

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy