/**
   * will mirror a feature from remote site, supports features stored on remote server, or features
   * stored on other servers, via an absolute url in the site.xml
   *
   * @param site
   * @param featureRef
   * @param mirrored
   * @throws StorageException
   * @throws IllegalOperationException
   */
  private void mirrorFeature(UpdateSite site, IFeatureRef featureRef, Set<String> mirrored)
      throws StorageException, IllegalOperationException {
    ResourceStoreRequest request = createResourceStoreRequest(featureRef);

    if (request == null || !mirrored.add(request.getRequestPath())) {
      return;
    }

    getLogger().debug("Mirroring feature " + featureRef);

    Feature feature = null;

    // request url will only be set when an absolute uri is found
    if (request.getRequestUrl() != null) {
      String absoluteUrl = request.getRequestUrl();
      request.setRequestUrl(null);

      feature = mirrorAbsoluteFeature(absoluteUrl, featureRef, request, mirrored);
    } else {
      feature = mirrorRelativeFeature(featureRef, request, mirrored);
    }

    if (feature != null) {
      List<PluginRef> includedPlugins = feature.getPlugins();
      List<Feature.FeatureRef> includedFeatures = feature.getIncludedFeatures();

      getLogger()
          .debug(
              featureRef
                  + " includes "
                  + includedFeatures.size()
                  + " features and "
                  + includedPlugins.size()
                  + " plugins");

      for (PluginRef plugin : includedPlugins) {
        mirrorPlugin(site, plugin, mirrored);
      }

      for (IFeatureRef includedFeature : includedFeatures) {
        mirrorFeature(site, includedFeature, mirrored);
      }
    }
  }
  /**
   * Mirror a relative feature. Note that the file name stored remotely may not be named to our
   * standard ${id}_${version}.jar so we need to cache locally as that instead of what is named
   * remotely
   *
   * @param featureRef
   * @param request
   * @param mirrored
   * @return
   */
  private Feature mirrorRelativeFeature(
      IFeatureRef featureRef, ResourceStoreRequest request, Set<String> mirrored) {
    try {
      ResourceStoreRequest localRequest = generateResourceStoreRequest(featureRef);
      mirrorRelativeItem(request, localRequest, mirrored);

      File file = getLocalStorage().getFileFromBase(this, localRequest);

      return Feature.readJar(file);
    } catch (Exception e) {
      getLogger()
          .warn(
              "Could not download feature "
                  + featureRef
                  + " referenced by update site "
                  + getName(),
              e);
    }

    return null;
  }
  /**
   * @param absoluteUrl
   * @param featureRef
   * @param request
   * @param mirrored
   * @return
   */
  private Feature mirrorAbsoluteFeature(
      String absoluteUrl,
      IFeatureRef featureRef,
      ResourceStoreRequest request,
      Set<String> mirrored) {
    try {
      // we are building the path from these ids, so if not set, we have a problem
      if (featureRef.getId() != null && featureRef.getVersion() != null) {
        // cerating update sites may not name there feature jars as we expect, ${id}_${version}.jar
        // if thats the case, download as they request, but store in local storage normalized, this
        // of course means updating the mirrored list so the file isn't deleted upon completion of
        // mirror process
        ResourceStoreRequest defaultResource = generateResourceStoreRequest(featureRef);

        File file =
            mirrorAbsoluteItem(
                absoluteUrl, request, DEFAULT_FEATURES_DIR, defaultResource, mirrored);

        return Feature.readJar(file);
      }

      getLogger()
          .warn(
              "Could not download feature "
                  + featureRef
                  + " referenced by update site "
                  + getName());
      return null;
    } catch (MalformedURLException e) {
      getLogger()
          .warn(
              "Could not download feature "
                  + featureRef
                  + " referenced by update site "
                  + getName(),
              e);
    } catch (IOException e) {
      getLogger()
          .warn(
              "Could not download feature "
                  + featureRef
                  + " referenced by update site "
                  + getName(),
              e);
    } catch (XmlPullParserException e) {
      getLogger()
          .warn(
              "Could not download feature "
                  + featureRef
                  + " referenced by update site "
                  + getName(),
              e);
    } catch (ItemNotFoundException e) {
      getLogger()
          .warn(
              "Could not download feature "
                  + featureRef
                  + " referenced by update site "
                  + getName(),
              e);
    }

    return null;
  }