/*******************************************************************************
* Copyright (c) 2004, 2006
* Thomas Hallgren, Kenneth Olwing, Mitch Sonies
* Pontus Rydin, Nils Unden, Peer Torngren
* The code, documentation and other materials contained herein have been
* licensed under the Eclipse Public License - v 1.0 by the individual
* copyright holders listed above, as Initial Contributors under such license.
* The text of such license is available at www.eclipse.org.
*******************************************************************************/
package org.eclipse.buckminster.core.resolver;
import java.net.MalformedURLException;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.Stack;
import java.util.UUID;
import org.eclipse.buckminster.core.CorePlugin;
import org.eclipse.buckminster.core.KeyConstants;
import org.eclipse.buckminster.core.Messages;
import org.eclipse.buckminster.core.common.model.Format;
import org.eclipse.buckminster.core.common.model.PropertyRef;
import org.eclipse.buckminster.core.cspec.QualifiedDependency;
import org.eclipse.buckminster.core.cspec.model.ComponentIdentifier;
import org.eclipse.buckminster.core.cspec.model.ComponentRequest;
import org.eclipse.buckminster.core.cspec.model.ComponentRequestConflictException;
import org.eclipse.buckminster.core.ctype.AbstractComponentType;
import org.eclipse.buckminster.core.ctype.IComponentType;
import org.eclipse.buckminster.core.helpers.FileUtils;
import org.eclipse.buckminster.core.metadata.MissingComponentException;
import org.eclipse.buckminster.core.metadata.StorageManager;
import org.eclipse.buckminster.core.metadata.WorkspaceInfo;
import org.eclipse.buckminster.core.metadata.model.BOMNode;
import org.eclipse.buckminster.core.metadata.model.BillOfMaterials;
import org.eclipse.buckminster.core.metadata.model.GeneratorNode;
import org.eclipse.buckminster.core.metadata.model.Materialization;
import org.eclipse.buckminster.core.metadata.model.Resolution;
import org.eclipse.buckminster.core.metadata.model.ResolvedNode;
import org.eclipse.buckminster.core.metadata.model.UnresolvedNode;
import org.eclipse.buckminster.core.query.builder.ComponentQueryBuilder;
import org.eclipse.buckminster.core.query.model.ComponentQuery;
import org.eclipse.buckminster.core.reader.IComponentReader;
import org.eclipse.buckminster.core.reader.IReaderType;
import org.eclipse.buckminster.core.rmap.model.BidirectionalTransformer;
import org.eclipse.buckminster.core.rmap.model.Provider;
import org.eclipse.buckminster.core.rmap.model.ProviderScore;
import org.eclipse.buckminster.core.rmap.model.VersionConverterDesc;
import org.eclipse.buckminster.core.version.ProviderMatch;
import org.eclipse.buckminster.core.version.VersionMatch;
import org.eclipse.buckminster.osgi.filter.Filter;
import org.eclipse.buckminster.runtime.Buckminster;
import org.eclipse.buckminster.runtime.BuckminsterException;
import org.eclipse.buckminster.runtime.IOUtils;
import org.eclipse.buckminster.runtime.Logger;
import org.eclipse.buckminster.runtime.MonitorUtils;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IWorkspaceRoot;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.MultiStatus;
import org.eclipse.core.runtime.NullProgressMonitor;
import org.eclipse.core.runtime.Status;
import org.eclipse.osgi.util.NLS;
/**
* The LocalResolver will attempt to resolve the query using locally available
* resources. This includes:
* <ul>
* <li>Projects in the Eclipse Workspace</li>
* <li>Materializations previously done by Buckminster using this workspace</li>
* <li>Features and bundles in the target platform</li>
* </ul>
*
* @author Thomas Hallgren
*/
@SuppressWarnings({ "serial" })
public class LocalResolver extends HashMap<String, ResolverNode[]> implements IResolver {
public static final Provider INSTALLED_BUNDLE_PROVIDER;
public static final Provider INSTALLED_FEATURE_PROVIDER;
static {
Map<String, String> props = new HashMap<String, String>(2);
props.put(KeyConstants.IS_MUTABLE, "false"); //$NON-NLS-1$
props.put(KeyConstants.IS_SOURCE, "false"); //$NON-NLS-1$
VersionConverterDesc pdeConverter = new VersionConverterDesc("tag", null, //$NON-NLS-1$
new BidirectionalTransformer[0]);
INSTALLED_BUNDLE_PROVIDER = new Provider(null, IReaderType.ECLIPSE_PLATFORM, new String[] { IComponentType.OSGI_BUNDLE }, pdeConverter,
new Format("plugin/${" //$NON-NLS-1$
+ KeyConstants.COMPONENT_NAME + "}"), null, null, null, props, null, null); //$NON-NLS-1$
INSTALLED_FEATURE_PROVIDER = new Provider(null, IReaderType.ECLIPSE_PLATFORM, new String[] { IComponentType.ECLIPSE_FEATURE }, pdeConverter,
new Format("feature/${" //$NON-NLS-1$
+ KeyConstants.COMPONENT_NAME + "}"), null, null, null, props, null, null); //$NON-NLS-1$
}
public static Resolution fromPath(IPath productPath, String name) throws CoreException {
return fromPath(productPath, name, null, new NullProgressMonitor());
}
public static Resolution fromPath(IPath productPath, String name, String givenCtypeId, IProgressMonitor monitor) throws CoreException {
Set<String> possibleTypes;
if (givenCtypeId != null)
possibleTypes = Collections.singleton(givenCtypeId);
else {
possibleTypes = new HashSet<String>();
for (IComponentType ctype : AbstractComponentType.getComponentTypes()) {
if (ctype.isMetaFileBased() && ctype.hasAllRequiredMetaFiles(productPath))
possibleTypes.add(ctype.getId());
}
if (possibleTypes.isEmpty())
possibleTypes.add(IComponentType.UNKNOWN);
}
Format repoURI;
try {
String repoStr = productPath.toFile().toURI().toURL().toString();
int nameIndex = repoStr.lastIndexOf(name);
if (nameIndex > 0) {
String parameterized = repoStr.substring(0, nameIndex);
parameterized += "{0}"; //$NON-NLS-1$
nameIndex += name.length();
if (repoStr.length() > nameIndex)
parameterized += repoStr.substring(nameIndex, repoStr.length());
repoURI = new Format(parameterized);
repoURI.addValueHolder(new PropertyRef<String>(String.class, KeyConstants.COMPONENT_NAME));
} else
repoURI = new Format(repoStr);
} catch (MalformedURLException e) {
throw BuckminsterException.wrap(e);
}
// We might have more then one possible type. Select the one that
// produces the
// largest CSPEC (should be fast considering the IPath is local
//
ComponentRequest rq = new ComponentRequest(name, givenCtypeId, null);
ComponentQueryBuilder queryBld = new ComponentQueryBuilder();
queryBld.setRootRequest(rq);
queryBld.setPlatformAgnostic(true);
ComponentQuery cquery = queryBld.createComponentQuery();
ResolutionContext context = new ResolutionContext(cquery);
NodeQuery nq = new NodeQuery(context, rq, null);
Provider provider = new Provider(null, IReaderType.LOCAL, possibleTypes.toArray(new String[possibleTypes.size()]), null, repoURI, null, null,
null, null, null, null);
monitor.beginTask(null, possibleTypes.size() * 100);
int largestCSpecSize = -1;
Resolution bestMatch = null;
for (String ctypeId : possibleTypes) {
IComponentType ctype = CorePlugin.getDefault().getComponentType(ctypeId);
ProviderMatch pm = new ProviderMatch(provider, ctype, VersionMatch.DEFAULT, ProviderScore.GOOD, nq);
try {
BOMNode node = ctype.getResolution(pm, MonitorUtils.subMonitor(monitor, 100));
Resolution resolution = node.getResolution();
if (resolution == null)
continue;
int imageSize = resolution.getCSpec().getImage().length;
if (bestMatch == null || largestCSpecSize < imageSize) {
largestCSpecSize = imageSize;
bestMatch = resolution;
}
} catch (Exception e) {
// Let's just care about folders where we the cspec generation
// is successful.
continue;
}
}
if (bestMatch == null)
throw new MissingComponentException(rq.toString());
return bestMatch;
}
public static Resolution fromPath(NodeQuery query, IPath path, Resolution oldInfo) throws CoreException {
ComponentRequest request = query.getComponentRequest();
Resolution resolution = fromPath(path, request.getName(), request.getComponentTypeID(), new NullProgressMonitor());
// Retain old component info if present. We only wanted the cspec
// changes
//
if (oldInfo != null)
resolution = new Resolution(resolution.getCSpec(), oldInfo);
return resolution;
}
private final ResolutionContext context;
private boolean recursiveResolve = true;
public LocalResolver(ComponentQuery componentQuery) throws CoreException {
this(new ResolutionContext(componentQuery));
}
public LocalResolver(ResolutionContext context) throws CoreException {
this.context = context;
}
@Override
public ResolutionContext getContext() {
return context;
}
@Override
public boolean isRecursiveResolve() {
return recursiveResolve;
}
public BOMNode localResolve(NodeQuery query, IProgressMonitor monitor) throws CoreException {
boolean isSilent = query.getResolutionContext().isSilentStatus();
Logger log = Buckminster.getLogger();
ComponentRequest request = query.getComponentRequest();
IProject existingProject = null;
if (query.useMaterialization() || query.useWorkspace()) {
if (!isSilent)
query.logDecision(ResolverDecisionType.TRYING_PROVIDER, IReaderType.LOCAL, "materialized"); //$NON-NLS-1$
try {
Materialization mat = WorkspaceInfo.getMaterialization(request);
if (mat == null) {
if (!isSilent)
log.debug("No materialization found for %s", request); //$NON-NLS-1$
} else {
if (!isSilent)
log.debug("Materialization found for %s", request); //$NON-NLS-1$
existingProject = WorkspaceInfo.getProject(mat);
if (existingProject == null || !FileUtils.pathEquals(mat.getComponentLocation(), existingProject.getLocation())) {
if (!isSilent)
log.debug("No workspace project found at %s", mat.getComponentLocation()); //$NON-NLS-1$
Resolution res = fromPath(query, mat.getComponentLocation(), null);
if (!isSilent)
query.logDecision(ResolverDecisionType.MATCH_FOUND, mat.getComponentIdentifier());
Filter[] failingFilter = new Filter[1];
if (res.isFilterMatchFor(query, failingFilter))
{
MonitorUtils.complete(monitor);
return new ResolvedNode(query, res);
}
if (!isSilent)
query.logDecision(ResolverDecisionType.MATCH_REJECTED, mat.getComponentIdentifier(),
NLS.bind(Messages.Filter_0_does_not_match_the_current_property_set, failingFilter[0]));
} else if (!isSilent)
log.debug("Workspace project found at %s", mat.getComponentLocation()); //$NON-NLS-1$
}
} catch (MissingComponentException e) {
}
} else if (!isSilent)
query.logDecision(ResolverDecisionType.REJECTING_PROVIDER, IReaderType.LOCAL, "materialized", //$NON-NLS-1$
Messages.materializations_disabled_in_query);
// If we get to this point, we didn't find any existing resolution that
// could be used.
//
if (query.useWorkspace()) {
// Generate the resolution from a project in the workspace
//
if (existingProject == null) {
if (!isSilent)
query.logDecision(ResolverDecisionType.TRYING_PROVIDER, IReaderType.LOCAL, "workspace"); //$NON-NLS-1$
IWorkspaceRoot wsRoot = ResourcesPlugin.getWorkspace().getRoot();
try {
existingProject = wsRoot.getProject(request.getProjectName());
} catch (IllegalArgumentException e) {
// Query did not produce a name that is valid for a project
}
}
if (existingProject != null && existingProject.isOpen()) {
if (!isSilent)
log.debug("Found workspace project for %s", request.getProjectName()); //$NON-NLS-1$
Resolution resolution = fromPath(query, existingProject.getLocation(), null);
ComponentIdentifier ci = resolution.getComponentIdentifier();
if (request.designates(ci)) {
if (!isSilent)
query.logDecision(ResolverDecisionType.MATCH_FOUND, ci);
Filter[] failingFilter = new Filter[1];
if (resolution.isFilterMatchFor(query, failingFilter)) {
// Make sure we have a materialization for the project.
//
StorageManager sm = StorageManager.getDefault();
Materialization mat = new Materialization(existingProject.getLocation().addTrailingSeparator(), ci);
mat.store(sm);
resolution.store(sm);
MonitorUtils.complete(monitor);
return new ResolvedNode(query, resolution);
}
if (!isSilent)
query.logDecision(ResolverDecisionType.MATCH_REJECTED, ci,
NLS.bind(Messages.Filter_0_does_not_match_the_current_property_set, failingFilter[0]));
} else if (!isSilent)
log.debug("Workspace project for %s is not designated by %s", request.getProjectName(), request); //$NON-NLS-1$
} else if (!isSilent)
log.debug("No open workspace project found that corresponds to %s", request); //$NON-NLS-1$
} else if (!isSilent)
query.logDecision(ResolverDecisionType.REJECTING_PROVIDER, IReaderType.LOCAL, "workspace", //$NON-NLS-1$
Messages.workspace_disable_in_query);
if (query.useTargetPlatform()) {
Resolution res = WorkspaceInfo.getResolution(request, true);
if (res != null)
return new ResolvedNode(query, res);
if (!isSilent)
query.logDecision(ResolverDecisionType.TRYING_PROVIDER, IReaderType.LOCAL, "target"); //$NON-NLS-1$
// Generate the resolution from the target platform
//
Provider provider;
String ctypeID = request.getComponentTypeID();
if (IComponentType.OSGI_BUNDLE.equals(ctypeID))
provider = INSTALLED_BUNDLE_PROVIDER;
else if (IComponentType.ECLIPSE_FEATURE.equals(ctypeID))
provider = INSTALLED_FEATURE_PROVIDER;
else {
query.logDecision(ResolverDecisionType.COMPONENT_TYPE_MISMATCH, ctypeID);
return null;
}
MultiStatus problemCollector = new MultiStatus(CorePlugin.getID(), IStatus.OK, "", null); //$NON-NLS-1$
ProviderMatch match = provider.findMatch(query, problemCollector, new NullProgressMonitor());
if (match == null)
// The reason will have been logged already
return null;
monitor.beginTask(null, 30);
try {
IComponentReader[] reader = new IComponentReader[] { match.getReader(MonitorUtils.subMonitor(monitor, 10)) };
BOMNode node = match.getComponentType().getResolutionBuilder(reader[0], MonitorUtils.subMonitor(monitor, 10))
.build(reader, false, MonitorUtils.subMonitor(monitor, 10));
IOUtils.close(reader[0]);
res = node.getResolution();
Filter[] failingFilter = new Filter[1];
if (res.isFilterMatchFor(query, failingFilter)) {
if (!isSilent)
query.logDecision(ResolverDecisionType.MATCH_FOUND, res.getComponentIdentifier());
res.store(StorageManager.getDefault());
return node;
}
query.logDecision(ResolverDecisionType.FILTER_MISMATCH, failingFilter[0]);
return null;
} finally {
monitor.done();
}
} else if (!isSilent)
query.logDecision(ResolverDecisionType.REJECTING_PROVIDER, IReaderType.LOCAL, "target", //$NON-NLS-1$
Messages.target_platform_disabled_in_query);
return null;
}
@Override
public ResolverDecision logDecision(ComponentRequest request, ResolverDecisionType decisionType, Object... args) {
return context.logDecision(request, decisionType, args);
}
@Override
public ResolverDecision logDecision(ResolverDecisionType decisionType, Object... args) {
return context.logDecision(decisionType, args);
}
@Override
public BillOfMaterials resolve(ComponentRequest request, IProgressMonitor monitor) throws CoreException {
monitor.beginTask(null, IProgressMonitor.UNKNOWN);
try {
NodeQuery query = context.getNodeQuery(request);
ResolverNode node = deepResolve(context, new HashMap<ComponentRequest, ResolverNode>(),
new UnresolvedNode(query.getQualifiedDependency()), null, monitor);
return createBillOfMaterials(node);
} finally {
monitor.done();
}
}
@Override
public BillOfMaterials resolve(IProgressMonitor monitor) throws CoreException {
return resolve(context.getComponentQuery().getExpandedRootRequest(context), monitor);
}
@Override
public BillOfMaterials resolveRemaining(BillOfMaterials bom, IProgressMonitor monitor) throws CoreException {
if (bom.isFullyResolved(context)) {
MonitorUtils.complete(monitor);
return bom;
}
monitor.beginTask(null, IProgressMonitor.UNKNOWN);
try {
ComponentQuery cquery = bom.getQuery();
ResolutionContext ctx = (cquery == null || cquery.equals(context.getComponentQuery())) ? context : new ResolutionContext(cquery, context);
BillOfMaterials newBom = createBillOfMaterials(deepResolve(ctx, new HashMap<ComponentRequest, ResolverNode>(), bom, bom.getTagInfo(),
monitor));
if (!newBom.contentEqual(bom))
bom = newBom;
return bom;
} finally {
monitor.done();
}
}
@Override
public void setRecursiveResolve(boolean flag) {
recursiveResolve = flag;
}
BillOfMaterials createBillOfMaterials(ResolverNode topNode) throws CoreException {
HashMap<UUID, BOMNode> nodeMap = new HashMap<UUID, BOMNode>();
Stack<Resolution> circularDepTrap = new Stack<Resolution>();
BOMNode node = topNode.collectNodes(nodeMap, circularDepTrap, true);
if (node == null)
node = new UnresolvedNode(topNode.getQuery().getQualifiedDependency());
return BillOfMaterials.create(node, getContext().getComponentQuery());
}
ResolverNode createResolverNode(ResolutionContext ctx, QualifiedDependency qDep, String requestorInfo) {
return new ResolverNode(ctx.getNodeQuery(qDep), requestorInfo);
}
ResolverNode getResolverNode(ResolutionContext ctx, QualifiedDependency qDep, String requestorInfo) throws CoreException {
// We use a ComponentName as the key since we don't want the
// designator to play a role here.
//
ComponentRequest request = qDep.getRequest();
String key = request.getName();
String type = request.getComponentTypeID();
ResolverNode[] nrs;
boolean infant;
synchronized (this) {
nrs = get(key);
infant = (nrs == null);
if (infant) {
nrs = new ResolverNode[] { createResolverNode(ctx, qDep, requestorInfo) };
put(key, nrs);
}
}
ResolverNode nr;
if (infant)
return nrs[0];
int top = nrs.length;
for (int idx = 0; idx < top; ++idx) {
nr = nrs[idx];
if (qDep.equals(nr.getQuery().getQualifiedDependency()))
return nr;
}
boolean newRqOptional = request.isOptional();
boolean invalidateInfant = false;
for (int idx = 0; idx < top; ++idx) {
nr = nrs[idx];
ComponentRequest oldRq = nr.getQuery().getComponentRequest();
if (!(type == null || oldRq.getComponentTypeID() == null || type.equals(oldRq.getComponentTypeID())))
continue;
if (newRqOptional != oldRq.isOptional()) {
// We don't want a version conflict if one of the requirements
// are optional.
//
try {
request.mergeDesignator(oldRq);
} catch (ComponentRequestConflictException e) {
if (!(request.isSyntheticSource() || oldRq.isSyntheticSource())) {
if (oldRq.isOptional()) {
// Previous request now in conflict and must be
// discarded.
nr.forceUnresolved();
continue;
}
// New request is optional and in conflict.
// Invalidate the new infant.
//
invalidateInfant = true;
break;
}
}
}
try {
nr.addDependencyQualification(qDep, requestorInfo);
return nr;
} catch (ComponentRequestConflictException e) {
// We have a conflict. Two components with the same
// name but incompatible versions or filters.
//
if (!(request.isSyntheticSource() || oldRq.isSyntheticSource())) {
IStatus err = e.getStatus();
context.addRequestStatus(nr.getQuery().getComponentRequest(), new Status(IStatus.WARNING, err.getPlugin(), err.getMessage()));
}
}
}
synchronized (this) {
// No known ResolverNode could accommodate the requirements from
// this qualified dependency. We need a new one.
//
nrs = get(key);
if (nrs.length == top) {
nr = createResolverNode(context, qDep, requestorInfo);
if (invalidateInfant)
nr.forceUnresolved();
ResolverNode[] newNrs = new ResolverNode[top + 1];
System.arraycopy(nrs, 0, newNrs, 0, top);
newNrs[top] = nr;
put(key, newNrs);
} else {
// Someone beat us to it. Break out from the synchronization
// and try again from square one
//
nr = null;
}
}
if (nr == null)
//
// Start from square one.
//
nr = getResolverNode(context, qDep, requestorInfo);
return nr;
}
private ResolverNode deepResolve(ResolutionContext ctx, Map<ComponentRequest, ResolverNode> visited, BOMNode depNode, String tagInfo,
IProgressMonitor monitor) throws CoreException {
QualifiedDependency qDep = depNode.getQualifiedDependency();
ComponentRequest key = qDep.getRequest();
// The visited map is to prevent endless recursion. The LocalResolver
// needs this since the query is often
// created on-the-fly and without a chance to allow circular
// dependencies.
//
ResolverNode node = visited.get(key);
if (node != null)
return node;
node = getResolverNode(ctx, qDep, tagInfo);
visited.put(key, node);
if (node.isResolved())
return node;
NodeQuery query = ctx.getNodeQuery(qDep);
if (query.skipComponent())
return node;
GeneratorNode generatorNode = query.getResolutionContext().getGeneratorNode(qDep.getRequest());
if (generatorNode != null) {
node.setGeneratorNode(generatorNode);
return node;
}
Resolution res = depNode.getResolution();
if (res == null) {
try {
depNode = localResolve(query, MonitorUtils.subMonitor(monitor, 1));
} catch (CoreException e) {
if (!context.isContinueOnError())
throw e;
}
if (depNode == null)
//
// We don't get any further.
//
return node;
res = depNode.getResolution();
if (res == null)
return node;
}
ctx = node.startResolvingChildren(depNode);
if (ctx == null)
//
// Resolution was unsuccessful
//
return node;
List<BOMNode> children = depNode.getChildren();
int top = children.size();
if (top == 0) {
node.setResolution(res, null);
return node;
}
ResolverNode[] resolvedChildren = new ResolverNode[top];
String childTagInfo = res.getCSpec().getTagInfo(tagInfo);
for (int idx = 0; idx < top; ++idx) {
BOMNode child = children.get(idx);
ComponentQuery cquery = child.getQuery();
ResolutionContext childContext = (cquery == null) ? ctx : new ResolutionContext(cquery, ctx);
resolvedChildren[idx] = recursiveResolve ? deepResolve(childContext, visited, child, childTagInfo, monitor) : getResolverNode(
childContext, child.getQualifiedDependency(), childTagInfo);
}
node.setResolution(res, resolvedChildren);
return node;
}
}