blob: b5d26f5530a81ea2d144f402b3a04cf901ca6401 [file] [log] [blame]
/*
* Copyright (C) 2014 The Android Open Source Project
*
* Licensed 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 com.android.tools.idea.gradle.service.repo;
import com.android.annotations.Nullable;
import com.android.sdklib.repository.FullRevision;
import com.google.common.collect.Maps;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.util.JDOMUtil;
import com.intellij.openapi.util.Pair;
import org.jdom.Document;
import org.jdom.Element;
import org.jdom.JDOMException;
import org.jetbrains.annotations.NotNull;
import java.io.IOException;
import java.net.URI;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.atomic.AtomicBoolean;
/**
* Facades external repositories (maven, jcenter etc).
*/
public class ExternalRepository {
// TODO current implementation is rather straightforward, e.g. it doesn't handle concurrent requests, doesn't attempt to re-query
// TODO when we become online etc. Also it was found out that there is also another place which has a similar job
// TODO (MavenDependencyLookupDialog.searchMavenCentral()), so, we'd like to move that code out from a UI class and consolidate
// TODO it inside the current one.
private static final String URL_TEMPLATE = "http://repo1.maven.org/maven2/%s/%s/maven-metadata.xml";
private static final String MAVEN_METADATA_VERSIONING = "versioning";
private static final String MAVEN_METADATA_LATEST = "latest";
private static final Logger LOG = Logger.getInstance(ExternalRepository.class);
private final ConcurrentMap<Pair<String/*group*/, String/*artifact*/>, FullRevision> myLatestVersionCache = Maps.newConcurrentMap();
/**
* Current implementation is rather straightforward, i.e. it's not expected that this service is used from a number of places
* concurrently. However, we want to protect ourselves from background thread pool starvation, so, we allow only one request at a time.
* <p/>
* This field controls that.
*/
private final AtomicBoolean myRequestInProgress = new AtomicBoolean();
@Nullable
public FullRevision getLatest(@NotNull String groupId, @NotNull String artifactId) {
return myLatestVersionCache.get(Pair.create(groupId, artifactId));
}
/**
* Asks to refresh information for the target artifact, i.e. there is a possible case that we know that soon we'll need
* to {@link #getLatest(String, String) get information about the latest availble artifact version}, so, we ask to prepare
* it beforehand.
*
* @param groupId target artifact's group id
* @param artifactId target artifact id
*/
public void refreshFor(@NotNull final String groupId, @NotNull final String artifactId) {
if (!myRequestInProgress.compareAndSet(false, true)) {
return;
}
ApplicationManager.getApplication().executeOnPooledThread(new Runnable() {
@Override
public void run() {
try {
doRefreshFor(groupId, artifactId);
}
finally {
myRequestInProgress.set(false);
}
}
});
}
private void doRefreshFor(@NotNull final String groupId, @NotNull final String artifactId) {
String url = String.format(URL_TEMPLATE, groupId.replaceAll("\\.", "/"), artifactId);
Document document;
try {
document = JDOMUtil.loadDocument(URI.create(url).toURL());
}
catch (JDOMException e) {
LOG.warn(String.format(
"Unexpected exception occurred on attempt to parse document from %s (checking the latest version " + "for artifact '%s:%s')",
url, groupId, artifactId));
return;
}
catch (IOException e) {
LOG.warn(String.format("Unexpected I/O exception occurred on attempt to check the latest version for artifact '%s:%s' at "
+ "external repository (url %s)", groupId, artifactId, url));
return;
}
Element versioning = document.getRootElement().getChild(MAVEN_METADATA_VERSIONING);
if (versioning == null) {
LOG.warn(String.format("Can't check the latest version for artifact '%s:%s'. Reason: artifact metadata info downloaded from "
+ "%s has unknown format - expected to find a <%s> element under a root element but it's not there",
groupId, artifactId, url, MAVEN_METADATA_VERSIONING));
return;
}
Element latest = versioning.getChild(MAVEN_METADATA_LATEST);
if (latest == null) {
LOG.warn(String.format("Can't check the latest version for artifact '%s:%s'. Reason: artifact metadata info downloaded from "
+ "%s has unknown format - expected to find a <%s> element under a <%s> element but it's not there",
groupId, artifactId, url, MAVEN_METADATA_LATEST, MAVEN_METADATA_VERSIONING));
return;
}
try {
FullRevision revision = FullRevision.parseRevision(latest.getText());
myLatestVersionCache.put(Pair.create(groupId, artifactId), revision);
}
catch (NumberFormatException e) {
LOG.warn(String.format("Can't check the latest version for artifact '%s:%s'. Reason: artifact metadata info downloaded from " +
"%s has unknown version format - '%s'", groupId, artifactId, url, latest.getText()));
}
}
}