@Override
  public void verifyCanStart(final Set<ControllerServiceNode> ignoredReferences) {
    switch (getScheduledState()) {
      case DISABLED:
        throw new IllegalStateException(this + " cannot be started because it is disabled");
      case RUNNING:
        throw new IllegalStateException(this + " cannot be started because it is already running");
      case STOPPED:
        break;
    }
    final int activeThreadCount = getActiveThreadCount();
    if (activeThreadCount > 0) {
      throw new IllegalStateException(
          this
              + " cannot be started because it has "
              + activeThreadCount
              + " active threads already");
    }

    final Set<String> ids = new HashSet<>();
    for (final ControllerServiceNode node : ignoredReferences) {
      ids.add(node.getIdentifier());
    }

    final Collection<ValidationResult> validationResults = getValidationErrors(ids);
    for (final ValidationResult result : validationResults) {
      if (!result.isValid()) {
        throw new IllegalStateException(
            this + " cannot be started because it is not valid: " + result);
      }
    }
  }
  @Override
  public boolean isValid() {
    readLock.lock();
    try {
      final ValidationContext validationContext =
          validationContextFactory.newValidationContext(getProperties(), getAnnotationData());

      final Collection<ValidationResult> validationResults;
      try (final NarCloseable narCloseable = NarCloseable.withNarLoader()) {
        validationResults = getProcessor().validate(validationContext);
      }

      for (final ValidationResult result : validationResults) {
        if (!result.isValid()) {
          return false;
        }
      }

      for (final Relationship undef : getUndefinedRelationships()) {
        if (!isAutoTerminated(undef)) {
          return false;
        }
      }

      switch (getInputRequirement()) {
        case INPUT_ALLOWED:
          break;
        case INPUT_FORBIDDEN:
          {
            if (!getIncomingNonLoopConnections().isEmpty()) {
              return false;
            }
            break;
          }
        case INPUT_REQUIRED:
          {
            if (getIncomingNonLoopConnections().isEmpty()) {
              return false;
            }
            break;
          }
      }
    } catch (final Throwable t) {
      return false;
    } finally {
      readLock.unlock();
    }

    return true;
  }
  @Override
  public void assertValid(final ControllerService service) {
    final StateManager serviceStateManager =
        controllerServiceStateManagers.get(service.getIdentifier());
    if (serviceStateManager == null) {
      throw new IllegalStateException(
          "Controller Service has not been added to this TestRunner via the #addControllerService method");
    }

    final ValidationContext validationContext =
        new MockValidationContext(context, serviceStateManager)
            .getControllerServiceValidationContext(service);
    final Collection<ValidationResult> results =
        context.getControllerService(service.getIdentifier()).validate(validationContext);

    for (final ValidationResult result : results) {
      if (!result.isValid()) {
        Assert.fail(
            "Expected Controller Service to be valid but it is invalid due to: "
                + result.toString());
      }
    }
  }
  @Override
  public Collection<ValidationResult> getValidationErrors() {
    final List<ValidationResult> results = new ArrayList<>();
    readLock.lock();
    try {
      final ValidationContext validationContext =
          validationContextFactory.newValidationContext(getProperties(), getAnnotationData());

      final Collection<ValidationResult> validationResults;
      try (final NarCloseable narCloseable = NarCloseable.withNarLoader()) {
        validationResults = getProcessor().validate(validationContext);
      }

      for (final ValidationResult result : validationResults) {
        if (!result.isValid()) {
          results.add(result);
        }
      }

      for (final Relationship relationship : getUndefinedRelationships()) {
        if (!isAutoTerminated(relationship)) {
          final ValidationResult error =
              new ValidationResult.Builder()
                  .explanation(
                      "Relationship '"
                          + relationship.getName()
                          + "' is not connected to any component and is not auto-terminated")
                  .subject("Relationship " + relationship.getName())
                  .valid(false)
                  .build();
          results.add(error);
        }
      }

      switch (getInputRequirement()) {
        case INPUT_ALLOWED:
          break;
        case INPUT_FORBIDDEN:
          {
            final int incomingConnCount = getIncomingNonLoopConnections().size();
            if (incomingConnCount != 0) {
              results.add(
                  new ValidationResult.Builder()
                      .explanation(
                          "Processor does not allow upstream connections but currently has "
                              + incomingConnCount)
                      .subject("Upstream Connections")
                      .valid(false)
                      .build());
            }
            break;
          }
        case INPUT_REQUIRED:
          {
            if (getIncomingNonLoopConnections().isEmpty()) {
              results.add(
                  new ValidationResult.Builder()
                      .explanation(
                          "Processor requires an upstream connection but currently has none")
                      .subject("Upstream Connections")
                      .valid(false)
                      .build());
            }
            break;
          }
      }
    } catch (final Throwable t) {
      results.add(
          new ValidationResult.Builder()
              .explanation("Failed to run validation due to " + t.toString())
              .valid(false)
              .build());
    } finally {
      readLock.unlock();
    }
    return results;
  }