org.broadleafcommerce.common.web.BroadleafThymeleafViewResolver.java Source code

Java tutorial

Introduction

Here is the source code for org.broadleafcommerce.common.web.BroadleafThymeleafViewResolver.java

Source

/*
 * #%L
 * BroadleafCommerce Common Libraries
 * %%
 * Copyright (C) 2009 - 2013 Broadleaf Commerce
 * %%
 * 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.
 * #L%
 */
package org.broadleafcommerce.common.web;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.broadleafcommerce.common.extension.ExtensionResultHolder;
import org.broadleafcommerce.common.util.BLCSystemProperty;
import org.broadleafcommerce.common.web.controller.BroadleafControllerUtility;
import org.springframework.util.PatternMatchUtils;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import org.springframework.web.servlet.View;
import org.springframework.web.servlet.view.RedirectView;
import org.thymeleaf.spring4.view.AbstractThymeleafView;
import org.thymeleaf.spring4.view.ThymeleafViewResolver;

import java.util.HashMap;
import java.util.Locale;
import java.util.Map;
import java.util.Map.Entry;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;

/**
 * This class extends the default ThymeleafViewResolver to facilitate rendering
 * template fragments (such as those used by AJAX modals or iFrames) within a 
 * full page container should the request for that template have occurred in a 
 * stand-alone context.
 * 
 * @author Andre Azzolini (apazzolini)
 */
public class BroadleafThymeleafViewResolver extends ThymeleafViewResolver {
    private static final Log LOG = LogFactory.getLog(BroadleafThymeleafViewResolver.class);

    @Resource(name = "blBroadleafThymeleafViewResolverExtensionManager")
    protected BroadleafThymeleafViewResolverExtensionManager extensionManager;

    public static final String EXTENSION_TEMPLATE_ATTR_NAME = "extensionTemplateAttr";

    /**
     * <p>
     *   Prefix to be used in view names (returned by controllers) for specifying an
     *   HTTP redirect with AJAX support. That is, if you want a redirect to be followed
     *   by the browser as the result of an AJAX call or within an iFrame at the parent 
     *   window, you can utilize this prefix. Note that this requires a JavaScript component,
     *   which is provided as part of BLC.js
     *   
     *   If the request was not performed in an AJAX / iFrame context, this method will
     *   delegate to the normal "redirect:" prefix.
     * </p>
     * <p>
     *   Value: <tt>ajaxredirect:</tt>
     * </p>
     */
    public static final String AJAX_REDIRECT_URL_PREFIX = "ajaxredirect:";

    protected Map<String, String> layoutMap = new HashMap<String, String>();
    protected String fullPageLayout = "layout/fullPageLayout";
    protected String iframeLayout = "layout/iframeLayout";

    protected boolean useThymeleafLayoutDialect() {
        return BLCSystemProperty.resolveBooleanSystemProperty("thymeleaf.useLayoutDialect");
    }

    /*
     * This method is a copy of the same method in ThymeleafViewResolver, but since it is marked private,
     * we are unable to call it from the BroadleafThymeleafViewResolver
     */
    protected boolean canHandle(final String viewName) {
        final String[] viewNamesToBeProcessed = getViewNames();
        final String[] viewNamesNotToBeProcessed = getExcludedViewNames();
        return ((viewNamesToBeProcessed == null || PatternMatchUtils.simpleMatch(viewNamesToBeProcessed, viewName))
                && (viewNamesNotToBeProcessed == null
                        || !PatternMatchUtils.simpleMatch(viewNamesNotToBeProcessed, viewName)));
    }

    @Override
    public View resolveViewName(String viewName, Locale locale) throws Exception {
        ExtensionResultHolder<String> erh = new ExtensionResultHolder<String>();
        extensionManager.getProxy().overrideView(erh, viewName, isAjaxRequest());

        String viewOverride = (String) erh.getResult();

        if (viewOverride != null) {
            viewName = viewOverride;
        }

        return super.resolveViewName(viewName, locale);
    }

    /**
     * Determines which internal method to call for creating the appropriate view. If no
     * Broadleaf specific methods match the viewName, it delegates to the parent 
     * ThymeleafViewResolver createView method
     */
    @Override
    protected View createView(final String viewName, final Locale locale) throws Exception {
        if (!canHandle(viewName)) {
            LOG.trace("[THYMELEAF] View {" + viewName
                    + "} cannot be handled by ThymeleafViewResolver. Passing on to the next resolver in the chain");
            return null;
        }

        if (viewName.startsWith(AJAX_REDIRECT_URL_PREFIX)) {
            LOG.trace("[THYMELEAF] View {" + viewName
                    + "} is an ajax redirect, and will be handled directly by BroadleafThymeleafViewResolver");
            String redirectUrl = viewName.substring(AJAX_REDIRECT_URL_PREFIX.length());
            return loadAjaxRedirectView(redirectUrl, locale);
        }

        return super.createView(viewName, locale);
    }

