Skip to content

Commit

Permalink
Merge pull request mapfish#488 from NielsCharlier/parallel
Browse files Browse the repository at this point in the history
Parallel download of layer images
  • Loading branch information
Tobias Sauerwein authored Dec 16, 2016
2 parents d97ab43 + 108262e commit b0a71e5
Show file tree
Hide file tree
Showing 16 changed files with 992 additions and 445 deletions.
12 changes: 12 additions & 0 deletions core/src/main/java/org/mapfish/print/attribute/map/MapLayer.java
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package org.mapfish.print.attribute.map;

import com.google.common.base.Optional;

import org.mapfish.print.http.HttpRequestCache;
import org.mapfish.print.http.MfClientHttpRequestFactory;

import java.awt.Graphics2D;
Expand Down Expand Up @@ -121,4 +123,14 @@ void render(
* @return render type
*/
RenderType getRenderType();

/**
* Cache any needed resources on disk.
* @param httpRequestCache TODO
* @param clientHttpRequestFactory client http request factory
* @param transformer transformer
*/
void cacheResources(final HttpRequestCache httpRequestCache,
final MfClientHttpRequestFactory clientHttpRequestFactory,
final MapfishMapContext transformer);
}
218 changes: 218 additions & 0 deletions core/src/main/java/org/mapfish/print/http/HttpRequestCache.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,218 @@
package org.mapfish.print.http;

import com.codahale.metrics.MetricRegistry;
import com.codahale.metrics.Timer;

import jsr166y.ForkJoinPool;
import org.apache.commons.io.IOUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.client.AbstractClientHttpResponse;
import org.springframework.http.client.ClientHttpRequest;
import org.springframework.http.client.ClientHttpResponse;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.URI;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Callable;

