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