/*
* #%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;
}
}