001/*
002 * Copyright 2002-2017 the original author or authors.
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 *      https://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016
017package org.springframework.web.servlet.view.groovy;
018
019import java.io.BufferedWriter;
020import java.io.IOException;
021import java.util.Locale;
022import java.util.Map;
023
024import javax.servlet.http.HttpServletRequest;
025import javax.servlet.http.HttpServletResponse;
026
027import groovy.text.Template;
028import groovy.text.markup.MarkupTemplateEngine;
029
030import org.springframework.beans.BeansException;
031import org.springframework.beans.factory.BeanFactoryUtils;
032import org.springframework.beans.factory.NoSuchBeanDefinitionException;
033import org.springframework.context.ApplicationContext;
034import org.springframework.context.ApplicationContextException;
035import org.springframework.lang.Nullable;
036import org.springframework.util.Assert;
037import org.springframework.web.servlet.view.AbstractTemplateView;
038import org.springframework.web.util.NestedServletException;
039
040/**
041 * An {@link AbstractTemplateView} subclass based on Groovy XML/XHTML markup templates.
042 *
043 * <p>Spring's Groovy Markup Template support requires Groovy 2.3.1 and higher.
044 *
045 * @author Brian Clozel
046 * @author Rossen Stoyanchev
047 * @since 4.1
048 * @see GroovyMarkupViewResolver
049 * @see GroovyMarkupConfigurer
050 * @see <a href="http://groovy-lang.org/templating.html#_the_markuptemplateengine">
051 * Groovy Markup Template engine documentation</a>
052 */
053public class GroovyMarkupView extends AbstractTemplateView {
054
055        @Nullable
056        private MarkupTemplateEngine engine;
057
058
059        /**
060         * Set the MarkupTemplateEngine to use in this view.
061         * <p>If not set, the engine is auto-detected by looking up a single
062         * {@link GroovyMarkupConfig} bean in the web application context and using
063         * it to obtain the configured {@code MarkupTemplateEngine} instance.
064         * @see GroovyMarkupConfig
065         */
066        public void setTemplateEngine(MarkupTemplateEngine engine) {
067                this.engine = engine;
068        }
069
070        /**
071         * Invoked at startup.
072         * If no {@link #setTemplateEngine(MarkupTemplateEngine) templateEngine} has
073         * been manually set, this method looks up a {@link GroovyMarkupConfig} bean
074         * by type and uses it to obtain the Groovy Markup template engine.
075         * @see GroovyMarkupConfig
076         * @see #setTemplateEngine(groovy.text.markup.MarkupTemplateEngine)
077         */
078        @Override
079        protected void initApplicationContext(ApplicationContext context) {
080                super.initApplicationContext();
081                if (this.engine == null) {
082                        setTemplateEngine(autodetectMarkupTemplateEngine());
083                }
084        }
085
086        /**
087         * Autodetect a MarkupTemplateEngine via the ApplicationContext.
088         * Called if a MarkupTemplateEngine has not been manually configured.
089         */
090        protected MarkupTemplateEngine autodetectMarkupTemplateEngine() throws BeansException {
091                try {
092                        return BeanFactoryUtils.beanOfTypeIncludingAncestors(obtainApplicationContext(),
093                                        GroovyMarkupConfig.class, true, false).getTemplateEngine();
094                }
095                catch (NoSuchBeanDefinitionException ex) {
096                        throw new ApplicationContextException("Expected a single GroovyMarkupConfig bean in the current " +
097                                        "Servlet web application context or the parent root context: GroovyMarkupConfigurer is " +
098                                        "the usual implementation. This bean may have any name.", ex);
099                }
100        }
101
102
103        @Override
104        public boolean checkResource(Locale locale) throws Exception {
105                Assert.state(this.engine != null, "No MarkupTemplateEngine set");
106                try {
107                        this.engine.resolveTemplate(getUrl());
108                }
109                catch (IOException ex) {
110                        return false;
111                }
112                return true;
113        }
114
115        @Override
116        protected void renderMergedTemplateModel(Map<String, Object> model,
117                        HttpServletRequest request, HttpServletResponse response) throws Exception {
118
119                String url = getUrl();
120                Assert.state(url != null, "'url' not set");
121
122                Template template = getTemplate(url);
123                template.make(model).writeTo(new BufferedWriter(response.getWriter()));
124        }
125
126        /**
127         * Return a template compiled by the configured Groovy Markup template engine
128         * for the given view URL.
129         */
130        protected Template getTemplate(String viewUrl) throws Exception {
131                Assert.state(this.engine != null, "No MarkupTemplateEngine set");
132                try {
133                        return this.engine.createTemplateByPath(viewUrl);
134                }
135                catch (ClassNotFoundException ex) {
136                        Throwable cause = (ex.getCause() != null ? ex.getCause() : ex);
137                        throw new NestedServletException(
138                                        "Could not find class while rendering Groovy Markup view with name '" +
139                                        getUrl() + "': " + ex.getMessage() + "'", cause);
140                }
141        }
142
143}