
org.broadleafcommerce.cms.page.service.PageServiceImpl Maven / Gradle / Ivy
/*
* Copyright 2008-2012 the original author or authors.
*
* 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.
*/
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.lang.StringUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.broadleafcommerce.cms.common.AbstractContentService;
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.PageImpl;
import org.broadleafcommerce.cms.page.domain.PageItemCriteria;
import org.broadleafcommerce.cms.page.domain.PageRule;
import org.broadleafcommerce.cms.page.domain.PageTemplate;
import org.broadleafcommerce.cms.page.dto.NullPageDTO;
import org.broadleafcommerce.cms.page.dto.PageDTO;
import org.broadleafcommerce.cms.page.message.ArchivedPagePublisher;
import org.broadleafcommerce.cms.structure.dto.ItemCriteriaDTO;
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.sandbox.dao.SandBoxDao;
import org.broadleafcommerce.common.sandbox.domain.SandBox;
import org.broadleafcommerce.common.sandbox.domain.SandBoxType;
import org.broadleafcommerce.openadmin.server.dao.SandBoxItemDao;
import org.broadleafcommerce.openadmin.server.domain.SandBoxItem;
import org.broadleafcommerce.openadmin.server.domain.SandBoxItemListener;
import org.broadleafcommerce.openadmin.server.domain.SandBoxItemType;
import org.broadleafcommerce.openadmin.server.domain.SandBoxOperationType;
import org.hibernate.Criteria;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
import java.util.ArrayList;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
/**
* Created by bpolster.
*/
@Service("blPageService")
public class PageServiceImpl extends AbstractContentService implements PageService, SandBoxItemListener {
private static final Log LOG = LogFactory.getLog(PageServiceImpl.class);
private static String AND = " && ";
@Resource(name="blPageDao")
protected PageDao pageDao;
@Resource(name="blSandBoxItemDao")
protected SandBoxItemDao sandBoxItemDao;
@Resource(name="blSandBoxDao")
protected SandBoxDao sandBoxDao;
@Resource(name="blPageRuleProcessors")
protected List pageRuleProcessors;
@Resource(name="blLocaleService")
protected LocaleService localeService;
@Resource(name="blStaticAssetService")
protected StaticAssetService staticAssetService;
@Value("${automatically.approve.pages}")
protected boolean automaticallyApproveAndPromotePages=true;
protected Cache pageCache;
protected List archivedPageListeners;
private PageDTO NULL_PAGE = new NullPageDTO();
private List EMPTY_PAGE_DTO = new ArrayList();
/**
* 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);
}
@Override
public PageTemplate findPageTemplateById(Long id) {
return pageDao.readPageTemplateById(id);
}
/**
* Returns the page-fields associated with the passed in page-id.
* This is preferred over the direct access from Page so that the
* two items can be cached distinctly
*
* @param pageId - The id of the page.
* @return The associated page.
*/
@Override
public Map findPageFieldsByPageId(Long pageId) {
Page page = (Page) findPageById(pageId);
return pageDao.readPageFieldsByPage(page);
}
/**
* This method is intended to be called from within the CMS
* admin only.
*
* Adds the passed in page to the DB.
*
*/
@Override
public Page addPage(Page page, SandBox destinationSandbox) {
if (automaticallyApproveAndPromotePages) {
if (destinationSandbox != null && destinationSandbox.getSite() != null) {
destinationSandbox = destinationSandbox.getSite().getProductionSandbox();
} else {
// Null means production for single-site installations.
destinationSandbox = null;
}
}
page.setSandbox(destinationSandbox);
page.setArchivedFlag(false);
page.setDeletedFlag(false);
Page newPage = pageDao.addPage(page);
if (! isProductionSandBox(destinationSandbox)) {
sandBoxItemDao.addSandBoxItem(destinationSandbox.getId(), SandBoxOperationType.ADD, SandBoxItemType.PAGE, newPage.getFullUrl(), newPage.getId(), null);
}
return newPage;
}
/**
* This method is intended to be called from within the CMS
* admin only.
*
* Updates the page according to the following rules:
*
* 1. If sandbox has changed from null to a value
* This means that the user is editing an item in production and
* the edit is taking place in a sandbox.
*
* Clone the page and add it to the new sandbox and set the cloned
* page's originalPageId to the id of the page being updated.
*
* 2. If the sandbox has changed from one value to another
* This means that the user is moving the item from one sandbox
* to another.
*
* Update the siteId for the page to the one associated with the
* new sandbox
*
* 3. If the sandbox has changed from a value to null
* This means that the item is moving from the sandbox to production.
*
* If the page has an originalPageId, then update that page by
* setting it's archived flag to true.
*
* Then, update the siteId of the page being updated to be the
* siteId of the original page.
*
* 4. If the sandbox is the same then just update the page.
*/
@Override
public Page updatePage(Page page, SandBox destSandbox) {
if (page.getLockedFlag()) {
throw new IllegalArgumentException("Unable to update a locked record");
}
if (automaticallyApproveAndPromotePages) {
if (destSandbox != null && destSandbox.getSite() != null) {
destSandbox = destSandbox.getSite().getProductionSandbox();
} else {
// Null means production for single-site installations.
destSandbox = null;
}
}
if (checkForSandboxMatch(page.getSandbox(), destSandbox)) {
if (page.getDeletedFlag()) {
SandBoxItem item = page.getSandbox()==null?null:sandBoxItemDao.retrieveBySandboxAndTemporaryItemId(page.getSandbox().getId(), SandBoxItemType.PAGE, page.getId());
if (page.getOriginalPageId() == null && item != null) {
// This page was added in this sandbox and now needs to be deleted.
item.setArchivedFlag(true);
page.setArchivedFlag(true);
} else if (item != null) {
// This page was being updated but now is being deleted - so change the
// sandbox operation type to deleted
item.setSandBoxOperationType(SandBoxOperationType.DELETE);
sandBoxItemDao.updateSandBoxItem(item);
} else if (automaticallyApproveAndPromotePages) {
page.setArchivedFlag(true);
}
}
return pageDao.updatePage(page);
} else if (isProductionSandBox(page.getSandbox())) {
// The passed in page is an existing page with updated values.
// Instead, we want to create a clone of this page for the destSandbox
Page clonedPage = page.cloneEntity();
clonedPage.setOriginalPageId(page.getId());
clonedPage.setSandbox(destSandbox);
// Detach the old page from the session so it is not updated.
pageDao.detachPage(page);
// Save the cloned page
Page returnPage = pageDao.addPage(clonedPage);
// Lookup the original page and mark it as locked
Page prod = findPageById(page.getId());
prod.setLockedFlag(true);
pageDao.updatePage(prod);
SandBoxOperationType type = SandBoxOperationType.UPDATE;
if (clonedPage.getDeletedFlag()) {
type = SandBoxOperationType.DELETE;
}
// Add this item to the sandbox.
sandBoxItemDao.addSandBoxItem(destSandbox.getId(), type, SandBoxItemType.PAGE, clonedPage.getFullUrl(), returnPage.getId(), returnPage.getOriginalPageId());
return returnPage;
} else {
// This should happen via a promote, revert, or reject in the sandbox service
throw new IllegalArgumentException("Update called when promote or reject was expected.");
}
}
// Returns true if the src and dest sandbox are the same.
private boolean checkForSandboxMatch(SandBox src, SandBox dest) {
if (src != null) {
if (dest != null) {
return src.getId().equals(dest.getId());
}
}
return (src == null && dest == null);
}
// Returns true if the dest sandbox is production.
private boolean isProductionSandBox(SandBox dest) {
if (dest == null) {
return true;
} else {
return SandBoxType.PRODUCTION.equals(dest.getSandBoxType());
}
}
/**
* If deleting and item where page.originalPageId != null
* then the item is deleted from the database.
*
* If the originalPageId is null, then this method marks
* the items as deleted within the passed in sandbox.
*
* @param page
* @param destinationSandbox
* @return
*/
@Override
public void deletePage(Page page, SandBox destinationSandbox) {
page.setDeletedFlag(true);
updatePage(page, destinationSandbox);
}
/**
* Converts a list of pages to a list of pageDTOs.
* Internally calls buildPageDTO(...).
*
* @param pageList
* @param secure
* @return
*/
protected List buildPageDTOList(List pageList, boolean secure) {
List dtoList = new ArrayList();
if (pageList != null) {
for(Page page : pageList) {
dtoList.add(buildPageDTOInternal(page, secure));
}
}
return dtoList;
}
protected PageDTO buildPageDTOInternal(Page page, boolean secure) {
PageDTO pageDTO = new PageDTO();
pageDTO.setId(page.getId());
pageDTO.setDescription(page.getDescription());
pageDTO.setUrl(page.getFullUrl());
pageDTO.setPriority(page.getPriority());
if (page.getSandbox() != null) {
pageDTO.setSandboxId(page.getSandbox().getId());
}
if (page.getPageTemplate() != null) {
pageDTO.setTemplatePath(page.getPageTemplate().getTemplatePath());
if (page.getPageTemplate().getLocale() != null) {
pageDTO.setLocaleCode(page.getPageTemplate().getLocale().getLocaleCode());
}
}
String envPrefix = staticAssetService.getStaticAssetEnvironmentUrlPrefix();
if (envPrefix != null && secure) {
envPrefix = staticAssetService.getStaticAssetEnvironmentSecureUrlPrefix();
}
String cmsPrefix = staticAssetService.getStaticAssetUrlPrefix();
for (String fieldKey : page.getPageFields().keySet()) {
PageField pf = page.getPageFields().get(fieldKey);
String originalValue = pf.getValue();
if (StringUtils.isNotBlank(envPrefix) && StringUtils.isNotBlank(originalValue) && StringUtils.isNotBlank(cmsPrefix) && originalValue.contains(cmsPrefix)) {
String fldValue = originalValue.replaceAll(cmsPrefix, envPrefix+cmsPrefix);
pageDTO.getPageFields().put(fieldKey, fldValue);
} else {
pageDTO.getPageFields().put(fieldKey, originalValue);
}
}
pageDTO.setRuleExpression(buildRuleExpression(page));
if (page.getQualifyingItemCriteria() != null && page.getQualifyingItemCriteria().size() > 0) {
pageDTO.setItemCriteriaDTOList(buildItemCriteriaDTOList(page));
}
return pageDTO;
}
protected String buildRuleExpression(Page page) {
StringBuffer ruleExpression = null;
Map ruleMap = page.getPageMatchRules();
if (ruleMap != null) {
for (String ruleKey : ruleMap.keySet()) {
if (ruleExpression == null) {
ruleExpression = new StringBuffer(ruleMap.get(ruleKey).getMatchRule());
} else {
ruleExpression.append(AND);
ruleExpression.append(ruleMap.get(ruleKey).getMatchRule());
}
}
}
if (ruleExpression != null) {
return ruleExpression.toString();
} else {
return null;
}
}
private List buildItemCriteriaDTOList(Page page) {
List itemCriteriaDTOList = new ArrayList();
for(PageItemCriteria criteria : page.getQualifyingItemCriteria()) {
ItemCriteriaDTO criteriaDTO = new ItemCriteriaDTO();
criteriaDTO.setMatchRule(criteria.getOrderItemMatchRule());
criteriaDTO.setQty(criteria.getQuantity());
itemCriteriaDTOList.add(criteriaDTO);
}
return itemCriteriaDTOList;
}
protected List mergePages(List productionPageList, List sandboxPageList, boolean secure) {
if (sandboxPageList == null || sandboxPageList.size() == 0) {
return productionPageList;
}
Map pageMap = new LinkedHashMap();
if (productionPageList != null) {
for(PageDTO page : productionPageList) {
pageMap.put(page.getId(), page);
}
}
for (Page page : sandboxPageList) {
if (page.getOriginalPageId() != null) {
pageMap.remove(page.getOriginalPageId());
}
if (! page.getDeletedFlag() && page.getOfflineFlag() != null && ! page.getOfflineFlag()) {
PageDTO convertedPage = buildPageDTOInternal(page, secure);
pageMap.put(page.getId(), convertedPage);
}
}
ArrayList returnList = new ArrayList(pageMap.values());
if (returnList.size() > 1) {
Collections.sort(returnList, new BeanComparator("priority"));
}
return returnList;
}
protected PageDTO evaluatePageRules(List pageDTOList, Locale locale, Map 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 (page.getLocaleCode().equals(locale.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 ruleDTOs) {
if (pageRuleProcessors != null) {
for (PageRuleProcessor processor : pageRuleProcessors) {
boolean matchFound = processor.checkForMatch(page, ruleDTOs);
if (! matchFound) {
return false;
}
}
}
return true;
}
private Locale findLanguageOnlyLocale(Locale locale) {
if (locale != null ) {
Locale languageOnlyLocale = localeService.findLocaleByCode(LocaleUtil.findLanguageCode(locale));
if (languageOnlyLocale != null) {
return languageOnlyLocale;
}
}
return locale;
}
/**
* Retrieve the page if one is available for the passed in uri.
*/
@Override
public PageDTO findPageByURI(SandBox currentSandbox, Locale locale, String uri, Map ruleDTOs, boolean secure) {
List returnList = null;
if (uri != null) {
SandBox productionSandbox = null;
if (currentSandbox != null && currentSandbox.getSite() != null) {
productionSandbox = currentSandbox.getSite().getProductionSandbox();
}
Locale languageOnlyLocale = findLanguageOnlyLocale(locale);
String key = buildKey(productionSandbox, locale, uri);
key = key + "-" + secure;
returnList = getPageListFromCache(key);
if (returnList == null) {
if (LOG.isTraceEnabled()) {
LOG.trace("Page not found in cache, searching DB for key: " + key);
}
List productionPages = pageDao.findPageByURI(productionSandbox, locale, languageOnlyLocale, uri);
if (productionPages != null) {
if (LOG.isTraceEnabled()) {
LOG.trace("Pages found, adding pages to cache with key: " + key);
}
returnList = buildPageDTOList(productionPages, secure);
Collections.sort(returnList, new BeanComparator("priority"));
addPageListToCache(returnList, key);
} else {
if (LOG.isTraceEnabled()) {
LOG.trace("No match found for passed in URI, locale, and sandbox. Key = " + key);
}
addPageListToCache(EMPTY_PAGE_DTO, key);
}
}
// If the request is from a non-production SandBox, we need to check to see if the SandBox has an override
// for this page before returning. No caching is used for Sandbox pages.
if (currentSandbox != null && ! currentSandbox.getSandBoxType().equals(SandBoxType.PRODUCTION)) {
List sandboxPages = pageDao.findPageByURI(currentSandbox, locale, languageOnlyLocale, uri);
if (sandboxPages != null && sandboxPages.size() > 0) {
returnList = mergePages(returnList, sandboxPages, secure);
}
}
}
return evaluatePageRules(returnList, locale, ruleDTOs);
}
@Override
public List findPages(SandBox sandbox, Criteria c) {
return (List) findItems(sandbox, c, Page.class, PageImpl.class, "originalPageId");
}
@Override
public Long countPages(SandBox sandbox, Criteria c) {
return countItems(sandbox, c, PageImpl.class, "originalPageId");
}
protected void productionItemArchived(Page page) {
// Immediately remove the page from this VM.
removePageFromCache(page);
if (archivedPageListeners != null) {
for (ArchivedPagePublisher listener : archivedPageListeners) {
listener.processPageArchive(page, buildKey(page));
}
}
}
@Override
public void itemPromoted(SandBoxItem sandBoxItem, SandBox destinationSandBox) {
if (! SandBoxItemType.PAGE.equals(sandBoxItem.getSandBoxItemType())) {
return;
}
Page page = pageDao.readPageById(sandBoxItem.getTemporaryItemId());
if (page == null) {
if (LOG.isDebugEnabled()) {
LOG.debug("Page not found " + sandBoxItem.getTemporaryItemId());
}
} else {
boolean productionSandBox = isProductionSandBox(destinationSandBox);
if (productionSandBox) {
page.setLockedFlag(false);
} else {
page.setLockedFlag(true);
}
if (productionSandBox && page.getOriginalPageId() != null) {
if (LOG.isDebugEnabled()) {
LOG.debug("Page promoted to production. " + page.getId() + ". Archiving original page " + page.getOriginalPageId());
}
Page originalPage = pageDao.readPageById(page.getOriginalPageId());
originalPage.setArchivedFlag(Boolean.TRUE);
pageDao.updatePage(originalPage);
productionItemArchived(originalPage);
// We are archiving the old page and making this the new "production page", so
// null out the original page id before saving.
page.setOriginalPageId(null);
if (page.getDeletedFlag()) {
// If this page is being deleted, set it as archived.
page.setArchivedFlag(true);
}
}
}
if (page.getOriginalSandBox() == null) {
page.setOriginalSandBox(page.getSandbox());
}
page.setSandbox(destinationSandBox);
pageDao.updatePage(page);
}
@Override
public void itemRejected(SandBoxItem sandBoxItem, SandBox destinationSandBox) {
if (! SandBoxItemType.PAGE.equals(sandBoxItem.getSandBoxItemType())) {
return;
}
Page page = pageDao.readPageById(sandBoxItem.getTemporaryItemId());
if (page != null) {
page.setSandbox(destinationSandBox);
page.setOriginalSandBox(null);
page.setLockedFlag(false);
pageDao.updatePage(page);
}
}
@Override
public void itemReverted(SandBoxItem sandBoxItem) {
if (! SandBoxItemType.PAGE.equals(sandBoxItem.getSandBoxItemType())) {
return;
}
Page page = pageDao.readPageById(sandBoxItem.getTemporaryItemId());
if (page != null) {
page.setArchivedFlag(Boolean.TRUE);
page.setLockedFlag(false);
pageDao.updatePage(page);
if (page.getOriginalPageId() != null) {
Page originalPage = pageDao.readPageById(page.getOriginalPageId());
originalPage.setLockedFlag(false);
pageDao.updatePage(originalPage);
}
}
}
private Cache getPageCache() {
if (pageCache == null) {
pageCache = CacheManager.getInstance().getCache("cmsPageCache");
}
return pageCache;
}
private String buildKey(SandBox currentSandbox, Locale locale, String uri) {
StringBuffer key = new StringBuffer(uri);
if (locale != null) {
key.append("-").append(locale.getLocaleCode());
}
if (currentSandbox != null) {
key.append("-").append(currentSandbox.getId());
}
return key.toString();
}
private String buildKey(Page page) {
return buildKey(page.getSandbox(), page.getPageTemplate().getLocale(), page.getFullUrl());
}
private void addPageListToCache(List pageList, String key) {
getPageCache().put(new Element(key, pageList));
}
@SuppressWarnings("unchecked")
private List getPageListFromCache(String key) {
Element cacheElement = getPageCache().get(key);
if (cacheElement != null && cacheElement.getValue() != null) {
return (List) cacheElement.getValue();
}
return null;
}
/**
* Call to evict an item from the cache.
* @param p
*/
public void removePageFromCache(Page p) {
// Remove secure and non-secure instances of the page.
// Typically the page will be in one or the other if at all.
removePageFromCache(buildKey(p));
}
/**
* Call to evict both secure and non-secure pages matching
* the passed in key.
*
* @param baseKey
*/
public void removePageFromCache(String baseKey) {
// Remove secure and non-secure instances of the page.
// Typically the page will be in one or the other if at all.
getPageCache().remove(baseKey+"-"+true);
getPageCache().remove(baseKey+"-"+false);
}
public List getArchivedPageListeners() {
return archivedPageListeners;
}
public void setArchivedPageListeners(List archivedPageListeners) {
this.archivedPageListeners = archivedPageListeners;
}
public boolean isAutomaticallyApproveAndPromotePages() {
return automaticallyApproveAndPromotePages;
}
public void setAutomaticallyApproveAndPromotePages(boolean automaticallyApproveAndPromotePages) {
this.automaticallyApproveAndPromotePages = automaticallyApproveAndPromotePages;
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy