/* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ using System; using System.Collections; using System.Collections.Generic; using System.Collections.Specialized; using System.Globalization; using System.Reflection; using System.Text; #if !NETCF using System.Web; #endif namespace Apache.NMS.Util { /// /// Class to provide support for Uri query parameters which uses .Net reflection /// to identify and set properties. /// public class URISupport { /// /// Given a string that could be a Composite Uri that uses syntax not compatible /// with the .NET Uri class such as an ActiveMQ failover Uri formatted as /// "failover://(tcp://localhost:61616)", the initial '://' must be changed /// to ':(' so that the Uri class doesn't attempt to parse the '(tcp:' as /// the Uri's Authority as that is not a valid host name. /// /// /// A string that could be a Composite Uri that uses syntax not compatible /// with the .NET Uri class /// public static Uri CreateCompatibleUri(string uriString) { string sanitized = uriString.Replace("://(", ":("); return new Uri(sanitized); } /// /// Parse a Uri query string of the form ?x=y&z=0 /// into a map of name/value pairs. /// /// The query string to parse. This string should not contain /// Uri escape characters. public static StringDictionary ParseQuery(String query) { StringDictionary map = new StringDictionary(); if(String.IsNullOrEmpty(query)) { return EmptyMap; } // strip the initial "?" if(query.StartsWith("?")) { query = query.Substring(1); } // split the query into parameters string[] parameters = query.Split('&'); foreach(string pair in parameters) { if(pair.Length > 0) { string[] nameValue = pair.Split('='); if(nameValue.Length != 2) { throw new NMSException(string.Format("Invalid Uri parameter: {0}", query)); } map[UrlDecode(nameValue[0])] = UrlDecode(nameValue[1]); } } return map; } public static StringDictionary ParseParameters(Uri uri) { return (uri.Query == null ? EmptyMap : ParseQuery(StripPrefix(uri.Query, "?"))); } /// /// Sets the public properties of a target object using a string map. /// This method uses .Net reflection to identify public properties of /// the target object matching the keys from the passed map. /// /// The object whose properties will be set. /// Map of key/value pairs. public static void SetProperties(object target, StringDictionary map) { Type type = target.GetType(); foreach(string key in map.Keys) { PropertyInfo prop = type.GetProperty(key, BindingFlags.FlattenHierarchy | BindingFlags.Public | BindingFlags.Instance | BindingFlags.IgnoreCase); if(null != prop) { prop.SetValue(target, Convert.ChangeType(map[key], prop.PropertyType, CultureInfo.InvariantCulture), null); } else { FieldInfo field = type.GetField(key, BindingFlags.FlattenHierarchy | BindingFlags.Public | BindingFlags.Instance | BindingFlags.IgnoreCase); if(null != field) { field.SetValue(target, Convert.ChangeType(map[key], field.FieldType, CultureInfo.InvariantCulture)); } else { throw new NMSException(string.Format("No such property or field: {0} on class: {1}", key, target.GetType().Name)); } } } } /// /// Sets the public properties of a target object using a string map. /// This method uses .Net reflection to identify public properties of /// the target object matching the keys from the passed map. /// /// The object whose properties will be set. /// Map of key/value pairs. /// Key value prefix. This is prepended to the property name /// before searching for a matching key value. public static void SetProperties(object target, StringDictionary map, string prefix) { Type type = target.GetType(); List matches = new List(); foreach(string key in map.Keys) { if(key.StartsWith(prefix, StringComparison.InvariantCultureIgnoreCase)) { string bareKey = key.Substring(prefix.Length); PropertyInfo prop = type.GetProperty(bareKey, BindingFlags.FlattenHierarchy | BindingFlags.Public | BindingFlags.Instance | BindingFlags.IgnoreCase); if(null != prop) { prop.SetValue(target, Convert.ChangeType(map[key], prop.PropertyType, CultureInfo.InvariantCulture), null); } else { FieldInfo field = type.GetField(bareKey, BindingFlags.FlattenHierarchy | BindingFlags.Public | BindingFlags.Instance | BindingFlags.IgnoreCase); if(null != field) { field.SetValue(target, Convert.ChangeType(map[key], field.FieldType, CultureInfo.InvariantCulture)); } else { throw new NMSException(string.Format("No such property or field: {0} on class: {1}", bareKey, target.GetType().Name)); } } // store for later removal. matches.Add(key); } } // Remove all the properties we set so they are used again later. foreach(string match in matches) { map.Remove(match); } } public static StringDictionary GetProperties(StringDictionary props, string prefix) { if(props == null) { throw new Exception("Properties Object was null"); } StringDictionary result = new StringDictionary(); foreach(string key in props.Keys) { if(key.StartsWith(prefix, StringComparison.InvariantCultureIgnoreCase)) { string bareKey = key.Substring(prefix.Length); String value = props[key]; result[bareKey] = value; } } return result; } public static StringDictionary ExtractProperties(StringDictionary props, string prefix) { if(props == null) { throw new Exception("Properties Object was null"); } StringDictionary result = new StringDictionary(); List matches = new List(); foreach(string key in props.Keys) { if(key.StartsWith(prefix, StringComparison.InvariantCultureIgnoreCase)) { String value = props[key]; result[key] = value; matches.Add(key); } } foreach(string match in matches) { props.Remove(match); } return result; } public static String UrlDecode(String s) { #if !NETCF return HttpUtility.UrlDecode(s); #else return Uri.UnescapeDataString(s); #endif } public static String UrlEncode(String s) { #if !NETCF return HttpUtility.UrlEncode(s); #else return Uri.EscapeUriString(s); #endif } public static String CreateQueryString(StringDictionary options) { if(options != null && options.Count > 0) { StringBuilder rc = new StringBuilder(); bool first = true; foreach(String key in options.Keys) { string value = options[key]; if(first) { first = false; } else { rc.Append("&"); } rc.Append(UrlEncode(key)); rc.Append("="); rc.Append(UrlEncode(value)); } return rc.ToString(); } else { return ""; } } public static Uri CreateRemainingUri(Uri originalUri, StringDictionary parameters) { string s = CreateQueryString(parameters); if(String.IsNullOrEmpty(s)) { s = null; } return CreateUriWithQuery(originalUri, s); } public class CompositeData { private String host; private String scheme; private String path; private Uri[] components; private StringDictionary parameters; private String fragment; public Uri[] Components { get { return components; } set { components = value; } } public String Fragment { get { return fragment; } set { fragment = value; } } public StringDictionary Parameters { get { return parameters; } set { parameters = value; } } public String Scheme { get { return scheme; } set { scheme = value; } } public String Path { get { return path; } set { path = value; } } public String Host { get { return host; } set { host = value; } } public Uri toUri() { StringBuilder sb = new StringBuilder(); if(scheme != null) { sb.Append(scheme); sb.Append(':'); } if(!string.IsNullOrEmpty(host)) { sb.Append(host); } else { sb.Append('('); for(int i = 0; i < components.Length; i++) { if(i != 0) { sb.Append(','); } sb.Append(components[i].ToString()); } sb.Append(')'); } if(path != null) { sb.Append('/'); sb.Append(path); } if(parameters.Count != 0) { sb.Append("?"); sb.Append(CreateQueryString(parameters)); } if(fragment != null) { sb.Append("#"); sb.Append(fragment); } return new Uri(sb.ToString()); } } public static String StripPrefix(String value, String prefix) { if(value.StartsWith(prefix, StringComparison.InvariantCultureIgnoreCase)) { return value.Substring(prefix.Length); } return value; } public static Uri CreateUriWithQuery(Uri uri, string query) { if(!String.IsNullOrEmpty(query) && !query.StartsWith("?")) { query = "?" + query; } if(String.IsNullOrEmpty(uri.Query)) { return new Uri(uri.OriginalString + query); } else { string originalUri = uri.OriginalString; int queryDelimPos = originalUri.LastIndexOf('?'); int compositeDelimPos = originalUri.LastIndexOf(')'); if(queryDelimPos <= compositeDelimPos) { // No Query or the Query is part of an inner Composite. return new Uri(originalUri + query); } else { // Outer Uri has a Query or not a Composite Uri with a Query string strippedUri = originalUri.Substring(0, queryDelimPos); return new Uri(strippedUri + query); } } } public static Uri RemoveQuery(Uri original) { return CreateUriWithQuery(original, null); } public static CompositeData ParseComposite(Uri uri) { CompositeData rc = new CompositeData(); rc.Scheme = uri.Scheme; // Start with original URI //String ssp = uri.Authority + uri.PathAndQuery; String ssp = uri.OriginalString; ssp = StripPrefix(ssp, rc.Scheme + ":"); ssp = StripPrefix(ssp, "//"); int lastPoundPos = ssp.LastIndexOf("#"); int lastParendPos = ssp.LastIndexOf(")"); // Only include a Fragment that's outside any Composte sections. if(lastPoundPos > lastParendPos) { rc.Fragment = ssp.Substring(lastPoundPos); ssp = ssp.Substring(0, lastPoundPos); } // Ensure any embedded URIs don't have malformed authority's by changing // them from '://(' which would cause the .NET Uri class to attempt to validate // the authority as a hostname with, ':(' which is valid. ssp = ssp.Replace("://(", ":("); // Handle the composite components ParseComposite(uri, rc, ssp); return rc; } /// /// /// /// /// private static void ParseComposite(Uri uri, CompositeData rc, String ssp) { String componentString; String parms; if(!CheckParenthesis(ssp)) { throw new NMSException(uri.ToString() + ": Not a matching number of '(' and ')' parenthesis"); } int p; int intialParen = ssp.IndexOf("("); if(intialParen >= 0) { rc.Host = ssp.Substring(0, intialParen); p = rc.Host.IndexOf("/"); if(p >= 0) { rc.Path = rc.Host.Substring(p); rc.Host = rc.Host.Substring(0, p); } p = ssp.LastIndexOf(")"); int start = intialParen + 1; int len = p - start; componentString = ssp.Substring(start, len); parms = ssp.Substring(p + 1).Trim(); } else { componentString = ssp; parms = ""; } String[] components = SplitComponents(componentString); rc.Components = new Uri[components.Length]; for(int i = 0; i < components.Length; i++) { rc.Components[i] = new Uri(components[i].Trim()); } p = parms.IndexOf("?"); if(p >= 0) { if(p > 0) { rc.Path = StripPrefix(parms.Substring(0, p), "/"); } rc.Parameters = ParseQuery(parms.Substring(p + 1)); } else { if(parms.Length > 0) { rc.Path = StripPrefix(parms, "/"); } rc.Parameters = EmptyMap; } } private static StringDictionary EmptyMap { get { return new StringDictionary(); } } /// /// /// private static String[] SplitComponents(String componentString) { ArrayList l = new ArrayList(); int last = 0; int depth = 0; char[] chars = componentString.ToCharArray(); for(int i = 0; i < chars.Length; i++) { switch(chars[i]) { case '(': depth++; break; case ')': depth--; break; case ',': if(depth == 0) { String s = componentString.Substring(last, i - last); l.Add(s); last = i + 1; } break; default: break; } } String ending = componentString.Substring(last); if(ending.Length != 0) { l.Add(ending); } String[] rc = new String[l.Count]; l.CopyTo(rc); return rc; } public static bool CheckParenthesis(String str) { bool result = true; if(str != null) { int open = 0; int closed = 0; int i = 0; while((i = str.IndexOf('(', i)) >= 0) { i++; open++; } i = 0; while((i = str.IndexOf(')', i)) >= 0) { i++; closed++; } result = (open == closed); } return result; } } }