/* (c) 2016 Open Source Geospatial Foundation - all rights reserved * This code is licensed under the GPL 2.0 license, available at the root * application directory. */ package org.geoserver.config; import java.io.IOException; import java.io.OutputStream; import java.util.Map; import java.util.logging.Level; import java.util.logging.Logger; import org.apache.commons.lang.ArrayUtils; import org.geoserver.config.impl.GeoServerLifecycleHandler; import org.geoserver.ows.AbstractDispatcherCallback; import org.geoserver.ows.HttpErrorCodeException; import org.geoserver.ows.Request; import org.geoserver.ows.Response; import org.geoserver.platform.GeoServerExtensions; import org.geoserver.platform.Operation; import org.geoserver.platform.ServiceException; import org.geotools.util.logging.Logging; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpStatus; /** * Adds proper caching headers to capabilites response clients paying attention to HTTP headers do * not think they are cacheable * * The callback can be turned off by setting "CAPABILITIES_CACHE_CONTROL_ENABLED" to "false", either * as a system, environment or servlet context variable. * * @author Andrea Aime - GeoSolutions */ public class CapabilitiesCacheHeadersCallback extends AbstractDispatcherCallback { static final Logger LOGGER = Logging.getLogger(CapabilitiesCacheHeadersCallback.class); boolean capabilitiesCacheHeadersEnabled; GeoServer gs; public CapabilitiesCacheHeadersCallback(GeoServer gs) { this.gs = gs; // initialize headers processing by grabbing the default from a property final String value = GeoServerExtensions.getProperty("CAPABILITIES_CACHE_CONTROL_ENABLED"); if(value != null) { capabilitiesCacheHeadersEnabled = Boolean.parseBoolean(value); } else { capabilitiesCacheHeadersEnabled = true; } if (LOGGER.isLoggable(Level.FINE)) { LOGGER.fine("Cache control for capabilities requests and 304 support is enabled: " + capabilitiesCacheHeadersEnabled); } } @Override public Response responseDispatched(Request request, Operation operation, Object result, Response response) { if (handleCachingHeaders(request)) { return new RevalidateTagResponse(response); } return response; } /** * Returns true if the caching headers are enabled and the request is a GetCapabilities one * @param request * @return */ private boolean handleCachingHeaders(Request request) { return capabilitiesCacheHeadersEnabled && "GetCapabilities".equalsIgnoreCase(request.getRequest()); } /** * Returns true if the callback will handle cache headers in GetCapabilities requests/responses * @return */ public boolean isCapabilitiesCacheHeadersEnabled() { return capabilitiesCacheHeadersEnabled; } /** * Enables/disables the caching headers processing for this callback * * @param capabilitiesCacheHeadersEnabled */ public void setCapabilitiesCacheHeadersEnabled(boolean capabilitiesCacheHeadersEnabled) { this.capabilitiesCacheHeadersEnabled = capabilitiesCacheHeadersEnabled; } /** * A Response wrapper adding caching headers on demand * @author aaime */ private class RevalidateTagResponse extends Response { Response delegate; public RevalidateTagResponse(Response delegate) { super(delegate.getBinding()); this.delegate = delegate; } public boolean canHandle(Operation operation) { return delegate.canHandle(operation); } public String getMimeType(Object value, Operation operation) throws ServiceException { return delegate.getMimeType(value, operation); } /** * See if we have to add cache control headers. Won't alter them if the response already set them. */ public String[][] getHeaders(Object value, Operation operation) throws ServiceException { String[][] headers = delegate.getHeaders(value, operation); if (headers == null) { // if no headers at all, add and exit return new String[][] { { HttpHeaders.CACHE_CONTROL, "max-age=0, must-revalidate" }}; } else { // will add only if not already there Map<String, String> map = ArrayUtils.toMap(headers); map.putIfAbsent(HttpHeaders.CACHE_CONTROL, "max-age=0, must-revalidate"); headers = new String[map.size()][2]; int i = 0; for (Map.Entry<String, String> entry : map.entrySet()) { headers[i][0] = entry.getKey(); headers[i][1] = entry.getValue(); i++; } } return headers; } public void write(Object value, OutputStream output, Operation operation) throws IOException, ServiceException { delegate.write(value, output, operation); } public String getPreferredDisposition(Object value, Operation operation) { return delegate.getPreferredDisposition(value, operation); } public String getAttachmentFileName(Object value, Operation operation) { return delegate.getAttachmentFileName(value, operation); } public String getCharset(Operation operation) { return delegate.getCharset(operation); } } }