Package org.broadleafcommerce.core.web.processor

Source Code of org.broadleafcommerce.core.web.processor.BroadleafCacheProcessor

/*
* #%L
* BroadleafCommerce Framework Web
* %%
* Copyright (C) 2009 - 2014 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.core.web.processor;

import net.sf.ehcache.Cache;
import net.sf.ehcache.CacheManager;

import org.apache.commons.lang.StringUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.broadleafcommerce.common.config.service.SystemPropertiesService;
import org.broadleafcommerce.common.web.BroadleafRequestContext;
import org.broadleafcommerce.core.web.service.SimpleCacheKeyResolver;
import org.broadleafcommerce.core.web.service.TemplateCacheKeyResolverService;
import org.springframework.web.context.request.WebRequest;
import org.thymeleaf.Arguments;
import org.thymeleaf.dom.Attribute;
import org.thymeleaf.dom.Element;
import org.thymeleaf.processor.ProcessorResult;
import org.thymeleaf.processor.attr.AbstractAttrProcessor;
import org.thymeleaf.standard.expression.Expression;
import org.thymeleaf.standard.expression.StandardExpressions;
import org.thymeleaf.standard.processor.attr.StandardFragmentAttrProcessor;

import java.util.Set;

import javax.annotation.Resource;

/**
* <p>
* Allows for a customizable cache mechanism that can be used to avoid expensive Thymeleaf processing for
* HTML fragments that are static. For high volume sites, even a 30 second cache of pages can have significant overall
* performance impacts.
*
* <p>
* When used as in conjunction within a {@code th:substituteby}, {@code th:replace} or {@code th:include} attribute this
* will cache the template being included. If used in conjunction with {@link th:remove} this will cache all of the child
* nodes of the element. If neither of these cases are true, this will cache the current node and all children.
*
* <p>
* The parameters allowed for this processor include a "cacheTimeout" and "cacheKey". This component will rely on
* an implementation of {@link TemplateCacheKeyResolverService} to build the actual cacheKey used by the underlying
* caching implementation.   The parameter named "cacheKey" will be used in the construction of the actual cacheKey
* which may rely on variables like
*
* <p>
* Implementors can create more functional cacheKey mechanisms. For example, Broadleaf Enterprise provides an
* additional implementation named {@code EnterpriseCacheKeyResolver} with support for additional caching
* features.
*
* @param cacheTimeout (optional) the maximum length of time that the fragment will be allowed to be cached.   This
* is important for fragments for which a good cacheKey would be difficult to generate.
* @param cacheKey (optional) Thymeleaf expression that should contribute to the final cache key to reference the cached
* element. The final key is determined by the {@link TemplateCacheKeyResolverService} but it is not required.
* Implementations of {@link TemplateCacheKeyResolverService} can rely on variables like the customer, site, theme, etc. to
* build the final cacheKey.
* @author bpolster
* @see {@link TemplateCacheKeyResolverService}
* @see {@link SimpleCacheKeyResolver}
*/
public class BroadleafCacheProcessor extends AbstractAttrProcessor {

    private static final Log LOG = LogFactory.getLog(BroadleafCacheProcessor.class);

    public static final String ATTR_NAME = "cache";

    protected Cache cache;

    @Resource(name = "blSystemPropertiesService")
    protected SystemPropertiesService systemPropertiesService;

    @Resource(name = "blTemplateCacheKeyResolver")
    protected TemplateCacheKeyResolverService cacheKeyResolver;

    public BroadleafCacheProcessor() {
        super(ATTR_NAME);
    }

    public void fixElement(Element element, Arguments arguments) {
        boolean elementAdded = false;
        boolean removeElement = false;
        Set<String> attributeNames = element.getAttributeMap().keySet();

        for (String a : attributeNames) {
            String attrName = a.toLowerCase();
            if (attrName.startsWith("th")) {
                if (attrName.equals("th:substituteby") || (attrName.equals("th:replace") || attrName.equals("th:include"))) {
                    if (!elementAdded) {
                        Element extraDiv = new Element("div");
                        String attrValue = element.getAttributeValue(attrName);
                        element.removeAttribute(attrName);
                        extraDiv.setAttribute(attrName, attrValue);
                        element.addChild(extraDiv);
                        elementAdded = true;
                        element.setNodeProperty("templateName", attrValue);

                        // This will ensure that the substituteby and replace processors only run for the child element
                        element.setRecomputeProcessorsImmediately(true);
                    }
                } else if (attrName.equals("th:remove")) {
                    Attribute attr = element.getAttributeMap().get(attrName);
                    if ("tag".equals(attr.getValue())) {
                        removeElement = true;

                        // The cache functionality will remove the element.
                        element.setAttribute(attrName, "none");
                    }
                }
            }
        }

        if (!elementAdded || removeElement) {
            element.setNodeProperty("blcOutputParentNode", Boolean.TRUE);
        }
    }