/**
*
* Creates tasks for caching Http Requests that can be run simultaneously.
*
*/
public final class HttpRequestCache {

private static final Logger LOGGER = LoggerFactory.getLogger(HttpRequestCache.class);

private class CachedClientHttpResponse extends AbstractClientHttpResponse {

private final File cachedFile;
private final HttpHeaders headers;
private final int status;
private final String statusText;
private InputStream body;

public CachedClientHttpResponse(final ClientHttpResponse originalResponse) throws IOException {
this.headers = originalResponse.getHeaders();
this.status = originalResponse.getRawStatusCode();
this.statusText = originalResponse.getStatusText();
this.cachedFile = File.createTempFile("cacheduri", null, HttpRequestCache.this.temporaryDirectory);
InputStream is = originalResponse.getBody();
try {
OutputStream os = new FileOutputStream(this.cachedFile);
try {
IOUtils.copy(is, os);
} finally {
os.close();
}
} finally {
is.close();
}
}

@Override
public InputStream getBody() throws IOException {
if (this.body == null) {
this.body = new FileInputStream(this.cachedFile);
}
return this.body;
}

@Override
public HttpHeaders getHeaders() {
return this.headers;
}

@Override
public int getRawStatusCode() throws IOException {
return this.status;
}

@Override
public String getStatusText() throws IOException {
return this.statusText;
}

@Override
public void close() {
if (this.body != null) {
try {
this.body.close();
} catch (IOException e) {
throw new IllegalStateException(e);
}
}
}
}

private class CachedClientHttpRequest implements ClientHttpRequest, Callable<Void> {
private final ClientHttpRequest originalRequest;
private CachedClientHttpResponse response;

public CachedClientHttpRequest(final ClientHttpRequest request) throws IOException {
this.originalRequest = request;
}

@Override
public HttpMethod getMethod() {
return this.originalRequest.getMethod();
}

@Override
public URI getURI() {
return this.originalRequest.getURI();
}

@Override
public HttpHeaders getHeaders() {
return this.originalRequest.getHeaders();
}

@Override
public OutputStream getBody() throws IOException {
//body should be written before creating this object
throw new UnsupportedOperationException();
}

@Override
public ClientHttpResponse execute() throws IOException {
if (!HttpRequestCache.this.cached) {
LOGGER.warn("Attempting to load cached URI before actual caching: " + this.originalRequest.getURI());
} else if (this.response == null) {
LOGGER.warn("Attempting to load cached URI from failed request: " + this.originalRequest.getURI());
} else {
LOGGER.debug("Loading cached URI resource " + this.originalRequest.getURI());
}
return this.response;
}

@Override
public Void call() throws Exception {
final String baseMetricName = HttpRequestCache.class.getName() + ".read." + getURI().getHost();
final Timer.Context timerDownload = HttpRequestCache.this.registry.timer(baseMetricName).time();
ClientHttpResponse originalResponse = this.originalRequest.execute();
try {
LOGGER.debug("Caching URI resource " + this.originalRequest.getURI());
this.response = new CachedClientHttpResponse(originalResponse);
} catch (IOException e) {
HttpRequestCache.this.registry.counter(baseMetricName + ".error").inc();
throw e;
} finally {
originalResponse.close();
timerDownload.stop();
}
return null;
}
}

private final List<CachedClientHttpRequest> requests = new ArrayList<CachedClientHttpRequest>();

private final File temporaryDirectory;

private final MetricRegistry registry;

private boolean cached = false;

/**
* Constructor.
*
* @param temporaryDirectory temporary directory for cached requests
* @param registry the metric registry
*/
public HttpRequestCache(final File temporaryDirectory, final MetricRegistry registry) {
this.temporaryDirectory = temporaryDirectory;
this.registry = registry;
}

private CachedClientHttpRequest save(final CachedClientHttpRequest request) {
this.requests.add(request);
return request;
}

/**
* Register a http request for caching. Returns a handle to the HttpRequest that will be cached.
*
* @param originalRequest the original request
* @return the cached http request
* @throws IOException
*/
public ClientHttpRequest register(final ClientHttpRequest originalRequest) throws IOException {
return save(new CachedClientHttpRequest(originalRequest));
}

/**
* Register a URI for caching. Returns a handle to the HttpRequest that will be cached.
*
* @param factory the request factory
* @param uri the uri
* @return the cached http request
* @throws IOException
*/
public ClientHttpRequest register(final MfClientHttpRequestFactory factory, final URI uri) throws IOException {
return register(factory.createRequest(uri, HttpMethod.GET));
}

/**
* Cache all requests at once.
*
* @param requestForkJoinPool request fork join pool
*/
public void cache(final ForkJoinPool requestForkJoinPool) {
if (!this.cached) {
requestForkJoinPool.invokeAll(this.requests);
this.cached = true;
} else {
LOGGER.warn("Attempting to cache twice!");
}
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
import org.mapfish.print.attribute.map.MapBounds;
import org.mapfish.print.attribute.map.MapLayer;
import org.mapfish.print.attribute.map.MapfishMapContext;
import org.mapfish.print.http.HttpRequestCache;
import org.mapfish.print.http.MfClientHttpRequestFactory;
import org.mapfish.print.map.AbstractLayerParams;

Expand Down Expand Up @@ -57,35 +58,22 @@ public final Optional<MapLayer> tryAddLayer(final MapLayer newLayer) {

@Override
public void prepareRender(final MapfishMapContext transformer) {

}

@Override
public final void render(
final Graphics2D graphics2D,
final MfClientHttpRequestFactory clientHttpRequestFactory,
final MapfishMapContext transformer) {
Rectangle paintArea = new Rectangle(transformer.getMapSize());
MapBounds bounds = transformer.getBounds();

MapfishMapContext layerTransformer = transformer;

MapfishMapContext layerTransformer = getLayerTransformer(transformer);

if (!FloatingPointUtil.equals(transformer.getRotation(), 0.0) && !this.supportsNativeRotation()) {
// if a rotation is set and the rotation can not be handled natively
// by the layer, we have to adjust the bounds and map size
final Rectangle2D.Double paintAreaPrecise = transformer.getRotatedMapSizePrecise();
paintArea = new Rectangle(MapfishMapContext.rectangleDoubleToDimension(paintAreaPrecise));
bounds = transformer.getRotatedBounds(paintAreaPrecise, paintArea);
graphics2D.setTransform(transformer.getTransform());
Dimension mapSize = new Dimension(paintArea.width, paintArea.height);
layerTransformer = new MapfishMapContext(
transformer, bounds, mapSize,
0, false,
transformer.getDPI(),
transformer.getRequestorDPI(),
transformer.isForceLongitudeFirst(),
transformer.isDpiSensitiveStyle()
);
}

Rectangle paintArea = new Rectangle(layerTransformer.getMapSize());
MapContent content = new MapContent();
try {
List<? extends Layer> layers = getLayers(clientHttpRequestFactory, layerTransformer);
Expand Down Expand Up @@ -126,7 +114,8 @@ public final void render(
renderer.setMapContent(content);
renderer.setThreadPool(this.executorService);

final ReferencedEnvelope mapArea = bounds.toReferencedEnvelope(paintArea, transformer.getDPI());
final ReferencedEnvelope mapArea = layerTransformer.getBounds().toReferencedEnvelope(paintArea,
transformer.getDPI());
renderer.paint(graphics2D, paintArea, mapArea);
} catch (Exception e) {
throw ExceptionUtils.getRuntimeException(e);
Expand Down Expand Up @@ -175,4 +164,34 @@ public final String getName() {
public final boolean getFailOnError() {
return this.params.failOnError;
}

/**
* If the layer transformer has not been prepared yet, do it.
*
* @param transformer the transformer
*/
protected final MapfishMapContext getLayerTransformer(final MapfishMapContext transformer) {
Rectangle paintArea = new Rectangle(transformer.getMapSize());
MapfishMapContext layerTransformer = transformer;

if (!FloatingPointUtil.equals(transformer.getRotation(), 0.0) && !this.supportsNativeRotation()) {
// if a rotation is set and the rotation can not be handled natively
// by the layer, we have to adjust the bounds and map size
MapBounds bounds = transformer.getBounds();
final Rectangle2D.Double paintAreaPrecise = transformer.getRotatedMapSizePrecise();
paintArea = new Rectangle(MapfishMapContext.rectangleDoubleToDimension(paintAreaPrecise));
bounds = transformer.getRotatedBounds(paintAreaPrecise, paintArea);
Dimension mapSize = new Dimension(paintArea.width, paintArea.height);
layerTransformer = new MapfishMapContext(transformer, bounds, mapSize, 0, false, transformer.getDPI(),
transformer.getRequestorDPI(), transformer.isForceLongitudeFirst(),
transformer.isDpiSensitiveStyle());
}

return layerTransformer;
}

@Override
public void cacheResources(final HttpRequestCache httpRequestCache,
final MfClientHttpRequestFactory clientHttpRequestFactory, final MapfishMapContext transformer) {
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import org.mapfish.print.Constants;
import org.mapfish.print.attribute.map.MapLayer;
import org.mapfish.print.attribute.map.MapfishMapContext;
import org.mapfish.print.http.HttpRequestCache;
import org.mapfish.print.http.MfClientHttpRequestFactory;
import org.mapfish.print.map.geotools.AbstractFeatureSourceLayer;
import org.mapfish.print.map.geotools.FeatureSourceSupplier;
Expand Down Expand Up @@ -150,4 +151,10 @@ List<? extends Layer> getLayers(final MfClientHttpRequestFactory httpRequestFact
public RenderType getRenderType() {
return this.grid.getRenderType();
}

@Override
public void cacheResources(final HttpRequestCache httpRequestCache,
final MfClientHttpRequestFactory clientHttpRequestFactory, final MapfishMapContext transformer) {
this.grid.cacheResources(httpRequestCache, clientHttpRequestFactory, transformer);
}
}
Loading

0 comments on commit b0a71e5

Please sign in to comment.