public boolean process(
      final T job,
      final FeatureProcessor featureProcessor,
      final JobLogger jobLogger,
      int featureLimit)
      throws Exception {
    boolean error = true;

    technicalLog.debug("START " + JobTypeIntrospector.getJobTypeName(job) + " Job");
    final LogStringBuilder<AbstractProcess.MessageKey> userLog =
        new LogStringBuilder<AbstractProcess.MessageKey>();
    userLog.setProperties(logProperties);
    userLog.setJobLogger(jobLogger);

    DatasetHandlers<PersistableFeature> datasetHandlers = createDatasetHandlers(job);
    if (datasetHandlers == null) {
      String message =
          userLog.logEvent(
              job,
              AbstractProcess.MessageKey.NO_DATASET_HANDLERS,
              LOG_LEVEL,
              job.getUuid(),
              job.getBronhouder().getNaam(),
              job.getBronhouder().getCode());
      job.setResult(message);
      technicalLog.warn(message);
      return error;
    }

    DatasetMetadata md = fetchDatasetMetadataAndAugmentJob(job);
    if (md != null) {
      updateVerversen(job);
      if (job.getVerversen()) {
        if (featureProcessor.requiresFeatureProcessing(job)) {
          FeatureCollection fc =
              datasetHandlers.retrieveFeaturesFromBronhouder(job, featureProcessor, userLog, md);
          if (fc != null) {
            fc = new NumberLimitedFeatureCollection(fc, featureLimit);
            error = processFeatures(featureProcessor, job, datasetHandlers, fc, jobLogger, userLog);
          } else {
            job.setFeatureCount(0);
          }
        } else {
          technicalLog.debug("feature processing phase skipped");
        }
      }
    } else {
      technicalLog.debug(
          "fetching of metadata failed for " + JobTypeIntrospector.getJobTypeName(job) + " Job");
    }

    technicalLog.debug("END " + JobTypeIntrospector.getJobTypeName(job) + " Job");
    return error;
  }
 private boolean processFeatures(
     FeatureProcessor processor,
     final T job,
     final DatasetHandlers<PersistableFeature> datasetHandlers,
     final FeatureCollection fc,
     final JobLogger jobLogger,
     final LogStringBuilder<AbstractProcess.MessageKey> userLog) {
   boolean error = false;
   try {
     int num = processFeatures(processor, job, datasetHandlers, fc, jobLogger);
     job.setFeatureCount(num);
     if (num == 0) {
       String message = userLog.logEvent(job, AbstractProcess.MessageKey.NO_FEATURES, LOG_LEVEL);
       technicalLog.warn(message);
       job.setResult(message);
       error = true;
     }
   } catch (ValidationException e) {
     String message =
         userLog.logEvent(
             job, AbstractProcess.MessageKey.XML_BLOCKING_FEATURE_ERROR, LogLevel.ERROR);
     technicalLog.warn(message);
     job.setResult(message);
     job.setFeatureCount(0);
     error = true;
   } catch (Exception e) {
     String message =
         userLog.logEvent(
             job,
             AbstractProcess.MessageKey.XML_FEATURES_EXCEPTION,
             LOG_LEVEL,
             ExceptionUtils.getRootCauseMessage(e));
     job.setResult(message);
     job.setFeatureCount(0);
     error = true;
     technicalLog.error("Error while parsing/processing features", e);
   }
   technicalLog.debug("processing features finished");
   return error;
 }