/*
* #%L
* BroadleafCommerce CMS Module
* %%
* 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.cms.web;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.broadleafcommerce.common.RequestDTOImpl;
import org.broadleafcommerce.common.locale.domain.Locale;
import org.broadleafcommerce.common.locale.service.LocaleService;
import org.broadleafcommerce.common.sandbox.domain.SandBox;
import org.broadleafcommerce.common.sandbox.domain.SandBoxType;
import org.broadleafcommerce.common.sandbox.service.SandBoxService;
import org.broadleafcommerce.common.site.domain.Site;
import org.broadleafcommerce.common.time.FixedTimeSource;
import org.broadleafcommerce.common.time.SystemTime;
import org.broadleafcommerce.common.web.BroadleafRequestContext;
import org.broadleafcommerce.common.web.util.StatusExposingServletResponse;
import org.springframework.web.filter.OncePerRequestFilter;
import com.google.common.cache.Cache;
import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import java.io.IOException;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Calendar;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import javax.annotation.Resource;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
/**
* @deprecated In favor of org.broadleafcommerce.common.web.BroadleafRequestFilter.
* formally component name "blProcessURLFilter"
*
* This filter sets up the CMS system by setting the current sandbox, locale, time of day, and languageCode
* that used by content items.
* <p/>
* After setting up content variables, it checks to see if a request can be processed by an instance of
* URLProcessor and if so, delegates the request to that processor.
*
* This filter creates an internal cache to quickly determine if the request should be processed
* by an instance of URLProcessor or be passed to the next filter in the filter chain. The
* cache settings (including expiration seconds, maximum elements, and concurrency) can be
* configured via Spring at startup. See {@code com.google.common.cache.CacheBuilder} for more information
* on these parameters.
*
* @author bpolster
*/
@Deprecated
public class BroadleafProcessURLFilter extends OncePerRequestFilter {
private final Log LOG = LogFactory.getLog(BroadleafProcessURLFilter.class);
// List of URLProcessors
private List<URLProcessor> urlProcessorList = new ArrayList<URLProcessor>();
// Cache-settings
// by default, expire cache every four hours (4 hours * 60 minutes * 60 seconds)
private int cacheExpirationSeconds = 4 * 60 * 60;
private int maxCacheElements = 10000;
private int maxCacheConcurrency = 3;
private Cache<String, URLProcessor> urlCache;
@Resource(name = "blSandBoxService")
private SandBoxService sandBoxService;
@Resource(name = "blLocaleService")
private LocaleService localeService;
protected Boolean sandBoxPreviewEnabled = true;
/**
* Parameter/Attribute name for the current language
*/
public static String LOCALE_VAR = "blLocale";
/**
* Parameter/Attribute name for the current language
*/
public static String LOCALE_CODE_PARAM = "blLocaleCode";
/**
* Parameter/Attribute name for the current language
*/
public static String REQUEST_DTO = "blRequestDTO";
/**
* Request attribute to store the current sandbox
*/
public static String SANDBOX_VAR = "blSandbox";
// Properties to manage URLs that will not be processed by this filter.
private static final String BLC_ADMIN_GWT = "org.broadleafcommerce.admin";
private static final String BLC_ADMIN_PREFIX = "blcadmin";
private static final String BLC_ADMIN_SERVICE = ".service";
private HashSet<String> ignoreSuffixes;
// Request Parameters and Attributes for Sandbox Mode properties - mostly date values.
private static String SANDBOX_ID_VAR = "blSandboxId";
private static String SANDBOX_DATE_TIME_VAR = "blSandboxDateTime";
private static final SimpleDateFormat CONTENT_DATE_FORMATTER = new SimpleDateFormat("yyyyMMddHHmm");
private static final SimpleDateFormat CONTENT_DATE_DISPLAY_FORMATTER = new SimpleDateFormat("MM/dd/yyyy");
private static final SimpleDateFormat CONTENT_DATE_DISPLAY_HOURS_FORMATTER = new SimpleDateFormat("h");
private static final SimpleDateFormat CONTENT_DATE_DISPLAY_MINUTES_FORMATTER = new SimpleDateFormat("mm");
private static final SimpleDateFormat CONTENT_DATE_PARSE_FORMAT = new SimpleDateFormat("MM/dd/yyyy hh:mm aa");
private static String SANDBOX_DATE_TIME_RIBBON_OVERRIDE_PARAM = "blSandboxDateTimeRibbonOverride";
private static final String SANDBOX_DISPLAY_DATE_TIME_DATE_PARAM = "blSandboxDisplayDateTimeDate";
private static final String SANDBOX_DISPLAY_DATE_TIME_HOURS_PARAM = "blSandboxDisplayDateTimeHours";
private static final String SANDBOX_DISPLAY_DATE_TIME_MINUTES_PARAM = "blSandboxDisplayDateTimeMinutes";
private static final String SANDBOX_DISPLAY_DATE_TIME_AMPM_PARAM = "blSandboxDisplayDateTimeAMPM";
/**
* (non-Javadoc)
*
* @see javax.servlet.Filter#doFilter(javax.servlet.ServletRequest, javax.servlet.ServletResponse, javax.servlet.FilterChain)
*/
@Override
public void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws IOException, ServletException {
if (!shouldProcessURL(request, request.getRequestURI())) {
if (LOG.isTraceEnabled()) {
LOG.trace("Process URL not processing URL " + request.getRequestURI());
}
filterChain.doFilter(request, response);
return;
}
final String requestURIWithoutContext;
if (request.getContextPath() != null) {
requestURIWithoutContext = request.getRequestURI().substring(request.getContextPath().length());
} else {
requestURIWithoutContext = request.getRequestURI();
}
if (LOG.isTraceEnabled()) {
LOG.trace("Process URL Filter Begin " + requestURIWithoutContext);
}
if (request.getAttribute(REQUEST_DTO) == null) {
request.setAttribute(REQUEST_DTO, new RequestDTOImpl(request));
}
Site site = determineSite(request);
SandBox currentSandbox = determineSandbox(request, site);
BroadleafRequestContext brc = new BroadleafRequestContext();
brc.setLocale(determineLocale(request, site));
brc.setSandBox(currentSandbox);
brc.setRequest(request);
brc.setResponse(response);
BroadleafRequestContext.setBroadleafRequestContext(brc);
try {
URLProcessor urlProcessor = null;
if (isProduction(currentSandbox)) {
try {
urlProcessor = lookupProcessorFromCache(requestURIWithoutContext);
} catch (ExecutionException e) {
LOG.error(e);
}
}
if (urlProcessor == null) {
urlProcessor = determineURLProcessor(requestURIWithoutContext);
}
if (urlProcessor instanceof NullURLProcessor) {
// Pass request down the filter chain
if (LOG.isTraceEnabled()) {
LOG.trace("URL not being processed by a Broadleaf URLProcessor " + requestURIWithoutContext);
}
StatusExposingServletResponse sesResponse = new StatusExposingServletResponse(response);
filterChain.doFilter(request, sesResponse);
if (sesResponse.getStatus() == sesResponse.SC_NOT_FOUND) {
if (LOG.isWarnEnabled()) {
LOG.warn("Page not found. Unable to render " + requestURIWithoutContext);
}
urlCache.invalidate(requestURIWithoutContext);
}
} else {
if (LOG.isTraceEnabled()) {
LOG.trace("URL about to be processed by a Broadleaf URLProcessor " + requestURIWithoutContext);
}
urlProcessor.processURL(requestURIWithoutContext);
}
} finally {
// If the system-time was overridden, set it back to normal
SystemTime.resetLocalTimeSource();
}
}
/**
* Returns true if the passed in sandbox is null or is of type SandBoxType.PRODUCTION.
*
* @param sandbox
* @return
*/
private boolean isProduction(SandBox sandbox) {
return (sandbox == null) || (SandBoxType.PRODUCTION.equals(sandbox));
}
/**
* Builds a cache for each URL that determines how it should be processed.
*
* @return
*/
private URLProcessor lookupProcessorFromCache(String requestURIWithoutContextPath) throws ExecutionException {
if (urlCache == null) {
urlCache = CacheBuilder.newBuilder()
.maximumSize(maxCacheElements)
.concurrencyLevel(maxCacheConcurrency)
.expireAfterWrite(cacheExpirationSeconds, TimeUnit.SECONDS)
.build(new CacheLoader<String,URLProcessor>() {
@Override
public URLProcessor load(String key) throws IOException, ServletException {
if (LOG.isDebugEnabled()) {
LOG.debug("Loading URL processor into Cache");
}
return determineURLProcessor(key);
}
});
}
return urlCache.getIfPresent(requestURIWithoutContextPath);
}
private URLProcessor determineURLProcessor(String requestURI) {
for (URLProcessor processor: getUrlProcessorList()) {
if (processor.canProcessURL(requestURI)) {
if (LOG.isDebugEnabled()) {
LOG.debug("URLProcessor found for URI " + requestURI + " - " + processor.getClass().getName());
}
return processor;
}
}
// Indicates that this URL is not handled by a URLProcessor
return NullURLProcessor.getInstance();
}
/**
* Determines if the passed in URL should be processed by the content management system.
* <p/>
* By default, this method returns false for any BLC-Admin URLs and service calls and for all
* common image/digital mime-types (as determined by an internal call to {@code getIgnoreSuffixes}.
* <p/>
* This check is called with the {@code doFilterInternal} method to short-circuit the content
* processing which can be expensive for requests that do not require it.
*
* @param requestURI - the HttpServletRequest.getRequestURI
* @return true if the {@code HttpServletRequest} should be processed
*/
protected boolean shouldProcessURL(HttpServletRequest request, String requestURI) {
if (requestURI.contains(BLC_ADMIN_GWT) ||
requestURI.endsWith(BLC_ADMIN_SERVICE) ||
requestURI.contains(BLC_ADMIN_PREFIX)) {
if (LOG.isTraceEnabled()) {
LOG.trace("BroadleafProcessURLFilter ignoring admin request URI " + requestURI);
}
return false;
} else {
int pos = requestURI.lastIndexOf(".");
if (pos > 0) {
String suffix = requestURI.substring(pos);
if (getIgnoreSuffixes().contains(suffix.toLowerCase())) {
if (LOG.isTraceEnabled()) {
LOG.trace("BroadleafProcessURLFilter ignoring request due to suffix " + requestURI);
}
return false;
}
}
}
return true;
}
private SandBox determineSandbox(HttpServletRequest request, Site site) {
SandBox currentSandbox = null;
if (!sandBoxPreviewEnabled) {
if (LOG.isTraceEnabled()) {
LOG.trace("Sandbox preview disabled. Setting sandbox to production");
}
request.setAttribute(SANDBOX_VAR, currentSandbox);
} else {
Long sandboxId = null;
if (request.getParameter("blSandboxDateTimeRibbonProduction") == null) {
sandboxId = lookupSandboxId(request);
} else {
request.getSession().removeAttribute(SANDBOX_DATE_TIME_VAR);
request.getSession().removeAttribute(SANDBOX_ID_VAR);
}
if (sandboxId != null) {
currentSandbox = sandBoxService.retrieveSandBoxById(sandboxId);
request.setAttribute(SANDBOX_VAR, currentSandbox);
if (currentSandbox != null && !SandBoxType.PRODUCTION.equals(currentSandbox.getSandBoxType())) {
setContentTime(request);
}
}
// if (currentSandbox == null && site != null) {
// currentSandbox = site.getProductionSandbox();
// }
}
if (LOG.isTraceEnabled()) {
LOG.trace("Serving request using sandbox: " + currentSandbox);
}
Date currentSystemDateTime = SystemTime.asDate(true);
Calendar sandboxDateTimeCalendar = Calendar.getInstance();
sandboxDateTimeCalendar.setTime(currentSystemDateTime);
request.setAttribute(SANDBOX_DISPLAY_DATE_TIME_DATE_PARAM, CONTENT_DATE_DISPLAY_FORMATTER.format(currentSystemDateTime));
request.setAttribute(SANDBOX_DISPLAY_DATE_TIME_HOURS_PARAM, CONTENT_DATE_DISPLAY_HOURS_FORMATTER.format(currentSystemDateTime));
request.setAttribute(SANDBOX_DISPLAY_DATE_TIME_MINUTES_PARAM, CONTENT_DATE_DISPLAY_MINUTES_FORMATTER.format(currentSystemDateTime));
request.setAttribute(SANDBOX_DISPLAY_DATE_TIME_AMPM_PARAM, sandboxDateTimeCalendar.get(Calendar.AM_PM));
return currentSandbox;
}
/**
* If another filter has already set the language as a request attribute, that will be honored.
* Otherwise, the request parameter is checked followed by the session attribute.
*
* @param request
* @param site
* @return
*/
private Locale determineLocale(HttpServletRequest request, Site site) {
Locale locale = null;
// First check for request attribute
locale = (Locale) request.getAttribute(LOCALE_VAR);
// Second, check for a request parameter
if (locale == null && request.getParameter(LOCALE_CODE_PARAM) != null) {
String localeCode = request.getParameter(LOCALE_CODE_PARAM);
locale = localeService.findLocaleByCode(localeCode);
if (LOG.isTraceEnabled()) {
LOG.trace("Attempt to find locale by param " + localeCode + " resulted in " + locale);
}
}
// Third, check the session
if (locale == null) {
HttpSession session = request.getSession(true);
if (session != null) {
locale = (Locale) session.getAttribute(LOCALE_VAR);
}
if (LOG.isTraceEnabled()) {
LOG.trace("Attempt to find locale from session resulted in " + locale);
}
}
// Finally, use the default
if (locale == null) {
locale = localeService.findDefaultLocale();
if (LOG.isTraceEnabled()) {
LOG.trace("Locale set to default locale " + locale);
}
}
request.setAttribute(LOCALE_VAR, locale);
request.getSession().setAttribute(LOCALE_VAR, locale);
Map<String, Object> ruleMap = (Map<String, Object>) request.getAttribute("blRuleMap");
if (ruleMap == null) {
ruleMap = new HashMap<String, Object>();
request.setAttribute("blRuleMap", ruleMap);
}
ruleMap.put("locale", locale);
return locale;
}
private Long lookupSandboxId(HttpServletRequest request) {
String sandboxIdStr = request.getParameter(SANDBOX_ID_VAR);
Long sandboxId = null;
if (sandboxIdStr != null) {
try {
sandboxId = Long.valueOf(sandboxIdStr);
if (LOG.isTraceEnabled()) {
LOG.trace("SandboxId found on request " + sandboxId);
}
} catch (NumberFormatException nfe) {
LOG.warn("blcSandboxId parameter could not be converted into a Long", nfe);
}
}
if (sandboxId == null) {
// check the session
HttpSession session = request.getSession(false);
if (session != null) {
sandboxId = (Long) session.getAttribute(SANDBOX_ID_VAR);
if (LOG.isTraceEnabled()) {
if (sandboxId != null) {
LOG.trace("SandboxId found in session " + sandboxId);
}
}
}
} else {
HttpSession session = request.getSession();
session.setAttribute(SANDBOX_ID_VAR, sandboxId);
}
return sandboxId;
}
private void setContentTime(HttpServletRequest request) {
String sandboxDateTimeParam = request.getParameter(SANDBOX_DATE_TIME_VAR);
if (sandBoxPreviewEnabled) {
sandboxDateTimeParam = null;
}
Date overrideTime = null;
try {
if (request.getParameter(SANDBOX_DATE_TIME_RIBBON_OVERRIDE_PARAM) != null) {
overrideTime = readDateFromRequest(request);
} else if (sandboxDateTimeParam != null) {
if (LOG.isDebugEnabled()) {
LOG.debug("Setting date/time using " + sandboxDateTimeParam);
}
overrideTime = CONTENT_DATE_FORMATTER.parse(sandboxDateTimeParam);
}
} catch (ParseException e) {
LOG.debug(e);
}
if (overrideTime == null) {
HttpSession session = request.getSession(false);
if (session != null) {
overrideTime = (Date) session.getAttribute(SANDBOX_DATE_TIME_VAR);
}
} else {
if (LOG.isDebugEnabled()) {
LOG.debug("Setting date-time for sandbox mode to " + overrideTime + " for sandboxDateTimeParam = " + sandboxDateTimeParam);
}
HttpSession session = request.getSession();
session.setAttribute(SANDBOX_DATE_TIME_VAR, overrideTime);
}
if (overrideTime != null) {
FixedTimeSource ft = new FixedTimeSource(overrideTime.getTime());
SystemTime.setLocalTimeSource(ft);
} else {
SystemTime.resetLocalTimeSource();
}
}
private Date readDateFromRequest(HttpServletRequest request) throws ParseException {
String date = request.getParameter(SANDBOX_DISPLAY_DATE_TIME_DATE_PARAM);
String minutes = request.getParameter(SANDBOX_DISPLAY_DATE_TIME_MINUTES_PARAM);
String hours = request.getParameter(SANDBOX_DISPLAY_DATE_TIME_HOURS_PARAM);
String ampm = request.getParameter(SANDBOX_DISPLAY_DATE_TIME_AMPM_PARAM);
if (StringUtils.isEmpty(minutes)) {
minutes = Integer.toString(SystemTime.asCalendar().get(Calendar.MINUTE));
}
if (StringUtils.isEmpty(hours)) {
hours = Integer.toString(SystemTime.asCalendar().get(Calendar.HOUR_OF_DAY));
}
String dateString = date + " " + hours + ":" + minutes + " " + ampm;
if (LOG.isDebugEnabled()) {
LOG.debug("Setting date/time using " + dateString);
}
Date parsedDate = CONTENT_DATE_PARSE_FORMAT.parse(dateString);
return parsedDate;
}
private Site determineSite(ServletRequest request) {
/* TODO: Multi-tennant: Need to add code that determines the site to support
SiteService.retrieveAllSites();
For each site, check the identifier type (e.g. hostname, url, param)
to determine the current site.
*/
return null;
}
/**
* Returns a set of suffixes that can be ignored by content processing. The following
* are returned:
* <p/>
* <B>List of suffixes ignored:</B>
*
* ".aif", ".aiff", ".asf", ".avi", ".bin", ".bmp", ".doc", ".eps", ".gif", ".hqx", ".jpg", ".jpeg", ".mid", ".midi", ".mov", ".mp3", ".mpg", ".mpeg", ".p65", ".pdf", ".pic", ".pict", ".png", ".ppt", ".psd", ".qxd", ".ram", ".ra", ".rm", ".sea", ".sit", ".stk", ".swf", ".tif", ".tiff", ".txt", ".rtf", ".vob", ".wav", ".wmf", ".xls", ".zip";
*
* @return set of suffixes to ignore.
*/
protected Set getIgnoreSuffixes() {
if (ignoreSuffixes == null || ignoreSuffixes.isEmpty()) {
String[] ignoreSuffixList = {".aif", ".aiff", ".asf", ".avi", ".bin", ".bmp", ".css", ".doc", ".eps", ".gif", ".hqx", ".js", ".jpg", ".jpeg", ".mid", ".midi", ".mov", ".mp3", ".mpg", ".mpeg", ".p65", ".pdf", ".pic", ".pict", ".png", ".ppt", ".psd", ".qxd", ".ram", ".ra", ".rm", ".sea", ".sit", ".stk", ".swf", ".tif", ".tiff", ".txt", ".rtf", ".vob", ".wav", ".wmf", ".xls", ".zip"};
ignoreSuffixes = new HashSet<String>(Arrays.asList(ignoreSuffixList));
}
return ignoreSuffixes;
}
public int getMaxCacheElements() {
return maxCacheElements;
}
public void setMaxCacheElements(int maxCacheElements) {
this.maxCacheElements = maxCacheElements;
}
public int getCacheExpirationSeconds() {
return cacheExpirationSeconds;
}
public void setCacheExpirationSeconds(int cacheExpirationSeconds) {
this.cacheExpirationSeconds = cacheExpirationSeconds;
}
public int getMaxCacheConcurrency() {
return maxCacheConcurrency;
}
public void setMaxCacheConcurrency(int maxCacheConcurrency) {
this.maxCacheConcurrency = maxCacheConcurrency;
}
public List<URLProcessor> getUrlProcessorList() {
return urlProcessorList;
}
public void setUrlProcessorList(List<URLProcessor> urlProcessorList) {
this.urlProcessorList = urlProcessorList;
}
public Boolean getSandBoxPreviewEnabled() {
return sandBoxPreviewEnabled;
}
public void setSandBoxPreviewEnabled(Boolean sandBoxPreviewEnabled) {
this.sandBoxPreviewEnabled = sandBoxPreviewEnabled;
}
}