public Map<String, Object> process(Map<String, Object> input) {
    if (!isValid) {
      validate(true);
    }
    Long currentProcessId = processId.incrementAndGet();
    Semaphore resultSemaphoreForProcess = new Semaphore(0);
    resultSemaphores.put(currentProcessId, resultSemaphoreForProcess);

    // send input to all input pipeline stages
    for (PipelineStage inputStage : inputStages.keySet()) {
      Map<String, String> inputPortMapping = inputStages.get(inputStage);
      for (String inputPort : inputPortMapping.keySet()) {
        Object inputParam = input.get(inputPort);
        inputStage.consume(currentProcessId, inputPortMapping.get(inputPort), inputParam);
      }
    }

    // wait for the output to become ready
    resultSemaphoreForProcess.acquireUninterruptibly();

    if (Boolean.FALSE == processingStatus.remove(currentProcessId)) {
      Throwable t = processingException.remove(currentProcessId);
      throw new PipelineProcessingException(
          "Processing failed for id '" + currentProcessId + "'.", t);
    }
    // cleanup and return the result
    return clear(currentProcessId);
  }
  /**
   * Abort the processing for a given id.
   *
   * @param id The processing id.
   * @param t The exception that was caught during the processing.
   */
  protected void abort(Long id, Throwable t) {
    processingStatus.put(id, Boolean.FALSE);
    processingException.put(id, t);

    for (PipelineStage c : pipelineStages.values()) {
      // TODO: This will result in a memory leak once we add multi threading. Threads might already
      // be running and producing new data.
      c.clear(id);
    }
  }
 /**
  * Validate that the pipeline is consistent.
  *
  * @param hard If this is set to true, an exception will be thrown if the validation fails.
  * @return True if the validation succeeded.
  */
 @Override
 public boolean validate(boolean hard) {
   // check that all input ports will be populated
   for (PipelineStage c : pipelineStages.values()) {
     if (this == c) {
       continue;
     }
     if (!c.validate(hard)) {
       return false;
     }
   }
   return true;
 }
  /**
   * Add a target stage for a given out-port.
   *
   * @param outPort The outgoing port.
   * @param target The target stage.
   * @param targetInPort The ingoing port of the target stage.
   */
  @Override
  public void addTarget(String outPort, PipelineStage target, String targetInPort) {
    Map<String, String> parametersForInputStage = this.inputStages.get(target);
    if (null == parametersForInputStage) {
      parametersForInputStage = new HashMap<>();
      this.inputStages.put(target, parametersForInputStage);
    }
    parametersForInputStage.put(outPort, targetInPort);

    // tell the target who will provide the data
    target.setInPortSupplier(this, targetInPort);
  }
 /**
  * Add a pipe between two workers.
  *
  * @param sourceWorker The source worker.
  * @param sourceOutPort The source port.
  * @param targetWorker The target worker.
  * @param targetInPort The target port.
  */
 public void addPipe(
     String sourceWorker, String sourceOutPort, String targetWorker, String targetInPort) {
   if (null == sourceWorker) {
     // this is an input pipe
     PipelineStage targetStage = pipelineStages.get(targetWorker);
     if (null == targetStage) {
       throw new ProcessingPipelineInitializationException(
           "No worker with name '" + targetWorker + "' available in pipeline + '" + name + "'.");
     }
     this.addTarget(sourceOutPort, targetStage, targetInPort);
   } else if (null == targetWorker) {
     // this is an output pipe
     PipelineStage sourceStage = pipelineStages.get(sourceWorker);
     if (!pipelineStages.containsKey(sourceWorker)) {
       throw new ProcessingPipelineInitializationException(
           "No worker with name '" + sourceWorker + "' available in pipeline + '" + name + "'.");
     }
     if (outputPorts.contains(targetInPort)) {
       throw new ProcessingPipelineInitializationException(
           "Output port '" + targetInPort + "' is already defined for pipeline + '" + name + "'.");
     }
     sourceStage.addTarget(sourceOutPort, this, targetInPort);
     this.outputPorts.add(targetInPort);
   } else {
     // this is an intermediate pipe
     PipelineStage sourceStage = pipelineStages.get(sourceWorker);
     if (null == sourceStage) {
       throw new ProcessingPipelineInitializationException(
           "No worker with name '" + sourceWorker + "' available in pipeline + '" + name + "'.");
     }
     PipelineStage targetStage = pipelineStages.get(targetWorker);
     if (null == targetStage) {
       throw new ProcessingPipelineInitializationException(
           "No worker with name '" + targetWorker + "' available in pipeline + '" + name + "'.");
     }
     sourceStage.addTarget(sourceOutPort, targetStage, targetInPort);
   }
   this.isValid = validate(false);
 }