/*
* 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.ivy.core.repository;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import java.util.TreeSet;
import org.apache.ivy.core.module.descriptor.DefaultDependencyDescriptor;
import org.apache.ivy.core.module.descriptor.DependencyDescriptor;
import org.apache.ivy.core.module.descriptor.ModuleDescriptor;
import org.apache.ivy.core.module.id.ModuleRevisionId;
import org.apache.ivy.core.resolve.ResolveData;
import org.apache.ivy.core.resolve.ResolveEngine;
import org.apache.ivy.core.resolve.ResolveOptions;
import org.apache.ivy.core.resolve.ResolvedModuleRevision;
import org.apache.ivy.core.search.SearchEngine;
import org.apache.ivy.plugins.latest.ArtifactInfo;
import org.apache.ivy.plugins.matcher.PatternMatcher;
import org.apache.ivy.plugins.matcher.RegexpPatternMatcher;
import org.apache.ivy.plugins.version.VersionMatcher;
import org.apache.ivy.util.MemoryUtil;
import org.apache.ivy.util.Message;
/**
* The repository management can be used to load all metadata from a repository, analyze them, and
* provide a bunch of information about the whole repository state.
* <p>
* Since loading all metadata from a repository is not a light task, this engine should only be used
* on a machine having good access to the repository (on the same filesystem being usually the best
* suited).
* </p>
* <p>
* To access information, you usually have before to call a method to init the data: {@link #load()}
* is used to load repository metadata, {@link #analyze()} is used to analyze them. These methods
* being very time consuming, they must always be called explicitly.
* </p>
* <p>
* On a large repository, this engine can be very memory consuming to use, it is not suited to be
* used in a long running process, but rather in short process loading data and taking action about
* the current state of the repository.
* </p>
* <p>
* This engine is not intended to be used concurrently with publish, the order of repository loaded
* being undeterministic and long, it could end up in having an inconsistent in memory state.
* </p>
* <p>
* For better performance, we strongly suggest using this engine with cache in useOrigin mode.
* </p>
*/
public class RepositoryManagementEngine {
private static final double THOUSAND = 1000.0;
private static final int KILO = 1024;
// /////////////////////////////////////////
// state loaded on #load()
// /////////////////////////////////////////
/**
* True if the repository has already been loaded, false otherwise.
*/
private boolean loaded;
/**
* ModuleDescriptors stored by ModuleRevisionId
*/
private Map/* <ModuleRevisionId,ModuleDescriptor> */revisions = new HashMap();
/**
* ModuleRevisionId for which loading was not possible, with corresponding error message.
*/
private Map/* <ModuleRevisionId,String> */errors = new HashMap();
/**
* List of ModuleRevisionId per ModuleId.
*/
private Map/* <ModuleId,Collection<ModuleRevisionId>> */modules = new HashMap();
// /////////////////////////////////////////
// state loaded on #analyze()
// /////////////////////////////////////////
/**
* True when the repository has been analyzed, false otherwise
*/
private boolean analyzed;
/**
* Cache from requested module revision id to actual module revision id.
*/
private Map/* <ModuleRevisionId,ModuleRevisionId> */cache = new HashMap();
/**
* list of dependers per ModuleRevisionId.
*/
private Map/* <ModuleRevisionId,List<ModuleRevisionId>> */dependers = new HashMap();
// /////////////////////////////////////////
// dependencies
// /////////////////////////////////////////
private SearchEngine searchEngine;
private ResolveEngine resolveEngine;
private RepositoryManagementEngineSettings settings;
public RepositoryManagementEngine(RepositoryManagementEngineSettings settings,
SearchEngine searchEngine, ResolveEngine resolveEngine) {
this.settings = settings;
this.searchEngine = searchEngine;
this.resolveEngine = resolveEngine;
}
/**
* Loads data from the repository.
* <p>
* This method usually takes a long time to proceed. It should never be called from event
* dispatch thread in a GUI.
* </p>
*/
public void load() {
long startingMemoryUse = 0;
if (settings.dumpMemoryUsage()) {
startingMemoryUse = MemoryUtil.getUsedMemory();
}
long startTime = System.currentTimeMillis();
Message.rawinfo("searching modules... ");
ModuleRevisionId[] mrids = searchModules();
Message.info("loading repository metadata...");
for (int i = 0; i < mrids.length; i++) {
try {
loadModuleRevision(mrids[i]);
} catch (Exception e) {
Message.debug(e);
errors.put(mrids[i], e.getMessage());
}
}
long endTime = System.currentTimeMillis();
Message.info("\nrepository loaded: "
+ modules.size()
+ " modules; "
+ revisions.size()
+ " revisions; "
+ (settings.dumpMemoryUsage() ? (MemoryUtil.getUsedMemory() - startingMemoryUse)
/ KILO + "kB; " : "") + (endTime - startTime) / THOUSAND + "s");
loaded = true;
}
/**
* Analyze data in the repository.
* <p>
* This method may take a long time to proceed. It should never be called from event dispatch
* thread in a GUI.
* </p>
*
* @throws IllegalStateException
* if the repository has not been loaded yet
* @see #load()
*/
public void analyze() {
ensureLoaded();
Message.info("\nanalyzing dependencies...");
for (Iterator iterator = revisions.values().iterator(); iterator.hasNext();) {
ModuleDescriptor md = (ModuleDescriptor) iterator.next();
DependencyDescriptor[] dds = md.getDependencies();
for (int i = 0; i < dds.length; i++) {
ModuleRevisionId dep = getDependency(dds[i]);
if (dep == null) {
Message.warn("inconsistent repository: declared dependency not found: "
+ dds[i]);
} else {
getDependers(dep).add(md.getModuleRevisionId());
}
}
Message.progress();
}
analyzed = true;
}
/**
* Returns the number of Module Revision in the repository.
*
* @return the number of module revisions in the repository.
* @throws IllegalStateException
* if the repository has not been loaded yet
* @see #load()
*/
public int getRevisionsNumber() {
ensureLoaded();
return revisions.size();
}
/**
* Returns the number of ModuleId in the repository.
*
* @return the number of ModuleId in the repository.
* @throws IllegalStateException
* if the repository has not been loaded yet
* @see #load()
*/
public int getModuleIdsNumber() {
ensureLoaded();
return modules.size();
}
/**
* Returns Module Revisions which have no dependers.
*
* @return a Collection of the {@link ModuleRevisionId} of module revisions which have no
* dependers in the repository.
* @throws IllegalStateException
* if the repository has not been analyzed yet
* @see #analyze()
*/
public Collection getOrphans() {
ensureAnalyzed();
Collection orphans = new HashSet(revisions.keySet());
orphans.removeAll(dependers.keySet());
return orphans;
}
private ModuleRevisionId[] searchModules() {
ModuleRevisionId[] mrids = searchEngine.listModules(ModuleRevisionId.newInstance(
PatternMatcher.ANY_EXPRESSION, PatternMatcher.ANY_EXPRESSION,
PatternMatcher.ANY_EXPRESSION, PatternMatcher.ANY_EXPRESSION),
RegexpPatternMatcher.INSTANCE);
return mrids;
}
private ModuleRevisionId getDependency(DependencyDescriptor dd) {
ModuleRevisionId askedMrid = dd.getDependencyRevisionId();
VersionMatcher vmatcher = settings.getVersionMatcher();
if (vmatcher.isDynamic(askedMrid)) {
ModuleRevisionId mrid = (ModuleRevisionId) cache.get(askedMrid);
if (mrid == null) {
Collection revs = getAllRevisions(askedMrid);
for (Iterator iterator = revs.iterator(); iterator.hasNext();) {
ModuleDescriptor md = (ModuleDescriptor) iterator.next();
if (vmatcher.needModuleDescriptor(askedMrid, md.getResolvedModuleRevisionId())) {
if (vmatcher.accept(askedMrid, md)) {
mrid = md.getResolvedModuleRevisionId();
break;
}
} else {
if (vmatcher.accept(askedMrid, md.getResolvedModuleRevisionId())) {
mrid = md.getResolvedModuleRevisionId();
break;
}
}
}
if (mrid == null) {
return null;
} else {
cache.put(askedMrid, mrid);
}
}
return mrid;
} else {
return askedMrid;
}
}
private Collection getDependers(ModuleRevisionId id) {
Collection depders = (Collection) dependers.get(id);
if (depders == null) {
depders = new ArrayList();
dependers.put(id, depders);
}
return depders;
}
private void loadModuleRevision(ModuleRevisionId mrid) throws Exception {
ResolvedModuleRevision module = settings.getResolver(mrid).getDependency(
new DefaultDependencyDescriptor(mrid, false), newResolveData());
if (module == null) {
Message.warn("module not found while listed: " + mrid);
} else {
revisions.put(module.getId(), module.getDescriptor());
getAllRevisions(module.getId()).add(module.getDescriptor());
}
Message.progress();
}
private Collection getAllRevisions(ModuleRevisionId id) {
Collection revisions = (Collection) modules.get(id.getModuleId());
if (revisions == null) {
revisions = new TreeSet(new Comparator() {
public int compare(Object o1, Object o2) {
ModuleDescriptor md1 = (ModuleDescriptor) o1;
ModuleDescriptor md2 = (ModuleDescriptor) o2;
// we use reverse order compared to latest revision, to have latest revision
// first
return settings.getDefaultLatestStrategy().sort(new ArtifactInfo[] {md1, md2})
.get(0).equals(md1) ? 1 : -1;
}
});
modules.put(id.getModuleId(), revisions);
}
return revisions;
}
private ResolveData newResolveData() {
return new ResolveData(resolveEngine, new ResolveOptions());
}
private void ensureAnalyzed() {
if (!analyzed) {
throw new IllegalStateException(
"repository must have been analyzed to perform this method");
}
}
private void ensureLoaded() {
if (!loaded) {
throw new IllegalStateException("repository must have be loaded to perform this method");
}
}
}