    protected boolean shouldCache(Arguments args, Element element, String attributeName) {
        String cacheAttrValue = element.getAttributeValue(attributeName);
        element.removeAttribute(attributeName);

        if (StringUtils.isEmpty(cacheAttrValue)) {
            return false;
        }

        cacheAttrValue = cacheAttrValue.toLowerCase();
        if (!isCachingEnabled() || "false".equals(cacheAttrValue)) {
            return false;
        } else if ("true".equals(cacheAttrValue)) {
            return true;
        }

        // Check for an expression
        Expression expression = (Expression) StandardExpressions.getExpressionParser(args.getConfiguration())
                .parseExpression(args.getConfiguration(), args, cacheAttrValue);
        Object o = expression.execute(args.getConfiguration(), args);
        if (o instanceof Boolean) {
            return (Boolean) o;
        } else if (o instanceof String) {
            cacheAttrValue = (String) o;
            cacheAttrValue = cacheAttrValue.toLowerCase();
            return "true".equals(cacheAttrValue);
        }
        return false;
    }

    @Override
    public ProcessorResult processAttribute(final Arguments arguments, final Element element, String attributeName) {
        if (shouldCache(arguments, element, attributeName)) {
            fixElement(element, arguments);
            if (checkCacheForElement(arguments, element)) {
                // This template has been cached.
                element.clearChildren();
                element.clearAttributes();
                element.setRecomputeProcessorsImmediately(true);
            }
        }
        return ProcessorResult.OK;
    }

    /**
     * If this template was found in cache, adds the response to the element and returns true.
     *
     * If not found in cache, adds the cacheKey to the element so that the Writer can cache after the
     * first process.
     *
     * @param arguments
     * @param element
     * @return
     */
    protected boolean checkCacheForElement(Arguments arguments, Element element) {

        if (isCachingEnabled()) {
            String cacheKey = cacheKeyResolver.resolveCacheKey(arguments, element);
   
            if (!StringUtils.isEmpty(cacheKey)) {
                element.setNodeProperty("cacheKey", cacheKey);
   
                net.sf.ehcache.Element cacheElement = getCache().get(cacheKey);
                if (cacheElement != null && !checkExpired(element, cacheElement)) {
                    if (LOG.isTraceEnabled()) {
                        LOG.trace("Template Cache Hit with cacheKey " + cacheKey + " found in cache.");
                    }
                    element.setNodeProperty("blCacheResponse", cacheElement.getObjectValue());
                    return true;
                } else {
                    if (LOG.isTraceEnabled()) {
                        LOG.trace("Template Cache Miss with cacheKey " + cacheKey + " not found in cache.");
                    }
                }
            } else {
                if (LOG.isTraceEnabled()) {
                    LOG.trace("Template not cached due to empty cacheKey");
                }
            }
        } else {
            if (LOG.isTraceEnabled()) {
                LOG.trace("Template caching disabled - not retrieving template from cache");
            }
        }
        return false;
    }

    /**
     * Returns true if the item has been
     * @param element
     * @param cacheElement
     * @return
     */
    protected boolean checkExpired(Element element, net.sf.ehcache.Element cacheElement) {
        if (cacheElement.isExpired()) {
            return true;
        } else {
            String cacheTimeout = element.getAttributeValue("cacheTimeout");
            if (!StringUtils.isEmpty(cacheTimeout) && StringUtils.isNumeric(cacheTimeout)) {
                Long timeout = Long.valueOf(cacheTimeout) * 1000;
                Long expiryTime = cacheElement.getCreationTime() + timeout;
                if (expiryTime < System.currentTimeMillis()) {
                    return true;
                }
            }
        }
        return false;
    }

    protected String getFragmentSignatureUnprefixedAttributeName(final Arguments arguments, final Element element,
            final String attributeName, final String attributeValue) {
        return StandardFragmentAttrProcessor.ATTR_NAME;
    }

    @Override
    public int getPrecedence() {
        return Integer.MIN_VALUE;
    }

    public Cache getCache() {
        if (cache == null) {
            cache = CacheManager.getInstance().getCache("blTemplateElements");
        }
        return cache;
    }

    public void setCache(Cache cache) {
        this.cache = cache;
    }

    public boolean isCachingEnabled() {
        boolean disabled = !systemPropertiesService.resolveBooleanSystemProperty("disableThymeleafTemplateCaching");
        if (!disabled) {
            // check for a URL param that overrides caching - useful for testing if this processor is incorrectly
            // caching a page (possibly due to an bad cacheKey).

            BroadleafRequestContext brc = BroadleafRequestContext.getBroadleafRequestContext();
            if (brc != null && brc.getWebRequest() != null) {
                WebRequest request = brc.getWebRequest();
                String disableCachingParam = request.getParameter("disableThymeleafTemplateCaching");
                if ("true".equals(disableCachingParam)) {
                    return false;
                }
            }
        }
        return disabled;
    }
}
TOP

Related Classes of org.broadleafcommerce.core.web.processor.BroadleafCacheProcessor

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.