/*
* #%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.page.service;
import net.sf.ehcache.Cache;
import net.sf.ehcache.CacheManager;
import net.sf.ehcache.Element;
import org.apache.commons.beanutils.BeanComparator;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.broadleafcommerce.cms.file.service.StaticAssetService;
import org.broadleafcommerce.cms.page.dao.PageDao;
import org.broadleafcommerce.cms.page.domain.Page;
import org.broadleafcommerce.cms.page.domain.PageField;
import org.broadleafcommerce.cms.page.domain.PageTemplate;
import org.broadleafcommerce.common.cache.CacheStatType;
import org.broadleafcommerce.common.cache.StatisticsService;
import org.broadleafcommerce.common.extensibility.jpa.SiteDiscriminator;
import org.broadleafcommerce.common.extension.ExtensionResultHolder;
import org.broadleafcommerce.common.locale.domain.Locale;
import org.broadleafcommerce.common.locale.service.LocaleService;
import org.broadleafcommerce.common.locale.util.LocaleUtil;
import org.broadleafcommerce.common.page.dto.NullPageDTO;
import org.broadleafcommerce.common.page.dto.PageDTO;
import org.broadleafcommerce.common.rule.RuleProcessor;
import org.broadleafcommerce.common.sandbox.domain.SandBox;
import org.broadleafcommerce.common.template.TemplateOverrideExtensionManager;
import org.broadleafcommerce.common.web.BroadleafRequestContext;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.annotation.Resource;
/**
* Created by bpolster.
*/
@Service("blPageService")
public class PageServiceImpl implements PageService {
protected static final Log LOG = LogFactory.getLog(PageServiceImpl.class);
protected static String AND = " && ";
protected static final String FOREIGN_LOOKUP = "BLC_FOREIGN_LOOKUP";
@Resource(name="blPageDao")
protected PageDao pageDao;
@Resource(name="blPageRuleProcessors")
protected List<RuleProcessor<PageDTO>> pageRuleProcessors;
@Resource(name="blLocaleService")
protected LocaleService localeService;
@Resource(name="blStaticAssetService")
protected StaticAssetService staticAssetService;
@Resource(name="blStatisticsService")
protected StatisticsService statisticsService;
@Resource(name = "blTemplateOverrideExtensionManager")
protected TemplateOverrideExtensionManager templateOverrideManager;
@Resource(name = "blPageServiceUtility")
protected PageServiceUtility pageServiceUtility;
@Resource(name = "blPageServiceExtensionManager")
protected PageServiceExtensionManager extensionManager;
protected Cache pageCache;
protected Cache pageMapCache;
protected final PageDTO NULL_PAGE = new NullPageDTO();
/**
* Returns the page with the passed in id.
*
* @param pageId - The id of the page.
* @return The associated page.
*/
@Override
public Page findPageById(Long pageId) {
return pageDao.readPageById(pageId);
}
/**
* Returns the page with the passed in id.
*
* @param pageId - The id of the page.
* @return The associated page.
*/
@Override
public Map<String, PageField> findPageFieldMapByPageId(Long pageId) {
Map<String, PageField> returnMap = new HashMap<String, PageField>();
List<PageField> pageFields = pageDao.readPageFieldsByPageId(pageId);
for (PageField pf : pageFields) {
returnMap.put(pf.getFieldKey(), pf);
}
return returnMap;
}
@Override
public PageTemplate findPageTemplateById(Long id) {
return pageDao.readPageTemplateById(id);
}
@Override
@Transactional("blTransactionManager")
public PageTemplate savePageTemplate(PageTemplate template) {
return pageDao.savePageTemplate(template);
}
/**
* Retrieve the page if one is available for the passed in uri.
*/
@Override
public PageDTO findPageByURI(Locale locale, String uri, Map<String,Object> ruleDTOs, boolean secure) {
List<PageDTO> returnList = null;
if (uri != null) {
Locale languageOnlyLocale = findLanguageOnlyLocale(locale);
BroadleafRequestContext context = BroadleafRequestContext.getBroadleafRequestContext();
//store the language only locale for cache since we have to use the lowest common denominator (i.e. the cache
//locale and the pageTemplate locale used for cache invalidation can be different countries)
Long sandBox = context.getSandBox() == null?null:context.getSandBox().getId();
Long site = context.getSite() == null?null:context.getSite().getId();
String key = buildKey(sandBox, site, languageOnlyLocale, uri);
key = key + "-" + secure;
if (context.isProductionSandBox()) {
returnList = getPageListFromCache(key);
}
if (returnList == null) {
//TODO does this pull the right sandbox in multitenant?
List<Page> pageList = pageDao.findPageByURI(uri);
returnList = buildPageDTOList(pageList, secure);
if (context.isProductionSandBox()) {
Collections.sort(returnList, new BeanComparator("priority"));
addPageListToCache(returnList, key, uri, sandBox, site);
}
}
}
PageDTO dto = evaluatePageRules(returnList, locale, ruleDTOs);
if (dto.getId() != null) {
Page page = findPageById(dto.getId());
ExtensionResultHolder<PageDTO> newDTO = new ExtensionResultHolder<PageDTO>();
// Allow an extension point to override the page to render.
extensionManager.getProxy().overridePageDto(newDTO, dto, page);
if (newDTO.getResult() != null) {
dto = newDTO.getResult();
}
}
if (dto != null) {
dto = pageServiceUtility.hydrateForeignLookups(dto);
}
return dto;
}
@Override
public List<Page> readAllPages() {
return pageDao.readAllPages();
}
@Override
public List<PageTemplate> readAllPageTemplates() {
return pageDao.readAllPageTemplates();
}
@Override
@SuppressWarnings("unchecked")
public void removePageFromCache(String key) {
Element e = getPageMapCache().get(key);
if (e != null && e.getObjectValue() != null) {
List<String> keys = (List<String>) e.getObjectValue();
for (String k : keys) {
getPageCache().remove(k);
}
}
}
/**
* Converts a list of pages to a list of pageDTOs.<br>
* Internally calls buildPageDTO(...).
*
* @param pageList
* @param secure
* @return
*/
@Override
public List<PageDTO> buildPageDTOList(List<Page> pageList, boolean secure) {
List<PageDTO> dtoList = new ArrayList<PageDTO>();
if (pageList != null) {
for(Page page : pageList) {
dtoList.add(pageServiceUtility.buildPageDTO(page, secure));
}
}
return dtoList;
}
protected PageDTO evaluatePageRules(List<PageDTO> pageDTOList, Locale locale, Map<String, Object> ruleDTOs) {
if (pageDTOList == null) {
return NULL_PAGE;
}
// First check to see if we have a page that matches on the full locale.
for (PageDTO page : pageDTOList) {
if (locale != null && locale.getLocaleCode() != null) {
if (locale.getLocaleCode().equals(page.getLocaleCode())) {
if (passesPageRules(page, ruleDTOs)) {
return page;
}
}
}
}
// Otherwise, we look for a match using just the language.
for (PageDTO page : pageDTOList) {
if (passesPageRules(page, ruleDTOs)) {
return page;
}
}
return NULL_PAGE;
}
protected boolean passesPageRules(PageDTO page, Map<String, Object> ruleDTOs) {
if (pageRuleProcessors != null) {
for (RuleProcessor<PageDTO> processor : pageRuleProcessors) {
boolean matchFound = processor.checkForMatch(page, ruleDTOs);
if (! matchFound) {
return false;
}
}
}
return true;
}
protected Locale findLanguageOnlyLocale(Locale locale) {
if (locale != null ) {
Locale languageOnlyLocale = localeService.findLocaleByCode(LocaleUtil.findLanguageCode(locale));
if (languageOnlyLocale != null) {
return languageOnlyLocale;
}
}
return locale;
}
protected String buildKey(Long currentSandBox, Long site, Locale locale, String uri) {
StringBuilder key = new StringBuilder(uri);
if (locale != null) {
key.append("-").append(locale.getLocaleCode());
}
if (currentSandBox != null) {
key.append("-").append(currentSandBox);
}
if (site != null) {
key.append("-").append(site);
}
return key.toString();
}
@Override
public Cache getPageCache() {
if (pageCache == null) {
pageCache = CacheManager.getInstance().getCache("cmsPageCache");
}
return pageCache;
}
@Override
public Cache getPageMapCache() {
if (pageMapCache == null) {
pageMapCache = CacheManager.getInstance().getCache("cmsPageMapCache");
}
return pageMapCache;
}
protected String buildKey(SandBox sandBox, Page page) {
Locale locale = page.getPageTemplate() == null ? null : page.getPageTemplate().getLocale();
Long sandBoxId = sandBox==null?null:sandBox.getId();
Long siteId = (page instanceof SiteDiscriminator)?((SiteDiscriminator) page).getSiteDiscriminator():null;
return buildKey(sandBoxId, siteId, findLanguageOnlyLocale(locale), page.getFullUrl());
}
protected void addPageListToCache(List<PageDTO> pageList, String key, String uri, Long sandBox, Long site) {
getPageCache().put(new Element(key, pageList));
addPageMapCacheEntry(key, uri, sandBox, site);
if (site != null) {
addPageMapCacheEntry(key, uri, sandBox, null);
}
}
@SuppressWarnings("unchecked")
protected void addPageMapCacheEntry(String keyToStore, String uri, Long sandBox, Long site) {
String key = getPageMapCacheKey(uri, sandBox, site);
Element e = getPageMapCache().get(key);
if (e == null || e.getObjectValue() == null) {
List<String> keys = new ArrayList<String>();
keys.add(keyToStore);
getPageMapCache().put(new Element(key, keys));
} else {
((List<String>) e.getObjectValue()).add(keyToStore);
}
}
@Override
public String getPageMapCacheKey(String uri, Long sandBox, Long site) {
return uri + "-" + sandBox + "-" + (site == null ? "ALL" : site);
}
protected List<PageDTO> getPageListFromCache(String key) {
Element cacheElement = getPageCache().get(key);
if (cacheElement != null && cacheElement.getValue() != null) {
statisticsService.addCacheStat(CacheStatType.PAGE_CACHE_HIT_RATE.toString(), true);
return (List<PageDTO>) cacheElement.getValue();
}
statisticsService.addCacheStat(CacheStatType.PAGE_CACHE_HIT_RATE.toString(), false);
return null;
}
}