@Override
  public void inputChanged(
      @SuppressWarnings("hiding") Viewer viewer, Object oldInput, Object newInput) {
    if (map != null) {
      dispose();
    }

    this.map = (IMap) newInput;
    this.viewer = (MapViewer) viewer;

    // listen to ProjectNodeCommitEvent
    projectNodeListener = new ProjectNodeListener();
    EventManager.instance()
        .subscribe(
            projectNodeListener,
            ifType(
                ProjectNodeCommittedEvent.class,
                ev -> {
                  ProjectNode src = ev.getEntity(map.belongsTo());
                  return src instanceof IMap && map.id().equals(src.id())
                      || src instanceof ILayer && map.containsLayer((ILayer) src);
                  // XXX check if structural change or just label changed
                }));

    // listen to LayerUserSettings#visible
    propertyListener = new PropertyListener();
    EventManager.instance()
        .subscribe(
            propertyListener,
            ifType(
                PropertyChangeEvent.class,
                ev -> {
                  if (ev.getSource() instanceof LayerUserSettings) {
                    String layerId = ((LayerUserSettings) ev.getSource()).layerId();
                    return map.layers
                        .stream()
                        .filter(l -> l.id().equals(layerId))
                        .findAny()
                        .isPresent();
                  }
                  return false;
                }));

    styleListener = new StyleListener();
    EventManager.instance()
        .subscribe(
            styleListener,
            ifType(
                FeatureStyleCommitedEvent.class,
                ev -> {
                  return map.layers
                      .stream()
                      .filter(l -> l.userSettings.get().visible.get())
                      .filter(l -> Objects.equals(l.styleIdentifier.get(), ev.getSource().id()))
                      .findAny()
                      .isPresent();
                }));
  }
  public IStatus execute(final IProgressMonitor monitor, IAdaptable info)
      throws ExecutionException {
    Display display = (Display) info.getAdapter(Display.class);

    // set map extent
    try {
      monitor.subTask(Messages.get("OpenMapOperation_calcLayersBounds"));

      if (map.getLayers().isEmpty()) {
        display.asyncExec(
            new Runnable() {
              public void run() {
                MessageBox box = new MessageBox(page.getWorkbenchWindow().getShell());
                box.setText(Messages.get("OpenMapOperation_noLayersText"));
                box.setMessage(Messages.get("OpenMapOperation_noLayersMsg"));
                box.open();
              }
            });
        return Status.CANCEL_STATUS;
      }

      final ReferencedEnvelope bbox =
          map.getMaxExtent() == null
              ? calcLayersBounds(map.getLayers(), map.getCRS(), monitor)
              : map.getMaxExtent();

      if (map.getMaxExtent() == null && bbox != null) {
        log.info("### No map max extent -> using calculated values: " + bbox);
        map.setMaxExtent(bbox);
      }
      if (bbox == null && !map.getLayers().isEmpty()) {
        display.syncExec(
            new Runnable() {
              public void run() {
                MessageBox box = new MessageBox(page.getWorkbenchWindow().getShell());
                box.setText(Messages.get("OpenMapOperation_bboxErrorText"));
                box.setMessage(Messages.get("OpenMapOperation_bboxErrorMsg"));
                box.open();
              }
            });
      } else {
        map.setVisible(true);
      }
      return Status.OK_STATUS;
    } catch (Exception e) {
      throw new ExecutionException(e.getLocalizedMessage(), e);
    }
  }
    @Override
    public void sendContent(
        OutputStream out, Range range, Map<String, String> params, String contentType)
        throws IOException, BadRequestException {
      WorkbenchState state = WorkbenchState.instance(SessionContext.current());
      IMap map = state.getMap();

      try {
        JSONObject json = new JSONObject();
        json.put("width", -1);
        json.put("height", -1);

        ReferencedEnvelope extent = map.getExtent();
        json.put("minX", extent.getMinX());
        json.put("minY", extent.getMinY());
        json.put("maxX", extent.getMaxX());
        json.put("maxY", extent.getMaxY());

        out.write(json.toString(4).getBytes("UTF-8"));
      } catch (JSONException e) {
        throw new RuntimeException(e);
      }
    }
    @Override
    public void sendContent(
        OutputStream out, Range range, final Map<String, String> params, String rContentType)
        throws IOException, BadRequestException {
      final int width = params.containsKey("width") ? Integer.parseInt(params.get("width")) : 300;
      final int height =
          params.containsKey("height") ? Integer.parseInt(params.get("height")) : 300;

      List<Job> jobs = new ArrayList();
      final Map<ILayer, Image> images = new HashMap();

      // run jobs for all layers
      WorkbenchState state = WorkbenchState.instance(SessionContext.current());
      final IMap map = state.getMap();
      if (map != null) {
        for (final ILayer layer : map.getLayers()) {
          if (layer.isVisible()) {
            UIJob job =
                new UIJob(getClass().getSimpleName() + ": " + layer.getLabel()) {
                  protected void runWithException(IProgressMonitor monitor) throws Exception {
                    try {
                      IGeoResource res = layer.getGeoResource();
                      if (res == null) {
                        throw new RuntimeException(
                            "Unable to find geo resource of layer: " + layer);
                      }
                      IService service = res.service(null);
                      Pipeline pipeline =
                          pipelineIncubator.newPipeline(
                              LayerUseCase.IMAGE, layer.getMap(), layer, service);
                      if (pipeline.length() == 0) {
                        throw new RuntimeException(
                            "Unable to build processor pipeline for layer: " + layer);
                      }

                      // processor request
                      GetMapRequest request =
                          new GetMapRequest(
                              null, // layers
                              map.getCRSCode(),
                              map.getExtent(),
                              contentType,
                              width,
                              height,
                              -1);

                      // process request
                      pipeline.process(
                          request,
                          new ResponseHandler() {
                            public void handle(ProcessorResponse pipeResponse) throws Exception {
                              Image image = ((ImageResponse) pipeResponse).getImage();
                              images.put(layer, image);
                            }
                          });
                    } catch (Exception e) {
                      // XXX put a special image in the map
                      log.warn("", e);
                      images.put(layer, null);
                      throw e;
                    }
                  }
                };
            jobs.add(job);
            job.schedule();
          }
        }

        // join jobs
        for (Job job : jobs) {
          try {
            job.join();
          } catch (InterruptedException e) {
            // XXX put a special image in the map
            log.warn("", e);
          }
        }
      }

      // put images together (MapContext order)
      Graphics2D g = null;
      try {
        // create image
        BufferedImage result = new BufferedImage(width, height, BufferedImage.TYPE_4BYTE_ABGR);
        g = result.createGraphics();

        // rendering hints
        RenderingHints hints =
            new RenderingHints(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
        hints.add(
            new RenderingHints(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON));
        hints.add(
            new RenderingHints(
                RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON));
        g.setRenderingHints(hints);

        if (map == null) {
          g.setFont(new Font("Serif", Font.PLAIN, 14));
          g.setColor(Color.RED);
          g.drawString("Melden Sie sich in der Workbench an, um hier eine Karte zu sehen!", 50, 50);
        }
        // FIXME honor layer.getOrderKey()
        for (Map.Entry<ILayer, Image> entry : images.entrySet()) {
          int rule = AlphaComposite.SRC_OVER;
          float alpha = ((float) entry.getKey().getOpacity()) / 100;

          g.setComposite(AlphaComposite.getInstance(rule, alpha));
          g.drawImage(entry.getValue(), 0, 0, null);
        }

        // encode image
        encodeImage(result, out);
      } finally {
        if (g != null) {
          g.dispose();
        }
      }
    }
 public OpenMapOperation(IMap map, IWorkbenchPage page) {
   super(Messages.get("OpenMapOperation_titlePrefix") + map.getLabel());
   this.map = map;
   this.page = page;
 }