private void loadResourceModelSources() {
    Map<String, Exception> exceptions =
        Collections.synchronizedMap(new HashMap<String, Exception>());
    Set<String> validSources = new HashSet<>();
    // generate Configuration for file source
    if (projectConfig.hasProperty(PROJECT_RESOURCES_FILE_PROPERTY)) {
      try {
        final Properties config = createFileSourceConfiguration();
        logger.info("Source (project.resources.file): loading with properties: " + config);
        nodesSourceList.add(
            loadResourceModelSource("file", config, shouldCacheForType("file"), "file.file"));
        validSources.add("project.file");
      } catch (ExecutionServiceException e) {
        logger.error("Failed to load file resource model source: " + e.getMessage(), e);
        exceptions.put("project.file", e);
      }
    }
    if (projectConfig.hasProperty(PROJECT_RESOURCES_URL_PROPERTY)) {
      try {
        final Properties config = createURLSourceConfiguration();
        logger.info("Source (project.resources.url): loading with properties: " + config);
        nodesSourceList.add(
            loadResourceModelSource("url", config, shouldCacheForType("url"), "file.url"));
        validSources.add("project.url");
      } catch (ExecutionServiceException e) {
        logger.error("Failed to load file resource model source: " + e.getMessage(), e);
        exceptions.put("project.url", e);
      }
    }

    final List<Map<String, Object>> list = listResourceModelConfigurations();
    int i = 1;
    for (final Map<String, Object> map : list) {
      final String providerType = (String) map.get("type");
      final Properties props = (Properties) map.get("props");

      logger.info("Source #" + i + " (" + providerType + "): loading with properties: " + props);
      try {
        nodesSourceList.add(
            loadResourceModelSource(
                providerType, props, shouldCacheForType(providerType), i + ".source"));
        validSources.add(i + ".source");
      } catch (ExecutionServiceException e) {
        logger.error(
            "Failed loading resource model source #" + i + ", skipping: " + e.getMessage(), e);
        exceptions.put(i + ".source", e);
      }
      i++;
    }
    synchronized (nodesSourceExceptions) {
      nodesSourceExceptions.putAll(exceptions);
      for (String validSource : validSources) {
        nodesSourceExceptions.remove(validSource);
      }
    }

    Date configLastModifiedTime = projectConfig.getConfigLastModifiedTime();
    nodesSourcesLastReload = configLastModifiedTime != null ? configLastModifiedTime.getTime() : -1;
  }
 /**
  * Returns the set of nodes for the project
  *
  * @return an instance of {@link INodeSet}
  */
 @Override
 public INodeSet getNodeSet() {
   // iterate through sources, and add nodes
   final NodeSetMerge list = getNodeSetMerge();
   Map<String, Exception> exceptions =
       Collections.synchronizedMap(new HashMap<String, Exception>());
   int index = 1;
   Set<String> validSources = new HashSet<>();
   for (final ResourceModelSource nodesSource : getResourceModelSources()) {
     try {
       INodeSet nodes = nodesSource.getNodes();
       if (null == nodes) {
         logger.warn("Empty nodes result from [" + nodesSource.toString() + "]");
       } else {
         list.addNodeSet(nodes);
       }
       boolean hasErrors = false;
       if (nodesSource instanceof ResourceModelSourceErrors) {
         ResourceModelSourceErrors nodeerrors = (ResourceModelSourceErrors) nodesSource;
         List<String> modelSourceErrors = nodeerrors.getModelSourceErrors();
         if (modelSourceErrors != null && modelSourceErrors.size() > 0) {
           hasErrors = true;
           logger.error(
               "Some errors getting nodes from ["
                   + nodesSource.toString()
                   + "]: "
                   + modelSourceErrors);
           exceptions.put(
               index + ".source",
               new ResourceModelSourceException(
                   TextUtils.join(
                       modelSourceErrors.toArray(new String[modelSourceErrors.size()]), ';')));
         }
       }
       if (!hasErrors) {
         validSources.add(index + ".source");
       }
     } catch (ResourceModelSourceException | RuntimeException e) {
       logger.error("Cannot get nodes from [" + nodesSource.toString() + "]: " + e.getMessage());
       logger.debug(
           "Cannot get nodes from [" + nodesSource.toString() + "]: " + e.getMessage(), e);
       exceptions.put(index + ".source", new ResourceModelSourceException(e.getMessage(), e));
     } catch (Throwable e) {
       logger.error("Cannot get nodes from [" + nodesSource.toString() + "]: " + e.getMessage());
       logger.debug(
           "Cannot get nodes from [" + nodesSource.toString() + "]: " + e.getMessage(), e);
       exceptions.put(index + ".source", new ResourceModelSourceException(e.getMessage()));
     }
     index++;
   }
   synchronized (nodesSourceExceptions) {
     nodesSourceExceptions.putAll(exceptions);
     for (String validSource : validSources) {
       nodesSourceExceptions.remove(validSource);
     }
   }
   return list;
 }
 public ProjectNodeSupport(
     final IRundeckProjectConfig projectConfig,
     final ResourceFormatGeneratorService resourceFormatGeneratorService,
     final ResourceModelSourceService resourceModelSourceService) {
   this.projectConfig = projectConfig;
   this.resourceFormatGeneratorService = resourceFormatGeneratorService;
   this.resourceModelSourceService = resourceModelSourceService;
   this.nodesSourceExceptions = Collections.synchronizedMap(new HashMap<String, Exception>());
 }
 /** @return the set of exceptions produced by the last attempt to invoke all node providers */
 @Override
 public Map<String, Exception> getResourceModelSourceExceptionsMap() {
   synchronized (nodesSourceExceptions) {
     return Collections.unmodifiableMap(nodesSourceExceptions);
   }
 }