blob: 4ac8b34168c4af640507868ca8e0c199b32349c2 [file] [log] [blame]
/*
* Copyright 2000-2009 JetBrains s.r.o.
*
* 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 org.jetbrains.idea.maven.indices;
import com.intellij.openapi.Disposable;
import com.intellij.openapi.application.AccessToken;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.components.ServiceManager;
import com.intellij.openapi.extensions.Extensions;
import com.intellij.openapi.progress.BackgroundTaskQueue;
import com.intellij.openapi.progress.ProgressIndicator;
import com.intellij.openapi.progress.Task;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.JDOMUtil;
import com.intellij.openapi.util.Pair;
import com.intellij.openapi.util.io.FileUtil;
import com.intellij.openapi.util.text.StringUtil;
import gnu.trove.THashSet;
import org.jdom.Document;
import org.jdom.Element;
import org.jdom.JDOMException;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.TestOnly;
import org.jetbrains.idea.maven.model.MavenArchetype;
import org.jetbrains.idea.maven.project.MavenGeneralSettings;
import org.jetbrains.idea.maven.project.MavenProjectsManager;
import org.jetbrains.idea.maven.server.MavenIndexerWrapper;
import org.jetbrains.idea.maven.server.MavenServerDownloadListener;
import org.jetbrains.idea.maven.server.MavenServerManager;
import org.jetbrains.idea.maven.utils.*;
import java.io.File;
import java.io.IOException;
import java.util.*;
public class MavenIndicesManager implements Disposable {
private static final String ELEMENT_ARCHETYPES = "archetypes";
private static final String ELEMENT_ARCHETYPE = "archetype";
private static final String ELEMENT_GROUP_ID = "groupId";
private static final String ELEMENT_ARTIFACT_ID = "artifactId";
private static final String ELEMENT_VERSION = "version";
private static final String ELEMENT_REPOSITORY = "repository";
private static final String ELEMENT_DESCRIPTION = "description";
private static final String LOCAL_REPOSITORY_ID = "local";
private MavenServerDownloadListener myDownloadListener;
public enum IndexUpdatingState {
IDLE, WAITING, UPDATING
}
private volatile File myTestIndicesDir;
private volatile MavenIndexerWrapper myIndexer;
private volatile MavenIndices myIndices;
private final Object myUpdatingIndicesLock = new Object();
private final List<MavenIndex> myWaitingIndices = new ArrayList<MavenIndex>();
private volatile MavenIndex myUpdatingIndex;
private final BackgroundTaskQueue myUpdatingQueue = new BackgroundTaskQueue(null, IndicesBundle.message("maven.indices.updating"));
private volatile List<MavenArchetype> myUserArchetypes = new ArrayList<MavenArchetype>();
public static MavenIndicesManager getInstance() {
return ServiceManager.getService(MavenIndicesManager.class);
}
@TestOnly
public void setTestIndexDir(File indicesDir) {
myTestIndicesDir = indicesDir;
}
public void clear() {
myUpdatingQueue.clear();
}
private synchronized MavenIndices getIndicesObject() {
ensureInitialized();
return myIndices;
}
private synchronized void ensureInitialized() {
if (myIndices != null) return;
myIndexer = MavenServerManager.getInstance().createIndexer();
myDownloadListener = new MavenServerDownloadListener() {
public void artifactDownloaded(File file, String relativePath) {
addArtifact(file, relativePath);
}
};
MavenServerManager.getInstance().addDownloadListener(myDownloadListener);
myIndices = new MavenIndices(myIndexer, getIndicesDir(), new MavenIndex.IndexListener() {
public void indexIsBroken(MavenIndex index) {
scheduleUpdate(null, Collections.singletonList(index), false);
}
});
loadUserArchetypes();
}
private File getIndicesDir() {
return myTestIndicesDir == null
? MavenUtil.getPluginSystemDir("Indices")
: myTestIndicesDir;
}
public void dispose() {
doShutdown();
if (ApplicationManager.getApplication().isUnitTestMode()) {
FileUtil.delete(getIndicesDir());
}
}
private synchronized void doShutdown() {
if (myDownloadListener != null) {
MavenServerManager.getInstance().removeDownloadListener(myDownloadListener);
myDownloadListener = null;
}
if (myIndices != null) {
try {
myIndices.close();
}
catch (Exception e) {
MavenLog.LOG.error("", e);
}
myIndices = null;
}
clear();
myIndexer = null;
}
@TestOnly
public void doShutdownInTests() {
doShutdown();
}
public List<MavenIndex> getIndices() {
return getIndicesObject().getIndices();
}
public synchronized List<MavenIndex> ensureIndicesExist(Project project,
File localRepository,
Collection<Pair<String, String>> remoteRepositoriesIdsAndUrls) {
// MavenIndices.add method returns an existing index if it has already been added, thus we have to use set here.
LinkedHashSet<MavenIndex> result = new LinkedHashSet<MavenIndex>();
MavenIndices indicesObjectCache = getIndicesObject();
try {
MavenIndex localIndex = indicesObjectCache.add(LOCAL_REPOSITORY_ID, localRepository.getPath(), MavenIndex.Kind.LOCAL);
result.add(localIndex);
if (localIndex.getUpdateTimestamp() == -1) {
scheduleUpdate(project, Collections.singletonList(localIndex));
}
}
catch (MavenIndexException e) {
MavenLog.LOG.warn(e);
}
for (Pair<String, String> eachIdAndUrl : remoteRepositoriesIdsAndUrls) {
try {
result.add(indicesObjectCache.add(eachIdAndUrl.first, eachIdAndUrl.second, MavenIndex.Kind.REMOTE));
}
catch (MavenIndexException e) {
MavenLog.LOG.warn(e);
}
}
return new ArrayList<MavenIndex>(result);
}
private void addArtifact(File artifactFile, String relativePath) {
String repositoryPath = getRepositoryUrl(artifactFile, relativePath);
MavenIndex index = getIndicesObject().find(LOCAL_REPOSITORY_ID, repositoryPath, MavenIndex.Kind.LOCAL);
if (index != null) {
index.addArtifact(artifactFile);
}
}
private static String getRepositoryUrl(File artifactFile, String name) {
List<String> parts = getArtifactParts(name);
File result = artifactFile;
for (int i = 0; i < parts.size(); i++) {
result = result.getParentFile();
}
return result.getPath();
}
private static List<String> getArtifactParts(String name) {
return StringUtil.split(name, "/");
}
public void scheduleUpdate(Project project, List<MavenIndex> indices) {
scheduleUpdate(project, indices, true);
}
private void scheduleUpdate(final Project projectOrNull, List<MavenIndex> indices, final boolean fullUpdate) {
final List<MavenIndex> toSchedule = new ArrayList<MavenIndex>();
synchronized (myUpdatingIndicesLock) {
for (MavenIndex each : indices) {
if (myWaitingIndices.contains(each)) continue;
toSchedule.add(each);
}
myWaitingIndices.addAll(toSchedule);
}
if (toSchedule.isEmpty()) return;
myUpdatingQueue.run(new Task.Backgroundable(projectOrNull, IndicesBundle.message("maven.indices.updating"), true) {
public void run(@NotNull ProgressIndicator indicator) {
try {
doUpdateIndices(projectOrNull, toSchedule, fullUpdate, new MavenProgressIndicator(indicator));
}
catch (MavenProcessCanceledException ignore) {
}
}
});
}
private void doUpdateIndices(final Project projectOrNull, List<MavenIndex> indices, boolean fullUpdate, MavenProgressIndicator indicator)
throws MavenProcessCanceledException {
MavenLog.LOG.assertTrue(!fullUpdate || projectOrNull != null);
List<MavenIndex> remainingWaiting = new ArrayList<MavenIndex>(indices);
try {
for (MavenIndex each : indices) {
if (indicator.isCanceled()) return;
indicator.setText(IndicesBundle.message("maven.indices.updating.index",
each.getRepositoryId(),
each.getRepositoryPathOrUrl()));
synchronized (myUpdatingIndicesLock) {
remainingWaiting.remove(each);
myWaitingIndices.remove(each);
myUpdatingIndex = each;
}
try {
getIndicesObject().updateOrRepair(each, fullUpdate, fullUpdate ? getMavenSettings(projectOrNull, indicator) : null, indicator);
if (projectOrNull != null) MavenRehighlighter.rehighlight(projectOrNull);
}
finally {
synchronized (myUpdatingIndicesLock) {
myUpdatingIndex = null;
}
}
}
}
finally {
synchronized (myUpdatingIndicesLock) {
myWaitingIndices.removeAll(remainingWaiting);
}
}
}
private static MavenGeneralSettings getMavenSettings(@NotNull final Project project, @NotNull MavenProgressIndicator indicator)
throws MavenProcessCanceledException {
MavenGeneralSettings settings;
AccessToken accessToken = ApplicationManager.getApplication().acquireReadActionLock();
try {
settings = project.isDisposed() ? null : MavenProjectsManager.getInstance(project).getGeneralSettings().clone();
}
finally {
accessToken.finish();
}
if (settings == null) {
// project was closed
indicator.cancel();
indicator.checkCanceled();
}
return settings;
}
public IndexUpdatingState getUpdatingState(MavenIndex index) {
synchronized (myUpdatingIndicesLock) {
if (myUpdatingIndex == index) return IndexUpdatingState.UPDATING;
if (myWaitingIndices.contains(index)) return IndexUpdatingState.WAITING;
return IndexUpdatingState.IDLE;
}
}
public synchronized Set<MavenArchetype> getArchetypes() {
ensureInitialized();
Set<MavenArchetype> result = new THashSet<MavenArchetype>(myIndexer.getArchetypes());
result.addAll(myUserArchetypes);
for (MavenArchetypesProvider each : Extensions.getExtensions(MavenArchetypesProvider.EP_NAME)) {
result.addAll(each.getArchetypes());
}
return result;
}
public synchronized void addArchetype(MavenArchetype archetype) {
ensureInitialized();
int idx = myUserArchetypes.indexOf(archetype);
if (idx >= 0) {
myUserArchetypes.set(idx, archetype);
}
else {
myUserArchetypes.add(archetype);
}
saveUserArchetypes();
}
private void loadUserArchetypes() {
try {
File file = getUserArchetypesFile();
if (!file.exists()) return;
Document doc = JDOMUtil.loadDocument(file);
Element root = doc.getRootElement();
if (root == null) return;
// Store artifact to set to remove duplicate created by old IDEA (http://youtrack.jetbrains.com/issue/IDEA-72105)
Collection<MavenArchetype> result = new LinkedHashSet<MavenArchetype>();
List<Element> children = root.getChildren(ELEMENT_ARCHETYPE);
for (int i = children.size() - 1; i >= 0; i--) {
Element each = children.get(i);
String groupId = each.getAttributeValue(ELEMENT_GROUP_ID);
String artifactId = each.getAttributeValue(ELEMENT_ARTIFACT_ID);
String version = each.getAttributeValue(ELEMENT_VERSION);
String repository = each.getAttributeValue(ELEMENT_REPOSITORY);
String description = each.getAttributeValue(ELEMENT_DESCRIPTION);
if (StringUtil.isEmptyOrSpaces(groupId)
|| StringUtil.isEmptyOrSpaces(artifactId)
|| StringUtil.isEmptyOrSpaces(version)) {
continue;
}
result.add(new MavenArchetype(groupId, artifactId, version, repository, description));
}
ArrayList<MavenArchetype> listResult = new ArrayList<MavenArchetype>(result);
Collections.reverse(listResult);
myUserArchetypes = listResult;
}
catch (IOException e) {
MavenLog.LOG.warn(e);
}
catch (JDOMException e) {
MavenLog.LOG.warn(e);
}
}
private void saveUserArchetypes() {
Element root = new Element(ELEMENT_ARCHETYPES);
for (MavenArchetype each : myUserArchetypes) {
Element childElement = new Element(ELEMENT_ARCHETYPE);
childElement.setAttribute(ELEMENT_GROUP_ID, each.groupId);
childElement.setAttribute(ELEMENT_ARTIFACT_ID, each.artifactId);
childElement.setAttribute(ELEMENT_VERSION, each.version);
if (each.repository != null) {
childElement.setAttribute(ELEMENT_REPOSITORY, each.repository);
}
if (each.description != null) {
childElement.setAttribute(ELEMENT_DESCRIPTION, each.description);
}
root.addContent(childElement);
}
try {
File file = getUserArchetypesFile();
file.getParentFile().mkdirs();
JDOMUtil.writeDocument(new Document(root), file, "\n");
}
catch (IOException e) {
MavenLog.LOG.warn(e);
}
}
private File getUserArchetypesFile() {
return new File(getIndicesDir(), "UserArchetypes.xml");
}
}