|
28 | 28 | import java.io.UncheckedIOException; |
29 | 29 | import java.time.OffsetDateTime; |
30 | 30 | import java.util.ArrayList; |
| 31 | +import java.util.Collections; |
31 | 32 | import java.util.Iterator; |
32 | 33 | import java.util.List; |
33 | 34 | import java.util.Map; |
| 35 | +import java.util.NoSuchElementException; |
34 | 36 | import java.util.Optional; |
35 | 37 | import java.util.Properties; |
36 | 38 | import java.util.concurrent.Callable; |
|
39 | 41 | import java.util.regex.Pattern; |
40 | 42 | import java.util.stream.Collectors; |
41 | 43 | import java.util.stream.Stream; |
| 44 | +import java.util.stream.StreamSupport; |
42 | 45 | import javax.annotation.Nullable; |
43 | 46 | import org.geotools.util.logging.Logging; |
44 | 47 | import org.geowebcache.filter.parameters.ParametersUtils; |
@@ -187,66 +190,101 @@ public boolean delete(TileObject obj) throws StorageException { |
187 | 190 |
|
188 | 191 | @Override |
189 | 192 | public boolean delete(TileRange tileRange) throws StorageException { |
190 | | - // see if there is anything to delete in that range by computing a prefix |
191 | | - final String coordsPrefix = keyBuilder.coordinatesPrefix(tileRange, false); |
192 | | - if (!client.prefixExists(coordsPrefix)) { |
193 | | - return false; |
194 | | - } |
| 193 | + try { |
| 194 | + // see if there is anything to delete in that range by computing a prefix |
| 195 | + final String coordsPrefix = keyBuilder.coordinatesPrefix(tileRange, false); |
| 196 | + if (!client.prefixExists(coordsPrefix)) { |
| 197 | + return false; |
| 198 | + } |
195 | 199 |
|
196 | | - List<BlobItem> blobsToDelete = findTileBlobsToDelete(tileRange, coordsPrefix); |
| 200 | + findTileBlobsToDelete(tileRange, coordsPrefix).forEach(blobs -> deleteTileBlobs(blobs, tileRange)); |
197 | 201 |
|
198 | | - if (listeners.isEmpty()) { |
199 | | - // if there are no listeners, don't bother requesting every tile |
200 | | - // metadata to notify the listeners |
201 | | - // split the iteration in parts to avoid memory accumulation |
202 | | - List<String> keysToDelete = |
203 | | - blobsToDelete.stream().map(BlobItem::getName).collect(Collectors.toList()); |
| 202 | + return true; |
204 | 203 |
|
205 | | - Iterator<List<String>> partition = Iterators.partition(keysToDelete.iterator(), DeleteManager.PAGE_SIZE); |
| 204 | + } catch (RuntimeException e) { |
| 205 | + throw new StorageException("Failed to delete tile range", e); |
| 206 | + } |
| 207 | + } |
206 | 208 |
|
207 | | - while (partition.hasNext() && !shutDown) { |
208 | | - deleteManager.deleteParallel(partition.next()); |
209 | | - } |
| 209 | + /** Splits the deletion in parts to avoid memory accumulation. */ |
| 210 | + private Stream<List<BlobItem>> findTileBlobsToDelete(TileRange tileRange, String coordsPrefix) { |
210 | 211 |
|
211 | | - } else { |
212 | | - // if we need to gather info, we'll end up just calling "delete" on each tile |
213 | | - // this is run here instead of inside the delete manager as we need high level info |
214 | | - // about tiles, e.g., TileObject, to inform the listeners |
215 | | - List<Callable<?>> tilesDeletions = blobsToDelete.stream() |
216 | | - .map(blobItem -> { |
217 | | - TileObject tile = createTileObject(blobItem, tileRange); |
218 | | - tile.setParametersId(tileRange.getParametersId()); |
219 | | - return (Callable<Object>) () -> delete(tile); |
220 | | - }) |
221 | | - .collect(Collectors.toList()); |
222 | | - |
223 | | - executeParallelDeletions(tilesDeletions); |
224 | | - } |
| 212 | + Iterable<List<BlobItem>> iterable = () -> new Iterator<>() { |
| 213 | + private final int zoomStart = tileRange.getZoomStart(); |
| 214 | + private final int zoomStop = tileRange.getZoomStop(); |
| 215 | + private int currentZoom = zoomStart; |
| 216 | + private Iterator<BlobItem> currentBlobIterator = Collections.emptyIterator(); |
225 | 217 |
|
226 | | - return true; |
227 | | - } |
| 218 | + @Override |
| 219 | + public boolean hasNext() { |
| 220 | + while (!currentBlobIterator.hasNext() && currentZoom <= zoomStop) { |
| 221 | + String zoomPrefix = coordsPrefix + "/" + currentZoom; |
| 222 | + |
| 223 | + if (!client.prefixExists(zoomPrefix)) { |
| 224 | + // empty level, skipping |
| 225 | + currentZoom++; |
| 226 | + continue; |
| 227 | + } |
228 | 228 |
|
229 | | - private List<BlobItem> findTileBlobsToDelete(TileRange tileRange, String coordsPrefix) { |
| 229 | + long[] rangeBoundsAtZoom = tileRange.rangeBounds(currentZoom); |
| 230 | + currentBlobIterator = client.listBlobs(zoomPrefix) |
| 231 | + .filter(tb -> isTileBlobInBounds(tb, rangeBoundsAtZoom)) |
| 232 | + .iterator(); |
230 | 233 |
|
231 | | - List<BlobItem> blobsToDelete = new ArrayList<>(); |
| 234 | + currentZoom++; |
| 235 | + } |
232 | 236 |
|
233 | | - for (int zoom = tileRange.getZoomStart(); zoom <= tileRange.getZoomStop(); zoom++) { |
| 237 | + return currentBlobIterator.hasNext(); |
| 238 | + } |
234 | 239 |
|
235 | | - String zoomPrefix = coordsPrefix + "/" + zoom; |
| 240 | + @Override |
| 241 | + public List<BlobItem> next() { |
| 242 | + if (!hasNext()) { |
| 243 | + throw new NoSuchElementException(); |
| 244 | + } |
236 | 245 |
|
237 | | - if (!client.prefixExists(zoomPrefix)) { |
238 | | - // empty level, skipping |
239 | | - continue; |
| 246 | + List<BlobItem> batch = new ArrayList<>(DeleteManager.PAGE_SIZE); |
| 247 | + while (currentBlobIterator.hasNext() && batch.size() < DeleteManager.PAGE_SIZE) { |
| 248 | + batch.add(currentBlobIterator.next()); |
| 249 | + } |
| 250 | + return batch; |
240 | 251 | } |
| 252 | + }; |
241 | 253 |
|
242 | | - long[] rangeBoundsAtZoom = tileRange.rangeBounds(zoom); |
| 254 | + return StreamSupport.stream(iterable.spliterator(), false); |
| 255 | + } |
243 | 256 |
|
244 | | - client.listBlobs(zoomPrefix) |
245 | | - .filter(tb -> isTileBlobInBounds(tb, rangeBoundsAtZoom)) |
246 | | - .forEach(blobsToDelete::add); |
| 257 | + private void deleteTileBlobs(List<BlobItem> blobs, TileRange tileRange) { |
| 258 | + try { |
| 259 | + if (listeners.isEmpty()) { |
| 260 | + // if there are no listeners, don't bother requesting every tile |
| 261 | + // metadata to notify the listeners |
| 262 | + List<String> keysToDelete = |
| 263 | + blobs.stream().map(BlobItem::getName).collect(Collectors.toList()); |
| 264 | + |
| 265 | + if (!shutDown) { |
| 266 | + deleteManager.deleteParallel(keysToDelete); |
| 267 | + } |
| 268 | + } else { |
| 269 | + // if we need to gather info, we'll end up just calling "delete" on each tile |
| 270 | + // this is run here instead of inside the delete manager as we need high level info |
| 271 | + // about tiles, e.g., TileObject, to inform the listeners |
| 272 | + List<Callable<?>> tilesDeletions = blobs.stream() |
| 273 | + .map(blobItem -> { |
| 274 | + TileObject tile = createTileObject(blobItem, tileRange); |
| 275 | + tile.setParametersId(tileRange.getParametersId()); |
| 276 | + |
| 277 | + // PMD is being picky with this lambda inlined |
| 278 | + Callable<Boolean> deleteCallable = () -> delete(tile); |
| 279 | + return deleteCallable; |
| 280 | + }) |
| 281 | + .collect(Collectors.toList()); |
| 282 | + |
| 283 | + executeParallelDeletions(tilesDeletions); |
| 284 | + } |
| 285 | + } catch (StorageException e) { |
| 286 | + throw new RuntimeException(e); |
247 | 287 | } |
248 | | - |
249 | | - return blobsToDelete; |
250 | 288 | } |
251 | 289 |
|
252 | 290 | private boolean isTileBlobInBounds(BlobItem tileBlob, long[] bounds) { |
|
0 commit comments