/*******************************************************************************
* 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.rmap.model;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.eclipse.buckminster.core.CorePlugin;
import org.eclipse.buckminster.core.Messages;
import org.eclipse.buckminster.core.XMLConstants;
import org.eclipse.buckminster.core.common.model.Documentation;
import org.eclipse.buckminster.core.common.model.ExpandingProperties;
import org.eclipse.buckminster.core.common.model.SAXEmitter;
import org.eclipse.buckminster.core.cspec.QualifiedDependency;
import org.eclipse.buckminster.core.cspec.model.CSpec;
import org.eclipse.buckminster.core.cspec.model.ComponentRequest;
import org.eclipse.buckminster.core.ctype.IComponentType;
import org.eclipse.buckminster.core.helpers.UnmodifiableMapUnion;
import org.eclipse.buckminster.core.metadata.model.BOMNode;
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.parser.IParser;
import org.eclipse.buckminster.core.parser.IParserFactory;
import org.eclipse.buckminster.core.resolver.NodeQuery;
import org.eclipse.buckminster.core.resolver.ResolverDecision;
import org.eclipse.buckminster.core.resolver.ResolverDecisionType;
import org.eclipse.buckminster.core.version.ProviderMatch;
import org.eclipse.buckminster.core.version.VersionMatch;
import org.eclipse.buckminster.download.DownloadManager;
import org.eclipse.buckminster.osgi.filter.Filter;
import org.eclipse.buckminster.runtime.BuckminsterException;
import org.eclipse.buckminster.runtime.IOUtils;
import org.eclipse.buckminster.runtime.MonitorUtils;
import org.eclipse.buckminster.sax.AbstractSaxableElement;
import org.eclipse.buckminster.sax.ISaxable;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.MultiStatus;
import org.eclipse.core.runtime.Status;
import org.eclipse.ecf.core.security.IConnectContext;
import org.eclipse.equinox.p2.metadata.Version;
import org.eclipse.equinox.p2.metadata.VersionRange;
import org.eclipse.osgi.util.NLS;
import org.xml.sax.ContentHandler;
import org.xml.sax.SAXException;
/**
* @author Thomas Hallgren
*/
public class ResourceMap extends AbstractSaxableElement implements ISaxable {
public static final String TAG = "rmap"; //$NON-NLS-1$
public static ResourceMap fromURL(URL url, IConnectContext cctx) throws CoreException {
IParserFactory pf = CorePlugin.getDefault().getParserFactory();
IParser<ResourceMap> rmapParser = pf.getResourceMapParser(true);
InputStream input = null;
try {
input = DownloadManager.read(url, cctx);
return rmapParser.parse(url.toString(), input);
} catch (IOException e) {
throw BuckminsterException.wrap(e);
} finally {
IOUtils.close(input);
}
}
private static IStatus transformToWarning(IStatus status) {
if (status instanceof MultiStatus) {
IStatus[] children = status.getChildren();
int idx = children.length;
while (--idx >= 0)
children[idx] = transformToWarning(children[idx]);
return new MultiStatus(status.getPlugin(), status.getCode(), children, status.getMessage(), status.getException());
}
return (status.getSeverity() < IStatus.ERROR) ? status : new Status(IStatus.WARNING, status.getPlugin(), status.getCode(),
status.getMessage(), status.getException());
}
private final ArrayList<Matcher> matchers = new ArrayList<Matcher>();
private final HashMap<String, SearchPath> searchPaths = new HashMap<String, SearchPath>();
private final Map<String, String> properties = new ExpandingProperties<String>(null);
private Documentation documentation;
private final URL contextURL;
public ResourceMap(URL contextURL) {
this.contextURL = contextURL;
}
public void addMatcher(Matcher matcher) {
matchers.add(matcher);
}
public void addPrefixMappings(HashMap<String, String> prefixMappings) {
prefixMappings.put("xsi", javax.xml.XMLConstants.W3C_XML_SCHEMA_INSTANCE_NS_URI); //$NON-NLS-1$
prefixMappings.put(XMLConstants.BM_RMAP_PREFIX, XMLConstants.BM_RMAP_NS);
for (SearchPath searchPath : searchPaths.values())
searchPath.addPrefixMappings(prefixMappings);
}
public void addSearchPath(SearchPath searchPath) {
searchPaths.put(searchPath.getName(), searchPath);
}
public void clear() {
matchers.clear();
searchPaths.clear();
properties.clear();
documentation = null;
}
public URL getContextURL() {
return contextURL;
}
@Override
public String getDefaultTag() {
return TAG;
}
public Documentation getDocumentation() {
return documentation;
}
/**
* Returns the first provider that the query appoints
*
* @param query
* @return The provider or null
* @throws CoreException
*/
public Provider getFirstProvider(NodeQuery query) throws CoreException {
ComponentRequest request = query.getComponentRequest();
Map<String, ? extends Object> props = query.getProperties();
if (!properties.isEmpty()) {
query = new NodeQuery(query, properties, false);
props = query.getProperties();
}
String componentName = request.getName();
for (Matcher matcher : matchers) {
if (!(matcher.isFilterMatchFor(query, null) && matcher.matches(componentName)))
continue;
if (matcher instanceof Redirect)
return ((Redirect) matcher).getResourceMap(query).getFirstProvider(query);
Locator locator = (Locator) matcher;
String searchPathRef = locator.getSearchPath();
searchPathRef = ExpandingProperties.expand(props, searchPathRef, 0);
SearchPath sp = getSearchPathByReference(searchPathRef);
for (Provider provider : sp.getProviders()) {
if (provider.isFilterMatchFor(query, null)) {
ProviderScore score = query.getProviderScore(provider.isMutable(), provider.hasSource());
if (score == ProviderScore.REJECTED || !provider.supportsComponentType(query.getComponentRequest().getComponentTypeID()))
continue;
return provider;
}
}
}
return null;
}
public List<Matcher> getMatchers() {
return matchers;
}
public Map<String, String> getProperties() {
return properties;
}
/**
* Returns the <code>props</code> argument in a union with the properties
* defined in this RMAP. The properties from the <code>props</code> argument
* has precedence.
*
* @param props
* The properties to back with defaults from this RMAP
* @return A union of the <code>props</code> argument and properties defined
* in this RMAP.
*/
public Map<String, ? extends Object> getProperties(Map<String, ? extends Object> props) {
if (!properties.isEmpty())
props = new UnmodifiableMapUnion<String, Object>(props, properties);
return props;
}
public SearchPath getSearchPathByReference(String searchPathRef) throws SearchPathNotFoundException {
SearchPath sp = searchPaths.get(searchPathRef);
if (sp == null)
throw new SearchPathNotFoundException("reference " + searchPathRef); //$NON-NLS-1$
return sp;
}
public Collection<SearchPath> getSearchPaths() {
return searchPaths.values();
}
public void removeMatcher(Matcher matcher) {
matchers.remove(matcher);
}
public BOMNode resolve(NodeQuery query, IProgressMonitor monitor) throws CoreException {
monitor.beginTask(null, 2000);
ComponentRequest request = query.getComponentRequest();
MultiStatus problemCollector = new MultiStatus(CorePlugin.getID(), 0, NLS.bind(
Messages.no_suitable_provider_for_0_was_found_in_resourceMap_1, request, getContextURL()), null);
Map<String, ? extends Object> props = query.getProperties();
if (!properties.isEmpty()) {
query = new NodeQuery(query, properties, false);
props = query.getProperties();
}
String componentName = request.getName();
for (Matcher matcher : matchers) {
Filter[] filterHandle = new Filter[1];
if (!(matcher.isFilterMatchFor(query, filterHandle) && matcher.matches(componentName)))
continue;
if (matcher instanceof Redirect)
return ((Redirect) matcher).getResourceMap(query).resolve(query, monitor);
Locator locator = (Locator) matcher;
try {
String searchPathRef = locator.getSearchPath();
searchPathRef = ExpandingProperties.expand(props, searchPathRef, 0);
SearchPath sp = getSearchPathByReference(searchPathRef);
query.logDecision(ResolverDecisionType.USING_SEARCH_PATH, sp.getName());
return resolve(query, sp, monitor);
} catch (CoreException e) {
problemCollector.add(e.getStatus());
if (locator.isFailOnError())
break;
}
}
if (problemCollector.getChildren().length == 0) {
query.logDecision(ResolverDecisionType.SEARCH_PATH_NOT_FOUND, (Object) null);
problemCollector.add(new Status(IStatus.ERROR, CorePlugin.getID(), IStatus.OK, NLS.bind(Messages.Unable_to_find_a_searchPath_for_0,
request), null));
}
if (request.isOptional()) {
// The component is optional so this should not be considered an
// error
// A warning is appropriate though, since this probably indicates
// some
// kind of problem.
//
if (!request.isSynthetic())
query.getContext().addRequestStatus(request, transformToWarning(problemCollector));
return new UnresolvedNode(new QualifiedDependency(request, query.getRequiredAttributes()));
}
throw new CoreException(problemCollector);
}
public void setDocumentation(Documentation documentation) {
this.documentation = documentation;
}
/**
* Emit the SAX events that forms the XML representation for this instance
*
* @param handler
* The handler that will receive the events.
* @throws SAXException
* if the handler throws an exception when receiving the events
*/
@Override
public void toSax(ContentHandler handler) throws SAXException {
handler.startDocument();
toSax(handler, XMLConstants.BM_RMAP_NS, XMLConstants.BM_RMAP_PREFIX, getDefaultTag());
handler.endDocument();
}
@Override
public void toSax(ContentHandler handler, String namespace, String prefix, String localName) throws SAXException {
HashMap<String, String> prefixMappings = new HashMap<String, String>();
addPrefixMappings(prefixMappings);
Set<Map.Entry<String, String>> pfxMappings = prefixMappings.entrySet();
for (Map.Entry<String, String> pfxMapping : pfxMappings)
handler.startPrefixMapping(pfxMapping.getKey(), pfxMapping.getValue());
super.toSax(handler, namespace, prefix, localName);
for (Map.Entry<String, String> pfxMapping : pfxMappings)
handler.endPrefixMapping(pfxMapping.getKey());
}
@Override
protected void emitElements(ContentHandler handler, String namespace, String prefix) throws SAXException {
if (documentation != null)
documentation.toSax(handler, namespace, prefix, documentation.getDefaultTag());
SAXEmitter.emitProperties(handler, properties, namespace, prefix, true, false);
for (SearchPath searchPath : searchPaths.values())
searchPath.toSax(handler, namespace, prefix, searchPath.getDefaultTag());
for (Matcher matcher : matchers)
matcher.toSax(handler, namespace, prefix, matcher.getDefaultTag());
}
private BOMNode resolve(NodeQuery query, SearchPath searchPath, IProgressMonitor monitor) throws CoreException {
MultiStatus problemCollector = new MultiStatus(CorePlugin.getID(), IStatus.ERROR, NLS.bind(
Messages.No_suitable_provider_for_component_0_was_found_in_searchPath_1, query.getComponentRequest(), searchPath.getName()), null);
ArrayList<Provider> noGoodList = new ArrayList<Provider>();
try {
for (boolean first = true;; first = false) {
ProviderMatch providerMatch = searchPath.getProvider(query, noGoodList, problemCollector,
MonitorUtils.subMonitor(monitor, first ? 1000 : 0));
MonitorUtils.testCancelStatus(monitor);
Provider provider = providerMatch.getProvider();
IComponentType cType = providerMatch.getComponentType();
try {
BOMNode node = cType.getResolution(providerMatch, MonitorUtils.subMonitor(monitor, first ? 1000 : 0));
Resolution resolution = node.getResolution();
MonitorUtils.testCancelStatus(monitor);
Filter[] filterHandle = new Filter[1];
if (!resolution.isFilterMatchFor(query, filterHandle)) {
ResolverDecision decision = query.logDecision(ResolverDecisionType.FILTER_MISMATCH, filterHandle[0]);
noGoodList.add(providerMatch.getOriginalProvider());
problemCollector.add(new Status(IStatus.ERROR, CorePlugin.getID(), IStatus.OK, decision.toString(), null));
continue;
}
CSpec cspec = resolution.getCSpec();
// Assert that the cspec can handle required actions and
// exports
//
Version version = cspec.getVersion();
VersionRange range = query.getVersionRange();
if (range != null && provider.getVersionConverterDesc() == null) {
// A missing version converter means that the actual
// version check is deferred
// and later performed on the retreived CSpec. Later is
// now ...
//
if (!range.isIncluded(version)) {
ResolverDecision decision = query.logDecision(ResolverDecisionType.VERSION_REJECTED, version,
NLS.bind(Messages.Not_designated_by_0, range));
noGoodList.add(providerMatch.getOriginalProvider());
problemCollector.add(new Status(IStatus.ERROR, CorePlugin.getID(), IStatus.OK, decision.toString(), null));
continue;
}
}
// Verify that all required attributes are in this cspec
//
cspec.getAttributes(query.getRequiredAttributes());
if (version != null) {
// Replace the resolution version if it is default and
// a non default version is present in the CSpec
//
VersionMatch vm = resolution.getVersionMatch();
if (vm.getVersion() == null)
node = new ResolvedNode(new Resolution(version, resolution), node.getChildren());
}
return node;
} catch (CoreException e) {
ResolverDecision decision = query.logDecision(ResolverDecisionType.EXCEPTION, e.getMessage());
problemCollector.add(new Status(IStatus.ERROR, CorePlugin.getID(), IStatus.OK, decision.toString(), e));
noGoodList.add(providerMatch.getOriginalProvider());
}
}
} finally {
monitor.done();
}
}
}