/* * JBoss, Home of Professional Open Source * Copyright 2013, Red Hat, Inc. and individual contributors * by the @authors tag. See the copyright.txt in the distribution for a * full listing of individual contributors. * * This is free software; you can redistribute it and/or modify it * under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation; either version 2.1 of * the License, or (at your option) any later version. * * This software is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this software; if not, write to the Free * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA * 02110-1301 USA, or see the FSF site: http://www.fsf.org. */ package org.richfaces.resource; import static org.richfaces.application.configuration.ConfigurationServiceHelper.getLongConfigurationValue; import static org.richfaces.resource.ResourceUtils.millisToSecond; import static org.richfaces.resource.ResourceUtils.secondToMillis; import java.util.Date; import java.util.Map; import javax.faces.context.ExternalContext; import javax.faces.context.FacesContext; import org.richfaces.application.CoreConfiguration; import org.richfaces.log.Logger; import org.richfaces.log.RichfacesLogger; /** * @author Nick Belaevski * @since 4.0 */ public abstract class AbstractCacheableResource extends AbstractBaseResource implements CacheableResource { private static final Logger LOGGER = RichfacesLogger.RESOURCE.getLogger(); private boolean cacheable = true; public boolean isCacheable(FacesContext context) { return cacheable; } // TODO add getExpired(FacesContext) for HTTP matching headers? private static boolean isUserCopyActual(Date lastModified, Date modifiedCondition) { // 1000 ms due to round modification time to seconds. return (lastModified.getTime() - modifiedCondition.getTime()) <= 1000; } @Deprecated protected Boolean isMatchesLastModified(FacesContext context) { ExternalContext externalContext = context.getExternalContext(); Map<String, String> requestHeaderMap = externalContext.getRequestHeaderMap(); String modifiedCondition = requestHeaderMap.get("If-Modified-Since"); if (modifiedCondition == null) { return null; } return isMatchesLastModified(context, modifiedCondition); } protected boolean isMatchesLastModified(FacesContext context, String modifiedCondition) { Date lastModified = getLastModified(context); if (lastModified == null) { return false; } return isUserCopyActual(lastModified, ResourceUtils.parseHttpDate(modifiedCondition)); } @Deprecated protected Boolean isMatchesEntityTag(FacesContext context) { ExternalContext externalContext = context.getExternalContext(); Map<String, String> requestHeaderMap = externalContext.getRequestHeaderMap(); String matchHeaderValue = requestHeaderMap.get("If-None-Match"); if (matchHeaderValue == null) { return null; } return isMatchesEntityTag(context, matchHeaderValue); } protected boolean isMatchesEntityTag(FacesContext context, String matchHeaderValue) { String resourceEntityTag = getEntityTag(context); if (resourceEntityTag == null) { return false; } return ResourceUtils.matchTag(resourceEntityTag, matchHeaderValue); } public void setCacheable(boolean cacheable) { this.cacheable = cacheable; } @Override public boolean userAgentNeedsUpdate(FacesContext context) { if (!isCacheable(context)) { if (LOGGER.isDebugEnabled()) { LOGGER.debug("User agent cache check: resource is not cacheable"); } return true; } ExternalContext externalContext = context.getExternalContext(); Map<String, String> requestHeaderMap = externalContext.getRequestHeaderMap(); String modifiedCondition = requestHeaderMap.get("If-Modified-Since"); String matchHeaderValue = requestHeaderMap.get("If-None-Match"); if ((modifiedCondition == null) && (matchHeaderValue == null)) { if (LOGGER.isDebugEnabled()) { LOGGER.debug("User agent cache check: no cache information was provided in request"); } return true; } if ((matchHeaderValue != null) && !isMatchesEntityTag(context, matchHeaderValue)) { if (LOGGER.isDebugEnabled()) { LOGGER.debug("User agent cache check: entity tags don't match"); } return true; } if ((modifiedCondition != null) && !isMatchesLastModified(context, modifiedCondition)) { if (LOGGER.isDebugEnabled()) { LOGGER.debug("User agent cache check: resource was modified since the last request"); } return true; } return false; } /** * <b>IMPORTANT:</b> this method returned TTL in RF 3.x, now it returns expiration time * * @return Returns the expired. */ public Date getExpires(FacesContext context) { return null; } public int getTimeToLive(FacesContext context) { return 0; } public String getEntityTag(FacesContext context) { int contentLength = getContentLength(context); Date lastModified = getLastModified(context); if ((contentLength < 0) || (lastModified == null)) { return null; } return ResourceUtils.formatWeakTag(contentLength + "-" + lastModified.getTime()); } @Override protected void addCacheControlResponseHeaders(FacesContext facesContext, Map<String, String> headers) { if (isCacheable(facesContext)) { long currentTime = getCurrentTime(); String formattedExpireDate; long maxAge = getTimeToLive(facesContext); if (maxAge > 0) { formattedExpireDate = ResourceUtils.formatHttpDate(currentTime + secondToMillis(maxAge)); } else { Date expired = getExpires(facesContext); if (expired != null) { formattedExpireDate = ResourceUtils.formatHttpDate(expired); maxAge = millisToSecond(expired.getTime() - currentTime); } else { maxAge = getLongConfigurationValue(facesContext, CoreConfiguration.Items.resourcesTTL); formattedExpireDate = ResourceUtils.formatHttpDate(currentTime + secondToMillis(maxAge)); } } if (formattedExpireDate != null) { headers.put("Expires", formattedExpireDate); } if (maxAge > 0) { headers.put("Cache-Control", "max-age=" + maxAge); } String entityTag = getEntityTag(facesContext); if (entityTag != null) { headers.put("ETag", entityTag); } } else { addNoCacheResponseHeaders(facesContext, headers); } } }