/* Copyright (c) 2011 TOPP - www.openplans.org. All rights reserved.
* This code is licensed under the GPL 2.0 license, available at the root
* application directory.
*/
package org.geoserver.gwc.wms;
import java.io.ByteArrayOutputStream;
import java.lang.reflect.Method;
import java.nio.channels.Channels;
import java.security.MessageDigest;
import java.util.Arrays;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.servlet.http.HttpServletResponse;
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
import org.geoserver.gwc.GWC;
import org.geoserver.ows.Dispatcher;
import org.geoserver.ows.HttpErrorCodeException;
import org.geoserver.wms.GetMapRequest;
import org.geoserver.wms.WebMap;
import org.geoserver.wms.WebMapService;
import org.geoserver.wms.map.RawMap;
import org.geotools.util.logging.Logging;
import org.geowebcache.conveyor.ConveyorTile;
import org.geowebcache.io.ByteArrayResource;
import org.geowebcache.io.Resource;
import org.springframework.util.Assert;
/**
* {@link WebMapService#getMap(GetMapRequest)} Spring's AOP method interceptor to serve cached tiles
* whenever the request matches a GeoWebCache tile.
*
* @author Gabriel Roldan
*
*/
public class CachingWebMapService implements MethodInterceptor {
private static final Logger LOGGER = Logging.getLogger(CachingWebMapService.class);
private GWC gwc;
public CachingWebMapService(GWC gwc) {
this.gwc = gwc;
}
/**
* Wraps {@link WebMapService#getMap(GetMapRequest)}, called by the {@link Dispatcher}
*
* @see WebMapService#getMap(GetMapRequest)
* @see org.aopalliance.intercept.MethodInterceptor#invoke(org.aopalliance.intercept.MethodInvocation)
*/
public WebMap invoke(MethodInvocation invocation) throws Throwable {
if (!gwc.getConfig().isDirectWMSIntegrationEnabled()) {
return (WebMap) invocation.proceed();
}
final Method method = invocation.getMethod();
Assert.isTrue(method.getDeclaringClass().equals(WebMapService.class));
Assert.isTrue("getMap".equals(method.getName()));
final Object[] arguments = invocation.getArguments();
Assert.isTrue(arguments.length == 1);
Assert.isInstanceOf(GetMapRequest.class, arguments[0]);
final GetMapRequest request = (GetMapRequest) arguments[0];
boolean tiled = request.isTiled();
if (!tiled) {
return (WebMap) invocation.proceed();
}
ConveyorTile cachedTile = gwc.dispatch(request);
if (cachedTile != null) {
if (LOGGER.isLoggable(Level.FINEST)) {
LOGGER.finest("GetMap request intercepted, serving cached content: " + request);
}
final byte[] tileBytes;
{
final Resource mapContents = cachedTile.getBlob();
if (mapContents instanceof ByteArrayResource) {
tileBytes = ((ByteArrayResource) mapContents).getContents();
} else {
ByteArrayOutputStream out = new ByteArrayOutputStream();
mapContents.transferTo(Channels.newChannel(out));
tileBytes = out.toByteArray();
}
}
// Handle Etags
final String ifNoneMatch = request.getHttpRequestHeader("If-None-Match");
final byte[] hash = MessageDigest.getInstance("MD5").digest(tileBytes);
final String etag = toHexString(hash);
if (etag.equals(ifNoneMatch)) {
// Client already has the current version
LOGGER.finer("ETag matches, returning 304");
throw new HttpErrorCodeException(HttpServletResponse.SC_NOT_MODIFIED);
}
LOGGER.finer("No matching ETag, returning cached tile");
final String mimeType = cachedTile.getMimeType().getMimeType();
RawMap map = new RawMap(null, tileBytes, mimeType);
map.setResponseHeader("Cache-Control", "no-cache");
map.setResponseHeader("ETag", etag);
map.setResponseHeader("geowebcache-tile-index",
Arrays.toString(cachedTile.getTileIndex()));
map.setContentDispositionHeader(null, "." + cachedTile.getMimeType().getFileExtension());
return map;
}
return (WebMap) invocation.proceed();
}
private String toHexString(byte[] hash) {
StringBuilder sb = new StringBuilder();
for (int i = 0; i < hash.length; i += 4) {
int c1 = 0xFF & hash[i];
int c2 = 0xFF & hash[i + 1];
int c3 = 0xFF & hash[i + 2];
int c4 = 0xFF & hash[i + 3];
int integer = ((c1 << 24) + (c2 << 16) + (c3 << 8) + (c4 << 0));
sb.append(Integer.toHexString(integer));
}
return sb.toString();
}
}