//---------------------------------------------------------------------------- // Copyright (c) Microsoft Corporation. All rights reserved. //---------------------------------------------------------------------------- namespace System.ServiceModel.Channels { using System.Collections.Generic; using System.Globalization; using System.Runtime; using System.Runtime.Collections; using System.ServiceModel; sealed class UriPrefixTable where TItem : class { int count; const int HopperSize = 128; volatile HopperCache lookupCache; // cache matches, for lookup speed SegmentHierarchyNode root; bool useWeakReferences; bool includePortInComparison; public UriPrefixTable() : this(false) { } public UriPrefixTable(bool includePortInComparison) : this(includePortInComparison, false) { } public UriPrefixTable(bool includePortInComparison, bool useWeakReferences) { this.includePortInComparison = includePortInComparison; this.useWeakReferences = useWeakReferences; this.root = new SegmentHierarchyNode(null, useWeakReferences); this.lookupCache = new HopperCache(HopperSize, useWeakReferences); } internal UriPrefixTable(UriPrefixTable objectToClone) : this(objectToClone.includePortInComparison, objectToClone.useWeakReferences) { if (objectToClone.Count > 0) { foreach (KeyValuePair current in objectToClone.GetAll()) { this.RegisterUri(current.Key.BaseAddress, current.Key.HostNameComparisonMode, current.Value); } } } object ThisLock { get { // The UriPrefixTable instance itself is used as a // synchronization primitive in the TransportManagers and the // TransportManagerContainers so we return 'this' to keep them in [....]. return this; } } public int Count { get { return this.count; } } public bool IsRegistered(BaseUriWithWildcard key) { Uri uri = key.BaseAddress; // don't need to normalize path since SegmentHierarchyNode is // already OrdinalIgnoreCase string[] paths = UriSegmenter.ToPath(uri, key.HostNameComparisonMode, this.includePortInComparison); bool exactMatch; SegmentHierarchyNode node; lock (ThisLock) { node = FindDataNode(paths, out exactMatch); } return exactMatch && node != null && node.Data != null; } public IEnumerable> GetAll() { lock (ThisLock) { List> result = new List>(); this.root.Collect(result); return result; } } bool TryCacheLookup(BaseUriWithWildcard key, out TItem item) { object value = this.lookupCache.GetValue(ThisLock, key); // We might return null and true in the case of DBNull (cached negative result). // When TItem is object, the cast isn't sufficient to ---- out DBNulls, so we need an explicit check. item = value == DBNull.Value ? null : (TItem)value; return value != null; } void AddToCache(BaseUriWithWildcard key, TItem item) { // Don't allow explicitly adding DBNulls. Fx.Assert(item != DBNull.Value, "Can't add DBNull to UriPrefixTable."); // HopperCache uses null as 'doesn't exist', so use DBNull as a stand-in for null. this.lookupCache.Add(key, item ?? (object)DBNull.Value); } void ClearCache() { this.lookupCache = new HopperCache(HopperSize, this.useWeakReferences); } public bool TryLookupUri(Uri uri, HostNameComparisonMode hostNameComparisonMode, out TItem item) { BaseUriWithWildcard key = new BaseUriWithWildcard(uri, hostNameComparisonMode); if (TryCacheLookup(key, out item)) { return item != null; } lock (ThisLock) { // exact match failed, perform the full lookup (which will also // catch case-insensitive variations that aren't yet in our cache) bool dummy; SegmentHierarchyNode node = FindDataNode( UriSegmenter.ToPath(key.BaseAddress, hostNameComparisonMode, this.includePortInComparison), out dummy); if (node != null) { item = node.Data; } // We want to cache both positive AND negative results AddToCache(key, item); return (item != null); } } public void RegisterUri(Uri uri, HostNameComparisonMode hostNameComparisonMode, TItem item) { Fx.Assert(HostNameComparisonModeHelper.IsDefined(hostNameComparisonMode), "RegisterUri: Invalid HostNameComparisonMode value passed in."); lock (ThisLock) { // Since every newly registered Uri could alter what Prefixes should have matched, we // should clear the cache of any existing results and start over ClearCache(); BaseUriWithWildcard key = new BaseUriWithWildcard(uri, hostNameComparisonMode); SegmentHierarchyNode node = FindOrCreateNode(key); if (node.Data != null) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new InvalidOperationException(SR.GetString( SR.DuplicateRegistration, uri))); } node.SetData(item, key); count++; } } public void UnregisterUri(Uri uri, HostNameComparisonMode hostNameComparisonMode) { lock (ThisLock) { // Since every removed Uri could alter what Prefixes should have matched, we // should clear the cache of any existing results and start over ClearCache(); string[] path = UriSegmenter.ToPath(uri, hostNameComparisonMode, this.includePortInComparison); // Never remove the root if (path.Length == 0) { this.root.RemoveData(); } else { this.root.RemovePath(path, 0); } count--; } } SegmentHierarchyNode FindDataNode(string[] path, out bool exactMatch) { Fx.Assert(path != null, "FindDataNode: path is null"); exactMatch = false; SegmentHierarchyNode current = this.root; SegmentHierarchyNode result = null; for (int i = 0; i < path.Length; ++i) { SegmentHierarchyNode next; if (!current.TryGetChild(path[i], out next)) { break; } else if (next.Data != null) { result = next; exactMatch = (i == path.Length - 1); } current = next; } return result; } SegmentHierarchyNode FindOrCreateNode(BaseUriWithWildcard baseUri) { Fx.Assert(baseUri != null, "FindOrCreateNode: baseUri is null"); string[] path = UriSegmenter.ToPath(baseUri.BaseAddress, baseUri.HostNameComparisonMode, this.includePortInComparison); SegmentHierarchyNode current = this.root; for (int i = 0; i < path.Length; ++i) { SegmentHierarchyNode next; if (!current.TryGetChild(path[i], out next)) { next = new SegmentHierarchyNode(path[i], useWeakReferences); current.SetChildNode(path[i], next); } current = next; } return current; } static class UriSegmenter { internal static string[] ToPath(Uri uriPath, HostNameComparisonMode hostNameComparisonMode, bool includePortInComparison) { if (null == uriPath) { return new string[0]; } UriSegmentEnum segmentEnum = new UriSegmentEnum(uriPath); // struct return segmentEnum.GetSegments(hostNameComparisonMode, includePortInComparison); } struct UriSegmentEnum { string segment; int segmentStartAt; int segmentLength; UriSegmentType type; Uri uri; internal UriSegmentEnum(Uri uri) { Fx.Assert(null != uri, "UreSegmentEnum: null uri"); this.uri = uri; this.type = UriSegmentType.Unknown; this.segment = null; this.segmentStartAt = 0; this.segmentLength = 0; } void ClearSegment() { this.type = UriSegmentType.None; this.segment = string.Empty; this.segmentStartAt = 0; this.segmentLength = 0; } public string[] GetSegments(HostNameComparisonMode hostNameComparisonMode, bool includePortInComparison) { List segments = new List(); while (this.Next()) { switch (this.type) { case UriSegmentType.Path: segments.Add(this.segment.Substring(this.segmentStartAt, this.segmentLength)); break; case UriSegmentType.Host: if (hostNameComparisonMode == HostNameComparisonMode.StrongWildcard) { segments.Add("+"); } else if (hostNameComparisonMode == HostNameComparisonMode.Exact) { segments.Add(this.segment); } else { segments.Add("*"); } break; case UriSegmentType.Port: if (includePortInComparison || hostNameComparisonMode == HostNameComparisonMode.Exact) { segments.Add(this.segment); } break; default: segments.Add(this.segment); break; } } return segments.ToArray(); } public bool Next() { while (true) { switch (this.type) { case UriSegmentType.Unknown: this.type = UriSegmentType.Scheme; this.SetSegment(this.uri.Scheme); return true; case UriSegmentType.Scheme: this.type = UriSegmentType.Host; string host = this.uri.Host; // The userName+password also accompany... string userInfo = this.uri.UserInfo; if (null != userInfo && userInfo.Length > 0) { host = userInfo + '@' + host; } this.SetSegment(host); return true; case UriSegmentType.Host: this.type = UriSegmentType.Port; int port = this.uri.Port; this.SetSegment(port.ToString(CultureInfo.InvariantCulture)); return true; case UriSegmentType.Port: this.type = UriSegmentType.Path; string absPath = this.uri.AbsolutePath; Fx.Assert(null != absPath, "Next: nill absPath"); if (0 == absPath.Length) { this.ClearSegment(); return false; } this.segment = absPath; this.segmentStartAt = 0; this.segmentLength = 0; return this.NextPathSegment(); case UriSegmentType.Path: return this.NextPathSegment(); case UriSegmentType.None: return false; default: Fx.Assert("Next: unknown enum value"); return false; } } } public bool NextPathSegment() { this.segmentStartAt += this.segmentLength; while (this.segmentStartAt < this.segment.Length && this.segment[this.segmentStartAt] == '/') { this.segmentStartAt++; } if (this.segmentStartAt < this.segment.Length) { int next = this.segment.IndexOf('/', this.segmentStartAt); if (-1 == next) { this.segmentLength = this.segment.Length - this.segmentStartAt; } else { this.segmentLength = next - this.segmentStartAt; } return true; } this.ClearSegment(); return false; } void SetSegment(string segment) { this.segment = segment; this.segmentStartAt = 0; this.segmentLength = segment.Length; } enum UriSegmentType { Unknown, Scheme, Host, Port, Path, None } } } } class SegmentHierarchyNode where TData : class { BaseUriWithWildcard path; TData data; string name; Dictionary> children; WeakReference weakData; bool useWeakReferences; public SegmentHierarchyNode(string name, bool useWeakReferences) { this.name = name; this.useWeakReferences = useWeakReferences; this.children = new Dictionary>(StringComparer.OrdinalIgnoreCase); } public TData Data { get { if (useWeakReferences) { if (this.weakData == null) { return null; } else { return this.weakData.Target as TData; } } else { return this.data; } } } public void SetData(TData data, BaseUriWithWildcard path) { this.path = path; if (useWeakReferences) { if (data == null) { this.weakData = null; } else { this.weakData = new WeakReference(data); } } else { this.data = data; } } public void SetChildNode(string name, SegmentHierarchyNode node) { this.children[name] = node; } public void Collect(List> result) { TData localData = this.Data; if (localData != null) { result.Add(new KeyValuePair(path, localData)); } foreach (SegmentHierarchyNode child in this.children.Values) { child.Collect(result); } } public bool TryGetChild(string segment, out SegmentHierarchyNode value) { return children.TryGetValue(segment, out value); } public void RemoveData() { SetData(null, null); } // bool is whether to remove this node public bool RemovePath(string[] path, int seg) { if (seg == path.Length) { RemoveData(); return this.children.Count == 0; } SegmentHierarchyNode node; if (!TryGetChild(path[seg], out node)) { return (this.children.Count == 0 && Data == null); } if (node.RemovePath(path, seg + 1)) { this.children.Remove(path[seg]); return (this.children.Count == 0 && Data == null); } else { return false; } } } }