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

grails.plugin.freemarker.GrailsFreeMarkerViewResolver.groovy Maven / Gradle / Ivy

The newest version!
/*
* Copyright 2019 Yak.Works - Licensed under the Apache License, Version 2.0 (the "License")
* You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
*/
package grails.plugin.freemarker

import groovy.transform.CompileStatic
import groovy.util.logging.Log4j
import groovy.util.logging.Slf4j

import org.apache.commons.logging.Log
import org.apache.commons.logging.LogFactory
import org.springframework.core.io.ContextResource
import org.springframework.core.io.Resource
import org.springframework.web.servlet.View
import org.springframework.web.servlet.view.AbstractUrlBasedView
import org.springframework.web.servlet.view.freemarker.FreeMarkerConfig
import org.springframework.web.servlet.view.freemarker.FreeMarkerViewResolver

import freemarker.template.Configuration
import no.api.freemarker.java8.Java8ObjectWrapper
import yakworks.grails.mvc.ViewResourceLocator

/**
 * Uses Springs ViewResolver design concepts. The primary lookup uses {@link grails.plugin.resourcelocator.ViewResourceLocator}
 * The main DispatcherServlet spins through and calls the ViewResolvers ViewResolver.resolveViewName(String viewName, Locale locale)
 * The inheritance chain here is FreeMarkerViewResolver -> AbstractTemplateViewResolver -> UrlBasedViewResolver -> AbstractCachingViewResolver
 * AbstractCachingViewResolver holds the resolveViewName() which calls loadView() and buildView()
 *
 * This uses the {@link grails.plugin.resourcelocator.ViewResourceLocator} to locate the resource
 * GrailsTemplateLoader does the lifting and caching and lastmodified checking as a freemarker internal
 * The reason for this is that the GrailsTemplateLoader will get called if a [#include] is used in the ftl.
 * So freemarker has to have templateLoaders setup.
 *
 * This resolver sets up a GrailsFreeMarkerView->FreeMarkerView and the meat of the logic is in
 * {@link org.springframework.web.servlet.view.freemarker.FreeMarkerView}
 *
 * the FreeMarkerView gets a freemarker.template.Configuration from the freeMarkerConfigurer.
 * {@link org.springframework.web.servlet.view.freemarker.FreeMarkerConfigurer}
 *
 * Then when DispatcherServlet calls FreeMarkerView.render() it uses the freeMarkerGrailsTemplateLoader
 * and any other TemplateLoaders that are set in the FreeMarkerConfigurer and template.Configuration
 * !!!!! That is how it actually finds the files
 * {@link grails.plugin.freemarker.GrailsTemplateLoader}
 *
 * This get called before Grails GroovyPageViewResolver and some of the code is
 * loosely based on that. This gets used simply by registering it as a spring bean (we call it freeMarkerViewResolver)
 * GroovyPageViewResolver details:
 *   - (setup can also be seen in GroovyPagesGrailsPlugin)
 *   - has setOrder(Ordered.LOWEST_PRECEDENCE - 20) which is basically Integer.MAX_VALUE - 20
 *
 * @TODO in the future we should look at caching the FOUND resource here, in the GrailsFreemarkerViewResolver
 * and/or the ViewResourceLocator as it can spin through a lot of plugin directories to find it
 *
 * @author Jeff Brown
 * @author Daniel Henrique Alves Lima
 * @author Joshua Burnett
 */
@Slf4j
//log
@CompileStatic
@SuppressWarnings(["LoggerForDifferentClass"])
public class GrailsFreeMarkerViewResolver extends FreeMarkerViewResolver {

    private static final Log EXCEPTION_LOG = LogFactory.getLog("GrailsFreeMarkerViewResolver.EXCEPTION")
    //private final Log log = LogFactory.getLog(getClass());

    //injected autowired
    //GrailsApplication grailsApplication
    FreeMarkerConfig freeMarkerConfigurer
    ViewResourceLocator viewResourceLocator
    boolean hideException = true
    boolean requireViewSuffix = true //set when bean is setup

    //end inject

    //static final String FTL_SUFFIX = ".ftl";

    // public GrailsFreeMarkerViewResolver() {
    //     setViewClass(GrailsFreeMarkerView.class);
    // }

    @Override
    protected Class requiredViewClass() {
        return GrailsFreeMarkerView.class
    }

    //Overriden for logging
    @Override
    public View resolveViewName(String viewName, Locale locale) throws Exception {
        log.debug("resolveViewName with $viewName")

        return super.resolveViewName(viewName, locale)
    }

    /**
     * AbstractCachingViewResolver calls this if it doesn't get a hit on the cache
     * Most of the calls to this will look like "/demo/xxx.ftl" etc..
     *
     * @param viewName
     * @param locale
     * @return
     */
    @Override
    @SuppressWarnings(['CatchException', 'ThrowRuntimeException'])
    protected View loadView(String viewName, Locale locale) {
        if (requireViewSuffix && !viewName.endsWith(suffix)) {
            //fast exit
            return null
        }
        log.debug("loadview running for ${viewName}, locale  $locale")

        if (viewName.endsWith(suffix)) {
            //remove the .ftl suffix if it was added so the normal process can add it later
            viewName = viewName.substring(0, viewName.length() - suffix.length())
        }

        View view = null
        try {
            GrailsFreeMarkerView gview = (GrailsFreeMarkerView) buildView(viewName)
            freeMarkerConfigurer.getConfiguration().setObjectWrapper(new Java8ObjectWrapper(Configuration.getVersion()))
            gview.freeMarkerConfigurer = freeMarkerConfigurer
            View result = (View) getApplicationContext().getAutowireCapableBeanFactory().initializeBean(gview, viewName)

            Resource resource = viewResourceLocator.getResource(gview.url)
            if (resource?.exists()) {
                if (resource instanceof ContextResource) {
                    gview.setUrl(resource.getURL().toString())
                }
                //this now call the freemarker getTemplate to make sure it can load/parse the template.
                // its all cached so it only loads it once
                view = (gview.checkResource(locale) ? result : null)
            }

        }
        catch (Exception e) {
            // by default return null if an exception occurs so the rest of the view
            // resolver chain gets an opportunity to  generate a View
            if (hideException) {
                EXCEPTION_LOG.debug("loadView", e)
            } else {
                EXCEPTION_LOG.error("loadView", e)
                throw new RuntimeException(e)
            }
        }
        return view
    }

    //Override so we can set the bean name
    @Override
    protected AbstractUrlBasedView buildView(String viewName) throws Exception {
        AbstractUrlBasedView view = super.buildView(viewName)
        view.setBeanName(viewName)
        log.debug("buildView ran with view: [$view] , url: [ ${view.url}]")
        return view
    }

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy