blob: fec188aaf8c7eedb00678c110cc6af1f84352757 [file] [log] [blame]
/*
* 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;
}
}