Author: gsim Date: Fri Oct 10 12:54:58 2014 New Revision: 1630836 URL: http://svn.apache.org/r1630836 Log: PROTON-693: Python Url class to wrap C function pni_parse_url
It was pointed out that pni_parse_url is an internal function and the interface is not suitable for public API. Rewrote the URL parser as a proper swigable C API pn_url_*. This gets rid of the need for previous swig insanity and is cleaner all round. Internally still uses the pni_parse_url parser, we can clean that up later. Added: qpid/proton/branches/examples/proton-c/include/proton/url.h qpid/proton/branches/examples/proton-c/src/url.c Modified: qpid/proton/branches/examples/proton-c/CMakeLists.txt qpid/proton/branches/examples/proton-c/bindings/perl/perl.i qpid/proton/branches/examples/proton-c/bindings/php/php.i qpid/proton/branches/examples/proton-c/bindings/python/cproton.i qpid/proton/branches/examples/proton-c/bindings/python/proton.py qpid/proton/branches/examples/proton-c/bindings/ruby/ruby.i qpid/proton/branches/examples/proton-c/include/proton/cproton.i qpid/proton/branches/examples/tests/python/proton_tests/url.py Modified: qpid/proton/branches/examples/proton-c/CMakeLists.txt URL: http://svn.apache.org/viewvc/qpid/proton/branches/examples/proton-c/CMakeLists.txt?rev=1630836&r1=1630835&r2=1630836&view=diff ============================================================================== --- qpid/proton/branches/examples/proton-c/CMakeLists.txt (original) +++ qpid/proton/branches/examples/proton-c/CMakeLists.txt Fri Oct 10 12:54:58 2014 @@ -270,6 +270,7 @@ set (qpid-proton-core src/object/iterator.c src/util.c + src/url.c src/error.c src/buffer.c src/parser.c Modified: qpid/proton/branches/examples/proton-c/bindings/perl/perl.i URL: http://svn.apache.org/viewvc/qpid/proton/branches/examples/proton-c/bindings/perl/perl.i?rev=1630836&r1=1630835&r2=1630836&view=diff ============================================================================== --- qpid/proton/branches/examples/proton-c/bindings/perl/perl.i (original) +++ qpid/proton/branches/examples/proton-c/bindings/perl/perl.i Fri Oct 10 12:54:58 2014 @@ -8,6 +8,7 @@ #include <proton/messenger.h> #include <proton/ssl.h> #include <proton/driver_extras.h> +#include <proton/url.h> %} %include <cstring.i> Modified: qpid/proton/branches/examples/proton-c/bindings/php/php.i URL: http://svn.apache.org/viewvc/qpid/proton/branches/examples/proton-c/bindings/php/php.i?rev=1630836&r1=1630835&r2=1630836&view=diff ============================================================================== --- qpid/proton/branches/examples/proton-c/bindings/php/php.i (original) +++ qpid/proton/branches/examples/proton-c/bindings/php/php.i Fri Oct 10 12:54:58 2014 @@ -29,6 +29,7 @@ %header %{ /* Include the headers needed by the code in this wrapper file */ #include <proton/types.h> +#include <proton/url.h> #include <proton/message.h> #include <proton/driver.h> #include <proton/driver_extras.h> Modified: qpid/proton/branches/examples/proton-c/bindings/python/cproton.i URL: http://svn.apache.org/viewvc/qpid/proton/branches/examples/proton-c/bindings/python/cproton.i?rev=1630836&r1=1630835&r2=1630836&view=diff ============================================================================== --- qpid/proton/branches/examples/proton-c/bindings/python/cproton.i (original) +++ qpid/proton/branches/examples/proton-c/bindings/python/cproton.i Fri Oct 10 12:54:58 2014 @@ -23,6 +23,7 @@ #include <winsock2.h> #endif #include <proton/engine.h> +#include <proton/url.h> #include <proton/message.h> #include <proton/sasl.h> #include <proton/driver.h> @@ -280,41 +281,4 @@ int pn_ssl_get_peer_hostname(pn_ssl_t *s } %} - -/** - pni_parse_url(char* url, char **scheme, char **user, char **pass, char **host, char **port, char **path) - The following type maps convert this into a python function that taks a URL string argument - and returns a list of strings [scheme, user, pass, host, port, path] - This probably could be done more neatly. -*/ - -// Typemap to copy the url string as it will be modified by parse_url -%typemap(in,noblock=1,fragment="SWIG_AsCharPtrAndSize") char *url (int res, char *t = 0, size_t n = 0, int alloc = 0) { - res = SWIG_AsCharPtrAndSize($input, &t, &n, &alloc); - if (!SWIG_IsOK(res)) { - %argument_fail(res, "char *url", $symname, $argnum); - } - $1 = %new_array(n, $*1_ltype); - memcpy($1,t,sizeof(char)*n); - if (alloc == SWIG_NEWOBJ) %delete_array(t); - $1[n-1] = 0; -} -%typemap(freearg,match="in") char *url "free($1);"; -%typemap(argout) char *url ""; - -// Typemap for char** return strings. Don't free them. -%typemap(in,numinputs=0) char **OUTSTR($*1_ltype temp = 0) "$1 = &temp;"; -%typemap(freearg,match="in") char **OUTSTR ""; -%typemap(argout,noblock=1,fragment="SWIG_FromCharPtr") char **OUTSTR { - %append_output(SWIG_FromCharPtr(*$1)); -} - -// Typemap to initialize result as empty list -%typemap(out) void "$result = PyList_New(0);"; - - -%apply char** OUTSTR {char **scheme, char **user, char **pass, char **host, char **port, char **path}; -void pni_parse_url(char* url, char **scheme, char **user, char **pass, char **host, char **port, char **path); -%ignore pni_parse_url; - %include "proton/cproton.i" Modified: qpid/proton/branches/examples/proton-c/bindings/python/proton.py URL: http://svn.apache.org/viewvc/qpid/proton/branches/examples/proton-c/bindings/python/proton.py?rev=1630836&r1=1630835&r2=1630836&view=diff ============================================================================== --- qpid/proton/branches/examples/proton-c/bindings/python/proton.py (original) +++ qpid/proton/branches/examples/proton-c/bindings/python/proton.py Fri Oct 10 12:54:58 2014 @@ -3657,114 +3657,98 @@ __all__ = [ class Url(object): - """ - Simple URL parser/constructor, handles URLs of the form: + """ + Simple URL parser/constructor, handles URLs of the form: - <scheme>://<user>:<password>@<host>:<port>/<path> + <scheme>://<user>:<password>@<host>:<port>/<path> - All components can be None if not specifeid in the URL string. + All components can be None if not specifeid in the URL string. - The port can be specified as a service name, e.g. 'amqp' in the - URL string but Url.port always gives the integer value. + The port can be specified as a service name, e.g. 'amqp' in the + URL string but Url.port always gives the integer value. + + @ivar scheme: Url scheme e.g. 'amqp' or 'amqps' + @ivar user: Username + @ivar password: Password + @ivar host: Host name, ipv6 literal or ipv4 dotted quad. + @ivar port: Integer port. + @ivar host_port: Returns host:port + """ + + AMQPS = "amqps" + AMQP = "amqp" + + class Port(int): + """An integer port number that can be constructed from a service name string""" + + def __new__(cls, value): + port = super(Url.Port, cls).__new__(cls, cls.port_int(value)) + setattr(port, 'name', str(value)) + return port + + def __eq__(self, x): return str(self) == x or int(self) == x + def __ne__(self, x): return not self == x + def __str__(self): return str(self.name) + + @staticmethod + def port_int(value): + """Convert service, an integer or a service name, into an integer port number.""" + try: + return int(value) + except ValueError: + try: + return socket.getservbyname(value) + except socket.error: + raise ValueError("Not a valid port number or service name: '%s'" % value) - @ivar scheme: Url scheme e.g. 'amqp' or 'amqps' - @ivar user: Username - @ivar password: Password - @ivar host: Host name, ipv6 literal or ipv4 dotted quad. - @ivar port: Integer port. - @ivar host_port: Returns host:port + def __init__(self, url=None, **kwargs): + """ + @param url: URL string to parse. + @param kwargs: scheme, user, password, host, port, path. + If specified, replaces corresponding part in url string. """ + if url: + self._url = pn_url_parse(str(url)) + if not self._url: raise ValueError("Invalid URL '%s'" % url) + else: + self._url = pn_url() + for k in kwargs: # Let kwargs override values parsed from url + getattr(self, k) # Check for invalid kwargs + setattr(self, k, kwargs[k]) + + class PartDescriptor(object): + def __init__(self, part): + self.getter = globals()["pn_url_%s" % part] + self.setter = globals()["pn_url_set_%s" % part] + def __get__(self, obj, type=None): return self.getter(obj._url) + def __set__(self, obj, value): return self.setter(obj._url, str(value)) + + scheme = PartDescriptor('scheme') + username = PartDescriptor('username') + password = PartDescriptor('password') + host = PartDescriptor('host') + path = PartDescriptor('path') + + @property + def port(self): + portstr = pn_url_port(self._url) + return portstr and Url.Port(portstr) + + @port.setter + def port(self, value): + if value is None: pn_url_set_port(self._url, None) + else: pn_url_set_port(self._url, str(Url.Port(value))) - AMQPS = "amqps" - AMQP = "amqp" + def __str__(self): return pn_url_str(self._url) - class Port(int): - """An integer port number that can also have an associated service name string""" + def __repr__(self): return "Url(%r)" % str(self) - def __new__(cls, value): - port = super(Url.Port, cls).__new__(cls, cls.port_int(value)) - setattr(port, 'name', str(value)) - return port - - def __eq__(self, x): return str(self) == x or int(self) == x - def __ne__(self, x): return not self == x - def __str__(self): return str(self.name) - - @staticmethod - def port_int(value): - """Convert service, an integer or a service name, into an integer port number.""" - try: - return int(value) - except ValueError: - try: - return socket.getservbyname(value) - except socket.error: - raise ValueError("Not a valid port number or service name: '%s'" % value) - - def __init__(self, url=None, **kwargs): - """ - @param url: String or Url instance to parse or copy. - @param kwargs: URL fields: scheme, user, password, host, port, path. - If specified, replaces corresponding component in url. - """ - - fields = ['scheme', 'user', 'password', 'host', 'port', 'path'] - - for f in fields: setattr(self, f, None) - for k in kwargs: getattr(self, k) # Check for invalid kwargs - - if isinstance(url, Url): # Copy from another Url instance. - self.__dict__.update(url.__dict__) - elif url is not None: # Parse from url - parts = pni_parse_url(str(url)) - if not filter(None, parts): raise ValueError("Invalid AMQP URL: '%s'" % url) - self.scheme, self.user, self.password, self.host, port, self.path = parts - if not self.host: self.host = None - self.port = port and self.Port(port) - - # Let kwargs override values previously set from url - for field in fields: - setattr(self, field, kwargs.get(field, getattr(self, field))) - - def __repr__(self): - return "Url(%r)" % str(self) - - def __str__(self): - s = "" - if self.scheme: - s += "%s://" % self.scheme - if self.user: - s += self.user - if self.password: - s += ":%s" % self.password - if self.user or self.password: - s += '@' - if self.host and ':' in self.host: - s += "[%s]" % self.host - elif self.host: - s += self.host - if self.port: - s += ":%s" % self.port - if self.path: - s += "/%s" % self.path - return s - - def __eq__(self, url): - return \ - self.scheme == url.scheme and \ - self.user == url.user and self.password == url.password and \ - self.host == url.host and self.port == url.port and \ - self.path == url.path - - def __ne__(self, url): - return not self.__eq__(url) - - def defaults(self): - """ - Fill in missing values with defaults - @return: self - """ - self.scheme = self.scheme or self.AMQP - self.host = self.host or '0.0.0.0' - self.port = self.port or self.Port(self.scheme) - return self + def defaults(self): + """ + Fill in missing values (scheme, host or port) with defaults + @return: self + """ + self.scheme = self.scheme or self.AMQP + self.host = self.host or '0.0.0.0' + self.port = self.port or self.Port(self.scheme) + return self Modified: qpid/proton/branches/examples/proton-c/bindings/ruby/ruby.i URL: http://svn.apache.org/viewvc/qpid/proton/branches/examples/proton-c/bindings/ruby/ruby.i?rev=1630836&r1=1630835&r2=1630836&view=diff ============================================================================== --- qpid/proton/branches/examples/proton-c/bindings/ruby/ruby.i (original) +++ qpid/proton/branches/examples/proton-c/bindings/ruby/ruby.i Fri Oct 10 12:54:58 2014 @@ -26,8 +26,8 @@ #include <proton/messenger.h> #include <proton/ssl.h> #include <proton/driver_extras.h> - #include <proton/types.h> +#include <proton/url.h> #include <uuid/uuid.h> %} Modified: qpid/proton/branches/examples/proton-c/include/proton/cproton.i URL: http://svn.apache.org/viewvc/qpid/proton/branches/examples/proton-c/include/proton/cproton.i?rev=1630836&r1=1630835&r2=1630836&view=diff ============================================================================== --- qpid/proton/branches/examples/proton-c/include/proton/cproton.i (original) +++ qpid/proton/branches/examples/proton-c/include/proton/cproton.i Fri Oct 10 12:54:58 2014 @@ -1394,3 +1394,6 @@ typedef unsigned long int uintptr_t; pn_delivery_t *pn_cast_pn_delivery(void *x) { return (pn_delivery_t *) x; } pn_transport_t *pn_cast_pn_transport(void *x) { return (pn_transport_t *) x; } %} + +%include "proton/url.h" + Added: qpid/proton/branches/examples/proton-c/include/proton/url.h URL: http://svn.apache.org/viewvc/qpid/proton/branches/examples/proton-c/include/proton/url.h?rev=1630836&view=auto ============================================================================== --- qpid/proton/branches/examples/proton-c/include/proton/url.h (added) +++ qpid/proton/branches/examples/proton-c/include/proton/url.h Fri Oct 10 12:54:58 2014 @@ -0,0 +1,83 @@ +#ifndef PROTON_URL_H +#define PROTON_URL_H +/* + * 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. + */ + +#include <proton/import_export.h> + +/** @file + * URL API for parsing URLs. + * + * @defgroup url URL + * @{ + */ + +/** A parsed URL */ +typedef struct pn_url_t pn_url_t; + +/** Create an empty URL */ +PN_EXTERN pn_url_t *pn_url(void); + +/** Parse a string URL as a pn_url_t. + *@param[in] url A URL string. + *@return The parsed pn_url_t or NULL if url is not a valid URL string. + */ +PN_EXTERN pn_url_t *pn_url_parse(const char *url); + +/** Free a URL */ +PN_EXTERN void pn_url_free(pn_url_t *url); + +/** Clear the contents of the URL. */ +PN_EXTERN void pn_url_clear(pn_url_t *url); + +/** Return the string form of a URL. Owned by the pn_url_t.*/ +PN_EXTERN const char *pn_url_str(pn_url_t *url); + +/** + *@name Getters for parts of the URL. + * + *Values belong to the URL. May return NULL if the value is not set. + * + *@{ + */ +PN_EXTERN const char *pn_url_scheme(pn_url_t *url); +PN_EXTERN const char *pn_url_username(pn_url_t *url); +PN_EXTERN const char *pn_url_password(pn_url_t *url); +PN_EXTERN const char *pn_url_host(pn_url_t *url); +PN_EXTERN const char *pn_url_port(pn_url_t *url); +PN_EXTERN const char *pn_url_path(pn_url_t *url); +///@} + +/** + *@name Setters for parts of the URL. + * + *Values are copied. Value can be NULL to indicate the part is not set. + * + *@{ + */ +PN_EXTERN void pn_url_set_scheme(pn_url_t *url, const char *scheme); +PN_EXTERN void pn_url_set_username(pn_url_t *url, const char *username); +PN_EXTERN void pn_url_set_password(pn_url_t *url, const char *password); +PN_EXTERN void pn_url_set_host(pn_url_t *url, const char *host); +PN_EXTERN void pn_url_set_port(pn_url_t *url, const char *port); +PN_EXTERN void pn_url_set_path(pn_url_t *url, const char *path); +///@} + +///@} +#endif Added: qpid/proton/branches/examples/proton-c/src/url.c URL: http://svn.apache.org/viewvc/qpid/proton/branches/examples/proton-c/src/url.c?rev=1630836&view=auto ============================================================================== --- qpid/proton/branches/examples/proton-c/src/url.c (added) +++ qpid/proton/branches/examples/proton-c/src/url.c Fri Oct 10 12:54:58 2014 @@ -0,0 +1,127 @@ +/* + * + * 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. + * + */ + +#include <proton/url.h> +#include <proton/util.h> +#include <stdlib.h> +#include <string.h> +#include <stdio.h> + +static char* copy(const char* str) { + if (str == NULL) return NULL; + char *str2 = (char*)malloc(strlen(str)); + if (str2) strcpy(str2, str); + return str2; +} + +struct pn_url_t { + char *scheme; + char *username; + char *password; + char *host; + char *port; + char *path; + char *str; +}; + +PN_EXTERN pn_url_t *pn_url() { + pn_url_t *url = (pn_url_t*)malloc(sizeof(pn_url_t)); + memset(url, 0, sizeof(*url)); + return url; +} + +/** Parse a string URL as a pn_url_t. + *@param[in] url A URL string. + *@return The parsed pn_url_t or NULL if url is not a valid URL string. + */ +PN_EXTERN pn_url_t *pn_url_parse(const char *str) { + if (!str || !*str) /* Empty string or NULL is illegal. */ + return NULL; + + pn_url_t *url = pn_url(); + char *str2 = copy(str); /* FIXME aconway 2014-09-19: clean up */ + pni_parse_url(str2, &url->scheme, &url->username, &url->password, &url->host, &url->port, &url->path); + url->scheme = copy(url->scheme); + url->username = copy(url->username); + url->password = copy(url->password); + url->host = (url->host && !*url->host) ? NULL : copy(url->host); + url->port = copy(url->port); + url->path = copy(url->path); + return url; +} + +/** Free a URL */ +PN_EXTERN void pn_url_free(pn_url_t *url) { + pn_url_clear(url); + free(url); +} + +/** Clear the contents of the URL. */ +PN_EXTERN void pn_url_clear(pn_url_t *url) { + pn_url_set_username(url, NULL); + pn_url_set_password(url, NULL); + pn_url_set_host(url, NULL); + pn_url_set_port(url, NULL); + pn_url_set_path(url, NULL); + free(url->str); url->str = NULL; +} + +static inline int len(const char *str) { return str ? strlen(str) : 0; } + +/** Return the string form of a URL. */ +PN_EXTERN const char *pn_url_str(pn_url_t *url) { + int size = len(url->scheme) + len(url->username) + len(url->password) + + len(url->host) + len(url->port) + len(url->path) + + len("s://u:p@[h]:p/p"); + free(url->str); + url->str = (char*)malloc(size); + if (!url->str) return NULL; + + int i = 0; + if (url->scheme) i += snprintf(url->str+i, size-i, "%s://", url->scheme); + if (url->username) i += snprintf(url->str+i, size-i, "%s", url->username); + if (url->password) i += snprintf(url->str+i, size-i, ":%s", url->password); + if (url->username || url->password) i += snprintf(url->str+i, size-i, "@"); + if (url->host) { + if (strchr(url->host, ':')) i += snprintf(url->str+i, size-i, "[%s]", url->host); + else i += snprintf(url->str+i, size-i, "%s", url->host); + } + if (url->port) i += snprintf(url->str+i, size-i, ":%s", url->port); + if (url->path) i += snprintf(url->str+i, size-i, "/%s", url->path); + return url->str; +} + +PN_EXTERN const char *pn_url_scheme(pn_url_t *url) { return url->scheme; } +PN_EXTERN const char *pn_url_username(pn_url_t *url) { return url->username; } +PN_EXTERN const char *pn_url_password(pn_url_t *url) { return url->password; } +PN_EXTERN const char *pn_url_host(pn_url_t *url) { return url->host; } +PN_EXTERN const char *pn_url_port(pn_url_t *url) { return url->port; } +PN_EXTERN const char *pn_url_path(pn_url_t *url) { return url->path; } + +#define SET(part) free(url->part); url->part = copy(part) +PN_EXTERN void pn_url_set_scheme(pn_url_t *url, const char *scheme) { SET(scheme); } +PN_EXTERN void pn_url_set_username(pn_url_t *url, const char *username) { SET(username); } +PN_EXTERN void pn_url_set_password(pn_url_t *url, const char *password) { SET(password); } +PN_EXTERN void pn_url_set_host(pn_url_t *url, const char *host) { SET(host); } +PN_EXTERN void pn_url_set_port(pn_url_t *url, const char *port) { SET(port); } +PN_EXTERN void pn_url_set_path(pn_url_t *url, const char *path) { SET(path); } + + Modified: qpid/proton/branches/examples/tests/python/proton_tests/url.py URL: http://svn.apache.org/viewvc/qpid/proton/branches/examples/tests/python/proton_tests/url.py?rev=1630836&r1=1630835&r2=1630836&view=diff ============================================================================== --- qpid/proton/branches/examples/tests/python/proton_tests/url.py (original) +++ qpid/proton/branches/examples/tests/python/proton_tests/url.py Fri Oct 10 12:54:58 2014 @@ -28,9 +28,9 @@ class UrlTest(common.Test): def assertNotEqual(self, a, b): assert a != b, "%s == %s" % (a, b) - def assertUrl(self, u, scheme, user, password, host, port, path): - self.assertEqual((u.scheme, u.user, u.password, u.host, u.port, u.path), - (scheme, user, password, host, port, path)) + def assertUrl(self, u, scheme, username, password, host, port, path): + self.assertEqual((u.scheme, u.username, u.password, u.host, u.port, u.path), + (scheme, username, password, host, port, path)) def testUrl(self): url = Url('amqp://me:secret@myhost:1234/foobar') @@ -40,7 +40,7 @@ class UrlTest(common.Test): def testDefaults(self): # Check that we allow None for scheme, port - url = Url(user='me', password='secret', host='myhost', path='foobar') + url = Url(username='me', password='secret', host='myhost', path='foobar') self.assertEqual(str(url), "me:secret@myhost/foobar") self.assertUrl(url, None, 'me', 'secret', 'myhost', None, 'foobar') @@ -97,21 +97,19 @@ class UrlTest(common.Test): def testMissing(self): self.assertUrl(Url(), None, None, None, None, None, None) self.assertUrl(Url('amqp://'), 'amqp', None, None, None, None, None) - self.assertUrl(Url('user@'), None, 'user', None, None, None, None) + self.assertUrl(Url('username@'), None, 'username', None, None, None, None) self.assertUrl(Url(':pass@'), None, '', 'pass', None, None, None) self.assertUrl(Url('host'), None, None, None, 'host', None, None) self.assertUrl(Url(':1234'), None, None, None, None, 1234, None) self.assertUrl(Url('/path'), None, None, None, None, None, 'path') - for s in ['amqp://', 'user@', ':pass@', ':1234', '/path']: + for s in ['amqp://', 'username@', ':pass@', ':1234', '/path']: self.assertEqual(s, str(Url(s))) for s, full in [ ('amqp://', 'amqp://0.0.0.0:amqp'), - ('user@', 'amqp://user@0.0.0.0:amqp'), + ('username@', 'amqp://username@0.0.0.0:amqp'), (':pass@', 'amqp://:pass@0.0.0.0:amqp'), (':1234', 'amqp://0.0.0.0:1234'), ('/path', 'amqp://0.0.0.0:amqp/path')]: self.assertEqual(str(Url(s).defaults()), full) - - self.assertRaises(ValueError, Url, '') --------------------------------------------------------------------- To unsubscribe, e-mail: commits-unsubscr...@qpid.apache.org For additional commands, e-mail: commits-h...@qpid.apache.org