/* | |
* 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 | |
{ | |
/// <summary> | |
/// Class to provide support for Uri query parameters which uses .Net reflection | |
/// to identify and set properties. | |
/// </summary> | |
public class URISupport | |
{ | |
/// <summary> | |
/// 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. | |
/// </summary> | |
/// <param name="uriString"> | |
/// A string that could be a Composite Uri that uses syntax not compatible | |
/// with the .NET Uri class | |
/// </param> | |
public static Uri CreateCompatibleUri(string uriString) | |
{ | |
string sanitized = uriString.Replace("://(", ":("); | |
return new Uri(sanitized); | |
} | |
/// <summary> | |
/// Parse a Uri query string of the form ?x=y&z=0 | |
/// into a map of name/value pairs. | |
/// </summary> | |
/// <param name="query">The query string to parse. This string should not contain | |
/// Uri escape characters.</param> | |
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[nameValue[0]] = nameValue[1]; | |
} | |
} | |
return map; | |
} | |
public static StringDictionary ParseParameters(Uri uri) | |
{ | |
return (uri.Query == null | |
? EmptyMap | |
: ParseQuery(StripPrefix(uri.Query, "?"))); | |
} | |
/// <summary> | |
/// 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. | |
/// </summary> | |
/// <param name="target">The object whose properties will be set.</param> | |
/// <param name="map">Map of key/value pairs.</param> | |
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)); | |
} | |
} | |
} | |
} | |
/// <summary> | |
/// 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. | |
/// </summary> | |
/// <param name="target">The object whose properties will be set.</param> | |
/// <param name="map">Map of key/value pairs.</param> | |
/// <param name="prefix">Key value prefix. This is prepended to the property name | |
/// before searching for a matching key value.</param> | |
public static void SetProperties(object target, StringDictionary map, string prefix) | |
{ | |
Type type = target.GetType(); | |
List<String> matches = new List<String>(); | |
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<String> matches = new List<String>(); | |
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.HtmlDecode(s); | |
#else | |
return Uri.UnescapeDataString(s); | |
#endif | |
} | |
public static String UrlEncode(String s) | |
{ | |
#if !NETCF | |
return HttpUtility.HtmlEncode(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(host != null && host.Length != 0) | |
{ | |
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; | |
} | |
/// <summary> | |
/// </summary> | |
/// <param name="uri"></param> | |
/// <param name="rc"></param> | |
/// <param name="ssp"></param> | |
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(); } | |
} | |
/// <summary> | |
/// </summary> | |
/// <param name="componentString"></param> | |
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; | |
} | |
} | |
} |