/******************************************************************************* * 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.util.Collections; import java.util.HashMap; import java.util.Map; 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.RMContext; import org.eclipse.buckminster.core.XMLConstants; import org.eclipse.buckminster.core.common.model.Documentation; import org.eclipse.buckminster.core.common.model.Format; import org.eclipse.buckminster.core.common.model.SAXEmitter; import org.eclipse.buckminster.core.cspec.model.ComponentRequest; import org.eclipse.buckminster.core.ctype.IComponentType; import org.eclipse.buckminster.core.ctype.MissingCSpecSourceException; import org.eclipse.buckminster.core.helpers.TextUtils; import org.eclipse.buckminster.core.helpers.UnmodifiableMapUnion; import org.eclipse.buckminster.core.metadata.ReferentialIntegrityException; import org.eclipse.buckminster.core.metadata.StorageManager; import org.eclipse.buckminster.core.metadata.model.IUUIDPersisted; import org.eclipse.buckminster.core.reader.IReaderType; import org.eclipse.buckminster.core.reader.IVersionFinder; 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.IVersionConverter; 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.BuckminsterException; import org.eclipse.buckminster.runtime.MonitorUtils; import org.eclipse.buckminster.runtime.Trivial; import org.eclipse.buckminster.sax.UUIDKeyed; import org.eclipse.buckminster.sax.Utils; 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.osgi.util.NLS; import org.xml.sax.ContentHandler; import org.xml.sax.SAXException; import org.xml.sax.helpers.AttributesImpl; /** * @author Thomas Hallgren */ public class Provider extends UUIDKeyed implements IUUIDPersisted { public static final String ATTR_COMPONENT_TYPES = "componentTypes"; //$NON-NLS-1$ public static final String ATTR_ALGORITHM = "algorithm"; //$NON-NLS-1$ public static final String ATTR_READER_TYPE = "readerType"; //$NON-NLS-1$ public static final String ATTR_RESOLUTION_FILTER = "resolutionFilter"; //$NON-NLS-1$ public static final String ATTR_VERSION_CONVERTER = "versionConverter"; //$NON-NLS-1$ public static final String TAG = "provider"; //$NON-NLS-1$ public static final String TAG_URI = "uri"; //$NON-NLS-1$ public static final int SEQUENCE_NUMBER = 2; public static final String TAG_DIGEST = "digest"; //$NON-NLS-1$ public static Provider immutableProvider(String readerType, String componentType, String uri) { return immutableProvider(readerType, componentType, uri, null); } public static Provider immutableProvider(String readerType, String componentType, String uri, Filter resolutionFilter) { 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$ return new Provider(null, readerType, new String[] { componentType }, null, new Format(uri), null, null, resolutionFilter, props, null, null); } private final Documentation documentation; private final String[] componentTypeIDs; private final String readerTypeId; private final Format uri; private final Format digest; private final String digestAlgorithm; private final VersionConverterDesc versionConverter; private final SearchPath searchPath; private final URIMatcher uriMatcher; private final Filter resolutionFilter; private final Map<String, String> properties; /** * Creates a new fully initialized Provider * * @param searchPath * The search path that this provider belongs to. * @param remoteReaderType * The reader type used by the provider * @param componentTypeIDs * An array of component types supported by this provider * @param versionConverterDesc * The description of the version converter or <code>null</code> * if not applicable. * @param uri * The URI used by the reader type. * @param digest * The digest URI or <code>null</code> if not applicable * @param digestAlgorithm * The digest algorithm or <code>null</code> if not applicable * @param resolutionFilter * Filter indicating when this provider is active * @param properties * Provider specific properties * @param uriMatcher * The URI matcher for the provider or <code>null</code> if not * applicable. * @param documentation * Documentation in xhtml format. */ public Provider(SearchPath searchPath, String remoteReaderType, String[] componentTypeIDs, VersionConverterDesc versionConverterDesc, Format uri, Format digest, String digestAlgorithm, Filter resolutionFilter, Map<String, String> properties, URIMatcher uriMatcher, Documentation documentation) { this.searchPath = searchPath; this.readerTypeId = remoteReaderType; this.componentTypeIDs = componentTypeIDs == null ? Trivial.EMPTY_STRING_ARRAY : componentTypeIDs; this.versionConverter = versionConverterDesc; this.uri = uri; this.digest = digest; this.digestAlgorithm = digestAlgorithm; this.resolutionFilter = resolutionFilter; this.properties = properties == null ? Collections.<String, String> emptyMap() : properties; this.uriMatcher = uriMatcher; this.documentation = documentation; } public void addPrefixMappings(HashMap<String, String> prefixMappings) { prefixMappings.put("xsi", javax.xml.XMLConstants.W3C_XML_SCHEMA_INSTANCE_NS_URI); //$NON-NLS-1$ } public ProviderMatch findMatch(NodeQuery query, MultiStatus problemCollector, IProgressMonitor monitor) throws CoreException { ProviderScore score = query.getProviderScore(isMutable(), hasSource()); if (score == ProviderScore.REJECTED) { ResolverDecision decision = query.logDecision(ResolverDecisionType.REJECTING_PROVIDER, getReaderTypeId(), getProviderURI(query), Messages.Score_is_below_threshold); problemCollector.add(new Status(IStatus.ERROR, CorePlugin.getID(), IStatus.OK, decision.toString(), null)); return null; } if (uriMatcher != null) { ProviderMatch result = uriMatcher.getMatch(this, query, monitor); if (result == null) { ResolverDecision decision = query.logDecision(ResolverDecisionType.REJECTING_PROVIDER, getReaderTypeId(), getProviderURI(query), "matcher didn't match any entries"); //$NON-NLS-1$ problemCollector.add(new Status(IStatus.ERROR, CorePlugin.getID(), IStatus.OK, decision.toString(), null)); } return result; } IVersionFinder versionFinder = null; monitor.beginTask(null, 120); try { ComponentRequest request = query.getComponentRequest(); String componentTypeID = request.getComponentTypeID(); // The component request is equipped with a component type. It must // match the types that this provider provides. // IComponentType[] componentTypes = getComponentTypes(); if (componentTypeID != null) { boolean found = false; int idx = componentTypes.length; while (--idx >= 0) { IComponentType ctype = componentTypes[idx]; if (ctype.getId().equals(componentTypeID)) { // Limit the component types to this one type // componentTypes = new IComponentType[] { ctype }; found = true; break; } } if (!found) { // The ECLIPSE_PLATFORM reader is silent here since it is // always consulted // if (!getReaderTypeId().equals(IReaderType.ECLIPSE_PLATFORM)) { ResolverDecision decision = query.logDecision(ResolverDecisionType.REJECTING_PROVIDER, getReaderTypeId(), getProviderURI(query), String.format(NLS.bind(Messages.Components_of_type_0_are_not_supported, componentTypeID))); problemCollector.add(new Status(IStatus.ERROR, CorePlugin.getID(), IStatus.OK, decision.toString(), null)); } return null; } } VersionMatch candidate = null; IComponentType ctypeUsed = null; CoreException problem = null; try { for (IComponentType ctype : componentTypes) { try { versionFinder = getReaderType().getVersionFinder(this, ctype, query, MonitorUtils.subMonitor(monitor, 20)); candidate = versionFinder.getBestVersion(MonitorUtils.subMonitor(monitor, 80)); if (candidate == null) continue; ctypeUsed = ctype; } catch (MissingCSpecSourceException e) { continue; } break; } } catch (CoreException e) { problem = e; } if (candidate == null) { ResolverDecision decision = query.logDecision(ResolverDecisionType.REJECTING_PROVIDER, getReaderTypeId(), getProviderURI(query), Messages.No_component_match_was_found); problemCollector.add(new Status(IStatus.ERROR, CorePlugin.getID(), IStatus.OK, decision.toString(), problem == null ? null : BuckminsterException.unwind(problem))); return null; } query.logDecision(ResolverDecisionType.MATCH_FOUND, candidate); return versionFinder.getProviderMatch(candidate, ctypeUsed, score); } finally { if (versionFinder != null) versionFinder.close(); monitor.done(); } } public final String[] getComponentTypeIDs() { return componentTypeIDs; } public final IComponentType[] getComponentTypes() throws CoreException { CorePlugin plugin = CorePlugin.getDefault(); int idx = componentTypeIDs.length; IComponentType[] ctypes = new IComponentType[idx]; while (--idx >= 0) ctypes[idx] = plugin.getComponentType(componentTypeIDs[idx]); return ctypes; } public IConnectContext getConnectContext() { return null; } /** * Get a connection context given a properties map. This allows for * properties and property expansion to be used from higher precedence * scopes. * * @param props * @return default implementation returns <code>null</code>. */ public IConnectContext getConnectContext(Map<String, ? extends Object> props) { return null; } @Override public String getDefaultTag() { return TAG; } /** * @return Returns the Digest URI. */ public final String getDigest(Map<String, String> props) { return digest == null ? null : digest.getValue(getProperties(props)); } public final String getDigestAlgorithm() { return digestAlgorithm; } public Documentation getDocumentation() { return documentation; } public Map<String, ? extends Object> getProperties(Map<String, ? extends Object> props) { if (!properties.isEmpty()) props = new UnmodifiableMapUnion<String, Object>(props, properties); if (searchPath != null) props = searchPath.getResourceMap().getProperties(props); return props; } public Map<String, String> getProviderProperties() { return properties; } public final IReaderType getReaderType() throws CoreException { return CorePlugin.getDefault().getReaderType(readerTypeId); } public final String getReaderTypeId() { return readerTypeId; } public Filter getResolutionFilter() { return resolutionFilter; } public final SearchPath getSearchPath() { return searchPath; } /** * @return Returns the possibly parameterized <code>Format</code> instance * that represents File or Repository URI. */ public final Format getURI() { return uri; } /** * @return Returns expanded the File or Repository URI. */ public final String getURI(Map<String, ? extends Object> props) { return uri.getValue(getProperties(props)); } public URIMatcher getURIMatcher() { return uriMatcher; } public IVersionConverter getVersionConverter() throws CoreException { VersionConverterDesc vcd = getVersionConverterDesc(); return vcd == null ? null : vcd.getVersionConverter(); } public final VersionConverterDesc getVersionConverterDesc() { return versionConverter; } public boolean hasLocalCache() { return false; } /** * @return Returns the hasSource. */ public final boolean hasSource() { String source = properties.get(KeyConstants.IS_SOURCE); return source == null ? true : Boolean.parseBoolean(source); } /** * Returns true if this provider is a match for the given <code>query</code> * with respect to provided properties. The method will update the filter * attributes map of the query context. * * @param The * query to match * @param A * one element array that will receive the failing filter. Can be * <code>null</code>. * @return True if this resolution is a match for the given query. * @see RMContext#getFilterAttributeUsageMap() */ public boolean isFilterMatchFor(NodeQuery query, Filter[] failingFilter) { if (resolutionFilter == null) return true; Map<String, String[]> attributeUsageMap = query.getContext().getFilterAttributeUsageMap(); Filter resFilter = getResolutionFilter(); Map<String, ? extends Object> props = query.getProperties(); resolutionFilter.addConsultedAttributes(attributeUsageMap); if (resolutionFilter.matchCase(props)) return true; if (failingFilter != null) failingFilter[0] = resFilter; return false; } /** * @return Returns the readOnly. */ public final boolean isMutable() { String mutable = properties.get(KeyConstants.IS_MUTABLE); return mutable == null ? true : Boolean.parseBoolean(mutable); } @Override public boolean isPersisted(StorageManager sm) throws CoreException { return sm.getProviders().contains(this); } @Override public void remove(StorageManager sm) throws CoreException { UUID thisId = getId(); if (!sm.getResolutions().getReferencingKeys(thisId, "providerId").isEmpty()) //$NON-NLS-1$ throw new ReferentialIntegrityException(this, "remove", Messages.Referenced_from_Resolution); //$NON-NLS-1$ sm.getProviders().removeElement(thisId); } @Override public void store(StorageManager sm) throws CoreException { sm.getProviders().putElement(this); } public boolean supportsComponentType(String componentTypeID) { if (componentTypeID == null) return true; String[] componentTypes = getComponentTypeIDs(); int idx = componentTypes.length; while (--idx >= 0) if (componentTypeID.equals(componentTypes[idx])) return true; return false; } @Override public void toSax(ContentHandler handler) throws SAXException { handler.startDocument(); HashMap<String, String> prefixMappings = new HashMap<String, String>(); addPrefixMappings(prefixMappings); for (Map.Entry<String, String> pfxMapping : prefixMappings.entrySet()) handler.startPrefixMapping(pfxMapping.getKey(), pfxMapping.getValue()); toSax(handler, XMLConstants.BM_RMAP_NS, XMLConstants.BM_RMAP_PREFIX, getDefaultTag()); for (String pfx : prefixMappings.keySet()) handler.endPrefixMapping(pfx); handler.endDocument(); } @Override protected void addAttributes(AttributesImpl attrs) throws SAXException { Utils.addAttribute(attrs, ATTR_READER_TYPE, readerTypeId); if (componentTypeIDs.length > 0) Utils.addAttribute(attrs, ATTR_COMPONENT_TYPES, TextUtils.concat(componentTypeIDs, ",")); //$NON-NLS-1$ if (resolutionFilter != null) Utils.addAttribute(attrs, ATTR_RESOLUTION_FILTER, resolutionFilter.toString()); } @Override protected void emitElements(ContentHandler handler, String namespace, String prefix) throws SAXException { if (documentation != null) documentation.toSax(handler, namespace, prefix, documentation.getDefaultTag()); if (uriMatcher != null) uriMatcher.toSax(handler, namespace, prefix, uriMatcher.getDefaultTag()); uri.toSax(handler, namespace, prefix, TAG_URI); SAXEmitter.emitProperties(handler, properties, namespace, prefix, true, false); if (digest != null) { AttributesImpl attrs = new AttributesImpl(); Utils.addAttribute(attrs, Format.ATTR_FORMAT, digest.getFormat()); Utils.addAttribute(attrs, ATTR_ALGORITHM, digestAlgorithm); String qName = Utils.makeQualifiedName(prefix, TAG_DIGEST); handler.startElement(namespace, TAG_DIGEST, qName, attrs); handler.endElement(namespace, TAG_DIGEST, qName); } if (versionConverter != null) versionConverter.toSax(handler, namespace, prefix, versionConverter.getDefaultTag()); } @Override protected String getElementNamespace(String namespace) { return XMLConstants.BM_RMAP_NS; } @Override protected String getElementPrefix(String prefix) { return XMLConstants.BM_RMAP_PREFIX; } private String getProviderURI(NodeQuery query) { return searchPath == null ? getURI(query.getProperties()) : searchPath.getProviderURI(query, this); } }