/**
   * Returns the <code>Dictionary</code> for the given <code>pid</code>.
   *
   * @param pid The identifier for the dictionary to load.
   * @return The dictionary for the identifier. This must not be <code>null</code> but may be empty.
   * @throws IOException If an error occurrs loading the dictionary. An <code>IOException</code>
   *     must also be thrown if no dictionary exists for the given identifier.
   */
  public Dictionary load(String pid) throws IOException {
    logger.debug("Config load call for {}", pid);
    Dictionary result = null;

    try {
      if (isReady(0) && requireRepository) {
        String id = pidToId(pid);
        ReadRequest readRequest = Requests.newReadRequest(id);
        Resource existing = repo.read(readRequest);
        Map<String, Object> existingConfig = existing.getContent().asMap();
        Object configMap = existingConfig.get(JSONEnhancedConfig.JSON_CONFIG_PROPERTY);
        String configString = serializeConfig(configMap);
        existingConfig.put(JSONEnhancedConfig.JSON_CONFIG_PROPERTY, configString);
        logger.debug("Config loaded {} {}", pid, existing);
        result = mapToDict(existingConfig);
      } else if (!requireRepository) {
        result = tempStore.get(pid);
        if (result == null) {
          throw new IOException("No entry for " + pid + " exists.");
        }

        logger.debug("Config loaded from temporary store {} {}", pid, result);
      }
    } catch (NotFoundException ex) {
      result = tempStore.get(pid);
      if (result == null) {
        throw new IOException("No entry for " + pid + " exists.");
      }

      logger.debug("Config loaded from temporary store {} {}", pid, result);
    } catch (ResourceException ex) {
      throw new IOException("Failed to load configuration in repository: " + ex.getMessage(), ex);
    }
    return result;
  }
  /**
   * Returns <code>true</code> if a persisted <code>Dictionary</code> exists for the given <code>pid
   * </code>.
   *
   * @param pid The identifier for the dictionary to test.
   */
  public boolean exists(String pid) {
    logger.debug("Config exists call for {}", pid);

    boolean exists = false;

    if (isReady(0) && requireRepository) {
      String id = pidToId(pid);
      try {
        ReadRequest readRequest = Requests.newReadRequest(id);
        Resource existing = repo.read(readRequest);
        exists = (existing != null);
      } catch (NotFoundException ex) {
        exists = false;
      } catch (ResourceException ex) {
        throw new RuntimeException(
            "Failed to check if configuration exists in repository: " + ex.getMessage(), ex);
      }
    }
    if (!exists) {
      exists = tempStore.containsKey(pid);
      if (exists) {
        logger.debug("Entry exists in temporary store for '{}'", pid);
      }
    } else {
      logger.debug("Entry exists for '{}'", pid);
    }

    if (!exists) {
      logger.debug("Entry does not exist for '{}'", pid);
    }
    return exists;
  }
  /**
   * Removes the <code>Dictionary</code> for the given <code>pid</code>. If such a dictionary does
   * not exist, this method has no effect.
   *
   * @param pid The identifier of the dictionary to delet.
   * @throws IOException If an error occurrs deleting the dictionary. This exception must not be
   *     thrown if no dictionary with the given identifier exists.
   */
  public void delete(String pid) throws IOException {
    logger.debug("delete call for {}", pid);
    Object removed = tempStore.remove(pid);
    if (removed != null) {
      logger.debug("Deleted {} from temporary store", pid);
    }
    try {
      if (isReady(0) && requireRepository) {
        String id = pidToId(pid);
        boolean retry;
        String rev = null;
        do {
          retry = false;
          try {

            ReadRequest readRequest = Requests.newReadRequest(id);
            Map<String, Object> existing = repo.read(readRequest).getContent().asMap();
            if (existing != null) {
              rev = (String) existing.get("_rev");
              DeleteRequest r = Requests.newDeleteRequest(id);
              r.setRevision(rev);
              repo.delete(r);
              logger.debug("Deleted {}", pid);
            }
          } catch (PreconditionFailedException ex) {
            logger.debug("Concurrent change during delete, retrying {} {}", pid, rev);
            retry = true;
          } catch (NotFoundException ex) {
            // If it doesn't exists (anymore) that's fine
          }
        } while (retry);
      }
    } catch (ResourceException ex) {
      throw new IOException(
          "Failed to delete configuration + " + pid + " in repository: " + ex.getMessage(), ex);
    }
  }
  /**
   * Stores the <code>Dictionary</code> under the given <code>pid</code>.
   *
   * @param pid The identifier of the dictionary.
   * @param properties The <code>Dictionary</code> to store.
   * @throws IOException If an error occurrs storing the dictionary. If this exception is thrown, it
   *     is expected, that {@link #exists(String) exists(pid} returns <code>false</code>.
   */
  public void store(String pid, Dictionary properties) throws IOException {
    logger.debug("Store call for {} {}", pid, properties);

    // Store config handling settings in memory
    if (pid.startsWith("org.apache.felix.fileinstall")) {
      tempStore.put(pid, properties);
      return;
    }

    try {
      if (isReady(0) && requireRepository) {
        String id = pidToId(pid);

        Map<String, Object> obj = dictToMap(properties);
        JsonValue content = new JsonValue(obj);
        String configResourceId =
            ConfigBootstrapHelper.getId(
                content.get(ConfigBootstrapHelper.CONFIG_ALIAS).asString(),
                content.get(ConfigBootstrapHelper.SERVICE_PID).asString(),
                content.get(ConfigBootstrapHelper.SERVICE_FACTORY_PID).asString());
        String configString = (String) obj.get(JSONEnhancedConfig.JSON_CONFIG_PROPERTY);
        Map<Object, Object> configMap = deserializeConfig(configString);
        if (configMap != null) {
          configMap.put("_id", configResourceId);
        }
        obj.put(JSONEnhancedConfig.JSON_CONFIG_PROPERTY, configMap);

        Map<String, Object> existing = null;
        try {
          ReadRequest readRequest = Requests.newReadRequest(id);
          existing = repo.read(readRequest).getContent().asMap();
        } catch (NotFoundException ex) {
          // Just detect that it doesn't exist
        }
        if (existing != null) {
          String rev = (String) existing.get("_rev");

          existing.remove("_rev");
          existing.remove("_id");
          obj.remove("_rev"); // beware, this means _id and _rev should not be in config file
          obj.remove("_id"); // beware, this means _id and _rev should not be in config file
          obj.remove(RepoPersistenceManager.BUNDLE_LOCATION);
          obj.remove(RepoPersistenceManager.FELIX_FILEINSTALL_FILENAME);
          if (!existing.equals(obj)) {
            logger.trace("Not matching {} {}", existing, obj);
            boolean retry;
            do {
              retry = false;
              try {
                UpdateRequest r = Requests.newUpdateRequest(id, new JsonValue(obj));
                r.setRevision(rev);
                repo.update(r);
              } catch (PreconditionFailedException ex) {
                logger.debug("Concurrent change during update, retrying {} {}", pid, rev);
                ReadRequest readRequest = Requests.newReadRequest(id);
                existing = repo.read(readRequest).getContent().asMap();
                retry = true;
              }
            } while (retry);
            logger.debug("Updated existing config {} {} {}", new Object[] {pid, rev, obj});
          } else {
            logger.debug(
                "Existing config same as store request, ignoring {} {} {}",
                new Object[] {pid, rev, obj});
          }
        } else {
          logger.trace("Creating: {} {} ", id, obj);
          // This may create a new (empty) configuration, which felix marks with
          // _felix___cm__newConfiguration=true
          String newResourceId = id.substring(CONFIG_CONTEXT_PREFIX.length());
          CreateRequest createRequest =
              Requests.newCreateRequest(CONFIG_CONTEXT_PREFIX, new JsonValue(obj));
          createRequest.setNewResourceId(newResourceId);
          obj = repo.create(createRequest).getContent().asMap();
          logger.debug("Stored new config in repository {} {}", pid, obj);
        }
      } else {
        tempStore.put(pid, properties);
        logger.debug("Stored in memory {} {}", pid, properties);
      }
    } catch (ResourceException ex) {
      throw new IOException("Failed to store configuration in repository: " + ex.getMessage(), ex);
    }
  }