//------------------------------------------------------------ // Copyright (c) Microsoft Corporation. All rights reserved. //------------------------------------------------------------ namespace System.ServiceModel.Dispatcher { using System; using System.Collections; using System.Collections.Generic; using System.Collections.ObjectModel; using System.Diagnostics.CodeAnalysis; using System.Runtime; using System.ServiceModel.Channels; using System.ServiceModel.Diagnostics; using System.Xml; using System.Xml.XPath; using System.Xml.Xsl; internal enum QueryCompilerFlags { None = 0x00000000, InverseQuery = 0x00000001 } internal struct FilterResult { QueryProcessor processor; bool result; internal FilterResult(QueryProcessor processor) { this.processor = processor; this.result = this.processor.Result; } internal FilterResult(bool result) { this.processor = null; this.result = result; } #if NO internal ICollection Matches { get { return this.processor.ResultSet; } } #endif internal QueryProcessor Processor { get { return this.processor; } } internal bool Result { get { return this.result; } } internal MessageFilter GetSingleMatch() { Collection matches = processor.MatchList; MessageFilter match; switch (matches.Count) { default: throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new MultipleFilterMatchesException(SR.GetString(SR.FilterMultipleMatches), null, matches)); case 0: match = null; break; case 1: match = matches[0]; break; } return match; } } // XPathResult.GetResultAsString and XPathResult.GetResultAsBoolean, // drive knowledge of TResult into the engine. internal class QueryResult : IEnumerable> { bool evalBody; QueryMatcher matcher; Message message; internal QueryResult(QueryMatcher matcher, Message message, bool evalBody) { this.matcher = matcher; this.message = message; this.evalBody = evalBody; } public TResult GetSingleResult() { QueryProcessor processor = this.matcher.CreateProcessor(); XPathResult result; try { processor.Eval(this.matcher.RootOpcode, this.message, this.evalBody); } catch (XPathNavigatorException e) { throw TraceUtility.ThrowHelperError(e.Process(this.matcher.RootOpcode), this.message); } catch (NavigatorInvalidBodyAccessException e) { throw TraceUtility.ThrowHelperError(e.Process(this.matcher.RootOpcode), this.message); } finally { if (this.evalBody) { this.message.Close(); } result = processor.QueryResult; this.matcher.ReleaseProcessor(processor); } if (typeof(TResult) == typeof(XPathResult) || typeof(TResult) == typeof(object)) { return (TResult)(object)result; } else if (typeof(TResult) == typeof(string)) { return (TResult)(object)result.GetResultAsString(); } else if (typeof(TResult) == typeof(bool)) { return (TResult)(object)result.GetResultAsBoolean(); } else { throw Fx.AssertAndThrowFatal("unsupported type"); } } public IEnumerator> GetEnumerator() { QueryProcessor processor = this.matcher.CreateProcessor(); Collection> results = new Collection>(); processor.ResultSet = results; try { processor.Eval(this.matcher.RootOpcode, this.message, this.evalBody); if (typeof(TResult) == typeof(XPathResult)) { return (IEnumerator>)(object)results.GetEnumerator(); } else if (typeof(TResult) == typeof(string) || typeof(TResult) == typeof(bool) || typeof(TResult) == typeof(object)) { Collection> typedResults = new Collection>(); foreach (var result in results) { if (typeof(TResult) == typeof(string)) { typedResults.Add( new KeyValuePair( result.Key, (TResult)(object)result.Value.GetResultAsString())); } else if (typeof(TResult) == typeof(bool)) { typedResults.Add( new KeyValuePair( result.Key, (TResult)(object)result.Value.GetResultAsBoolean())); } else { typedResults.Add(new KeyValuePair( result.Key, (TResult)(object)result.Value)); } } return (IEnumerator>)typedResults.GetEnumerator(); } else { throw Fx.AssertAndThrowFatal("unsupported type"); } } catch (XPathNavigatorException e) { throw TraceUtility.ThrowHelperError(e.Process(this.matcher.RootOpcode), this.message); } catch (NavigatorInvalidBodyAccessException e) { throw TraceUtility.ThrowHelperError(e.Process(this.matcher.RootOpcode), this.message); } finally { if (this.evalBody) { this.message.Close(); } this.matcher.ReleaseProcessor(processor); } } IEnumerator IEnumerable.GetEnumerator() { return this.GetEnumerator(); } } /// /// /// internal abstract class QueryMatcher { static IFunctionLibrary[] defaultFunctionLibs; // The set of function libraries that our XPath compiler will link to static XPathNavigator fxCompiler; // fx compiler protected int maxNodes; // Maximum # of nodes that we will process while performing any individual match protected Opcode query; // root opcode - this is where query evaluation starts protected int subExprVars; // the number of subexpr node sequences the processing context must hold // Processor Pool protected WeakReference processorPool; internal class QueryProcessorPool { QueryProcessor processor; internal QueryProcessorPool() { } internal QueryProcessor Pop() { QueryProcessor p = this.processor; if (null != p) { this.processor = (QueryProcessor)p.next; p.next = null; return p; } return null; } internal void Push(QueryProcessor p) { p.next = this.processor; this.processor = p; } } [SuppressMessage("Microsoft.Security.Xml", "CA3057:DoNotUseLoadXml")] static QueryMatcher() { QueryMatcher.defaultFunctionLibs = new IFunctionLibrary[] { new XPathFunctionLibrary() }; // For some incomprehensible reason, the Framework XPath compiler requires an instance of an XPath navigator // to compile an xpath. This compiler uses a dummy xml document to create a navigator XmlDocument doc = new XmlDocument(); doc.LoadXml(""); QueryMatcher.fxCompiler = doc.CreateNavigator(); } internal QueryMatcher() { this.maxNodes = int.MaxValue; this.query = null; this.processorPool = new WeakReference(null); this.subExprVars = 0; } #if NO internal QueryMatcher(QueryMatcher matcher) { this.processorPool = new WeakReference(null); this.maxNodes = matcher.maxNodes; this.query = matcher.query; this.subExprVars = matcher.subExprVars; } #endif internal bool IsCompiled { get { return (null != this.query); } } internal int NodeQuota { get { return this.maxNodes; } set { Fx.Assert(value > 0, ""); this.maxNodes = value; } } internal Opcode RootOpcode { get { return this.query; } } internal int SubExprVarCount { get { return this.subExprVars; } } /// /// Compile the given filter to run on an external (fx) xpath engine /// internal static OpcodeBlock CompileForExternalEngine(string expression, XmlNamespaceManager namespaces, object item, bool match) { // Compile... XPathExpression xpathExpr = QueryMatcher.fxCompiler.Compile(expression); // Fx will bind prefixes and functions here. if (namespaces != null) { // There's a bug in System.Xml.XPath. If we pass an XsltContext to SetContext it won't throw if there's // an undefined prefix. if (namespaces is XsltContext) { // Lex the xpath to find all prefixes used XPathLexer lexer = new XPathLexer(expression, false); while (lexer.MoveNext()) { string prefix = lexer.Token.Prefix; if (prefix.Length > 0 && namespaces.LookupNamespace(prefix) == null) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new XsltException(SR.GetString(SR.FilterUndefinedPrefix, prefix))); } } } xpathExpr.SetContext(namespaces); } // // FORCE the function to COMPILE - they won't bind namespaces unless we check the return type // if (XPathResultType.Error == xpathExpr.ReturnType) { // This should never be reached. The above property should throw if there's an error throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new XPathException(SR.GetString(SR.FilterCouldNotCompile, expression))); } OpcodeBlock codeBlock = new OpcodeBlock(); SingleFxEngineResultOpcode op; if (!match) { op = new QuerySingleFxEngineResultOpcode(); } else { op = new MatchSingleFxEngineResultOpcode(); } op.XPath = xpathExpr; op.Item = item; codeBlock.Append(op); return codeBlock; } /// /// Compile the given filter for evaluation using the internal engine. /// /// Caller customizes optimizations via the flags parameter /// Every xpath expression has a return type /// The opcode block we execute to evaluate internal static OpcodeBlock CompileForInternalEngine(XPathMessageFilter filter, QueryCompilerFlags flags, IFunctionLibrary[] functionLibs, out ValueDataType returnType) { return QueryMatcher.CompileForInternalEngine(filter.XPath.Trim(), filter.namespaces, flags, functionLibs, out returnType); } internal static OpcodeBlock CompileForInternalEngine(string xpath, XmlNamespaceManager nsManager, QueryCompilerFlags flags, IFunctionLibrary[] functionLibs, out ValueDataType returnType) { OpcodeBlock codeBlock; returnType = ValueDataType.None; if (0 == xpath.Length) { // 0 length XPaths always match codeBlock = new OpcodeBlock(); codeBlock.Append(new PushBooleanOpcode(true)); // Always match by pushing true on the eval stack } else { // Try to parse the xpath. Bind to default function libraries // The parser returns an expression tree XPathParser parser = new XPathParser(xpath, nsManager, functionLibs); XPathExpr parseTree = parser.Parse(); if (null == parseTree) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new QueryCompileException(QueryCompileError.CouldNotParseExpression)); } returnType = parseTree.ReturnType; // Compile the expression tree XPathCompiler compiler = new XPathCompiler(flags); codeBlock = compiler.Compile(parseTree); } return codeBlock; } internal static OpcodeBlock CompileForInternalEngine(string xpath, XmlNamespaceManager ns, QueryCompilerFlags flags, out ValueDataType returnType) { return QueryMatcher.CompileForInternalEngine(xpath, ns, flags, QueryMatcher.defaultFunctionLibs, out returnType); } internal SeekableXPathNavigator CreateMessageNavigator(Message message, bool matchBody) { SeekableXPathNavigator nav = message.GetNavigator(matchBody, this.maxNodes); // Position the navigator at the root element // This allows a caller to run relative XPaths on message nav.MoveToRoot(); return nav; } /// /// Checks the context pool for a generic navigator first. If none is available, creates a new one /// internal SeekableXPathNavigator CreateSeekableNavigator(XPathNavigator navigator) { return new GenericSeekableNavigator(navigator); } internal SeekableXPathNavigator CreateSafeNavigator(SeekableXPathNavigator navigator) { INodeCounter counter = navigator as INodeCounter; if (counter != null) { counter.CounterMarker = this.maxNodes; counter.MaxCounter = this.maxNodes; } else { navigator = new SafeSeekableNavigator(navigator, this.maxNodes); } return navigator; } /// /// Checks the context pool for a processor first. If none is available, creates a new one /// internal QueryProcessor CreateProcessor() { QueryProcessor p = null; lock (this.processorPool) { QueryProcessorPool pool = this.processorPool.Target as QueryProcessorPool; if (null != pool) { p = pool.Pop(); } } if (null != p) { p.ClearProcessor(); } else { p = new QueryProcessor(this); } p.AddRef(); return p; } internal FilterResult Match(MessageBuffer messageBuffer, ICollection matches) { Message message = messageBuffer.CreateMessage(); FilterResult result; try { result = this.Match(message, true, matches); } finally { message.Close(); } return result; } internal FilterResult Match(Message message, bool matchBody, ICollection matches) { QueryProcessor processor = this.CreateProcessor(); processor.MatchSet = matches; processor.EnsureFilterCollection(); try { processor.Eval(this.query, message, matchBody); } catch (XPathNavigatorException e) { throw TraceUtility.ThrowHelperError(e.Process(this.query), message); } catch (NavigatorInvalidBodyAccessException e) { throw TraceUtility.ThrowHelperError(e.Process(this.query), message); } return new FilterResult(processor); } internal QueryResult Evaluate(MessageBuffer messageBuffer) { Message message = messageBuffer.CreateMessage(); return this.Evaluate(message, true); } internal QueryResult Evaluate(Message message, bool matchBody) { return new QueryResult(this, message, matchBody); } /// /// Execute matches over the given seekable navigator. If the navigator is not safe, wrap it with one that is /// internal FilterResult Match(SeekableXPathNavigator navigator, ICollection matches) { // If the matcher places restrictions on the # of nodes we will inspect, and the navigator passed does // not do any nodecounting itself, we must make that navigator safe by wrapping it if (this.maxNodes < int.MaxValue) { navigator = this.CreateSafeNavigator(navigator); } QueryProcessor processor = this.CreateProcessor(); processor.MatchSet = matches; processor.EnsureFilterCollection(); try { processor.Eval(this.query, navigator); } catch (XPathNavigatorException e) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(e.Process(this.query)); } catch (NavigatorInvalidBodyAccessException e) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(e.Process(this.query)); } return new FilterResult(processor); } /// /// Execute matches over the given navigator by wrapping it with a Seekable Navigator /// internal FilterResult Match(XPathNavigator navigator, ICollection matches) { SeekableXPathNavigator nav = this.CreateSeekableNavigator(navigator); return this.Match(nav, matches); } /// /// Release the given processor and place it back in the context pool /// internal void ReleaseProcessor(QueryProcessor processor) { if (!processor.ReleaseRef()) { return; } lock (this.processorPool) { QueryProcessorPool pool = this.processorPool.Target as QueryProcessorPool; if (null == pool) { pool = new QueryProcessorPool(); this.processorPool.Target = pool; } pool.Push(processor); } } internal void ReleaseResult(FilterResult result) { if (null != result.Processor) { result.Processor.MatchSet = null; this.ReleaseProcessor(result.Processor); } } /// /// Trim all pool /// internal virtual void Trim() { if (this.query != null) { this.query.Trim(); } } } internal enum XPathFilterFlags { None = 0x00, AlwaysMatch = 0x01, // filter always matches IsFxFilter = 0x02, // filter is matched using the framework engine } /// /// A matcher used to evalute single XPath expressions /// internal class XPathQueryMatcher : QueryMatcher { XPathFilterFlags flags; bool match; static PushBooleanOpcode matchAlwaysFilter; // used for compiling xpaths that always match - i.e. xpath.Length == 0 static OpcodeBlock rootFilter; // used for compiling "/" static XPathQueryMatcher() { XPathQueryMatcher.matchAlwaysFilter = new PushBooleanOpcode(true); //dummy ValueDataType returnType; XPathQueryMatcher.rootFilter = QueryMatcher.CompileForInternalEngine("/", null, QueryCompilerFlags.None, out returnType); XPathQueryMatcher.rootFilter.Append(new MatchResultOpcode()); } internal XPathQueryMatcher(bool match) : base() { this.flags = XPathFilterFlags.None; this.match = match; } #if NO internal XPathFilterMatcher(XPathFilterMatcher matcher) : base(matcher) { this.flags = matcher.flags; } #endif internal bool IsAlwaysMatch { get { return (0 != (this.flags & XPathFilterFlags.AlwaysMatch)); } } internal bool IsFxFilter { get { return (0 != (this.flags & XPathFilterFlags.IsFxFilter)); } } /// /// If the xpath is an empty string, there is nothing to compile and the filter always matches /// If not, try to compile the filter for execution within the filter engine's own query processor /// If that query processor cannot accept the filter (it doesn't fall within the class of xpaths it can handle), /// then revert to the fall-back solution - the slower Fx engine /// internal void Compile(string expression, XmlNamespaceManager namespaces) { if (null == this.query) { // Try to compile for the internal engine first try { this.CompileForInternal(expression, namespaces); } catch (QueryCompileException) { } if (null == this.query) { // Try for an external engine that might work.. this.CompileForExternal(expression, namespaces); } } } /// /// Compile this xpath to run on an external (fx) xpath engine /// internal void CompileForExternal(string xpath, XmlNamespaceManager names) { Opcode op = QueryMatcher.CompileForExternalEngine(xpath, names, null, this.match).First; this.query = op; this.flags |= XPathFilterFlags.IsFxFilter; } /// /// Compile for the internal engine with default flags /// By defalt, we compile an xpath to run stand alone, with standard optimizations /// internal void CompileForInternal(string xpath, XmlNamespaceManager names) { this.query = null; xpath = xpath.Trim(); if (0 == xpath.Length) { // Empty xpaths always match. Same for xpaths that refer to the root only // We will evaluate such filters with minimal overhead. However, we // don't want a null value for this.query, so we stick a dummy value in there this.query = XPathQueryMatcher.matchAlwaysFilter; this.flags |= (XPathFilterFlags.AlwaysMatch); } else if (1 == xpath.Length && '/' == xpath[0]) { this.query = XPathQueryMatcher.rootFilter.First; this.flags |= (XPathFilterFlags.AlwaysMatch); } else { ValueDataType returnType; OpcodeBlock codeBlock = QueryMatcher.CompileForInternalEngine(xpath, names, QueryCompilerFlags.None, out returnType); // Inject a final opcode that will place the query result on the query context // This query is now ready for execution STAND ALONE if (this.match) { codeBlock.Append(new MatchResultOpcode()); } else { codeBlock.Append(new QueryResultOpcode()); } this.query = codeBlock.First; } this.flags &= ~XPathFilterFlags.IsFxFilter; } internal FilterResult Match(MessageBuffer messageBuffer) { Message message = messageBuffer.CreateMessage(); FilterResult result; try { result = this.Match(message, true); } finally { message.Close(); } return result; } internal FilterResult Match(Message message, bool matchBody) { if (this.IsAlwaysMatch) { // No need to do any expensive query evaluation if we know that the query will always match return new FilterResult(true); } return base.Match(message, matchBody, null); } internal FilterResult Match(SeekableXPathNavigator navigator) { if (this.IsAlwaysMatch) { // No need to do any expensive query evaluation if we know that the query will always match return new FilterResult(true); } // Is it a filter that we will evaluate using the framework engine? // We can evaluate that without having to allocate a query processor if (this.IsFxFilter) { return new FilterResult(this.MatchFx(navigator)); } return base.Match(navigator, null); } internal FilterResult Match(XPathNavigator navigator) { Fx.Assert(null != this.query, ""); if (this.IsAlwaysMatch) { return new FilterResult(true); } // Is it a filter that we will evaluate using the framework engine? // We can evaluate that without having to allocate a query processor if (this.IsFxFilter) { return new FilterResult(this.MatchFx(navigator)); } return base.Match(navigator, null); } /// /// Evaluates the filter over infosets surfaced via the given navigator by using the Fx engine /// We assume that the filter was pre-compiled using the framework engine /// internal bool MatchFx(XPathNavigator navigator) { INodeCounter counter = navigator as INodeCounter; if (counter == null) { navigator = new SafeSeekableNavigator(new GenericSeekableNavigator(navigator), this.NodeQuota); } else { counter.CounterMarker = this.NodeQuota; counter.MaxCounter = this.NodeQuota; } Fx.Assert(null != this.query && OpcodeID.MatchSingleFx == this.query.ID, ""); try { return ((MatchSingleFxEngineResultOpcode)this.query).Match(navigator); } catch (XPathNavigatorException e) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(e.Process(this.query)); } catch (NavigatorInvalidBodyAccessException e) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(e.Process(this.query)); } } } internal class InverseQueryMatcher : QueryMatcher { SubExprEliminator elim; Dictionary lastLookup; bool match; internal InverseQueryMatcher(bool match) : base() { this.elim = new SubExprEliminator(); this.lastLookup = new Dictionary(); this.match = match; } internal void Add(string expression, XmlNamespaceManager names, object item, bool forceExternal) { Fx.Assert(null != item, ""); // Compile the new filter bool compiled = false; OpcodeBlock codeBlock = new OpcodeBlock(); codeBlock.Append(new NoOpOpcode(OpcodeID.QueryTree)); if (!forceExternal) { try { ValueDataType returnType = ValueDataType.None; // Try to compile and merge the compiled query into the query tree codeBlock.Append(QueryMatcher.CompileForInternalEngine(expression, names, QueryCompilerFlags.InverseQuery, out returnType)); MultipleResultOpcode opcode; if (!this.match) { opcode = new QueryMultipleResultOpcode(); } else { opcode = new MatchMultipleResultOpcode(); } opcode.AddItem(item); codeBlock.Append(opcode); compiled = true; // Perform SubExpression Elimination codeBlock = new OpcodeBlock(this.elim.Add(item, codeBlock.First)); this.subExprVars = this.elim.VariableCount; } catch (QueryCompileException) { // If the filter couldn't be compiled, we drop down to the framework engine } } if (!compiled) { codeBlock.Append(QueryMatcher.CompileForExternalEngine(expression, names, item, this.match)); } // Merge the compiled query into the query tree QueryTreeBuilder builder = new QueryTreeBuilder(); this.query = builder.Build(this.query, codeBlock); // To de-merge this filter from the tree, we'll have to walk backwards up the tree... so we // have to remember the last opcode that is executed on behalf of this filter this.lastLookup[item] = builder.LastOpcode; } internal void Clear() { foreach (object item in this.lastLookup.Keys) { this.Remove(this.lastLookup[item], item); this.elim.Remove(item); } this.subExprVars = this.elim.VariableCount; this.lastLookup.Clear(); } internal void Remove(object item) { Fx.Assert(this.lastLookup.ContainsKey(item), ""); this.Remove(this.lastLookup[item], item); this.lastLookup.Remove(item); // Remove filter from subexpr eliminator this.elim.Remove(item); this.subExprVars = this.elim.VariableCount; } void Remove(Opcode opcode, object item) { MultipleResultOpcode multiOpcode = opcode as MultipleResultOpcode; if (multiOpcode != null) { multiOpcode.RemoveItem(item); } else { opcode.Remove(); } } internal override void Trim() { base.Trim(); elim.Trim(); } } }