    /**
     * Performs a Broadleaf AJAX redirect. This is used in conjunction with BLC.js to support
     * doing a browser page change as as result of an AJAX call.
     * 
     * @param redirectUrl
     * @param locale
     * @return
     * @throws Exception
     */
    protected View loadAjaxRedirectView(String redirectUrl, final Locale locale) throws Exception {
        if (isAjaxRequest()) {
            String viewName = "utility/blcRedirect";
            addStaticVariable(BroadleafControllerUtility.BLC_REDIRECT_ATTRIBUTE, redirectUrl);
            return super.loadView(viewName, locale);
        } else {
            return new RedirectView(redirectUrl, false, isRedirectHttp10Compatible());
        }
    }

    @Override
    protected View loadView(final String originalViewName, final Locale locale) throws Exception {
        String viewName = originalViewName;

        if (!isAjaxRequest() && !useThymeleafLayoutDialect()) {
            String longestPrefix = "";

            for (Entry<String, String> entry : layoutMap.entrySet()) {
                String viewPrefix = entry.getKey();
                String viewLayout = entry.getValue();

                if (viewPrefix.length() > longestPrefix.length()) {
                    if (originalViewName.startsWith(viewPrefix)) {
                        longestPrefix = viewPrefix;

                        if (!"NONE".equals(viewLayout)) {
                            viewName = viewLayout;
                        }
                    }
                }
            }

            if (longestPrefix.equals("")) {
                viewName = getFullPageLayout();
            }
        }

        AbstractThymeleafView view = null;
        boolean ajaxRequest = isAjaxRequest();

        ExtensionResultHolder<String> erh = new ExtensionResultHolder<String>();
        extensionManager.getProxy().provideTemplateWrapper(erh, originalViewName, ajaxRequest);
        String templateWrapper = erh.getResult();

        if (templateWrapper != null && ajaxRequest) {
            view = (AbstractThymeleafView) super.loadView(templateWrapper, locale);
            view.addStaticVariable("wrappedTemplate", viewName);
        } else {
            view = (AbstractThymeleafView) super.loadView(viewName, locale);
        }

        if (!ajaxRequest) {
            view.addStaticVariable("templateName", originalViewName);
        }

        return view;
    }

    @Override
    protected Object getCacheKey(String viewName, Locale locale) {
        String cacheKey = viewName + "_" + locale + "_" + isAjaxRequest();

        ExtensionResultHolder<String> erh = new ExtensionResultHolder<String>();
        extensionManager.getProxy().appendCacheKey(erh, viewName, isAjaxRequest());

        String addlCacheKey = (String) erh.getResult();

        if (addlCacheKey != null) {
            cacheKey = cacheKey + "_" + addlCacheKey;
        }

        return cacheKey;
    }

    protected boolean isIFrameRequest() {
        HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes())
                .getRequest();
        String iFrameParameter = request.getParameter("blcIFrame");
        return (iFrameParameter != null && "true".equals(iFrameParameter));
    }

    protected boolean isAjaxRequest() {
        // First, let's try to get it from the BroadleafRequestContext
        HttpServletRequest request = null;
        if (BroadleafRequestContext.getBroadleafRequestContext() != null) {
            HttpServletRequest brcRequest = BroadleafRequestContext.getBroadleafRequestContext().getRequest();
            if (brcRequest != null) {
                request = brcRequest;
            }
        }

        // If we didn't find it there, we might be outside of a security-configured uri. Let's see if the filter got it
        if (request == null) {
            try {
                request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
            } catch (ClassCastException e) {
                // In portlet environments, we won't be able to cast to a ServletRequestAttributes. We don't want to 
                // blow up in these scenarios.
                LOG.warn("Unable to cast to ServletRequestAttributes and the request in BroadleafRequestContext "
                        + "was not set. This may introduce incorrect AJAX behavior.");
            }
        }

        // If we still don't have a request object, we'll default to non-ajax
        if (request == null) {
            return false;
        }

        return BroadleafControllerUtility.isAjaxRequest(request);
    }

    /**
     * Gets the map of prefix : layout for use in determining which layout
     * to dispatch the request to in non-AJAX calls
     * 
     * @return the layout map
     */
    public Map<String, String> getLayoutMap() {
        return layoutMap;
    }

    /**
     * @see #getLayoutMap()
     * @param layoutMap
     */
    public void setLayoutMap(Map<String, String> layoutMap) {
        this.layoutMap = layoutMap;
    }

    /**
     * The default layout to use if there is no specifc entry in the layout map
     * 
     * @return the full page layout
     */
    public String getFullPageLayout() {
        return fullPageLayout;
    }

    /**
     * @see #getFullPageLayout()
     * @param fullPageLayout
     */
    public void setFullPageLayout(String fullPageLayout) {
        this.fullPageLayout = fullPageLayout;
    }

    /**
     * The layout to use for iframe requests
     * 
     * @return the iframe layout
     */
    public String getIframeLayout() {
        return iframeLayout;
    }

    /**
     * @see #getIframeLayout()
     * @param iframeLayout
     */
    public void setIframeLayout(String iframeLayout) {
        this.iframeLayout = iframeLayout;
    }

}