docs: protected boolean saveContentSpec(final DataProviderFactory providerFactory, final ProcessorData processorData, final boolean edit) {
          final ContentSpecProvider contentSpecProvider = providerFactory.getProvider(ContentSpecProvider.class);
  
          try {
              final ContentSpec contentSpec = processorData.contentSpec;
              final LocaleWrapper locale = contentSpec.getLocale() != null ?
                      EntityUtilities.findLocaleFromString(serverSettings.getLocales(), contentSpec.getLocale())
                      : serverSettings.getDefaultLocale();
              final List<ITopicNode> topicNodes = contentSpec.getAllTopicNodes();
  
              // Create the duplicate topic map
              final Map<ITopicNode, ITopicNode> duplicatedTopicMap = createDuplicatedTopicMap(topicNodes);
  
              // Create the new topic entities
              createOrUpdateTopics(topicNodes, topics, processorData, locale);
  
              // Check if the app should be shutdown
              if (isShuttingDown.get()) {
                  shutdown.set(true);
                  throw new ProcessingException("Shutdown Requested");
              }
  
              // From here on the main saving happens so this shouldn't be interrupted
  
              // Save the new topic entities
              if (!topics.savePool()) {
                  log.error(ProcessorConstants.ERROR_DATABASE_ERROR_MSG);
                  throw new ProcessingException("Failed to save the pool of topics.");
              }
  
              // Initialise the new and cloned topics using the populated topic pool
              for (final ITopicNode topicNode : topicNodes) {
                  topics.initialiseFromPool(topicNode);
                  cleanSpecTopicWhenCreatedOrUpdated(topicNode);
              }
  
              // Sync the Duplicated Topics (ID = X<Number>)
              syncDuplicatedTopics(duplicatedTopicMap);
  
              // Save the content spec
              mergeAndSaveContentSpec(providerFactory, processorData, !edit);
          } catch (ProcessingException e) {
              LOG.debug("", e);
              if (providerFactory.isTransactionsSupported()) {
                  providerFactory.rollback();
              } else {
                  // Clean up the data that was created
                  if (processorData.getContentSpec().getId() != null && !edit) {
                      try {
                          contentSpecProvider.deleteContentSpec(processorData.getContentSpec().getId());
                      } catch (Exception e1) {
                          log.error("Unable to clean up the Content Specification from the database.", e);
                      }
                  }
                  if (topics.isInitialised()) topics.rollbackPool();
              }
              return false;
          } catch (Exception e) {
              LOG.error("", e);
              if (providerFactory.isTransactionsSupported()) {
                  providerFactory.rollback();
              } else {
                  // Clean up the data that was created
                  if (processorData.getContentSpec().getId() != null && !edit) {
                      try {
                          contentSpecProvider.deleteContentSpec(processorData.getContentSpec().getId());
                      } catch (Exception e1) {
                          log.error("Unable to clean up the Content Specification from the database.", e);
                      }
                  }
                  if (topics.isInitialised()) topics.rollbackPool();
              }
              log.debug("", e);
              return false;
          }
          return true;
      }