@Test
  public void testSeedMetaTiled() throws Exception {
    WMSLayer layer = createWMSLayer("image/png");

    WMSSourceHelper mockSourceHelper = new MockWMSSourceHelper();
    MockLockProvider lockProvider = new MockLockProvider();
    layer.setSourceHelper(mockSourceHelper);
    layer.setLockProvider(lockProvider);

    final StorageBroker mockStorageBroker = EasyMock.createMock(StorageBroker.class);
    Capture<TileObject> captured = new Capture<TileObject>();
    expect(mockStorageBroker.put(EasyMock.capture(captured))).andReturn(true).anyTimes();
    replay(mockStorageBroker);

    String layerId = layer.getName();
    HttpServletRequest servletReq = new MockHttpServletRequest();
    HttpServletResponse servletResp = new MockHttpServletResponse();

    long[] gridLoc = {0, 0, 0}; // x, y, level
    MimeType mimeType = layer.getMimeTypes().get(0);
    GridSet gridSet = gridSetBroker.WORLD_EPSG4326;
    String gridSetId = gridSet.getName();
    ConveyorTile tile =
        new ConveyorTile(
            mockStorageBroker,
            layerId,
            gridSetId,
            gridLoc,
            mimeType,
            null,
            servletReq,
            servletResp);

    boolean tryCache = false;
    layer.seedTile(tile, tryCache);

    assertEquals(1, captured.getValues().size());
    TileObject value = captured.getValue();
    assertNotNull(value);
    assertEquals("image/png", value.getBlobFormat());
    assertNotNull(value.getBlob());
    assertTrue(value.getBlob().getSize() > 0);

    verify(mockStorageBroker);

    // check the lock provider was called in a symmetric way
    lockProvider.verify();
    lockProvider.clear();
  }
    private void installMockBroker() throws Exception {
      expect(storageBroker.getTransient((TileObject) anyObject()))
          .andAnswer(
              new IAnswer<Boolean>() {

                public Boolean answer() throws Throwable {
                  TileObject tile = (TileObject) EasyMock.getCurrentArguments()[0];
                  String key = TransientCache.computeTransientKey(tile);
                  Resource resource;
                  synchronized (transientCache) {
                    resource = transientCache.get(key);
                  }
                  if (resource != null) {
                    cacheHits.incrementAndGet();
                  } else {
                    cacheMisses.incrementAndGet();
                  }
                  tile.setBlob(resource);
                  return resource != null;
                }
              })
          .anyTimes();

      storageBroker.putTransient(
          capture(
              new Capture<TileObject>() {

                @Override
                public void setValue(TileObject tile) {
                  String key = TransientCache.computeTransientKey(tile);
                  synchronized (transientCache) {
                    transientCache.put(key, tile.getBlob());
                  }
                }
              }));
      expectLastCall().anyTimes();

      final HashSet<String> puts = new HashSet<String>();
      expect(
              storageBroker.put(
                  capture(
                      new Capture<TileObject>() {
                        @Override
                        public void setValue(TileObject value) {
                          puts.add(TransientCache.computeTransientKey(value));
                          storagePutCounter.incrementAndGet();
                        }
                      })))
          .andReturn(true)
          .anyTimes();
      expect(storageBroker.get((TileObject) anyObject()))
          .andAnswer(
              new IAnswer<Boolean>() {
                public Boolean answer() throws Throwable {
                  TileObject tile = (TileObject) EasyMock.getCurrentArguments()[0];
                  if (puts.contains(TransientCache.computeTransientKey(tile))) {
                    tile.setBlob(new ByteArrayResource(fakeWMSResponse));
                    storageGetCounter.incrementAndGet();
                    return true;
                  } else {
                    return false;
                  }
                }
              })
          .anyTimes();
      replay(storageBroker);
    }
  @Test
  public void testSeedJpegPngMetaTiled() throws Exception {
    WMSLayer layer = createWMSLayer("image/vnd.jpeg-png");

    WMSSourceHelper mockSourceHelper =
        new WMSSourceHelper() {

          @Override
          protected void makeRequest(
              TileResponseReceiver tileRespRecv,
              WMSLayer layer,
              Map<String, String> wmsParams,
              MimeType expectedMimeType,
              Resource target)
              throws GeoWebCacheException {
            int width = Integer.parseInt(wmsParams.get("WIDTH"));
            int height = Integer.parseInt(wmsParams.get("HEIGHT"));
            assertEquals(768, width);
            assertEquals(768, height);
            BufferedImage img = new BufferedImage(width, height, BufferedImage.TYPE_4BYTE_ABGR);
            Graphics2D graphics = img.createGraphics();
            graphics.setColor(Color.BLACK);
            // fill an L shaped set of tiles, making a few partially filled
            graphics.fillRect(0, 0, width, 300);
            graphics.fillRect(0, 0, 300, height);
            graphics.dispose();
            ByteArrayOutputStream output = new ByteArrayOutputStream();
            try {
              ImageIO.write(img, "PNG", output);
              ImageIO.write(img, "PNG", new java.io.File("/tmp/meta.png"));
            } catch (IOException e) {
              throw new RuntimeException(e);
            }

            try {
              target.transferFrom(
                  Channels.newChannel(new ByteArrayInputStream(output.toByteArray())));
            } catch (IOException e) {
              throw new RuntimeException(e);
            }
          }
        };
    MockLockProvider lockProvider = new MockLockProvider();
    layer.setSourceHelper(mockSourceHelper);
    layer.setLockProvider(lockProvider);

    final StorageBroker mockStorageBroker = EasyMock.createMock(StorageBroker.class);
    Capture<TileObject> captured = new Capture<TileObject>(CaptureType.ALL);
    expect(mockStorageBroker.put(EasyMock.capture(captured)))
        .andAnswer(
            new IAnswer<Boolean>() {

              @Override
              public Boolean answer() throws Throwable {
                TileObject to = (TileObject) EasyMock.getCurrentArguments()[0];
                assertEquals("image/vnd.jpeg-png", to.getBlobFormat());
                assertNotNull(to.getBlob());
                assertTrue(to.getBlob().getSize() > 0);
                String format = ImageMime.jpegPng.getMimeType(to.getBlob());
                long[] xyz = to.getXYZ();
                assertEquals(10, xyz[2]);
                // check the ones in the full black area are jpeg, the others png
                if (xyz[0] == 900 || xyz[1] == 602) {
                  assertEquals("image/jpeg", format);
                } else {
                  assertEquals("image/png", format);
                }

                return true;
              }
            })
        .anyTimes();
    replay(mockStorageBroker);

    String layerId = layer.getName();
    HttpServletRequest servletReq = new MockHttpServletRequest();
    HttpServletResponse servletResp = new MockHttpServletResponse();

    long[] gridLoc = {900, 600, 10}; // x, y, level
    MimeType mimeType = layer.getMimeTypes().get(0);
    GridSet gridSet = gridSetBroker.WORLD_EPSG4326;
    String gridSetId = gridSet.getName();
    ConveyorTile tile =
        new ConveyorTile(
            mockStorageBroker,
            layerId,
            gridSetId,
            gridLoc,
            mimeType,
            null,
            servletReq,
            servletResp);

    boolean tryCache = false;
    layer.seedTile(tile, tryCache);

    assertEquals(9, captured.getValues().size());
    verify(mockStorageBroker);

    // check the lock provider was called in a symmetric way
    lockProvider.verify();
    lockProvider.clear();
  }
  /**
   * For a metatiled seed request over a given zoom level, make sure the correct wms calls are
   * issued
   *
   * @throws Exception
   */
  @SuppressWarnings("serial")
  public void testSeedWMSRequests() throws Exception {
    WMSLayer tl = createWMSLayer("image/png");

    // create an image to be returned by the mock WMSSourceHelper
    final byte[] fakeWMSResponse = createFakeSourceImage(tl);

    // WMSSourceHelper that on makeRequest() returns always the saqme fake image
    WMSSourceHelper mockSourceHelper = EasyMock.createMock(WMSSourceHelper.class);

    final AtomicInteger wmsRequestsCounter = new AtomicInteger();
    Capture<WMSMetaTile> wmsRequestsCapturer =
        new Capture<WMSMetaTile>() {
          /** Override because setValue with anyTimes() resets the list of values */
          @Override
          public void setValue(WMSMetaTile o) {
            wmsRequestsCounter.incrementAndGet();
          }
        };
    Capture<Resource> resourceCapturer =
        new Capture<Resource>() {
          @Override
          public void setValue(Resource target) {
            try {
              target.transferFrom(Channels.newChannel(new ByteArrayInputStream(fakeWMSResponse)));
            } catch (IOException e) {
              throw new RuntimeException(e);
            }
          }
        };
    mockSourceHelper.makeRequest(capture(wmsRequestsCapturer), capture(resourceCapturer));
    mockSourceHelper.makeRequest(capture(wmsRequestsCapturer), capture(resourceCapturer));
    mockSourceHelper.makeRequest(capture(wmsRequestsCapturer), capture(resourceCapturer));
    mockSourceHelper.setConcurrency(32);
    mockSourceHelper.setBackendTimeout(120);
    replay(mockSourceHelper);

    tl.setSourceHelper(mockSourceHelper);

    final int zoomLevel = 4;
    SeedRequest req = createRequest(tl, TYPE.SEED, zoomLevel, zoomLevel);

    TileRange tr = TileBreeder.createTileRange(req, tl);
    TileRangeIterator trIter = new TileRangeIterator(tr, tl.getMetaTilingFactors());

    /*
     * Create a mock storage broker that does nothing
     */
    final StorageBroker mockStorageBroker = EasyMock.createMock(StorageBroker.class);
    expect(mockStorageBroker.put((TileObject) anyObject())).andReturn(true).anyTimes();
    expect(mockStorageBroker.get((TileObject) anyObject())).andReturn(false).anyTimes();
    replay(mockStorageBroker);

    boolean reseed = false;
    SeedTask seedTask = new SeedTask(mockStorageBroker, trIter, tl, reseed, false);
    seedTask.setTaskId(1L);
    seedTask.setThreadInfo(new AtomicInteger(), 0);
    /*
     * HACK: avoid SeedTask.getCurrentThreadArrayIndex failure.
     */
    Thread.currentThread().setName("pool-fake-thread-1");

    /*
     * Call the seed process
     */
    seedTask.doAction();

    final long expectedWmsRequestsCount = 3; // due to metatiling
    final long wmsRequestCount = wmsRequestsCounter.get();
    assertEquals(expectedWmsRequestsCount, wmsRequestCount);
  }
  /**
   * Make sure when seeding a given zoom level, the correct tiles are sent to the {@link
   * StorageBroker}
   *
   * @throws Exception
   */
  @SuppressWarnings("serial")
  public void testSeedStoredTiles() throws Exception {

    WMSLayer tl = createWMSLayer("image/png");

    // create an image to be returned by the mock WMSSourceHelper
    // / final byte[] fakeWMSResponse = createFakeSourceImage(tl);
    // WMSSourceHelper that on makeRequest() returns always the saqme fake image
    WMSSourceHelper mockSourceHelper =
        new MockWMSSourceHelper(); // EasyMock.createMock(WMSSourceHelper.class);
    // expect(mockSourceHelper.makeRequest((WMSMetaTile)
    // anyObject())).andReturn(fakeWMSResponse)
    // .anyTimes();
    // replay(mockSourceHelper);
    tl.setSourceHelper(mockSourceHelper);

    final String gridSetId = tl.getGridSubsets().iterator().next();
    final int zoomLevel = 2;
    SeedRequest req = createRequest(tl, TYPE.SEED, zoomLevel, zoomLevel);

    /*
     * Create a mock storage broker that has never an image in its blob store and that captures
     * the TileObject the seeder requests it to store for further test validation
     */
    final StorageBroker mockStorageBroker = EasyMock.createMock(StorageBroker.class);
    Capture<TileObject> storedObjects =
        new Capture<TileObject>() {
          /** Override because setValue with anyTimes() resets the list of values */
          @Override
          public void setValue(TileObject o) {
            super.getValues().add(o);
          }
        };
    expect(mockStorageBroker.put(capture(storedObjects))).andReturn(true).anyTimes();
    expect(mockStorageBroker.get((TileObject) anyObject())).andReturn(false).anyTimes();
    replay(mockStorageBroker);

    TileRange tr = TileBreeder.createTileRange(req, tl);
    TileRangeIterator trIter = new TileRangeIterator(tr, tl.getMetaTilingFactors());

    boolean reseed = false;
    SeedTask task = new SeedTask(mockStorageBroker, trIter, tl, reseed, false);
    task.setTaskId(1L);
    task.setThreadInfo(new AtomicInteger(), 0);
    /*
     * HACK: avoid SeedTask.getCurrentThreadArrayIndex failure.
     */
    Thread.currentThread().setName("pool-fake-thread-1");

    /*
     * Call the seed process
     */
    task.doAction();

    final GridSubset gridSubset = tl.getGridSubset(gridSetId);

    /*
     * Make sure the seed process asked for the expected tiles to be stored
     */
    final long expectedSavedTileCount;

    final long[] coveredGridLevels = gridSubset.getCoverage(zoomLevel);

    // seeding should not include edge tiles produced by the meta tiling that don't fall into
    // the gridsubset's coverage
    long starty = coveredGridLevels[1];
    long startx = coveredGridLevels[0];

    expectedSavedTileCount =
        (coveredGridLevels[2] - startx + 1) * (coveredGridLevels[3] - starty + 1);

    List<TileObject> storedTiles = storedObjects.getValues();
    final int seededTileCount = storedTiles.size();

    assertEquals(expectedSavedTileCount, seededTileCount);

    Set<Tuple<Long>> tileKeys = new TreeSet<Tuple<Long>>();
    Set<Tuple<Long>> expectedTiles = new TreeSet<Tuple<Long>>();
    for (long x = startx; x <= coveredGridLevels[2]; x++) {
      for (long y = starty; y <= coveredGridLevels[3]; y++) {
        expectedTiles.add(new Tuple<Long>(x, y, (long) zoomLevel));
      }
    }
    for (TileObject obj : storedTiles) {
      tileKeys.add(new Tuple<Long>(obj.getXYZ()[0], obj.getXYZ()[1], obj.getXYZ()[2]));
    }

    assertEquals(expectedTiles, tileKeys);
  }
  /**
   * For a metatiled seed request over a given zoom level, make sure the correct wms calls are
   * issued
   *
   * @throws Exception
   */
  public void testSeedRetries() throws Exception {
    WMSLayer tl = createWMSLayer("image/png");

    // create an image to be returned by the mock WMSSourceHelper
    final byte[] fakeWMSResponse = createFakeSourceImage(tl);

    // WMSSourceHelper that on makeRequest() returns always the saqme fake image
    // WMSSourceHelper mockSourceHelper = new MockWMSSourceHelper();///
    // EasyMock.createMock(WMSSourceHelper.class);
    WMSSourceHelper mockSourceHelper =
        new MockWMSSourceHelper() {
          private int numCalls;

          @Override
          protected void makeRequest(
              TileResponseReceiver tileRespRecv,
              WMSLayer layer,
              Map<String, String> wmsParams,
              MimeType expectedMimeType,
              Resource target)
              throws GeoWebCacheException {
            numCalls++;
            switch (numCalls) {
              case 1:
                throw new GeoWebCacheException("test exception");
              case 2:
                throw new RuntimeException("test unexpected exception");
              case 3:
                throw new GeoWebCacheException("second test exception");
              case 4:
                throw new RuntimeException("second test unexpected exception");
              default:
                try {
                  target.transferFrom(
                      Channels.newChannel(new ByteArrayInputStream(fakeWMSResponse)));
                } catch (IOException e) {
                  throw new RuntimeException(e);
                }
            }
          }
        };

    tl.setSourceHelper(mockSourceHelper);

    final int zoomLevel = 4;
    SeedRequest req = createRequest(tl, TYPE.SEED, zoomLevel, zoomLevel);

    TileRange tr = TileBreeder.createTileRange(req, tl);
    TileRangeIterator trIter = new TileRangeIterator(tr, tl.getMetaTilingFactors());

    /*
     * Create a mock storage broker that does nothing
     */
    final StorageBroker mockStorageBroker = EasyMock.createMock(StorageBroker.class);
    expect(mockStorageBroker.put((TileObject) anyObject())).andReturn(true).anyTimes();
    expect(mockStorageBroker.get((TileObject) anyObject())).andReturn(false).anyTimes();
    replay(mockStorageBroker);

    boolean reseed = false;
    SeedTask seedTask = new SeedTask(mockStorageBroker, trIter, tl, reseed, false);
    seedTask.setTaskId(1L);
    seedTask.setThreadInfo(new AtomicInteger(), 0);

    int tileFailureRetryCount = 1;
    long tileFailureRetryWaitTime = 10;
    long totalFailuresBeforeAborting = 4;
    AtomicLong sharedFailureCounter = new AtomicLong();
    seedTask.setFailurePolicy(
        tileFailureRetryCount,
        tileFailureRetryWaitTime,
        totalFailuresBeforeAborting,
        sharedFailureCounter);
    /*
     * HACK: avoid SeedTask.getCurrentThreadArrayIndex failure.
     */
    Thread.currentThread().setName("pool-fake-thread-1");

    /*
     * Call the seed process
     */
    seedTask.doAction();
    assertEquals(totalFailuresBeforeAborting, sharedFailureCounter.get());
  }