| /* |
| * 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 |
| * |
| * http://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.sling.cli.impl.nexus; |
| |
| import java.io.IOException; |
| import java.io.InputStream; |
| import java.io.InputStreamReader; |
| import java.nio.file.Files; |
| import java.nio.file.Path; |
| import java.util.HashMap; |
| import java.util.HashSet; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Set; |
| import java.util.concurrent.locks.ReentrantReadWriteLock; |
| import java.util.function.Consumer; |
| import java.util.function.Function; |
| import java.util.stream.Collectors; |
| |
| import javax.xml.parsers.DocumentBuilder; |
| import javax.xml.parsers.DocumentBuilderFactory; |
| import javax.xml.parsers.ParserConfigurationException; |
| import javax.xml.xpath.XPath; |
| import javax.xml.xpath.XPathConstants; |
| import javax.xml.xpath.XPathExpressionException; |
| import javax.xml.xpath.XPathFactory; |
| |
| import org.apache.commons.io.IOUtils; |
| import org.apache.http.HttpHeaders; |
| import org.apache.http.client.methods.CloseableHttpResponse; |
| import org.apache.http.client.methods.HttpGet; |
| import org.apache.http.impl.client.CloseableHttpClient; |
| import org.apache.sling.cli.impl.ComponentContextHelper; |
| import org.apache.sling.cli.impl.http.HttpClientFactory; |
| import org.apache.sling.cli.impl.nexus.StagingRepository.Status; |
| import org.apache.sling.cli.impl.release.Release; |
| import org.jetbrains.annotations.NotNull; |
| import org.osgi.service.component.ComponentContext; |
| import org.osgi.service.component.annotations.Activate; |
| import org.osgi.service.component.annotations.Component; |
| import org.osgi.service.component.annotations.Reference; |
| import org.slf4j.Logger; |
| import org.slf4j.LoggerFactory; |
| import org.w3c.dom.Document; |
| import org.xml.sax.SAXException; |
| |
| import com.google.gson.Gson; |
| import com.google.gson.JsonArray; |
| import com.google.gson.JsonElement; |
| import com.google.gson.JsonObject; |
| import com.google.gson.JsonParser; |
| |
| @Component(service = RepositoryService.class) |
| public class RepositoryService { |
| |
| private static final Logger LOGGER = LoggerFactory.getLogger(RepositoryService.class); |
| private static final String REPOSITORY_PREFIX = "orgapachesling-"; |
| private static final String DEFAULT_NEXUS_URL_PREFIX = "https://repository.apache.org"; |
| private static final String CONTENT_TYPE_JSON = "application/json"; |
| |
| private Map<String, LocalRepository> repositories = new HashMap<>(); |
| private ReentrantReadWriteLock readWriteLock = new ReentrantReadWriteLock(); |
| private final XPathFactory xPathFactory = XPathFactory.newInstance(); |
| private final DocumentBuilderFactory builderFactory = DocumentBuilderFactory.newInstance(); |
| |
| @Reference |
| private HttpClientFactory httpClientFactory; |
| private String nexusUrlPrefix; |
| |
| @Activate |
| private void activate(ComponentContext componentContext) { |
| ComponentContextHelper helper = ComponentContextHelper.wrap(componentContext); |
| nexusUrlPrefix = helper.getProperty("nexus.url.prefix", DEFAULT_NEXUS_URL_PREFIX); |
| } |
| |
| public List<StagingRepository> list() throws IOException { |
| return this.withStagingRepositories(reader -> { |
| Gson gson = new Gson(); |
| return gson.fromJson(reader, StagingRepositories.class).getData().stream() |
| .filter(r -> r.getType() == Status.closed) |
| .filter(r -> r.getRepositoryId().startsWith(REPOSITORY_PREFIX)) |
| .collect(Collectors.toList()); |
| }); |
| } |
| |
| public StagingRepository find(int stagingRepositoryId) throws IOException { |
| return this.withStagingRepositories(reader -> { |
| Gson gson = new Gson(); |
| return gson.fromJson(reader, StagingRepositories.class).getData().stream() |
| .filter(r -> r.getType() == Status.closed) |
| .filter(r -> r.getRepositoryId().startsWith(REPOSITORY_PREFIX)) |
| .filter(r -> r.getRepositoryId().endsWith("-" + stagingRepositoryId)) |
| .findFirst() |
| .orElseThrow(() -> new IllegalArgumentException("No repository found with id " + stagingRepositoryId)); |
| }); |
| } |
| |
| private <T> T withStagingRepositories(Function<InputStreamReader, T> function) throws IOException { |
| try (CloseableHttpClient client = httpClientFactory.newClient()) { |
| HttpGet get = newGet("/service/local/staging/profile_repositories"); |
| try (CloseableHttpResponse response = client.execute(get)) { |
| try (InputStream content = response.getEntity().getContent(); |
| InputStreamReader reader = new InputStreamReader(content)) { |
| if (response.getStatusLine().getStatusCode() != 200) { |
| throw new IOException("Status line : " + response.getStatusLine()); |
| } |
| return function.apply(reader); |
| } |
| } |
| } |
| } |
| |
| @NotNull |
| public LocalRepository download(@NotNull StagingRepository repository) throws IOException { |
| readWriteLock.readLock().lock(); |
| LocalRepository localRepository = repositories.get(repository.getRepositoryId()); |
| if (localRepository == null) { |
| readWriteLock.readLock().unlock(); |
| readWriteLock.writeLock().lock(); |
| try { |
| if (!repositories.containsKey(repository.getRepositoryId())) { |
| Path rootFolder = Files.createTempDirectory(repository.getRepositoryId() + "_"); |
| Set<Artifact> artifacts = getArtifacts(repository); |
| try (CloseableHttpClient client = httpClientFactory.newClient()) { |
| for (Artifact artifact : artifacts) { |
| String fileRelativePath = artifact.getRepositoryRelativePath(); |
| String relativeFolderPath = fileRelativePath.substring(0, fileRelativePath.lastIndexOf('/')); |
| Path artifactFolderPath = Files.createDirectories(rootFolder.resolve(relativeFolderPath)); |
| downloadFileFromRepository(repository, client, artifactFolderPath, fileRelativePath); |
| downloadFileFromRepository(repository, client, artifactFolderPath, |
| artifact.getRepositoryRelativeSignaturePath()); |
| downloadFileFromRepository(repository, client, artifactFolderPath, |
| artifact.getRepositoryRelativeSha1SumPath()); |
| downloadFileFromRepository(repository, client, artifactFolderPath, |
| artifact.getRepositoryRelativeMd5SumPath()); |
| } |
| } |
| localRepository = new LocalRepository(repository, artifacts, rootFolder); |
| repositories.put(localRepository.getRepositoryId(), localRepository); |
| } |
| readWriteLock.readLock().lock(); |
| } finally { |
| readWriteLock.writeLock().unlock(); |
| } |
| } |
| try { |
| if (localRepository == null) { |
| throw new IOException("Failed to download repository artifacts."); |
| } |
| return localRepository; |
| } finally { |
| readWriteLock.readLock().unlock(); |
| } |
| } |
| |
| public Set<Artifact> getArtifacts(StagingRepository repository) throws IOException { |
| Set<Artifact> artifacts = new HashSet<>(); |
| try (CloseableHttpClient client = httpClientFactory.newClient()) { |
| HttpGet get = |
| newGet("/service/local/lucene/search?g=org.apache.sling&repositoryId=" + |
| repository.getRepositoryId()); |
| try (CloseableHttpResponse response = client.execute(get)) { |
| try (InputStream content = response.getEntity().getContent(); |
| InputStreamReader reader = new InputStreamReader(content)) { |
| JsonParser parser = new JsonParser(); |
| JsonObject json = parser.parse(reader).getAsJsonObject(); |
| JsonArray data = json.get("data").getAsJsonArray(); |
| |
| for (JsonElement dataElement : data) { |
| JsonObject dataElementJson = dataElement.getAsJsonObject(); |
| String groupId = dataElementJson.get("groupId").getAsString(); |
| String artifactId = dataElementJson.get("artifactId").getAsString(); |
| String version = dataElementJson.get("version").getAsString(); |
| JsonArray artifactLinksArray = |
| dataElementJson.get("artifactHits").getAsJsonArray().get(0).getAsJsonObject().get("artifactLinks") |
| .getAsJsonArray(); |
| for (JsonElement artifactLinkElement : artifactLinksArray) { |
| JsonObject artifactLinkJson = artifactLinkElement.getAsJsonObject(); |
| String type = artifactLinkJson.get("extension").getAsString(); |
| String classifier = null; |
| if (artifactLinkJson.has("classifier")) { |
| classifier = artifactLinkJson.get("classifier").getAsString(); |
| } |
| artifacts.add(new Artifact(repository, groupId, artifactId, version, classifier, type)); |
| } |
| } |
| } |
| } |
| } |
| return artifacts; |
| } |
| |
| public void processArtifactStream(Artifact artifact, Consumer<InputStream> consumer) throws IOException { |
| try (CloseableHttpClient client = httpClientFactory.newClient()) { |
| HttpGet get = new HttpGet(artifact.getUri()); |
| try (CloseableHttpResponse response = client.execute(get)) { |
| int statusCode = response.getStatusLine().getStatusCode(); |
| if (statusCode != 200) { |
| throw new IOException(String.format("Got %d instead of 200 when retrieving %s.", statusCode, get.getURI())); |
| } |
| consumer.accept(response.getEntity().getContent()); |
| } |
| } |
| } |
| |
| public Set<Release> getReleases(StagingRepository stagingRepository) throws IOException { |
| Set<Release> releases = new HashSet<>(); |
| getArtifacts(stagingRepository).stream().filter(artifact -> "pom".equals(artifact.getType())).forEach(pom -> { |
| try { |
| XPath xPath = xPathFactory.newXPath(); |
| processArtifactStream(pom, stream -> { |
| try { |
| DocumentBuilder builder = builderFactory.newDocumentBuilder(); |
| Document xmlDocument = builder.parse(stream); |
| String name = (String) xPath.compile("/project/name/text()").evaluate(xmlDocument, XPathConstants.STRING); |
| String version = (String) xPath.compile("/project/version/text()").evaluate(xmlDocument, XPathConstants.STRING); |
| try { |
| releases.addAll(Release.fromString(name + " " + version)); |
| } catch (IllegalArgumentException e) { |
| LOGGER.error(String.format("Unable to determine a valid release from '%s %s'", name, version), e); |
| } |
| } catch (ParserConfigurationException | SAXException | XPathExpressionException | IOException e) { |
| LOGGER.error(String.format("Unable to process artifact %s.", pom), e); |
| } |
| }); |
| } catch (IOException e) { |
| LOGGER.error(String.format("Unable to process artifact %s.", pom), e); |
| } |
| }); |
| return Set.copyOf(releases); |
| } |
| |
| private void downloadFileFromRepository(@NotNull StagingRepository repository, @NotNull CloseableHttpClient client, |
| @NotNull Path artifactFolderPath, @NotNull String relativeFilePath) throws IOException { |
| String fileName = relativeFilePath.substring(relativeFilePath.lastIndexOf('/') + 1); |
| Path filePath = Files.createFile(artifactFolderPath.resolve(fileName)); |
| HttpGet get = new HttpGet(repository.getRepositoryURI() + "/" + relativeFilePath); |
| if (LOGGER.isDebugEnabled()) { |
| LOGGER.debug("Downloading {}.", get.getURI()); |
| } |
| try (CloseableHttpResponse response = client.execute(get)) { |
| try (InputStream content = response.getEntity().getContent()) { |
| IOUtils.copyLarge(content, Files.newOutputStream(filePath)); |
| } |
| } |
| } |
| |
| private HttpGet newGet(String suffix) { |
| HttpGet get = new HttpGet(nexusUrlPrefix + suffix); |
| get.addHeader(HttpHeaders.ACCEPT, CONTENT_TYPE_JSON); |
| return get; |
| } |
| |
| } |