content: /*
   *  Licensed to the Apache Software Foundation (ASF) under one or more
   *  contributor license agreements.  See the NOTICE file distributed with
   *  this work for additional information regarding copyright ownership.
   *  The ASF licenses this file to You under the Apache License, Version 2.0
   *  (the "License"); you may not use this file except in compliance with
   *  the License.  You may obtain a copy of the License at
   *
   *      https://www.apache.org/licenses/LICENSE-2.0
   *
   *  Unless required by applicable law or agreed to in writing, software
   *  distributed under the License is distributed on an "AS IS" BASIS,
   *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   *  See the License for the specific language governing permissions and
   *  limitations under the License.
   *
   */
  package org.apache.ivy.osgi.updatesite;
  
  import java.io.FileInputStream;
  import java.io.IOException;
  import java.io.InputStream;
  import java.net.URI;
  import java.net.URISyntaxException;
  import java.net.URL;
  import java.text.ParseException;
  import java.util.zip.ZipEntry;
  import java.util.zip.ZipInputStream;
  
  import org.apache.ivy.core.cache.CacheResourceOptions;
  import org.apache.ivy.core.cache.RepositoryCacheManager;
  import org.apache.ivy.core.event.EventManager;
  import org.apache.ivy.core.report.ArtifactDownloadReport;
  import org.apache.ivy.core.report.DownloadStatus;
  import org.apache.ivy.core.settings.TimeoutConstraint;
  import org.apache.ivy.osgi.core.ExecutionEnvironmentProfileProvider;
  import org.apache.ivy.osgi.p2.P2ArtifactParser;
  import org.apache.ivy.osgi.p2.P2CompositeParser;
  import org.apache.ivy.osgi.p2.P2Descriptor;
  import org.apache.ivy.osgi.p2.P2MetadataParser;
  import org.apache.ivy.osgi.p2.XMLInputParser;
  import org.apache.ivy.osgi.repo.RepoDescriptor;
  import org.apache.ivy.osgi.updatesite.xml.EclipseFeature;
  import org.apache.ivy.osgi.updatesite.xml.EclipseUpdateSiteParser;
  import org.apache.ivy.osgi.updatesite.xml.FeatureParser;
  import org.apache.ivy.osgi.updatesite.xml.UpdateSite;
  import org.apache.ivy.osgi.updatesite.xml.UpdateSiteDigestParser;
  import org.apache.ivy.plugins.repository.url.URLRepository;
  import org.apache.ivy.plugins.repository.url.URLResource;
  import org.apache.ivy.util.Message;
  import org.xml.sax.SAXException;
  
  public class UpdateSiteLoader {
  
      private final RepositoryCacheManager repositoryCacheManager;
  
      private final URLRepository urlRepository = new URLRepository();
  
      private final CacheResourceOptions options;
  
      private final TimeoutConstraint timeoutConstraint;
  
      private int logLevel = Message.MSG_INFO;
  
      public UpdateSiteLoader(final RepositoryCacheManager repositoryCacheManager,
                              final EventManager eventManager, final CacheResourceOptions options,
                              final TimeoutConstraint timeoutConstraint) {
          this.repositoryCacheManager = repositoryCacheManager;
          this.options = options;
          this.timeoutConstraint = timeoutConstraint;
          if (eventManager != null) {
              urlRepository.addTransferListener(eventManager);
          }
      }
  
      public void setLogLevel(int logLevel) {
          this.logLevel = logLevel;
      }
  
      public RepoDescriptor load(URI repoUri) throws IOException, ParseException, SAXException {
          if (!repoUri.toString().endsWith("/")) {
              try {
                  repoUri = new URI(repoUri.toString() + "/");
              } catch (URISyntaxException e) {
                  throw new RuntimeException("Cannot make an uri for the repo");
              }
          }
          Message.info("Loading the update site " + repoUri);
          // first look for a p2 repository
          RepoDescriptor repo = loadP2(repoUri);
          if (repo != null) {
              return repo;
          }
          Message.verbose("\tNo P2 artifacts, falling back on the old fashioned updatesite");
          // then try the old update site
          UpdateSite site = loadSite(repoUri);
          if (site == null) {
              return null;
          }
          repo = loadFromDigest(site);
          if (repo != null) {
              return repo;
          }
          return loadFromSite(site);
      }
  
      private P2Descriptor loadP2(URI repoUri) throws IOException, ParseException, SAXException {
          P2Descriptor p2Descriptor = new P2Descriptor(repoUri,
                  ExecutionEnvironmentProfileProvider.getInstance());
          p2Descriptor.setLogLevel(logLevel);
          if (!populateP2Descriptor(repoUri, p2Descriptor)) {
              return null;
          }
          p2Descriptor.finish();
          return p2Descriptor;
      }
  
      private boolean populateP2Descriptor(URI repoUri, P2Descriptor p2Descriptor)
              throws IOException, ParseException, SAXException {
          Message.verbose("Loading P2 repository " + repoUri);
          boolean contentExists = readContent(repoUri, p2Descriptor);
          boolean artifactExists = readArtifacts(repoUri, p2Descriptor);
          return artifactExists || contentExists;
      }
  
      private boolean readContent(URI repoUri, P2Descriptor p2Descriptor) throws IOException,
              ParseException, SAXException {
          boolean contentExists = readCompositeContent(repoUri, "compositeContent", p2Descriptor);
          if (!contentExists) {
              P2MetadataParser metadataParser = new P2MetadataParser(p2Descriptor);
              metadataParser.setLogLevel(logLevel);
              contentExists = readJarOrXml(repoUri, "content", metadataParser);
          }
          return contentExists;
      }
  
      private boolean readArtifacts(URI repoUri, P2Descriptor p2Descriptor) throws IOException,
              ParseException, SAXException {
          boolean artifactExists = readCompositeArtifact(repoUri, "compositeArtifacts", p2Descriptor);
          if (!artifactExists) {
              artifactExists = readJarOrXml(repoUri, "artifacts", new P2ArtifactParser(p2Descriptor,
                      repoUri.toURL().toExternalForm()));
          }
  
          return artifactExists;
      }
  
      private boolean readCompositeContent(URI repoUri, String name, P2Descriptor p2Descriptor)
              throws IOException, ParseException, SAXException {
          P2CompositeParser p2CompositeParser = new P2CompositeParser();
          boolean exist = readJarOrXml(repoUri, name, p2CompositeParser);
          if (exist) {
              for (String childLocation : p2CompositeParser.getChildLocations()) {
                  if (!childLocation.endsWith("/")) {
                      childLocation += "/";
                  }
                  URI childUri = repoUri.resolve(childLocation);
                  readContent(childUri, p2Descriptor);
              }
          }
          return exist;
      }
  
      private boolean readCompositeArtifact(URI repoUri, String name, P2Descriptor p2Descriptor)
              throws IOException, ParseException, SAXException {
          P2CompositeParser p2CompositeParser = new P2CompositeParser();
          boolean exist = readJarOrXml(repoUri, name, p2CompositeParser);
          if (exist) {
              for (String childLocation : p2CompositeParser.getChildLocations()) {
                  if (!childLocation.endsWith("/")) {
                      childLocation += "/";
                  }
                  URI childUri = repoUri.resolve(childLocation);
                  readArtifacts(childUri, p2Descriptor);
              }
          }
          return exist;
      }
  
      private boolean readJarOrXml(URI repoUri, String baseName, XMLInputParser reader)
              throws IOException, ParseException, SAXException {
          InputStream readIn = null; // the input stream from which the xml should be read
  
          URL contentUrl = repoUri.resolve(baseName + ".jar").toURL();
          URLResource res = new URLResource(contentUrl, this.timeoutConstraint);
  
          ArtifactDownloadReport report = repositoryCacheManager.downloadRepositoryResource(res,
              baseName, baseName, "jar", options, urlRepository);
  
          if (report.getDownloadStatus() == DownloadStatus.FAILED) {
              // no jar file, try the xml one
              contentUrl = repoUri.resolve(baseName + ".xml").toURL();
              res = new URLResource(contentUrl, this.timeoutConstraint);
  
              report = repositoryCacheManager.downloadRepositoryResource(res, baseName, baseName,
                  "xml", options, urlRepository);
  
              if (report.getDownloadStatus() == DownloadStatus.FAILED) {
                  // no xml either
                  return false;
              }
  
              readIn = new FileInputStream(report.getLocalFile());
          } else {
              InputStream in = new FileInputStream(report.getLocalFile());
  
              try {
                  // compressed, let's get the pointer on the actual xml
                  readIn = findEntry(in, baseName + ".xml");
                  if (readIn == null) {
                      in.close();
                      return false;
                  }
              } catch (IOException e) {
                  in.close();
                  throw e;
              }
  
          }
  
          try {
              reader.parse(readIn);
          } finally {
              readIn.close();
          }
  
          return true;
      }
  
      private UpdateSite loadSite(URI repoUri) throws IOException, SAXException {
          URI siteUri = normalizeSiteUri(repoUri, null);
          URL u = siteUri.resolve("site.xml").toURL();
  
          final URLResource res = new URLResource(u, this.timeoutConstraint);
          ArtifactDownloadReport report = repositoryCacheManager.downloadRepositoryResource(res,
              "site", "updatesite", "xml", options, urlRepository);
          if (report.getDownloadStatus() == DownloadStatus.FAILED) {
              return null;
          }
          try (InputStream in = new FileInputStream(report.getLocalFile())) {
              UpdateSite site = EclipseUpdateSiteParser.parse(in);
              site.setUri(normalizeSiteUri(site.getUri(), siteUri));
              return site;
          }
      }
  
      private URI normalizeSiteUri(URI uri, URI defaultValue) {
          if (uri == null) {
              return defaultValue;
          }
          String uriString = uri.toString();
          if (uriString.endsWith("site.xml")) {
              try {
                  return new URI(uriString.substring(0, uriString.length() - 8));
              } catch (URISyntaxException e) {
                  throw new RuntimeException("Illegal uri", e);
              }
          }
          if (!uriString.endsWith("/")) {
              try {
                  return new URI(uriString + "/");
              } catch (URISyntaxException e) {
                  throw new RuntimeException("Illegal uri", e);
              }
          }
          return uri;
      }
  
      private UpdateSiteDescriptor loadFromDigest(UpdateSite site) throws IOException,
              SAXException {
          URI digestBaseUri = site.getDigestUri();
          if (digestBaseUri == null) {
              digestBaseUri = site.getUri();
          } else if (!digestBaseUri.isAbsolute()) {
              digestBaseUri = site.getUri().resolve(digestBaseUri);
          }
          URL digest = digestBaseUri.resolve("digest.zip").toURL();
          Message.verbose("\tReading " + digest);
  
          final URLResource res = new URLResource(digest, this.timeoutConstraint);
          ArtifactDownloadReport report = repositoryCacheManager.downloadRepositoryResource(res,
              "digest", "digest", "zip", options, urlRepository);
          if (report.getDownloadStatus() == DownloadStatus.FAILED) {
              return null;
          }
          try (InputStream in = new FileInputStream(report.getLocalFile())) {
              ZipInputStream zipped = findEntry(in, "digest.xml");
              if (zipped == null) {
                  return null;
              }
              return UpdateSiteDigestParser.parse(zipped, site);
          }
      }
  
      private UpdateSiteDescriptor loadFromSite(UpdateSite site) throws IOException, SAXException {
          UpdateSiteDescriptor repoDescriptor = new UpdateSiteDescriptor(site.getUri(),
                  ExecutionEnvironmentProfileProvider.getInstance());
  
          for (EclipseFeature feature : site.getFeatures()) {
              URL url = site.getUri().resolve(feature.getUrl()).toURL();
  
              final URLResource res = new URLResource(url, this.timeoutConstraint);
              ArtifactDownloadReport report = repositoryCacheManager.downloadRepositoryResource(res,
                  feature.getId(), "feature", "jar", options, urlRepository);
              if (report.getDownloadStatus() == DownloadStatus.FAILED) {
                  return null;
              }
              try (InputStream in = new FileInputStream(report.getLocalFile())) {
                  ZipInputStream zipped = findEntry(in, "feature.xml");
                  if (zipped == null) {
                      return null;
                  }
                  EclipseFeature f = FeatureParser.parse(zipped);
                  f.setURL(feature.getUrl());
                  repoDescriptor.addFeature(f);
              }
          }
  
          return repoDescriptor;
      }
  
      private ZipInputStream findEntry(InputStream in, String entryName) throws IOException {
          ZipInputStream zipped = new ZipInputStream(in);
          ZipEntry zipEntry = zipped.getNextEntry();
          while (zipEntry != null && !zipEntry.getName().equals(entryName)) {
              zipEntry = zipped.getNextEntry();
          }
          if (zipEntry == null) {
              return null;
          }
          return zipped;
      }
  }
  