/**
   * Sends one feature
   *
   * @param values
   */
  protected void sendWFSFeature(List<Object> values) {
    if (values == null || values.size() == 0) {
      log.warn("Failed to send feature");
      return;
    }
    Map<String, Object> output = new HashMap<String, Object>();
    output.put(OUTPUT_LAYER_ID, this.layerId);
    output.put(OUTPUT_FEATURE, values);

    this.service.addResults(this.session.getClient(), ResultProcessor.CHANNEL_FEATURE, output);
  }
  /**
   * Sends properties (fields and locales)
   *
   * @param fields
   * @param locales
   */
  protected void sendWFSProperties(List<String> fields, List<String> locales) {
    if (fields == null || fields.size() == 0) {
      log.warn("Failed to send properties");
      return;
    }

    if (!fields.contains("__fid")) fields.add(0, "__fid");
    if (!fields.contains("__centerX")) fields.add("__centerX");
    if (!fields.contains("__centerY")) fields.add("__centerY");

    if (locales != null && !locales.isEmpty()) {
      locales.add(0, "ID");
      locales.add("x");
      locales.add("y");
    } else {
      locales = new ArrayList<String>();
    }
    Map<String, Object> output = new HashMap<String, Object>();
    output.put(OUTPUT_LAYER_ID, this.layerId);
    output.put(OUTPUT_FIELDS, fields);
    output.put(OUTPUT_LOCALES, locales);

    this.service.addResults(this.session.getClient(), ResultProcessor.CHANNEL_PROPERTIES, output);
  }
  public boolean runNormalJob() {
    // make single request
    if (!this.layer.isTileRequest()) {
      log.debug("single request");
      if (!this.normalHandlers(null, true)) {
        log.debug("!normalHandlers leaving");
        return false;
      } else {
        log.debug("single request - continue");
      }
    } else {
      log.debug("MAKING TILED REQUESTS");
    }
    log.debug("normal tile images handling");

    // init enlarged envelope
    List<List<Double>> grid = this.session.getGrid().getBounds();
    if (grid.size() > 0) {
      this.session.getLocation().setEnlargedEnvelope(grid.get(0));
    }

    boolean first = true;
    int index = 0;
    for (List<Double> bounds : grid) {
      if (!goNext()) {
        return false;
      }

      log.debug("Tile bounds:", bounds);

      // make a request per tile
      if (this.layer.isTileRequest()) {
        if (!this.normalHandlers(bounds, first)) {
          continue;
        }
      }

      if (!goNext()) {
        return false;
      }

      boolean isThisTileNeeded = true;

      if (!this.sendImage) {
        log.debug("[fe] !sendImage - not sending PNG");
        isThisTileNeeded = false;
      }

      if (!this.sessionLayer.isTile(bounds)) {
        log.debug("[fe] !layer.isTile - not sending PNG");
        isThisTileNeeded = false;
      }

      if (isThisTileNeeded) {
        Double[] bbox = bounds.toArray(new Double[4]);

        // get from cache
        BufferedImage bufferedImage = getImageCache(bbox);
        boolean isboundaryTile =
            this.session.getGrid().isBoundsOnBoundary2(this.session.getLocation(), bbox);

        if (bufferedImage == null) {
          if (this.image == null) {
            this.image = createResponseImage();
          }
          bufferedImage =
              this.image.draw(
                  this.session.getTileSize(), this.session.getLocation(), bounds, this.features);
          if (bufferedImage == null) {
            this.imageParsingFailed();
            throw new RuntimeException("Image parsing failed!");
          }

          // setup cachekey
          String cacheStyleName = this.session.getLayers().get(this.layerId).getStyleName();
          if (cacheStyleName.startsWith(WFSImage.PREFIX_CUSTOM_STYLE)) {
            cacheStyleName += "_" + this.session.getSession();
          }

          // save to cache
          setImageCache(bufferedImage, cacheStyleName, bbox, !isboundaryTile);
        }

        String url =
            createImageURL(this.session.getLayers().get(this.layerId).getStyleName(), bbox);
        this.sendWFSImage(url, bufferedImage, bbox, true, isboundaryTile);
      } else {
        log.debug("Tile not needed?", bounds);
      }

      if (first) {
        first = false;
        // keep the next tiles
        this.session.setKeepPrevious(true);
      }
      index++;
    }
    return true;
  }