blob: 37d6906e398c1c154a2f969c2aab821bd7658db5 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2008-2010 Sonatype, Inc.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License 2.0
* which accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* Sonatype, Inc. - initial API and implementation
*******************************************************************************/
package org.eclipse.m2e.core.internal.index.nexus;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import java.util.TreeMap;
import java.util.WeakHashMap;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;
import org.eclipse.core.runtime.jobs.Job;
import org.eclipse.osgi.util.NLS;
import org.codehaus.plexus.PlexusContainer;
import org.codehaus.plexus.component.repository.exception.ComponentLookupException;
import org.codehaus.plexus.util.FileUtils;
import org.codehaus.plexus.util.IOUtil;
import org.apache.lucene.search.BooleanClause;
import org.apache.lucene.search.BooleanClause.Occur;
import org.apache.lucene.search.BooleanQuery;
import org.apache.lucene.search.FilteredQuery;
import org.apache.lucene.search.Query;
import org.apache.lucene.search.QueryWrapperFilter;
import org.apache.lucene.store.Directory;
import org.apache.lucene.store.FSDirectory;
import org.apache.maven.archetype.source.ArchetypeDataSource;
import org.apache.maven.index.ArtifactContext;
import org.apache.maven.index.ArtifactContextProducer;
import org.apache.maven.index.ArtifactInfo;
import org.apache.maven.index.Field;
import org.apache.maven.index.IteratorSearchRequest;
import org.apache.maven.index.IteratorSearchResponse;
import org.apache.maven.index.MAVEN;
import org.apache.maven.index.NexusIndexer;
import org.apache.maven.index.SearchType;
import org.apache.maven.index.artifact.Gav;
import org.apache.maven.index.artifact.IllegalArtifactCoordinateException;
import org.apache.maven.index.context.IndexCreator;
import org.apache.maven.index.context.IndexingContext;
import org.apache.maven.index.creator.JarFileContentsIndexCreator;
import org.apache.maven.index.creator.MavenArchetypeArtifactInfoIndexCreator;
import org.apache.maven.index.creator.MavenPluginArtifactInfoIndexCreator;
import org.apache.maven.index.creator.MinimalArtifactInfoIndexCreator;
import org.apache.maven.index.fs.Lock;
import org.apache.maven.index.updater.IndexUpdateRequest;
import org.apache.maven.index.updater.IndexUpdateResult;
import org.apache.maven.index.updater.IndexUpdater;
import org.apache.maven.wagon.authentication.AuthenticationInfo;
import org.apache.maven.wagon.proxy.ProxyInfo;
import org.eclipse.m2e.core.MavenPlugin;
import org.eclipse.m2e.core.embedder.ArtifactKey;
import org.eclipse.m2e.core.embedder.ArtifactRepositoryRef;
import org.eclipse.m2e.core.embedder.IMaven;
import org.eclipse.m2e.core.embedder.IMavenConfiguration;
import org.eclipse.m2e.core.internal.IMavenConstants;
import org.eclipse.m2e.core.internal.MavenPluginActivator;
import org.eclipse.m2e.core.internal.Messages;
import org.eclipse.m2e.core.internal.NoSuchComponentException;
import org.eclipse.m2e.core.internal.equinox.EquinoxLocker;
import org.eclipse.m2e.core.internal.index.IIndex;
import org.eclipse.m2e.core.internal.index.IndexListener;
import org.eclipse.m2e.core.internal.index.IndexManager;
import org.eclipse.m2e.core.internal.index.IndexedArtifact;
import org.eclipse.m2e.core.internal.index.IndexedArtifactFile;
import org.eclipse.m2e.core.internal.index.MatchTyped;
import org.eclipse.m2e.core.internal.index.MatchTyped.MatchType;
import org.eclipse.m2e.core.internal.index.SearchExpression;
import org.eclipse.m2e.core.internal.index.SourcedSearchExpression;
import org.eclipse.m2e.core.internal.index.nexus.IndexUpdaterJob.IndexCommand;
import org.eclipse.m2e.core.internal.repository.IRepositoryIndexer;
import org.eclipse.m2e.core.project.IMavenProjectChangedListener;
import org.eclipse.m2e.core.project.IMavenProjectFacade;
import org.eclipse.m2e.core.project.IMavenProjectRegistry;
import org.eclipse.m2e.core.project.MavenProjectChangedEvent;
import org.eclipse.m2e.core.repository.IRepository;
import org.eclipse.m2e.core.repository.IRepositoryRegistry;
/**
* @author Eugene Kuleshov
*/
public class NexusIndexManager implements IndexManager, IMavenProjectChangedListener, IRepositoryIndexer {
private static final Logger log = LoggerFactory.getLogger(NexusIndexManager.class);
public static final int MIN_CLASS_QUERY_LENGTH = 6;
/**
* Lazy instantiated nexus indexer instance.
*/
private NexusIndexer indexer;
/**
* Lazy instantiated nexus indexer's contextProducer.
*/
private ArtifactContextProducer artifactContextProducer;
/**
* Lock guarding lazy instantiation of indexerLock instance
*/
private final Object indexerLock = new Object();
/**
* Lock guarding lazy instantiation of contextProducer instance
*/
private final Object contextProducerLock = new Object();
private final IMaven maven;
private final IMavenProjectRegistry projectManager;
private final IRepositoryRegistry repositoryRegistry;
private final List<IndexCreator> fullCreators;
private final List<IndexCreator> minCreators;
private final File baseIndexDir;
private final List<IndexListener> indexListeners = new ArrayList<IndexListener>();
private NexusIndex localIndex;
private final NexusIndex workspaceIndex;
private final IndexUpdaterJob updaterJob;
private Properties indexDetails = new Properties();
private Set<String> updatingIndexes = new HashSet<String>();
private final IndexUpdater indexUpdater;
private static final EquinoxLocker locker = new EquinoxLocker();
/**
* Maps repository UID to the lock object associated with the repository. Entries are only added but never directly
* removed from the map, although jvm garbage collector may remove otherwise unused entries to reclaim the little
* memory they use. Never access this map directly. #getIndexLock must be used to get repository lock object.
*/
private final Map<String, Object> indexLocks = new WeakHashMap<String, Object>();
private final PlexusContainer container;
public NexusIndexManager(PlexusContainer container, IMavenProjectRegistry projectManager,
IRepositoryRegistry repositoryRegistry, File stateDir) {
this.container = container;
this.projectManager = projectManager;
this.repositoryRegistry = repositoryRegistry;
this.baseIndexDir = new File(stateDir, "nexus"); //$NON-NLS-1$
this.maven = MavenPlugin.getMaven();
try {
this.indexUpdater = container.lookup(IndexUpdater.class);
this.fullCreators = Collections.unmodifiableList(getFullCreator());
this.minCreators = Collections.unmodifiableList(getMinCreator());
} catch(ComponentLookupException ex) {
throw new NoSuchComponentException(ex);
}
this.updaterJob = new IndexUpdaterJob(this);
this.workspaceIndex = new NexusIndex(this, repositoryRegistry.getWorkspaceRepository(), NexusIndex.DETAILS_MIN);
}
private NexusIndex newLocalIndex(IRepository localRepository) {
return new NexusIndex(this, localRepository, NexusIndex.DETAILS_FULL);
}
private List<IndexCreator> getFullCreator() throws ComponentLookupException {
List<IndexCreator> creators = new ArrayList<IndexCreator>();
IndexCreator min = container.lookup(IndexCreator.class, MinimalArtifactInfoIndexCreator.ID);
IndexCreator mavenPlugin = container.lookup(IndexCreator.class, MavenPluginArtifactInfoIndexCreator.ID);
IndexCreator mavenArchetype = container.lookup(IndexCreator.class, MavenArchetypeArtifactInfoIndexCreator.ID);
IndexCreator jar = container.lookup(IndexCreator.class, JarFileContentsIndexCreator.ID);
creators.add(min);
creators.add(jar);
creators.add(mavenPlugin);
creators.add(mavenArchetype);
return creators;
}
private List<IndexCreator> getMinCreator() throws ComponentLookupException {
List<IndexCreator> creators = new ArrayList<IndexCreator>();
IndexCreator min = container.lookup(IndexCreator.class, MinimalArtifactInfoIndexCreator.ID);
IndexCreator mavenArchetype = container.lookup(IndexCreator.class, MavenArchetypeArtifactInfoIndexCreator.ID);
creators.add(min);
creators.add(mavenArchetype);
return creators;
}
/** for Unit test */
public IndexedArtifactFile getIndexedArtifactFile(IRepository repository, ArtifactKey gav) throws CoreException {
try {
BooleanQuery query = new BooleanQuery();
query.add(constructQuery(MAVEN.GROUP_ID, gav.getGroupId(), SearchType.EXACT), BooleanClause.Occur.MUST);
query.add(constructQuery(MAVEN.ARTIFACT_ID, gav.getArtifactId(), SearchType.EXACT), BooleanClause.Occur.MUST);
query.add(constructQuery(MAVEN.VERSION, gav.getVersion(), SearchType.EXACT), BooleanClause.Occur.MUST);
if(gav.getClassifier() != null) {
query.add(constructQuery(MAVEN.CLASSIFIER, gav.getClassifier(), SearchType.EXACT), BooleanClause.Occur.MUST);
}
synchronized(getIndexLock(repository)) {
ArtifactInfo artifactInfo = getIndexer().identify(query, Collections.singleton(getIndexingContext(repository)));
if(artifactInfo != null) {
return getIndexedArtifactFile(artifactInfo);
}
}
} catch(Exception ex) {
String msg = "Illegal artifact coordinate " + ex.getMessage();
log.error(msg, ex);
throw new CoreException(new Status(IStatus.ERROR, IMavenConstants.PLUGIN_ID, -1,
Messages.NexusIndexManager_error_search, ex));
}
return null;
}
/** for Unit test */
public IndexedArtifactFile getIndexedArtifactFile(ArtifactInfo artifactInfo) {
String groupId = artifactInfo.groupId;
String artifactId = artifactInfo.artifactId;
String repository = artifactInfo.repository;
String version = artifactInfo.version;
String classifier = artifactInfo.classifier;
String packaging = artifactInfo.packaging;
String fname = artifactInfo.fname;
if(fname == null) {
fname = artifactId + '-' + version
+ (classifier != null ? '-' + classifier : "") + (packaging != null ? ('.' + packaging) : ""); //$NON-NLS-1$ //$NON-NLS-2$
}
long size = artifactInfo.size;
Date date = new Date(artifactInfo.lastModified);
int sourcesExists = artifactInfo.sourcesExists.ordinal();
int javadocExists = artifactInfo.javadocExists.ordinal();
String prefix = artifactInfo.prefix;
List<String> goals = artifactInfo.goals;
return new IndexedArtifactFile(repository, groupId, artifactId, version, packaging, classifier, fname, size, date,
sourcesExists, javadocExists, prefix, goals);
}
public IndexedArtifactFile identify(File file) throws CoreException {
try {
ArtifactInfo artifactInfo = getIndexer().identify(file);
return artifactInfo == null ? null : getIndexedArtifactFile(artifactInfo);
} catch(IOException ex) {
throw new CoreException(new Status(IStatus.ERROR, IMavenConstants.PLUGIN_ID, -1,
Messages.NexusIndexManager_error_search, ex));
}
}
protected IndexedArtifactFile identify(IRepository repository, File file) throws CoreException {
try {
IndexingContext context = getIndexingContext(repository);
if(context == null) {
return null;
}
ArtifactInfo artifactInfo = identify(file, Collections.singleton(context));
return artifactInfo == null ? null : getIndexedArtifactFile(artifactInfo);
} catch(IOException ex) {
throw new CoreException(new Status(IStatus.ERROR, IMavenConstants.PLUGIN_ID, -1,
Messages.NexusIndexManager_error_search, ex));
}
}
/**
* Method to construct Lucene Queries without need to actually know the structure and details (field names, analyze
* details, etc) of the underlying index. Also, using this methods makes you "future proof". Naturally, at caller
* level you can still combine these queries using BooleanQuery to suit your needs.
*
* @param field
* @param query
* @param type
* @return
*/
public Query constructQuery(Field field, SearchExpression query) {
// let the default be "scored" search
SearchType st = SearchType.SCORED;
if(query instanceof MatchTyped) {
MatchType mt = ((MatchTyped) query).getMatchType();
if(MatchType.EXACT.equals(mt)) {
st = SearchType.EXACT;
}
}
return constructQuery(field, query.getStringValue(), st);
}
private Query constructQuery(Field field, String query, SearchType searchType) {
return getIndexer().constructQuery(field, query, searchType);
}
public Map<String, IndexedArtifact> search(SearchExpression term, String type) throws CoreException {
return search(null, term, type, IIndex.SEARCH_ALL);
}
public Map<String, IndexedArtifact> search(SearchExpression term, String type, int classifier) throws CoreException {
return search(null, term, type, classifier);
}
private void addClassifiersToQuery(BooleanQuery bq, int classifier) {
boolean includeJavaDocs = (classifier & IIndex.SEARCH_JAVADOCS) > 0;
Query tq = null;
if(!includeJavaDocs) {
tq = constructQuery(MAVEN.CLASSIFIER, "javadoc", SearchType.EXACT); //$NON-NLS-1$
bq.add(tq, Occur.MUST_NOT);
}
boolean includeSources = (classifier & IIndex.SEARCH_SOURCES) > 0;
if(!includeSources) {
tq = constructQuery(MAVEN.CLASSIFIER, "sources", SearchType.EXACT); //$NON-NLS-1$
bq.add(tq, Occur.MUST_NOT);
}
boolean includeTests = (classifier & IIndex.SEARCH_TESTS) > 0;
if(!includeTests) {
tq = constructQuery(MAVEN.CLASSIFIER, "tests", SearchType.EXACT); //$NON-NLS-1$
bq.add(tq, Occur.MUST_NOT);
}
}
/**
* @return Map<String, IndexedArtifact>
*/
protected Map<String, IndexedArtifact> search(IRepository repository, SearchExpression term, String type,
int classifier) throws CoreException {
Query query;
if(IIndex.SEARCH_GROUP.equals(type)) {
query = constructQuery(MAVEN.GROUP_ID, term);
// query = new TermQuery(new Term(ArtifactInfo.GROUP_ID, term));
// query = new PrefixQuery(new Term(ArtifactInfo.GROUP_ID, term));
} else if(IIndex.SEARCH_ARTIFACT.equals(type)) {
BooleanQuery bq = new BooleanQuery();
bq.add(constructQuery(MAVEN.GROUP_ID, term), Occur.SHOULD); //$NON-NLS-1$
bq.add(constructQuery(MAVEN.ARTIFACT_ID, term), Occur.SHOULD); //$NON-NLS-1$
bq.add(
constructQuery(MAVEN.SHA1, term.getStringValue(), term.getStringValue().length() == 40 ? SearchType.EXACT
: SearchType.SCORED), Occur.SHOULD);
addClassifiersToQuery(bq, classifier);
query = bq;
} else if(IIndex.SEARCH_PARENTS.equals(type)) {
if(term == null) {
query = constructQuery(MAVEN.PACKAGING, "pom", SearchType.EXACT); //$NON-NLS-1$
} else {
BooleanQuery bq = new BooleanQuery();
bq.add(constructQuery(MAVEN.GROUP_ID, term), Occur.SHOULD); //$NON-NLS-1$
bq.add(constructQuery(MAVEN.ARTIFACT_ID, term), Occur.SHOULD); //$NON-NLS-1$
bq.add(
constructQuery(MAVEN.SHA1, term.getStringValue(), term.getStringValue().length() == 40 ? SearchType.EXACT
: SearchType.SCORED), Occur.SHOULD);
Query tq = constructQuery(MAVEN.PACKAGING, "pom", SearchType.EXACT); //$NON-NLS-1$
query = new FilteredQuery(tq, new QueryWrapperFilter(bq));
}
} else if(IIndex.SEARCH_PLUGIN.equals(type)) {
if(term == null) {
query = constructQuery(MAVEN.PACKAGING, "maven-plugin", SearchType.EXACT); //$NON-NLS-1$
} else {
BooleanQuery bq = new BooleanQuery();
bq.add(constructQuery(MAVEN.GROUP_ID, term), Occur.SHOULD); //$NON-NLS-1$
bq.add(constructQuery(MAVEN.ARTIFACT_ID, term), Occur.SHOULD); //$NON-NLS-1$
Query tq = constructQuery(MAVEN.PACKAGING, "maven-plugin", SearchType.EXACT); //$NON-NLS-1$
query = new FilteredQuery(tq, new QueryWrapperFilter(bq));
}
} else if(IIndex.SEARCH_ARCHETYPE.equals(type)) {
BooleanQuery bq = new BooleanQuery();
bq.add(constructQuery(MAVEN.GROUP_ID, term), Occur.SHOULD); //$NON-NLS-1$
bq.add(constructQuery(MAVEN.ARTIFACT_ID, term), Occur.SHOULD); //$NON-NLS-1$
Query tq = constructQuery(MAVEN.PACKAGING, "maven-archetype", SearchType.EXACT); //$NON-NLS-1$
query = new FilteredQuery(tq, new QueryWrapperFilter(bq));
} else if(IIndex.SEARCH_PACKAGING.equals(type)) {
query = constructQuery(MAVEN.PACKAGING, term);
} else if(IIndex.SEARCH_SHA1.equals(type)) {
// if hash is 40 chars, it is "complete", otherwise assume prefix
query = constructQuery(MAVEN.SHA1, term.getStringValue(), term.getStringValue().length() == 40 ? SearchType.EXACT
: SearchType.SCORED);
} else {
return Collections.emptyMap();
}
Map<String, IndexedArtifact> result = new TreeMap<String, IndexedArtifact>();
try {
IteratorSearchResponse response;
synchronized(getIndexLock(repository)) {
IndexingContext context = getIndexingContext(repository);
if(context == null) {
response = getIndexer().searchIterator(new IteratorSearchRequest(query));
} else {
response = getIndexer().searchIterator(new IteratorSearchRequest(query, context));
}
for(ArtifactInfo artifactInfo : response.getResults()) {
addArtifactFile(result, getIndexedArtifactFile(artifactInfo), null, null, artifactInfo.packaging);
}
// https://issues.sonatype.org/browse/MNGECLIPSE-1630
// lucene can't handle prefix queries that match many index entries.
// to workaround, use term query to locate group artifacts and manually
// match subgroups
if(IIndex.SEARCH_GROUP.equals(type) && context != null) {
Set<String> groups = context.getAllGroups();
for(String group : groups) {
if(term == null || group.startsWith(term.getStringValue()) && !group.equals(term.getStringValue())) {
String key = getArtifactFileKey(group, group, null, null);
result.put(key, new IndexedArtifact(group, group, null, null, null));
}
}
}
}
} catch(IOException ex) {
throw new CoreException(new Status(IStatus.ERROR, IMavenConstants.PLUGIN_ID, -1,
Messages.NexusIndexManager_error_search, ex));
}
return result;
}
/**
* @return Map<String, IndexedArtifact>
*/
protected Map<String, IndexedArtifact> search(IRepository repository, SearchExpression term, String type)
throws CoreException {
return search(repository, term, type, IIndex.SEARCH_ALL);
}
/**
* @return Map<String, IndexedArtifact>
*/
protected Map<String, IndexedArtifact> search(IRepository repository, Query query) throws CoreException {
Map<String, IndexedArtifact> result = new TreeMap<String, IndexedArtifact>();
try {
IteratorSearchResponse response;
synchronized(getIndexLock(repository)) {
IndexingContext context = getIndexingContext(repository);
if(context == null) {
response = getIndexer().searchIterator(new IteratorSearchRequest(query));
} else {
response = getIndexer().searchIterator(new IteratorSearchRequest(query, context));
}
}
for(ArtifactInfo artifactInfo : response.getResults()) {
addArtifactFile(result, getIndexedArtifactFile(artifactInfo), null, null, artifactInfo.packaging);
}
} catch(IOException ex) {
throw new CoreException(new Status(IStatus.ERROR, IMavenConstants.PLUGIN_ID, -1,
Messages.NexusIndexManager_error_search, ex));
}
return result;
}
private void addArtifactFile(Map<String, IndexedArtifact> result, IndexedArtifactFile af, String className,
String packageName, String packaging) {
String group = af.group;
String artifact = af.artifact;
String key = getArtifactFileKey(group, artifact, packageName, className);
IndexedArtifact indexedArtifact = result.get(key);
if(indexedArtifact == null) {
indexedArtifact = new IndexedArtifact(group, artifact, packageName, className, packaging);
result.put(key, indexedArtifact);
}
indexedArtifact.addFile(af);
}
protected String getArtifactFileKey(String group, String artifact, String packageName, String className) {
return className + " : " + packageName + " : " + group + " : " + artifact; //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
}
private void purgeCurrentIndex(IndexingContext context) throws IOException {
context.purge();
}
private void reindexLocalRepository(IRepository repository, boolean force, final IProgressMonitor monitor)
throws CoreException {
if(!force) {
return;
}
try {
fireIndexUpdating(repository);
//IndexInfo indexInfo = getIndexInfo(indexName);
IndexingContext context = getIndexingContext(repository);
purgeCurrentIndex(context);
if(context.getRepository().isDirectory()) {
getIndexer().scan(context, new ArtifactScanningMonitor(context.getRepository(), monitor), false);
}
log.info("Updated local repository index");
} catch(Exception ex) {
log.error("Unable to re-index " + repository.toString(), ex);
throw new CoreException(new Status(IStatus.ERROR, IMavenConstants.PLUGIN_ID, -1,
Messages.NexusIndexManager_error_reindexing, ex));
} finally {
fireIndexChanged(repository);
}
}
private void reindexWorkspace(boolean force, IProgressMonitor monitor) throws CoreException {
IRepository workspaceRepository = repositoryRegistry.getWorkspaceRepository();
if(!force)
return;
try {
IndexingContext context = getIndexingContext(workspaceRepository);
purgeCurrentIndex(context);
for(IMavenProjectFacade facade : projectManager.getProjects()) {
addDocument(workspaceRepository, facade.getPomFile(), //
facade.getArtifactKey());
}
} catch(Exception ex) {
log.error("Unable to re-index " + workspaceRepository.toString(), ex);
throw new CoreException(new Status(IStatus.ERROR, IMavenConstants.PLUGIN_ID, -1,
Messages.NexusIndexManager_error_reindexing, ex));
} finally {
fireIndexChanged(workspaceRepository);
}
}
protected void addDocument(IRepository repository, File file, ArtifactKey key) {
synchronized(getIndexLock(repository)) {
IndexingContext context = getIndexingContext(repository);
if(context == null) {
// TODO log
return;
}
try {
ArtifactContext artifactContext;
if(repository.isScope(IRepositoryRegistry.SCOPE_WORKSPACE)) {
IMavenProjectFacade facade = getProjectByArtifactKey(key);
artifactContext = getWorkspaceArtifactContext(facade, context);
} else {
artifactContext = getArtifactContext(file, context);
}
getIndexer().addArtifactToIndex(artifactContext, context);
} catch(Exception ex) {
String msg = "Unable to add " + getDocumentKey(key);
log.error(msg, ex);
}
}
}
private IMavenProjectFacade getProjectByArtifactKey(ArtifactKey artifactKey) throws CoreException {
for(IMavenProjectFacade facade : projectManager.getProjects()) {
if(facade.getArtifactKey().equals(artifactKey)) {
return facade;
}
}
throw new CoreException(new Status(IStatus.ERROR, IMavenConstants.PLUGIN_ID, -1,
Messages.NexusIndexManager_error_unexpected, new IllegalArgumentException(String.format(
"Workspace project with key %s not found!", new Object[] {artifactKey})))); //$NON-NLS-1$
}
protected void removeDocument(IRepository repository, File file, ArtifactKey key, IMavenProjectFacade facade) {
synchronized(getIndexLock(repository)) {
try {
IndexingContext context = getIndexingContext(repository);
if(context == null) {
String msg = "Unable to find document to remove" + getDocumentKey(key);
log.error(msg); //$NON-NLS-1$
return;
}
ArtifactContext artifactContext;
if(repository.isScope(IRepositoryRegistry.SCOPE_WORKSPACE)) {
if(facade == null) {
// try to get one, but you MUST have facade in case of project deletion, see mavenProjectChanged()
facade = getProjectByArtifactKey(key);
}
artifactContext = getWorkspaceArtifactContext(facade, context);
} else {
artifactContext = getArtifactContext(file, context);
}
getIndexer().deleteArtifactFromIndex(artifactContext, context);
} catch(Exception ex) {
String msg = "Unable to remove " + getDocumentKey(key);
log.error(msg, ex);
}
}
fireIndexChanged(repository);
}
private ArtifactContext getArtifactContext(File file, IndexingContext context)
throws IllegalArtifactCoordinateException {
return getArtifactContextProducer().getArtifactContext(context, file);
}
private ArtifactContext getWorkspaceArtifactContext(IMavenProjectFacade facade, IndexingContext context)
throws CoreException {
IRepository workspaceRepository = repositoryRegistry.getWorkspaceRepository();
ArtifactKey key = facade.getArtifactKey();
ArtifactInfo ai = new ArtifactInfo(workspaceRepository.getUid(), key.getGroupId(), key.getArtifactId(),
key.getVersion(), null);
ai.packaging = facade.getPackaging();
File pomFile = facade.getPomFile();
File artifactFile = (pomFile != null) ? pomFile.getParentFile() : null;
try {
Gav gav = new Gav(key.getGroupId(), key.getArtifactId(), key.getVersion());
return new ArtifactContext(pomFile, artifactFile, null, ai, gav);
} catch(IllegalArtifactCoordinateException ex) {
throw new CoreException(new Status(IStatus.ERROR, IMavenConstants.PLUGIN_ID, -1,
Messages.NexusIndexManager_error_unexpected, ex));
}
}
protected void scheduleIndexUpdate(final IRepository repository, final boolean force) {
if(repository != null) {
IndexCommand command = monitor -> updateIndex(repository, force, monitor);
updaterJob.addCommand(command);
updaterJob.schedule(1000L);
}
}
/** for unit tests */
public IndexedArtifactGroup[] getRootIndexedArtifactGroups(IRepository repository) throws CoreException {
synchronized(getIndexLock(repository)) {
IndexingContext context = getIndexingContext(repository);
if(context != null) {
try {
Set<String> rootGroups = context.getRootGroups();
IndexedArtifactGroup[] groups = new IndexedArtifactGroup[rootGroups.size()];
int i = 0;
for(String group : rootGroups) {
groups[i++ ] = new IndexedArtifactGroup(repository, group);
}
return groups;
} catch(IOException ex) {
throw new CoreException(new Status(IStatus.ERROR, IMavenConstants.PLUGIN_ID, -1, //
NLS.bind(Messages.NexusIndexManager_error_root_grp, repository.toString()), ex));
}
}
return new IndexedArtifactGroup[0];
}
}
/** public for unit tests only! */
public IndexingContext getIndexingContext(IRepository repository) {
return repository == null ? null : getIndexer().getIndexingContexts().get(repository.getUid());
}
public NexusIndexer getIndexer() {
synchronized(indexerLock) {
if(indexer == null) {
try {
indexer = container.lookup(NexusIndexer.class);
} catch(ComponentLookupException ex) {
throw new NoSuchComponentException(ex);
}
}
}
return indexer;
}
public ArtifactContextProducer getArtifactContextProducer() {
synchronized(contextProducerLock) {
if(artifactContextProducer == null) {
try {
artifactContextProducer = container.lookup(ArtifactContextProducer.class);
} catch(ComponentLookupException ex) {
throw new NoSuchComponentException(ex);
}
}
}
return artifactContextProducer;
}
public static String getDocumentKey(ArtifactKey artifact) {
String groupId = artifact.getGroupId();
if(groupId == null) {
groupId = Messages.NexusIndexManager_inherited;
}
String artifactId = artifact.getArtifactId();
String version = artifact.getVersion();
if(version == null) {
version = Messages.NexusIndexManager_inherited;
}
String key = groupId.replace('.', '/') + '/' + artifactId + '/' + version + '/' + artifactId + "-" + version; //$NON-NLS-1$
String classifier = artifact.getClassifier();
if(classifier != null) {
key += "-" + classifier; //$NON-NLS-1$
}
// TODO use artifact handler to retrieve extension
// cstamas: will not work since ArtifactKey misses type
// either get packaging from POM or store/honor extension
return key + ".pom"; //$NON-NLS-1$
}
public void mavenProjectChanged(MavenProjectChangedEvent[] events, IProgressMonitor monitor) {
/*
* This method is called while holding workspace lock. Avoid long-running operations if possible.
*/
synchronized(getIndexLock(repositoryRegistry.getWorkspaceRepository())) {
IndexingContext context = getIndexingContext(repositoryRegistry.getWorkspaceRepository());
if(context != null) {
// workspace indexing context can by null during startup due to MNGECLIPSE-1633
for(MavenProjectChangedEvent event : events) {
IMavenProjectFacade oldFacade = event.getOldMavenProject();
IMavenProjectFacade facade = event.getMavenProject();
if(oldFacade != null) {
if(facade != null) {
addDocument(repositoryRegistry.getWorkspaceRepository(), facade.getPomFile(), facade.getArtifactKey());
fireIndexChanged(repositoryRegistry.getWorkspaceRepository());
} else {
removeDocument(repositoryRegistry.getWorkspaceRepository(), oldFacade.getPomFile(),
oldFacade.getArtifactKey(), oldFacade);
fireIndexRemoved(repositoryRegistry.getWorkspaceRepository());
}
} else if(facade != null) {
addDocument(repositoryRegistry.getWorkspaceRepository(), facade.getPomFile(), facade.getArtifactKey());
fireIndexAdded(repositoryRegistry.getWorkspaceRepository());
}
}
}
}
}
public NexusIndex getWorkspaceIndex() {
return workspaceIndex;
}
public NexusIndex getLocalIndex() {
IRepository localRepository = repositoryRegistry.getLocalRepository();
synchronized(getIndexLock(localRepository)) {
if(localIndex == null) {
localIndex = newLocalIndex(localRepository);
}
}
return localIndex;
}
public IIndex getIndex(IProject project) {
IMavenProjectFacade projectFacade = project != null ? projectManager.getProject(project) : null;
ArrayList<IIndex> indexes = new ArrayList<IIndex>();
indexes.add(getWorkspaceIndex());
indexes.add(getLocalIndex());
if(projectFacade != null) {
LinkedHashSet<ArtifactRepositoryRef> repositories = new LinkedHashSet<ArtifactRepositoryRef>();
repositories.addAll(projectFacade.getArtifactRepositoryRefs());
repositories.addAll(projectFacade.getPluginArtifactRepositoryRefs());
for(ArtifactRepositoryRef repositoryRef : repositories) {
IRepository repository = repositoryRegistry.getRepository(repositoryRef);
if(repository != null) {
indexes.add(getIndex(repository));
}
}
} else {
for(IRepository repository : repositoryRegistry.getRepositories(IRepositoryRegistry.SCOPE_SETTINGS)) {
indexes.add(getIndex(repository));
}
}
return new CompositeIndex(indexes);
}
public IIndex getAllIndexes() {
ArrayList<IIndex> indexes = new ArrayList<IIndex>();
indexes.add(getWorkspaceIndex());
indexes.add(getLocalIndex());
LinkedHashSet<ArtifactRepositoryRef> repositories = new LinkedHashSet<ArtifactRepositoryRef>();
for(IMavenProjectFacade facade : projectManager.getProjects()) {
repositories.addAll(facade.getArtifactRepositoryRefs());
repositories.addAll(facade.getPluginArtifactRepositoryRefs());
}
for(ArtifactRepositoryRef repositoryRef : repositories) {
IRepository repository = repositoryRegistry.getRepository(repositoryRef);
if(repository != null) {
indexes.add(getIndex(repository));
}
}
return new CompositeIndex(indexes);
}
public NexusIndex getIndex(IRepository repository) {
String details = getIndexDetails(repository);
return new NexusIndex(this, repository, details);
}
protected File getIndexDirectoryFile(IRepository repository) {
return new File(baseIndexDir, repository.getUid());
}
protected Directory getIndexDirectory(IRepository repository) throws IOException {
return FSDirectory.getDirectory(getIndexDirectoryFile(repository));
}
public IndexedArtifactGroup resolveGroup(IndexedArtifactGroup group) {
IRepository repository = group.getRepository();
String prefix = group.getPrefix();
try {
IndexedArtifactGroup g = new IndexedArtifactGroup(repository, prefix);
for(IndexedArtifact a : search(repository, new SourcedSearchExpression(prefix), IIndex.SEARCH_GROUP).values()) {
String groupId = a.getGroupId();
if(groupId.equals(prefix)) {
g.getFiles().put(a.getArtifactId(), a);
} else if(groupId.startsWith(prefix + ".")) { //$NON-NLS-1$
int start = prefix.length() + 1;
int end = groupId.indexOf('.', start);
String key = end > -1 ? groupId.substring(0, end) : groupId;
g.getNodes().put(key, new IndexedArtifactGroup(repository, key));
}
}
return g;
} catch(CoreException ex) {
log.error("Can't retrieve groups for " + repository.toString() + ":" + prefix, ex); //$NON-NLS-2$
return group;
}
}
public void repositoryAdded(IRepository repository, IProgressMonitor monitor) throws CoreException {
String details = getIndexDetails(repository);
// for consistency, always process indexes using our background thread
setIndexDetails(repository, null, details, null/*async*/);
}
/** For tests only */
public String getIndexDetails(IRepository repository) {
String details = indexDetails.getProperty(repository.getUid());
if(details == null) {
if(repository.isScope(IRepositoryRegistry.SCOPE_SETTINGS) && repository.getMirrorId() == null) {
details = NexusIndex.DETAILS_MIN;
} else if(repository.isScope(IRepositoryRegistry.SCOPE_LOCAL)) {
details = NexusIndex.DETAILS_MIN;
} else if(repository.isScope(IRepositoryRegistry.SCOPE_WORKSPACE)) {
details = NexusIndex.DETAILS_MIN;
} else {
details = NexusIndex.DETAILS_DISABLED;
}
}
return details;
}
/**
* Updates index synchronously if monitor!=null. Schedules index update otherwise. ... and yes, I know this ain't
* kosher. Public for unit tests only!
*/
public void setIndexDetails(IRepository repository, String details, IProgressMonitor monitor) throws CoreException {
setIndexDetails(repository, details, details, monitor);
}
private void setIndexDetails(IRepository repository, String details, String defaultDetails, IProgressMonitor monitor)
throws CoreException {
if(details != null) {
indexDetails.setProperty(repository.getUid(), details);
writeIndexDetails();
} else {
details = defaultDetails;
}
synchronized(getIndexLock(repository)) {
IndexingContext indexingContext = getIndexingContext(repository);
try {
if(NexusIndex.DETAILS_DISABLED.equals(details)) {
if(indexingContext != null) {
getIndexer().removeIndexingContext(indexingContext, false /*removeFiles*/);
fireIndexRemoved(repository);
}
} else {
if(indexingContext != null) {
getIndexer().removeIndexingContext(indexingContext, false);
}
createIndexingContext(repository, details);
fireIndexAdded(repository);
if(monitor != null) {
updateIndex(repository, false, monitor);
} else {
scheduleIndexUpdate(repository, false);
}
}
} catch(IOException ex) {
String msg = "Error changing index details " + repository.toString();
log.error(msg, ex);
throw new CoreException(new Status(IStatus.ERROR, IMavenConstants.PLUGIN_ID, -1,
Messages.NexusIndexManager_error_add_repo, ex));
}
if(repository.isScope(IRepositoryRegistry.SCOPE_LOCAL)) {
// note that we are still synchronized on repository lock at this point
this.localIndex = newLocalIndex(repositoryRegistry.getLocalRepository());
}
}
}
protected IndexingContext createIndexingContext(IRepository repository, String details) throws IOException {
IndexingContext indexingContext;
Directory directory = getIndexDirectory(repository);
File repositoryPath = null;
if(repository.getBasedir() != null) {
repositoryPath = repository.getBasedir().getCanonicalFile();
}
indexingContext = getIndexer().addIndexingContextForced(repository.getUid(), //
repository.getUrl(), //
repositoryPath, //
directory, //
repository.getUrl(), null, //
minCreators);
indexingContext.setSearchable(false);
return indexingContext;
}
protected List<IndexCreator> getIndexers(String details) {
boolean fullIndex = NexusIndex.DETAILS_FULL.equals(details);
return fullIndex ? fullCreators : minCreators;
}
public void repositoryRemoved(IRepository repository, IProgressMonitor monitor) {
synchronized(getIndexLock(repository)) {
try {
IndexingContext context = getIndexingContext(repository);
if(context == null) {
return;
}
getIndexer().removeIndexingContext(context, false);
} catch(IOException ie) {
String msg = "Unable to delete files for index";
log.error(msg, ie);
}
}
fireIndexRemoved(repository);
}
protected void fireIndexAdded(IRepository repository) {
synchronized(indexListeners) {
for(IndexListener listener : indexListeners) {
listener.indexAdded(repository);
}
}
}
protected void fireIndexRemoved(IRepository repository) {
synchronized(updatingIndexes) {
if(repository != null) {
//since workspace index can be null at startup, guard against nulls
updatingIndexes.remove(repository.getUid());
}
}
synchronized(indexListeners) {
for(IndexListener listener : indexListeners) {
listener.indexRemoved(repository);
}
}
}
protected boolean isUpdatingIndex(IRepository repository) {
synchronized(updatingIndexes) {
return updatingIndexes.contains(repository.getUid());
}
}
protected void fireIndexUpdating(IRepository repository) {
synchronized(updatingIndexes) {
if(repository != null) {
//since workspace index can be null at startup, guard against nulls
updatingIndexes.add(repository.getUid());
}
}
synchronized(indexListeners) {
for(IndexListener listener : indexListeners) {
listener.indexUpdating(repository);
}
}
}
protected void fireIndexChanged(IRepository repository) {
if(repository == null) {
return;
}
synchronized(updatingIndexes) {
updatingIndexes.remove(repository.getUid());
}
synchronized(indexListeners) {
for(IndexListener listener : indexListeners) {
listener.indexChanged(repository);
}
}
}
public void removeIndexListener(IndexListener listener) {
synchronized(indexListeners) {
indexListeners.remove(listener);
}
}
public void addIndexListener(IndexListener listener) {
synchronized(indexListeners) {
if(!indexListeners.contains(listener)) {
indexListeners.add(listener);
}
}
}
//Public for testing purpose.
public void updateIndex(IRepository repository, boolean force, IProgressMonitor monitor) throws CoreException {
synchronized(getIndexLock(repository)) {
if(repository.isScope(IRepositoryRegistry.SCOPE_WORKSPACE)) {
reindexWorkspace(force, monitor);
} else {
IndexingContext context = getIndexingContext(repository);
if(context != null) {
if(context.getRepository() != null) {
reindexLocalRepository(repository, force, monitor);
} else {
if(!force) {
//if 'force' is not set, then only do the remote update if this value is set
IMavenConfiguration mavenConfig = MavenPlugin.getMavenConfiguration();
if(mavenConfig.isUpdateIndexesOnStartup()) {
updateRemoteIndex(repository, force, monitor);
}
} else {
updateRemoteIndex(repository, force, monitor);
}
}
}
}
IndexingContext context = getIndexingContext(repository);
if(context != null) {
context.setSearchable(true);
}
}
}
/*
* Callers must hold repository access synchronisation lock
*/
private void updateRemoteIndex(IRepository repository, boolean force, IProgressMonitor monitor) {
if(repository == null) {
return;
}
long start = System.currentTimeMillis();
if(monitor != null) {
monitor.setTaskName(NLS.bind(Messages.NexusIndexManager_task_updating, repository.toString()));
}
log.info("Updating index for repository: {}", repository.toString()); //$NON-NLS-1$
try {
fireIndexUpdating(repository);
IndexingContext context = getIndexingContext(repository);
if(context != null) {
IndexUpdateRequest request = newIndexUpdateRequest(repository, context, monitor);
request.setForceFullUpdate(force);
Lock cacheLock = locker.lock(request.getLocalIndexCacheDir());
try {
boolean updated;
request.setCacheOnly(true);
IndexUpdateResult result = indexUpdater.fetchAndUpdateIndex(request);
if(result.isFullUpdate() || !context.isSearchable()) {
// need to fully recreate index
// 1. process index gz into cached/shared lucene index. this can be a noop if cache is uptodate
String details = getIndexDetails(repository);
String id = repository.getUid() + "-cache"; //$NON-NLS-1$
File luceneCache = new File(request.getLocalIndexCacheDir(), details);
Directory directory = FSDirectory.getDirectory(luceneCache);
IndexingContext cacheCtx = getIndexer().addIndexingContextForced(id, id, null, directory, null, null,
getIndexers(details));
request = newIndexUpdateRequest(repository, cacheCtx, monitor);
request.setOffline(true);
indexUpdater.fetchAndUpdateIndex(request);
// 2. copy cached/shared (this is not very elegant, oh well)
getIndexer().removeIndexingContext(context, true); // nuke workspace index files
getIndexer().removeIndexingContext(cacheCtx, false); // keep the cache!
FileUtils.cleanDirectory(context.getIndexDirectoryFile());
FileUtils.copyDirectory(luceneCache, context.getIndexDirectoryFile()); // copy cached lucene index
context = createIndexingContext(repository, details); // re-create indexing context
updated = true;
} else {
// incremental change
request = newIndexUpdateRequest(repository, context, monitor);
request.setOffline(true); // local cache is already uptodate, no need to
result = indexUpdater.fetchAndUpdateIndex(request);
updated = result.getTimestamp() != null;
}
if(updated) {
log.info("Updated index for repository: {} in {} ms", repository.toString(), System.currentTimeMillis()
- start);
} else {
log.info("No index update available for repository: {}", repository.toString());
}
} finally {
cacheLock.release();
}
}
} catch(FileNotFoundException e) {
String msg = "Unable to update index for " + repository.toString() + ": " + e.getMessage(); //$NON-NLS-2$
log.error(msg, e);
} catch(Exception ie) {
String msg = "Unable to update index for " + repository.toString();
log.error(msg, ie);
} finally {
fireIndexChanged(repository);
}
}
protected IndexUpdateRequest newIndexUpdateRequest(IRepository repository, IndexingContext context,
IProgressMonitor monitor) throws IOException, CoreException {
//TODO: remove Wagon API
ProxyInfo proxyInfo = maven.getProxyInfo(repository.getProtocol());
AuthenticationInfo authenticationInfo = repository.getAuthenticationInfo();
IndexUpdateRequest request = new IndexUpdateRequest(context, new AetherClientResourceFetcher(authenticationInfo,
proxyInfo, monitor));
File localRepo = repositoryRegistry.getLocalRepository().getBasedir();
File indexCacheBasedir = new File(localRepo, ".cache/m2e/" + MavenPluginActivator.getVersion()).getCanonicalFile(); //$NON-NLS-1$
File indexCacheDir = new File(indexCacheBasedir, repository.getUid());
indexCacheDir.mkdirs();
request.setLocalIndexCacheDir(indexCacheDir);
return request;
}
public void initialize(IProgressMonitor monitor) throws CoreException {
try {
BufferedInputStream is = new BufferedInputStream(new FileInputStream(getIndexDetailsFile()));
try {
indexDetails.load(is);
} finally {
is.close();
}
} catch(FileNotFoundException e) {
// that's quite alright
} catch(IOException e) {
throw new CoreException(new Status(IStatus.ERROR, IMavenConstants.PLUGIN_ID, -1,
Messages.NexusIndexManager_error_read_index, e));
}
}
protected void writeIndexDetails() throws CoreException {
try {
File indexDetailsFile = getIndexDetailsFile();
indexDetailsFile.getParentFile().mkdirs();
BufferedOutputStream os = new BufferedOutputStream(new FileOutputStream(indexDetailsFile));
try {
indexDetails.store(os, null);
} finally {
os.close();
}
} catch(IOException e) {
throw new CoreException(new Status(IStatus.ERROR, IMavenConstants.PLUGIN_ID, -1,
Messages.NexusIndexManager_error_write_index, e));
}
}
private File getIndexDetailsFile() {
return new File(baseIndexDir, "indexDetails.properties"); //$NON-NLS-1$
}
/** for unit tests only */
public Job getIndexUpdateJob() {
return updaterJob;
}
public String getIndexerId() {
return Messages.NexusIndexManager_78;
}
private Object getIndexLock(IRepository repository) {
if(repository == null) {
return new Object();
}
// NOTE: We ultimately want to prevent concurrent access to the IndexingContext so we sync on the repo UID and not on the repo instance.
synchronized(indexLocks) {
Object lock = indexLocks.get(repository.getUid());
if(lock == null) {
lock = new Object();
indexLocks.put(repository.getUid(), lock);
}
return lock;
}
}
/// REMOVE THIS BELOW ONCE Maven Indexer upgraded to 3.2.0-SNAPSHOT
/// In that moment this code becomes duplicated and already in place, this method added
protected ArtifactInfo identify(File artifact, Collection<IndexingContext> contexts) throws IOException {
FileInputStream is = null;
try {
MessageDigest sha1 = MessageDigest.getInstance("SHA-1");
is = new FileInputStream(artifact);
byte[] buff = new byte[4096];
int n;
while((n = is.read(buff)) > -1) {
sha1.update(buff, 0, n);
}
byte[] digest = sha1.digest();
Query q = getIndexer().constructQuery(MAVEN.SHA1, encode(digest), SearchType.EXACT);
return getIndexer().identify(q, contexts);
} catch(NoSuchAlgorithmException ex) {
throw new IOException("Unable to calculate digest");
} finally {
IOUtil.close(is);
}
}
private static final char[] DIGITS = "0123456789abcdef".toCharArray();
private static String encode(byte[] digest) {
char[] buff = new char[digest.length * 2];
int n = 0;
for(byte b : digest) {
buff[n++ ] = DIGITS[(0xF0 & b) >> 4];
buff[n++ ] = DIGITS[0x0F & b];
}
return new String(buff);
}
/**
* @since 1.5
*/
public IndexUpdater getIndexUpdate() {
return indexUpdater;
}
/**
* @since 1.5
*/
public ArchetypeDataSource getArchetypeCatalog() {
try {
return container.lookup(ArchetypeDataSource.class, "nexus");
} catch(ComponentLookupException ex) {
throw new NoSuchComponentException(ex);
}
}
}