/*
* #%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.processor;
import org.apache.commons.lang.ObjectUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.builder.CompareToBuilder;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.broadleafcommerce.cms.file.service.StaticAssetService;
import org.broadleafcommerce.cms.structure.domain.StructuredContentType;
import org.broadleafcommerce.cms.structure.service.StructuredContentService;
import org.broadleafcommerce.cms.web.deeplink.ContentDeepLinkServiceImpl;
import org.broadleafcommerce.common.RequestDTO;
import org.broadleafcommerce.common.TimeDTO;
import org.broadleafcommerce.common.locale.domain.Locale;
import org.broadleafcommerce.common.sandbox.domain.SandBox;
import org.broadleafcommerce.common.structure.dto.StructuredContentDTO;
import org.broadleafcommerce.common.time.SystemTime;
import org.broadleafcommerce.common.web.BroadleafRequestContext;
import org.broadleafcommerce.common.web.deeplink.DeepLink;
import org.broadleafcommerce.common.web.dialect.AbstractModelVariableModifierProcessor;
import org.thymeleaf.Arguments;
import org.thymeleaf.context.IWebContext;
import org.thymeleaf.dom.Element;
import org.thymeleaf.standard.expression.Assignation;
import org.thymeleaf.standard.expression.AssignationSequence;
import org.thymeleaf.standard.expression.AssignationUtils;
import org.thymeleaf.standard.expression.Expression;
import org.thymeleaf.standard.expression.IStandardExpressionParser;
import org.thymeleaf.standard.expression.StandardExpressions;
import com.google.common.primitives.Ints;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.TimeZone;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
/**
* Processor used to display structured content that is maintained with the Broadleaf CMS.
*
* Usage based on the following attributes:<br>
* <ul>
* <li>contentType (required *) - only required if an extension manager is not defined to handle content lookup.
* If the content type is not found, it will try to retrieve content from any registered
* extension handlers.
* Specifies the content you are retrieving.</li>
* <li>contentName - if included will retrieve only content that matches the name. When no name is specified,
* all matching content items of the passed in type are retrieved.</li>
* <li>maxResults - if specified limits the results to a specified number of items. The content will be returned
* according to priority. If content items share the same priority, then they will be returned
* randomly. Consider the example with 5 matching items with priorities (1,2,3,3,3) respectively. If
* the count is set to 3. Items 1 and 2 will ALWAYS be returned. The third item returned will
* randomy rotate through the 3rd, 4th, and 5th item.
* </li>
* <li>contentListVar - allows you to specify an alternate name for the list of content results. By default,
* the results are returned in the page attributed "contentList"</li>
* <li>contentItemVar - since a typical usage is to only return one item, the first item is returned in the
* variable "contentItem". This variable can be used to change the attribute name.</li>
* <li>numResultsVar - variable holding the returns the number of results being returned to through the tag-lib.
* defaults to "numResults".</li>
* <li>fieldFilters - Thymeleaf key-value pair to filter the resulting StructuredContentDTO by particular field values.
* For instance, if you had a field in a piece of structured content called 'featured' and you
* wanted to return all of the featured content items, you could do the following:
*
* <blc:content fieldFilters="featured=${'true'},otherField=${'someValue'}" />
* <li>sorts - sorts to apply to the resulting list of content. These should be key-value pairs corresponding
* where the key is the field to sort and the value is the direction of the sort. If unspecified,
* the default sorting is used (by priority). The sort fields must occur in the dynamic fields
* for that piece of structured content. For instance:
*
* <blc:content sort="dynamicFieldA='DESCENDING',dynamicFieldB='ASCENDING'" />
*
* The list will be sorted first by dynamicFieldA descending and then dynamicFieldB ascending
* </ul>
*/
public class ContentProcessor extends AbstractModelVariableModifierProcessor {
protected final Log LOG = LogFactory.getLog(getClass());
public static final String REQUEST_DTO = "blRequestDTO";
public static final String BLC_RULE_MAP_PARAM = "blRuleMap";
@Resource(name = "blStructuredContentService")
protected StructuredContentService structuredContentService;
@Resource(name = "blStaticAssetService")
protected StaticAssetService staticAssetService;
@Resource(name = "blContentProcessorExtensionManager")
protected ContentProcessorExtensionManager extensionManager;
@Resource(name = "blContentDeepLinkService")
protected ContentDeepLinkServiceImpl contentDeepLinkService;
/**
* Sets the name of this processor to be used in Thymeleaf template
*/
public ContentProcessor() {
super("content");
}
public ContentProcessor(String elementName) {
super(elementName);
}
@Override
public int getPrecedence() {
return 10000;
}
/**
* Returns a default name
* @param element
* @param valueName
* @return
*/
protected String getAttributeValue(Element element, String valueName, String defaultValue) {
String returnValue = element.getAttributeValue(valueName);
if (returnValue == null) {
return defaultValue;
} else {
return returnValue;
}
}
@Override
protected void modifyModelAttributes(final Arguments arguments, Element element) {
String contentType = element.getAttributeValue("contentType");
String contentName = element.getAttributeValue("contentName");
String maxResultsStr = element.getAttributeValue("maxResults");
if (StringUtils.isEmpty(contentType) && StringUtils.isEmpty(contentName)) {
throw new IllegalArgumentException("The content processor must have a non-empty attribute value for 'contentType' or 'contentName'");
}
Integer maxResults = null;
if (maxResultsStr != null) {
maxResults = Ints.tryParse(maxResultsStr);
}
if (maxResults == null) {
maxResults = Integer.MAX_VALUE;
}
String contentListVar = getAttributeValue(element, "contentListVar", "contentList");
String contentItemVar = getAttributeValue(element, "contentItemVar", "contentItem");
String numResultsVar = getAttributeValue(element, "numResultsVar", "numResults");
String fieldFilters = element.getAttributeValue("fieldFilters");
final String sorts = element.getAttributeValue("sorts");
IWebContext context = (IWebContext) arguments.getContext();
HttpServletRequest request = context.getHttpServletRequest();
BroadleafRequestContext blcContext = BroadleafRequestContext.getBroadleafRequestContext();
Map<String, Object> mvelParameters = buildMvelParameters(request, arguments, element);
SandBox currentSandbox = blcContext.getSandBox();
List<StructuredContentDTO> contentItems;
StructuredContentType structuredContentType = null;
if (contentType != null ) {
structuredContentType = structuredContentService.findStructuredContentTypeByName(contentType);
}
Locale locale = blcContext.getLocale();
contentItems = getContentItems(contentName, maxResults, request, mvelParameters, currentSandbox, structuredContentType, locale, arguments, element);
if (contentItems.size() > 0) {
// sort the resulting list by the configured property sorts on the tag
if (StringUtils.isNotEmpty(sorts)) {
Collections.sort(contentItems, new Comparator<StructuredContentDTO>() {
@Override
public int compare(StructuredContentDTO o1, StructuredContentDTO o2) {
AssignationSequence sortAssignments = AssignationUtils.parseAssignationSequence(arguments.getConfiguration(), arguments, sorts, false);
CompareToBuilder compareBuilder = new CompareToBuilder();
for (Assignation sortAssignment : sortAssignments) {
String property = sortAssignment.getLeft().getStringRepresentation();
Object val1 = o1.getPropertyValue(property);
Object val2 = o2.getPropertyValue(property);
if (sortAssignment.getRight().execute(arguments.getConfiguration(), arguments).equals("ASCENDING")) {
compareBuilder.append(val1, val2);
} else {
compareBuilder.append(val2, val1);
}
}
return compareBuilder.toComparison();
}
});
}
List<Map<String, Object>> contentItemFields = new ArrayList<Map<String, Object>>();
for (StructuredContentDTO item : contentItems) {
if (StringUtils.isNotEmpty(fieldFilters)) {
AssignationSequence assignments = AssignationUtils.parseAssignationSequence(arguments.getConfiguration(), arguments, fieldFilters, false);
boolean valid = true;
for (Assignation assignment : assignments) {
if (ObjectUtils.notEqual(assignment.getRight().execute(arguments.getConfiguration(), arguments),
item.getValues().get(assignment.getLeft().getStringRepresentation()))) {
LOG.info("Excluding content " + item.getId() + " based on the property value of " + assignment.getLeft().getStringRepresentation());
valid = false;
break;
}
}
if (valid) {
contentItemFields.add(item.getValues());
}
} else {
contentItemFields.add(item.getValues());
}
}
Map<String, Object> contentItem = null;
if (contentItemFields.size() > 0) {
contentItem = contentItemFields.get(0);
}
addToModel(arguments, contentItemVar, contentItem);
addToModel(arguments, contentListVar, contentItemFields);
addToModel(arguments, numResultsVar, contentItems.size());
} else {
if (LOG.isInfoEnabled()) {
LOG.info("**************************The contentItems is null*************************");
}
addToModel(arguments, contentItemVar, null);
addToModel(arguments, contentListVar, null);
addToModel(arguments, numResultsVar, 0);
}
String deepLinksVar = element.getAttributeValue("deepLinks");
if (StringUtils.isNotBlank(deepLinksVar) && contentItems.size() > 0 ) {
List<DeepLink> links = contentDeepLinkService.getLinks(contentItems.get(0));
extensionManager.getProxy().addExtensionFieldDeepLink(links, arguments, element);
extensionManager.getProxy().postProcessDeepLinks(links);
addToModel(arguments, deepLinksVar, links);
}
}
/**
* @param contentName name of the content to be looked up (can be null)
* @param maxResults maximum results to return
* @param request servlet request
* @param mvelParameters values that should be considered when filtering the content list by rules
* @param structuredContentType the type of content that should be returned
* @param locale current locale
* @param arguments Thymeleaf Arguments passed into the tag
* @param element element context that this Thymeleaf processor is being executed in
* @return
*/
protected List<StructuredContentDTO> getContentItems(String contentName, Integer maxResults, HttpServletRequest request,
Map<String, Object> mvelParameters,
SandBox currentSandbox,
StructuredContentType structuredContentType,
Locale locale, Arguments arguments, Element element) {
List<StructuredContentDTO> contentItems;
if (structuredContentType == null) {
contentItems = structuredContentService.lookupStructuredContentItemsByName(contentName, locale, maxResults, mvelParameters, isSecure(request));
} else {
if (contentName == null || "".equals(contentName)) {
contentItems = structuredContentService.lookupStructuredContentItemsByType(structuredContentType, locale, maxResults, mvelParameters, isSecure(request));
} else {
contentItems = structuredContentService.lookupStructuredContentItemsByName(structuredContentType, contentName, locale, maxResults, mvelParameters, isSecure(request));
}
}
//add additional fields to the model
extensionManager.getProxy().addAdditionalFieldsToModel(arguments, element);
return contentItems;
}
/**
* MVEL is used to process the content targeting rules.
*
* @param request
* @return
*/
protected Map<String, Object> buildMvelParameters(HttpServletRequest request, Arguments arguments, Element element) {
TimeZone timeZone = BroadleafRequestContext.getBroadleafRequestContext().getTimeZone();
final TimeDTO timeDto;
if (timeZone != null) {
timeDto = new TimeDTO(SystemTime.asCalendar(timeZone));
} else {
timeDto = new TimeDTO();
}
RequestDTO requestDto = (RequestDTO) request.getAttribute(REQUEST_DTO);
Map<String, Object> mvelParameters = new HashMap<String, Object>();
mvelParameters.put("time", timeDto);
mvelParameters.put("request", requestDto);
String productString = element.getAttributeValue("product");
if (productString != null) {
final IStandardExpressionParser expressionParser = StandardExpressions.getExpressionParser(arguments.getConfiguration());
Expression expression = (Expression) expressionParser.parseExpression(arguments.getConfiguration(), arguments, productString);
Object product = expression.execute(arguments.getConfiguration(), arguments);
if (product != null) {
mvelParameters.put("product", product);
}
}
String categoryString = element.getAttributeValue("category");
if (categoryString != null) {
final IStandardExpressionParser expressionParser = StandardExpressions.getExpressionParser(arguments.getConfiguration());
Expression expression = (Expression) expressionParser.parseExpression(arguments.getConfiguration(), arguments, productString);
Object category = expression.execute(arguments.getConfiguration(), arguments);
if (category != null) {
mvelParameters.put("category", category);
}
}
@SuppressWarnings("unchecked")
Map<String,Object> blcRuleMap = (Map<String,Object>) request.getAttribute(BLC_RULE_MAP_PARAM);
if (blcRuleMap != null) {
for (String mapKey : blcRuleMap.keySet()) {
mvelParameters.put(mapKey, blcRuleMap.get(mapKey));
}
}
return mvelParameters;
}
public boolean isSecure(HttpServletRequest request) {
boolean secure = false;
if (request != null) {
secure = ("HTTPS".equalsIgnoreCase(request.getScheme()) || request.isSecure());
}
return secure;
}
}