You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@qpid.apache.org by tr...@apache.org on 2017/09/25 19:31:34 UTC

qpid-dispatch git commit: DISPATCH-813: add address pattern matching to link routes This closes #201

Repository: qpid-dispatch
Updated Branches:
  refs/heads/master 71b3b7dfd -> 1bc362f26


DISPATCH-813: add address pattern matching to link routes
This closes #201

(cherry picked from commit 2f70cd685c382b9f51e119d19edcbc925a0c137b)


Project: http://git-wip-us.apache.org/repos/asf/qpid-dispatch/repo
Commit: http://git-wip-us.apache.org/repos/asf/qpid-dispatch/commit/1bc362f2
Tree: http://git-wip-us.apache.org/repos/asf/qpid-dispatch/tree/1bc362f2
Diff: http://git-wip-us.apache.org/repos/asf/qpid-dispatch/diff/1bc362f2

Branch: refs/heads/master
Commit: 1bc362f266111fb843005dc0591b4cafa8dc8c52
Parents: 71b3b7d
Author: Kenneth Giusti <kg...@apache.org>
Authored: Mon Sep 11 14:10:32 2017 -0400
Committer: Ted Ross <tr...@redhat.com>
Committed: Mon Sep 25 15:15:48 2017 -0400

----------------------------------------------------------------------
 .../dashboard/dispatch/dispatch.comService.js   |   2 +
 .../dispatch/overv/overview.controller.js       |   4 +-
 .../src/main/webapp/plugin/js/qdrOverview.js    |   2 +
 console/stand-alone/plugin/js/qdrOverview.js    |   2 +
 console/stand-alone/plugin/js/qdrService.js     |   4 +-
 python/qpid_dispatch/management/qdrouter.json   |  12 +-
 python/qpid_dispatch_internal/router/engine.py  |   4 +-
 src/parse_tree.c                                |  15 +-
 src/parse_tree.h                                |  18 +-
 src/router_config.c                             |  26 ++-
 src/router_core/agent_config_address.c          |   1 -
 src/router_core/agent_config_link_route.c       |  40 +++-
 src/router_core/agent_config_link_route.h       |   2 +-
 src/router_core/connections.c                   |  24 +--
 src/router_core/route_control.c                 | 199 +++++++++++++++---
 src/router_core/route_control.h                 |   4 +
 src/router_core/route_tables.c                  |  19 +-
 src/router_core/router_core.c                   |  24 ++-
 src/router_core/router_core_private.h           |  20 +-
 tests/parse_tree_tests.c                        |   8 +-
 tests/system_tests_link_routes.py               | 205 ++++++++++++++-----
 tools/qdstat                                    |  12 +-
 22 files changed, 504 insertions(+), 143 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/qpid-dispatch/blob/1bc362f2/console/dispatch-dashboard/dispatch/static/dashboard/dispatch/dispatch.comService.js
----------------------------------------------------------------------
diff --git a/console/dispatch-dashboard/dispatch/static/dashboard/dispatch/dispatch.comService.js b/console/dispatch-dashboard/dispatch/static/dashboard/dispatch/dispatch.comService.js
index bbc267e..72883ec 100644
--- a/console/dispatch-dashboard/dispatch/static/dashboard/dispatch/dispatch.comService.js
+++ b/console/dispatch-dashboard/dispatch/static/dashboard/dispatch/dispatch.comService.js
@@ -368,7 +368,9 @@ var QDR = (function(QDR) {
 	        if (addr[0] == 'A')  return "area"
 	        if (addr[0] == 'L')  return "local"
 	        if (addr[0] == 'C')  return "link-incoming"
+	        if (addr[0] == 'E')  return "link-incoming"
 	        if (addr[0] == 'D')  return "link-outgoing"
+	        if (addr[0] == 'F')  return "link-outgoing"
 	        if (addr[0] == 'T')  return "topo"
 	        return "unknown: " + addr[0]
 		},

http://git-wip-us.apache.org/repos/asf/qpid-dispatch/blob/1bc362f2/console/dispatch-dashboard/dispatch/static/dashboard/dispatch/overv/overview.controller.js
----------------------------------------------------------------------
diff --git a/console/dispatch-dashboard/dispatch/static/dashboard/dispatch/overv/overview.controller.js b/console/dispatch-dashboard/dispatch/static/dashboard/dispatch/overv/overview.controller.js
index 8ed64a1..4c24a23 100644
--- a/console/dispatch-dashboard/dispatch/static/dashboard/dispatch/overv/overview.controller.js
+++ b/console/dispatch-dashboard/dispatch/static/dashboard/dispatch/overv/overview.controller.js
@@ -586,7 +586,9 @@ var QDR = (function (QDR) {
             if (addr[0] == 'A')  return "area"
             if (addr[0] == 'L')  return "local"
             if (addr[0] == 'C')  return "link-incoming"
+            if (addr[0] == 'E')  return "link-incoming"
             if (addr[0] == 'D')  return "link-outgoing"
+            if (addr[0] == 'F')  return "link-outgoing"
             if (addr[0] == 'T')  return "topo"
             return "unknown: " + addr[0]
       }
@@ -1425,4 +1427,4 @@ var QDR = (function (QDR) {
 
   return QDR;
 
-}(QDR || {}));
\ No newline at end of file
+}(QDR || {}));

http://git-wip-us.apache.org/repos/asf/qpid-dispatch/blob/1bc362f2/console/hawtio/src/main/webapp/plugin/js/qdrOverview.js
----------------------------------------------------------------------
diff --git a/console/hawtio/src/main/webapp/plugin/js/qdrOverview.js b/console/hawtio/src/main/webapp/plugin/js/qdrOverview.js
index f89b2bc..065d5d1 100644
--- a/console/hawtio/src/main/webapp/plugin/js/qdrOverview.js
+++ b/console/hawtio/src/main/webapp/plugin/js/qdrOverview.js
@@ -325,7 +325,9 @@ var QDR = (function (QDR) {
             if (addr[0] == 'A')  return "area"
             if (addr[0] == 'L')  return "local"
             if (addr[0] == 'C')  return "link-incoming"
+            if (addr[0] == 'E')  return "link-incoming"
             if (addr[0] == 'D')  return "link-outgoing"
+            if (addr[0] == 'F')  return "link-outgoing"
             if (addr[0] == 'T')  return "topo"
             return "unknown: " + addr[0]
       }

http://git-wip-us.apache.org/repos/asf/qpid-dispatch/blob/1bc362f2/console/stand-alone/plugin/js/qdrOverview.js
----------------------------------------------------------------------
diff --git a/console/stand-alone/plugin/js/qdrOverview.js b/console/stand-alone/plugin/js/qdrOverview.js
index c271a64..3370f09 100644
--- a/console/stand-alone/plugin/js/qdrOverview.js
+++ b/console/stand-alone/plugin/js/qdrOverview.js
@@ -345,7 +345,9 @@ var QDR = (function (QDR) {
             if (addr[0] == 'A')  return "area"
             if (addr[0] == 'L')  return "local"
             if (addr[0] == 'C')  return "link-incoming"
+            if (addr[0] == 'E')  return "link-incoming"
             if (addr[0] == 'D')  return "link-outgoing"
+            if (addr[0] == 'F')  return "link-outgoing"
             if (addr[0] == 'T')  return "topo"
             return "unknown: " + addr[0]
       }

http://git-wip-us.apache.org/repos/asf/qpid-dispatch/blob/1bc362f2/console/stand-alone/plugin/js/qdrService.js
----------------------------------------------------------------------
diff --git a/console/stand-alone/plugin/js/qdrService.js b/console/stand-alone/plugin/js/qdrService.js
index a57c853..a9c15e7 100644
--- a/console/stand-alone/plugin/js/qdrService.js
+++ b/console/stand-alone/plugin/js/qdrService.js
@@ -372,7 +372,9 @@ console.dump(e)
         if (addr[0] == 'A') return "area"
         if (addr[0] == 'L') return "local"
         if (addr[0] == 'C') return "link-incoming"
+        if (addr[0] == 'E') return "link-incoming"
         if (addr[0] == 'D') return "link-outgoing"
+        if (addr[0] == 'F') return "link-outgoing"
         if (addr[0] == 'T') return "topo"
         return "unknown: " + addr[0]
       },
@@ -1226,4 +1228,4 @@ if (!Array.prototype.findIndex) {
       return -1;
     }
   });
-}
\ No newline at end of file
+}

http://git-wip-us.apache.org/repos/asf/qpid-dispatch/blob/1bc362f2/python/qpid_dispatch/management/qdrouter.json
----------------------------------------------------------------------
diff --git a/python/qpid_dispatch/management/qdrouter.json b/python/qpid_dispatch/management/qdrouter.json
index b1c5220..5811982 100644
--- a/python/qpid_dispatch/management/qdrouter.json
+++ b/python/qpid_dispatch/management/qdrouter.json
@@ -989,15 +989,21 @@
         },
 
         "router.config.linkRoute": {
-            "description": "Entity type for link-route configuration.  This is used to identify remote containers that shall be destinations for routed link-attaches.  The link-routing configuration applies to an addressing space defined by a prefix.",
+            "description": "Entity type for link-route configuration.  This is used to identify remote containers that shall be destinations for routed link-attaches.  The link-routing configuration applies to an addressing space defined by a prefix or a pattern.",
             "extends": "configurationEntity",
             "operations": ["CREATE", "DELETE"],
             "attributes": {
                 "prefix": {
                     "type": "string",
-                    "description": "The address prefix for the configured settings",
+                    "description": "The address prefix for the configured settings. Cannot be used with the pattern attribute.",
                     "create": true,
-                    "required": true
+                    "required": false
+                },
+                "pattern": {
+                    "type": "string",
+                    "description": "A wildcarded pattern for address matching. Link addresses are matched against this pattern. Matching addresses use the configured settings. The pattern consists of one or more tokens separated by a forward slash '/'. A token can be one of the following: a * character, a # character, or a sequence of characters that do not include /, *, or #.  The * token matches any single token.  The # token matches zero or more tokens. * has higher precedence than #, and exact match has the highest precedence. Cannot be used with the prefix attribute.",
+                    "create": true,
+                    "required": false
                 },
                 "containerId": {
                     "type": "string",

http://git-wip-us.apache.org/repos/asf/qpid-dispatch/blob/1bc362f2/python/qpid_dispatch_internal/router/engine.py
----------------------------------------------------------------------
diff --git a/python/qpid_dispatch_internal/router/engine.py b/python/qpid_dispatch_internal/router/engine.py
index 5849d09..0f6bf74 100644
--- a/python/qpid_dispatch_internal/router/engine.py
+++ b/python/qpid_dispatch_internal/router/engine.py
@@ -95,7 +95,7 @@ class RouterEngine:
         """
         """
         try:
-            if addr[0] in 'MCD':
+            if addr[0] in 'MCDEF':
                 self.mobile_address_engine.add_local_address(addr)
         except Exception:
             self.log_ma(LOG_ERROR, "Exception in new-address processing\n%s" % format_exc(LOG_STACK_LIMIT))
@@ -104,7 +104,7 @@ class RouterEngine:
         """
         """
         try:
-            if addr[0] in 'MCD':
+            if addr[0] in 'MCDEF':
                 self.mobile_address_engine.del_local_address(addr)
         except Exception:
             self.log_ma(LOG_ERROR, "Exception in del-address processing\n%s" % format_exc(LOG_STACK_LIMIT))

http://git-wip-us.apache.org/repos/asf/qpid-dispatch/blob/1bc362f2/src/parse_tree.c
----------------------------------------------------------------------
diff --git a/src/parse_tree.c b/src/parse_tree.c
index 5560224..ac0b116 100644
--- a/src/parse_tree.c
+++ b/src/parse_tree.c
@@ -170,6 +170,7 @@ static bool normalize_pattern(char *pattern)
 //    +-->x-->y-->...
 //
 
+typedef struct qd_parse_node qd_parse_node_t;
 DEQ_DECLARE(qd_parse_node_t, qd_parse_node_list_t);
 struct qd_parse_node {
     DEQ_LINKS(qd_parse_node_t); // siblings
@@ -525,7 +526,7 @@ static void parse_node_free(qd_parse_node_t *node)
 }
 
 
-qd_parse_node_t *qd_parse_tree_new()
+qd_parse_tree_t *qd_parse_tree_new()
 {
     return new_parse_node(NULL);
 }
@@ -539,7 +540,7 @@ static bool get_first(void *handle, const char *pattern, void *payload)
     return false;
 }
 
-bool qd_parse_tree_retrieve_match(qd_parse_node_t *node,
+bool qd_parse_tree_retrieve_match(qd_parse_tree_t *node,
                                   const qd_iterator_t *value,
                                   void **payload)
 {
@@ -552,7 +553,7 @@ bool qd_parse_tree_retrieve_match(qd_parse_node_t *node,
 
 
 // Invoke callback for each pattern that matches 'value'
-void qd_parse_tree_search(qd_parse_node_t *node,
+void qd_parse_tree_search(qd_parse_tree_t *node,
                           const qd_iterator_t *value,
                           qd_parse_tree_visit_t *callback, void *handle)
 {
@@ -573,7 +574,7 @@ void qd_parse_tree_search(qd_parse_node_t *node,
 
 
 // returns old payload or NULL if new
-void *qd_parse_tree_add_pattern(qd_parse_node_t *node,
+void *qd_parse_tree_add_pattern(qd_parse_tree_t *node,
                                 const qd_iterator_t *pattern,
                                 void *payload)
 {
@@ -596,7 +597,7 @@ void *qd_parse_tree_add_pattern(qd_parse_node_t *node,
 
 
 // returns true if pattern exists in tree
-bool qd_parse_tree_get_pattern(qd_parse_node_t *node,
+bool qd_parse_tree_get_pattern(qd_parse_tree_t *node,
                                const qd_iterator_t *pattern,
                                void **payload)
 {
@@ -620,7 +621,7 @@ bool qd_parse_tree_get_pattern(qd_parse_node_t *node,
 
 
 // returns the payload void *
-void *qd_parse_tree_remove_pattern(qd_parse_node_t *node,
+void *qd_parse_tree_remove_pattern(qd_parse_tree_t *node,
                                    const qd_iterator_t *pattern)
 {
     token_iterator_t key;
@@ -641,7 +642,7 @@ void *qd_parse_tree_remove_pattern(qd_parse_node_t *node,
 }
 
 
-bool qd_parse_tree_walk(qd_parse_node_t *node, qd_parse_tree_visit_t *callback, void *handle)
+bool qd_parse_tree_walk(qd_parse_tree_t *node, qd_parse_tree_visit_t *callback, void *handle)
 {
     if (node->pattern) {  // terminal node for pattern
         if (!callback(handle, node->pattern, node->payload))

http://git-wip-us.apache.org/repos/asf/qpid-dispatch/blob/1bc362f2/src/parse_tree.h
----------------------------------------------------------------------
diff --git a/src/parse_tree.h b/src/parse_tree.h
index 2e53269..7aaed23 100644
--- a/src/parse_tree.h
+++ b/src/parse_tree.h
@@ -26,25 +26,25 @@
 #include "alloc.h"
 
 
-typedef struct qd_parse_node qd_parse_node_t;
+typedef struct qd_parse_node qd_parse_tree_t;
 extern const char * const QD_PARSE_TREE_TOKEN_SEP;
 
-qd_parse_node_t *qd_parse_tree_new();
-void qd_parse_tree_free(qd_parse_node_t *node);
+qd_parse_tree_t *qd_parse_tree_new(void);
+void qd_parse_tree_free(qd_parse_tree_t *tree);
 
 
 // returns old payload or NULL if new
-void *qd_parse_tree_add_pattern(qd_parse_node_t *node,
+void *qd_parse_tree_add_pattern(qd_parse_tree_t *node,
                                 const qd_iterator_t *pattern,
                                 void *payload);
 
 // returns old payload or NULL if not present
-void *qd_parse_tree_remove_pattern(qd_parse_node_t *node,
+void *qd_parse_tree_remove_pattern(qd_parse_tree_t *node,
                                    const qd_iterator_t *pattern);
 
 // retrieves the payload pointer
 // returns true if pattern found
-bool qd_parse_tree_get_pattern(qd_parse_node_t *node,
+bool qd_parse_tree_get_pattern(qd_parse_tree_t *tree,
                                const qd_iterator_t *pattern,
                                void **payload);
 
@@ -66,7 +66,7 @@ bool qd_parse_tree_get_pattern(qd_parse_node_t *node,
 //  'a.b' and 'a.b.c.x' will match 3
 //
 // returns true on match and sets *payload
-bool qd_parse_tree_retrieve_match(qd_parse_node_t *node,
+bool qd_parse_tree_retrieve_match(qd_parse_tree_t *tree,
                                   const qd_iterator_t *value,
                                   void **payload);
 
@@ -79,10 +79,10 @@ typedef bool qd_parse_tree_visit_t(void *handle,
 
 // visit each matching pattern that matches value in the order based on the
 // above precedence rules
-void qd_parse_tree_search(qd_parse_node_t *node, const qd_iterator_t *value,
+void qd_parse_tree_search(qd_parse_tree_t *tree, const qd_iterator_t *value,
                           qd_parse_tree_visit_t *callback, void *handle);
 
 // visit each terminal node on the tree, returns last value returned by callback
-bool qd_parse_tree_walk(qd_parse_node_t *node, qd_parse_tree_visit_t *callback, void *handle);
+bool qd_parse_tree_walk(qd_parse_tree_t *tree, qd_parse_tree_visit_t *callback, void *handle);
 
 #endif /* parse_tree.h */

http://git-wip-us.apache.org/repos/asf/qpid-dispatch/blob/1bc362f2/src/router_config.c
----------------------------------------------------------------------
diff --git a/src/router_config.c b/src/router_config.c
index 9dd1121..0bc5b95 100644
--- a/src/router_config.c
+++ b/src/router_config.c
@@ -64,7 +64,8 @@ qd_error_t qd_router_configure_address(qd_router_t *router, qd_entity_t *entity)
 
         if (prefix && pattern) {
             qd_log(router->log_source, QD_LOG_WARNING,
-                   "Cannot set both 'prefix' and 'pattern': ignoring %s, %s",
+                   "Cannot set both 'prefix' and 'pattern': ignoring"
+                   " configured address %s, %s",
                    prefix, pattern);
             break;
         } else if (!prefix && !pattern) {
@@ -137,6 +138,7 @@ qd_error_t qd_router_configure_link_route(qd_router_t *router, qd_entity_t *enti
 
     char *name      = 0;
     char *prefix    = 0;
+    char *pattern   = 0;
     char *container = 0;
     char *c_name    = 0;
     char *distrib   = 0;
@@ -144,12 +146,26 @@ qd_error_t qd_router_configure_link_route(qd_router_t *router, qd_entity_t *enti
 
     do {
         name      = qd_entity_opt_string(entity, "name", 0);         QD_ERROR_BREAK();
-        prefix    = qd_entity_get_string(entity, "prefix");          QD_ERROR_BREAK();
         container = qd_entity_opt_string(entity, "containerId", 0);  QD_ERROR_BREAK();
         c_name    = qd_entity_opt_string(entity, "connection", 0);   QD_ERROR_BREAK();
         distrib   = qd_entity_opt_string(entity, "distribution", 0); QD_ERROR_BREAK();
         dir       = qd_entity_opt_string(entity, "dir", 0);          QD_ERROR_BREAK();
 
+        prefix    = qd_entity_opt_string(entity, "prefix", 0);
+        pattern   = qd_entity_opt_string(entity, "pattern", 0);
+
+        if (prefix && pattern) {
+            qd_log(router->log_source, QD_LOG_WARNING,
+                   "Cannot set both 'prefix' and 'pattern': ignoring link route %s, %s",
+                   prefix, pattern);
+            break;
+        } else if (!prefix && !pattern) {
+            qd_log(router->log_source, QD_LOG_WARNING,
+                   "Must set either 'prefix' or 'pattern' attribute:"
+                   " ignoring link route address");
+            break;
+        }
+
         //
         // Formulate this configuration as a route and create it through the core management API.
         //
@@ -166,6 +182,11 @@ qd_error_t qd_router_configure_link_route(qd_router_t *router, qd_entity_t *enti
             qd_compose_insert_string(body, prefix);
         }
 
+        if (pattern) {
+            qd_compose_insert_string(body, "pattern");
+            qd_compose_insert_string(body, pattern);
+        }
+
         if (container) {
             qd_compose_insert_string(body, "containerId");
             qd_compose_insert_string(body, container);
@@ -198,6 +219,7 @@ qd_error_t qd_router_configure_link_route(qd_router_t *router, qd_entity_t *enti
     free(c_name);
     free(distrib);
     free(dir);
+    free(pattern);
 
     return qd_error_code();
 }

http://git-wip-us.apache.org/repos/asf/qpid-dispatch/blob/1bc362f2/src/router_core/agent_config_address.c
----------------------------------------------------------------------
diff --git a/src/router_core/agent_config_address.c b/src/router_core/agent_config_address.c
index e054c7d..3e8e11c 100644
--- a/src/router_core/agent_config_address.c
+++ b/src/router_core/agent_config_address.c
@@ -297,7 +297,6 @@ void qdra_config_address_delete_CT(qdr_core_t    *core,
     qdr_agent_enqueue_response_CT(core, query);
 }
 
-void qd_parse_tree_dump(qd_parse_node_t *node, int depth);
 
 void qdra_config_address_create_CT(qdr_core_t         *core,
                                    qd_iterator_t      *name,

http://git-wip-us.apache.org/repos/asf/qpid-dispatch/blob/1bc362f2/src/router_core/agent_config_link_route.c
----------------------------------------------------------------------
diff --git a/src/router_core/agent_config_link_route.c b/src/router_core/agent_config_link_route.c
index ab2a333..92ef02f 100644
--- a/src/router_core/agent_config_link_route.c
+++ b/src/router_core/agent_config_link_route.c
@@ -32,6 +32,7 @@
 #define QDR_CONFIG_LINK_ROUTE_CONTAINER_ID  6
 #define QDR_CONFIG_LINK_ROUTE_DIR           7
 #define QDR_CONFIG_LINK_ROUTE_OPER_STATUS   8
+#define QDR_CONFIG_LINK_ROUTE_PATTERN       9
 
 const char *qdr_config_link_route_columns[] =
     {"name",
@@ -43,6 +44,7 @@ const char *qdr_config_link_route_columns[] =
      "containerId",
      "dir",
      "operStatus",
+     "pattern",
      0};
 
 const char *CONFIG_LINKROUTE_TYPE = "org.apache.qpid.dispatch.router.config.linkRoute";
@@ -76,14 +78,24 @@ static void qdr_config_link_route_insert_column_CT(qdr_link_route_t *lr, int col
         qd_compose_insert_string(body, CONFIG_LINKROUTE_TYPE);
         break;
 
-    case QDR_CONFIG_LINK_ROUTE_PREFIX:
-        key = (const char*) qd_hash_key_by_handle(lr->addr->hash_handle);
-        if (key && (key[0] == 'C' || key[0] == 'D'))
-            qd_compose_insert_string(body, &key[1]);
+    case QDR_CONFIG_LINK_ROUTE_PATTERN:
+        if (lr->pattern && !lr->is_prefix)
+            qd_compose_insert_string(body, lr->pattern);
         else
             qd_compose_insert_null(body);
         break;
 
+    case QDR_CONFIG_LINK_ROUTE_PREFIX:
+        if (lr->pattern && lr->is_prefix) {
+            // the prefix is converted to a pattern by appending '.#' to the
+            // prefix, so strip it off
+            const size_t len = strlen(lr->pattern);
+            assert(len > 2);
+            qd_compose_insert_string_n(body, lr->pattern, len - 2);
+        } else
+            qd_compose_insert_null(body);
+        break;
+
     case QDR_CONFIG_LINK_ROUTE_DISTRIBUTION:
         switch (lr->treatment) {
         case QD_TREATMENT_LINK_BALANCED: text = "linkBalanced"; break;
@@ -375,6 +387,7 @@ void qdra_config_link_route_create_CT(qdr_core_t        *core,
         // Extract the fields from the request
         //
         qd_parsed_field_t *prefix_field     = qd_parse_value_by_key(in_body, qdr_config_link_route_columns[QDR_CONFIG_LINK_ROUTE_PREFIX]);
+        qd_parsed_field_t *pattern_field    = qd_parse_value_by_key(in_body, qdr_config_link_route_columns[QDR_CONFIG_LINK_ROUTE_PATTERN]);
         qd_parsed_field_t *distrib_field    = qd_parse_value_by_key(in_body, qdr_config_link_route_columns[QDR_CONFIG_LINK_ROUTE_DISTRIBUTION]);
         qd_parsed_field_t *connection_field = qd_parse_value_by_key(in_body, qdr_config_link_route_columns[QDR_CONFIG_LINK_ROUTE_CONNECTION]);
         qd_parsed_field_t *container_field  = qd_parse_value_by_key(in_body, qdr_config_link_route_columns[QDR_CONFIG_LINK_ROUTE_CONTAINER_ID]);
@@ -392,11 +405,21 @@ void qdra_config_link_route_create_CT(qdr_core_t        *core,
         }
 
         //
-        // Prefix and dir fields are mandatory.  Fail if they're not both here.
+        // The dir field is mandatory.
+        // Either a prefix or a pattern field is mandatory.  However prefix and pattern
+        // are mutually exclusive. Fail if either both or none are given.
         //
-        if (!prefix_field || !dir_field) {
+        const char *msg = NULL;
+        if (!dir_field) {
+            msg = "No 'dir' attribute provided - it is mandatory";
+        } else if (!prefix_field && !pattern_field) {
+            msg = "Either a 'prefix' or 'pattern' attribute must be provided";
+        } else if (prefix_field && pattern_field) {
+            msg = "Cannot specify both a 'prefix' and a 'pattern' attribute";
+        }
+        if (msg) {
             query->status = QD_AMQP_BAD_REQUEST;
-            query->status.description = "prefix and dir fields are mandatory";
+            query->status.description = msg;
             qd_log(core->agent_log, QD_LOG_ERROR, "Error performing CREATE of %s: %s", CONFIG_LINKROUTE_TYPE, query->status.description);
             break;
         }
@@ -422,7 +445,8 @@ void qdra_config_link_route_create_CT(qdr_core_t        *core,
         //
         // The request is good.  Create the entity.
         //
-        lr = qdr_route_add_link_route_CT(core, name, prefix_field, container_field, connection_field, trt, dir);
+
+        lr = qdr_route_add_link_route_CT(core, name, prefix_field, pattern_field, container_field, connection_field, trt, dir);
 
         //
         // Compose the result map for the response.

http://git-wip-us.apache.org/repos/asf/qpid-dispatch/blob/1bc362f2/src/router_core/agent_config_link_route.h
----------------------------------------------------------------------
diff --git a/src/router_core/agent_config_link_route.h b/src/router_core/agent_config_link_route.h
index 6b3d632..6824697 100644
--- a/src/router_core/agent_config_link_route.h
+++ b/src/router_core/agent_config_link_route.h
@@ -33,7 +33,7 @@ void qdra_config_link_route_get_CT(qdr_core_t    *core,
                                    qdr_query_t   *query,
                                    const char    *qdr_config_link_route_columns[]);
 
-#define QDR_CONFIG_LINK_ROUTE_COLUMN_COUNT 9
+#define QDR_CONFIG_LINK_ROUTE_COLUMN_COUNT 10
 
 const char *qdr_config_link_route_columns[QDR_CONFIG_LINK_ROUTE_COLUMN_COUNT + 1];
 

http://git-wip-us.apache.org/repos/asf/qpid-dispatch/blob/1bc362f2/src/router_core/connections.c
----------------------------------------------------------------------
diff --git a/src/router_core/connections.c b/src/router_core/connections.c
index da767bd..9cb33da 100644
--- a/src/router_core/connections.c
+++ b/src/router_core/connections.c
@@ -922,17 +922,11 @@ static void qdr_link_outbound_second_attach_CT(qdr_core_t *core, qdr_link_t *lin
 }
 
 
-static char qdr_prefix_for_dir(qd_direction_t dir)
-{
-    return (dir == QD_INCOMING) ? 'C' : 'D';
-}
-
 qd_address_treatment_t qdr_treatment_for_address_CT(qdr_core_t *core, qdr_connection_t *conn, qd_iterator_t *iter, int *in_phase, int *out_phase)
 {
     qdr_address_config_t *addr = 0;
     qd_iterator_view_t old_view = qd_iterator_get_view(iter);
 
-    // @TODO(kgiusti) - why not lookup exact match in has first??
     qd_iterator_reset_view(iter, ITER_VIEW_ADDRESS_WITH_SPACE);
     if (conn && conn->tenant_space)
         qd_iterator_annotate_space(iter, conn->tenant_space, conn->tenant_space_len);
@@ -964,7 +958,7 @@ qd_address_treatment_t qdr_treatment_for_address_hash_CT(qdr_core_t *core, qd_it
 
     qd_iterator_strncpy(iter, copy, length + 1);
 
-    if (copy[0] == 'C' || copy[0] == 'D')
+    if (QDR_IS_LINK_ROUTE(copy[0]))
         //
         // Handle the link-route address case
         // TODO - put link-routes into the config table with a different prefix from 'Z'
@@ -975,8 +969,6 @@ qd_address_treatment_t qdr_treatment_for_address_hash_CT(qdr_core_t *core, qd_it
         //
         // Handle the mobile address case
         //
-
-        // @TODO(kgiusti) ask ted if tenant needs to be added first!
         qd_iterator_t *config_iter = qd_iterator_string(&copy[2], ITER_VIEW_ADDRESS_WITH_SPACE);
         qdr_address_config_t *addr = 0;
         qd_parse_tree_retrieve_match(core->addr_parse_tree, config_iter, (void **) &addr);
@@ -1065,18 +1057,16 @@ static qdr_address_t *qdr_lookup_terminus_address_CT(qdr_core_t       *core,
         //
         qd_iterator_t *dnp_address = qdr_terminus_dnp_address(terminus);
         if (dnp_address) {
-            qd_iterator_reset_view(dnp_address, ITER_VIEW_ADDRESS_HASH);
-            qd_iterator_annotate_prefix(dnp_address, qdr_prefix_for_dir(dir));
+            qd_iterator_reset_view(dnp_address, ITER_VIEW_ADDRESS_WITH_SPACE);
             if (conn->tenant_space)
                 qd_iterator_annotate_space(dnp_address, conn->tenant_space, conn->tenant_space_len);
-            qd_hash_retrieve_prefix(core->addr_hash, dnp_address, (void**) &addr);
+            qd_parse_tree_retrieve_match(core->link_route_tree[dir], dnp_address, (void**) &addr);
 
             if (addr && conn->tenant_space) {
                 //
                 // If this link is in a tenant space, translate the dnp address to
                 // the fully-qualified view
                 //
-                qd_iterator_reset_view(dnp_address, ITER_VIEW_ADDRESS_WITH_SPACE);
                 qdr_terminus_set_dnp_address_iterator(terminus, dnp_address);
             }
 
@@ -1131,11 +1121,10 @@ static qdr_address_t *qdr_lookup_terminus_address_CT(qdr_core_t       *core,
     // a link-route destination for the address.
     //
     qd_iterator_t *iter = qdr_terminus_get_address(terminus);
-    qd_iterator_reset_view(iter, ITER_VIEW_ADDRESS_HASH);
-    qd_iterator_annotate_prefix(iter, qdr_prefix_for_dir(dir));
+    qd_iterator_reset_view(iter, ITER_VIEW_ADDRESS_WITH_SPACE);
     if (conn->tenant_space)
         qd_iterator_annotate_space(iter, conn->tenant_space, conn->tenant_space_len);
-    qd_hash_retrieve_prefix(core->addr_hash, iter, (void**) &addr);
+    qd_parse_tree_retrieve_match(core->link_route_tree[dir], iter, (void**) &addr);
     if (addr) {
         *link_route = true;
 
@@ -1144,7 +1133,6 @@ static qdr_address_t *qdr_lookup_terminus_address_CT(qdr_core_t       *core,
         // the fully-qualified view
         //
         if (conn->tenant_space) {
-            qd_iterator_reset_view(iter, ITER_VIEW_ADDRESS_WITH_SPACE);
             qdr_terminus_set_address_iterator(terminus, iter);
         }
         return addr;
@@ -1158,6 +1146,7 @@ static qdr_address_t *qdr_lookup_terminus_address_CT(qdr_core_t       *core,
     int addr_phase;
     qd_address_treatment_t treat = qdr_treatment_for_address_CT(core, conn, iter, &in_phase, &out_phase);
 
+    qd_iterator_reset_view(iter, ITER_VIEW_ADDRESS_HASH);
     qd_iterator_annotate_prefix(iter, '\0'); // Cancel previous override
     addr_phase = dir == QD_INCOMING ? in_phase : out_phase;
     qd_iterator_annotate_phase(iter, (char) addr_phase + '0');
@@ -1181,7 +1170,6 @@ static qdr_address_t *qdr_lookup_terminus_address_CT(qdr_core_t       *core,
     if (qdr_terminus_is_coordinator(terminus))
         *unavailable = false;
 
-
     return addr;
 }
 

http://git-wip-us.apache.org/repos/asf/qpid-dispatch/blob/1bc362f2/src/router_core/route_control.c
----------------------------------------------------------------------
diff --git a/src/router_core/route_control.c b/src/router_core/route_control.c
index 7101ca4..fa5c7d4 100644
--- a/src/router_core/route_control.c
+++ b/src/router_core/route_control.c
@@ -113,10 +113,74 @@ static void qdr_route_log_CT(qdr_core_t *core, const char *text, const char *nam
 }
 
 
-static void qdr_link_route_activate_CT(qdr_core_t *core, qdr_link_route_t *lr, qdr_connection_t *conn)
+// true if pattern is equivalent to an old style prefix match
+// (e.g.  non-wildcard-prefix.#
+static bool qdr_link_route_pattern_is_prefix(const char *pattern)
 {
-    const char *key;
+    const int len = (int) strlen(pattern);
+    return (!strchr(pattern, '*') &&
+            (strchr(pattern, '#') == &pattern[len - 1]));
+
+}
+
+
+// convert a link route pattern to a mobile address hash string
+// e.g. "a.b.c.#" --> "Ca.b.c"; "a.*.b.#" --> "Ea.*.b.#"
+// Caller must free returned string
+static char *qdr_link_route_pattern_to_address(const char *pattern,
+                                               qd_direction_t dir)
+{
+    unsigned char *addr;
+    qd_iterator_t *iter;
+    const int len = (int) strlen(pattern);
+
+    assert(len > 0);
+    if (qdr_link_route_pattern_is_prefix(pattern)) {
+        // a pattern that is compatible with the old prefix config
+        // (i.e. only wildcard is '#' at the end), strip the trailing '#' and
+        // advertise them as 'C' or 'D' for backwards compatibility
+        iter = qd_iterator_binary(pattern, len - 1, ITER_VIEW_ADDRESS_HASH);
+        qd_iterator_annotate_prefix(iter, QDR_LINK_ROUTE_HASH(dir, true));
+    } else {
+        // a true link route pattern
+        iter = qd_iterator_string(pattern, ITER_VIEW_ADDRESS_HASH);
+        qd_iterator_annotate_prefix(iter, QDR_LINK_ROUTE_HASH(dir, false));
+    }
+    addr = qd_iterator_copy(iter);
+    qd_iterator_free(iter);
+
+    // caller must free() result
+    return (char *)addr;
+}
+
 
+// convert a link route address in hash format back to a proper pattern.
+// e.g. "Ca.b.c" --> "a.b.c.#"; "Ea.*.#.b --> "a.*.#.b"
+// Caller responsible for calling free() on returned string
+static char *qdr_address_to_link_route_pattern(qd_iterator_t *addr_hash,
+                                               qd_direction_t *dir)
+{
+    int len = qd_iterator_length(addr_hash);
+    char *pattern = malloc(len + 3);   // append ".#" if prefix
+    char *rc = 0;
+    qd_iterator_strncpy(addr_hash, pattern, len + 1);
+    qd_iterator_reset(addr_hash);
+    if (QDR_IS_LINK_ROUTE_PREFIX(pattern[0])) {
+        // old style link route prefix address.  It needs to be converted to a
+        // pattern by appending ".#"
+        strcat(pattern, ".#");
+    }
+    rc = strdup(&pattern[1]);   // skip the prefix
+    if (dir)
+        *dir = QDR_LINK_ROUTE_DIR(pattern[0]);
+    free(pattern);
+    return rc;
+}
+
+
+static void qdr_link_route_activate_CT(qdr_core_t *core, qdr_link_route_t *lr, qdr_connection_t *conn)
+{
+    char *address;
     qdr_route_log_CT(core, "Link Route Activated", lr->name, lr->identity, conn);
 
     //
@@ -126,9 +190,10 @@ static void qdr_link_route_activate_CT(qdr_core_t *core, qdr_link_route_t *lr, q
     if (lr->addr) {
         qdr_add_connection_ref(&lr->addr->conns, conn);
         if (DEQ_SIZE(lr->addr->conns) == 1) {
-            key = (const char*) qd_hash_key_by_handle(lr->addr->hash_handle);
-            if (key)
-                qdr_post_mobile_added_CT(core, key);
+            address = qdr_link_route_pattern_to_address(lr->pattern, lr->dir);
+            qd_log(core->log, QD_LOG_TRACE, "Activating link route pattern [%s]", address);
+            qdr_post_mobile_added_CT(core, address);
+            free(address);
         }
     }
 
@@ -138,8 +203,6 @@ static void qdr_link_route_activate_CT(qdr_core_t *core, qdr_link_route_t *lr, q
 
 static void qdr_link_route_deactivate_CT(qdr_core_t *core, qdr_link_route_t *lr, qdr_connection_t *conn)
 {
-    const char *key;
-
     qdr_route_log_CT(core, "Link Route Deactivated", lr->name, lr->identity, conn);
 
     //
@@ -148,9 +211,10 @@ static void qdr_link_route_deactivate_CT(qdr_core_t *core, qdr_link_route_t *lr,
     if (lr->addr) {
         qdr_del_connection_ref(&lr->addr->conns, conn);
         if (DEQ_IS_EMPTY(lr->addr->conns)) {
-            key = (const char*) qd_hash_key_by_handle(lr->addr->hash_handle);
-            if (key)
-                qdr_post_mobile_removed_CT(core, key);
+            char *address = qdr_link_route_pattern_to_address(lr->pattern, lr->dir);
+            qd_log(core->log, QD_LOG_TRACE, "Deactivating link route pattern [%s]", address);
+            qdr_post_mobile_removed_CT(core, address);
+            free(address);
         }
     }
 
@@ -205,36 +269,62 @@ static void qdr_auto_link_deactivate_CT(qdr_core_t *core, qdr_auto_link_t *al, q
 qdr_link_route_t *qdr_route_add_link_route_CT(qdr_core_t             *core,
                                               qd_iterator_t          *name,
                                               qd_parsed_field_t      *prefix_field,
+                                              qd_parsed_field_t      *pattern_field,
                                               qd_parsed_field_t      *container_field,
                                               qd_parsed_field_t      *connection_field,
                                               qd_address_treatment_t  treatment,
                                               qd_direction_t          dir)
 {
-    qdr_link_route_t *lr = new_qdr_link_route_t();
+    const bool is_prefix = !!prefix_field;
+    qd_iterator_t *iter = qd_parse_raw(is_prefix ? prefix_field : pattern_field);
+    int len = qd_iterator_length(iter);
+    char *pattern = malloc(len + 1 + (is_prefix ? 2 : 0));
+
+    qd_iterator_strncpy(iter, pattern, len + 1);
+
+    // forward compatibility hack: convert the old style prefix addresses into
+    // a proper pattern addresses by appending ".#"
+    if (is_prefix) {
+        char suffix = pattern[strlen(pattern) - 1];
+        if (suffix == '#') {
+            // already converted - do nothing
+        } else {
+            if (!strchr(QD_PARSE_TREE_TOKEN_SEP, suffix))
+                strcat(pattern, ".");  // use . for legacy
+            strcat(pattern, "#");
+        }
+    }
 
     //
     // Set up the link_route structure
     //
+    qdr_link_route_t *lr = new_qdr_link_route_t();
     ZERO(lr);
     lr->identity  = qdr_identifier(core);
     lr->name      = name ? (char*) qd_iterator_copy(name) : 0;
     lr->dir       = dir;
     lr->treatment = treatment;
+    lr->is_prefix = is_prefix;
+    lr->pattern   = pattern;
+
 
     //
-    // Find or create an address for link-attach routing
+    // Add the address to the routing hash table and map it as a pattern in the
+    // wildcard pattern parse tree
     //
-    qd_iterator_t *iter = qd_parse_raw(prefix_field);
-    qd_iterator_reset_view(iter, ITER_VIEW_ADDRESS_HASH);
-    qd_iterator_annotate_prefix(iter, dir == QD_INCOMING ? 'C' : 'D');
-
-    qd_hash_retrieve(core->addr_hash, iter, (void*) &lr->addr);
-    if (!lr->addr) {
-        lr->addr = qdr_address_CT(core, treatment);
-        DEQ_INSERT_TAIL(core->addrs, lr->addr);
-        qd_hash_insert(core->addr_hash, iter, lr->addr, &lr->addr->hash_handle);
+    {
+        char *addr_hash = qdr_link_route_pattern_to_address(lr->pattern, dir);
+        qd_iterator_t *a_iter = qd_iterator_string(addr_hash, ITER_VIEW_ALL);
+        qd_hash_retrieve(core->addr_hash, a_iter, (void*) &lr->addr);
+        if (!lr->addr) {
+            lr->addr = qdr_address_CT(core, treatment);
+            DEQ_INSERT_TAIL(core->addrs, lr->addr);
+            qd_hash_insert(core->addr_hash, a_iter, lr->addr, &lr->addr->hash_handle);
+        }
+        qdr_link_route_map_pattern_CT(core, a_iter, lr->addr);
+        qd_iterator_free(a_iter);
+        free(addr_hash);
     }
-
     lr->addr->ref_count++;
 
     //
@@ -254,6 +344,8 @@ qdr_link_route_t *qdr_route_add_link_route_CT(qdr_core_t             *core,
     // Add the link route to the core list
     //
     DEQ_INSERT_TAIL(core->link_routes, lr);
+    qd_log(core->log, QD_LOG_TRACE, "Link route %spattern added: pattern=%s name=%s",
+           is_prefix ? "prefix " : "", lr->pattern, lr->name);
 
     return lr;
 }
@@ -277,6 +369,17 @@ void qdr_route_del_link_route_CT(qdr_core_t *core, qdr_link_route_t *lr)
     }
 
     //
+    // Pull the pattern from the parse tree
+    //
+    {
+        char *addr = qdr_link_route_pattern_to_address(lr->pattern, lr->dir);
+        qd_iterator_t *iter = qd_iterator_string(addr, ITER_VIEW_ALL);
+        qdr_link_route_unmap_pattern_CT(core, iter);
+        qd_iterator_free(iter);
+        free(addr);
+    }
+
+    //
     // Disassociate the link route from its address.  Check to see if the address
     // should be removed.
     //
@@ -287,9 +390,9 @@ void qdr_route_del_link_route_CT(qdr_core_t *core, qdr_link_route_t *lr)
     //
     // Remove the link route from the core list.
     //
-    DEQ_REMOVE(core->link_routes, lr);
-    free(lr->name);
-    free_qdr_link_route_t(lr);
+    qd_log(core->log, QD_LOG_TRACE, "Link route %spattern removed: pattern=%s name=%s",
+           lr->is_prefix ? "prefix " : "", lr->pattern, lr->name);
+    qdr_core_delete_link_route(core, lr);
 }
 
 
@@ -459,3 +562,49 @@ void qdr_route_connection_closed_CT(qdr_core_t *core, qdr_connection_t *conn)
     }
 }
 
+
+void qdr_link_route_map_pattern_CT(qdr_core_t *core, qd_iterator_t *address, qdr_address_t *addr)
+{
+    qd_direction_t dir;
+    char *pattern = qdr_address_to_link_route_pattern(address, &dir);
+    qd_iterator_t *iter = qd_iterator_string(pattern, ITER_VIEW_ALL);
+
+    qdr_address_t *other_addr;
+    bool found = qd_parse_tree_get_pattern(core->link_route_tree[dir], iter, (void **)&other_addr);
+    if (!found) {
+        qd_parse_tree_add_pattern(core->link_route_tree[dir], iter, addr);
+        addr->ref_count++;
+    } else {
+        // already mapped
+        if (other_addr != addr)
+            qd_log(core->log, QD_LOG_CRITICAL, "Link route %s not correctly mapped",
+                   pattern);
+    }
+    addr->map_count++;
+
+    qd_iterator_free(iter);
+    free(pattern);
+}
+
+
+void qdr_link_route_unmap_pattern_CT(qdr_core_t *core, qd_iterator_t *address)
+{
+    qd_direction_t dir;
+    char *pattern = qdr_address_to_link_route_pattern(address, &dir);
+    qd_iterator_t *iter = qd_iterator_string(pattern, ITER_VIEW_ALL);
+    qdr_address_t *addr;
+    bool found = qd_parse_tree_get_pattern(core->link_route_tree[dir], iter, (void **)&addr);
+    if (found) {
+        assert(addr->map_count > 0);
+        addr->map_count--;
+        if (addr->map_count == 0) {
+            qd_parse_tree_remove_pattern(core->link_route_tree[dir], iter);
+            addr->ref_count--;
+        }
+    } else
+        qd_log(core->log, QD_LOG_CRITICAL, "link route pattern ummap: Pattern '%s' not found",
+               pattern);
+
+    qd_iterator_free(iter);
+    free(pattern);
+}

http://git-wip-us.apache.org/repos/asf/qpid-dispatch/blob/1bc362f2/src/router_core/route_control.h
----------------------------------------------------------------------
diff --git a/src/router_core/route_control.h b/src/router_core/route_control.h
index 39bb32c..3c715bc 100644
--- a/src/router_core/route_control.h
+++ b/src/router_core/route_control.h
@@ -24,6 +24,7 @@
 qdr_link_route_t *qdr_route_add_link_route_CT(qdr_core_t             *core,
                                               qd_iterator_t          *name,
                                               qd_parsed_field_t      *prefix_field,
+                                              qd_parsed_field_t      *pattern_field,
                                               qd_parsed_field_t      *container_field,
                                               qd_parsed_field_t      *connection_field,
                                               qd_address_treatment_t  treatment,
@@ -49,4 +50,7 @@ void qdr_route_connection_opened_CT(qdr_core_t       *core,
 
 void qdr_route_connection_closed_CT(qdr_core_t *core, qdr_connection_t *conn);
 
+void qdr_link_route_map_pattern_CT(qdr_core_t *core, qd_iterator_t *address, qdr_address_t *addr);
+void qdr_link_route_unmap_pattern_CT(qdr_core_t *core, qd_iterator_t *address);
+
 #endif

http://git-wip-us.apache.org/repos/asf/qpid-dispatch/blob/1bc362f2/src/router_core/route_tables.c
----------------------------------------------------------------------
diff --git a/src/router_core/route_tables.c b/src/router_core/route_tables.c
index 963ff65..855ec45 100644
--- a/src/router_core/route_tables.c
+++ b/src/router_core/route_tables.c
@@ -18,6 +18,7 @@
  */
 
 #include "router_core_private.h"
+#include "route_control.h"
 #include <stdio.h>
 
 static void qdr_add_router_CT        (qdr_core_t *core, qdr_action_t *action, bool discard);
@@ -220,7 +221,9 @@ void qdr_route_table_setup_CT(qdr_core_t *core)
     core->addr_hash    = qd_hash(12, 32, 0);
     core->conn_id_hash = qd_hash(6, 4, 0);
     core->cost_epoch   = 1;
-    core->addr_parse_tree = qd_parse_tree_new("/");
+    core->addr_parse_tree = qd_parse_tree_new();
+    core->link_route_tree[QD_INCOMING] = qd_parse_tree_new();
+    core->link_route_tree[QD_OUTGOING] = qd_parse_tree_new();
 
     if (core->router_mode == QD_ROUTER_MODE_INTERIOR) {
         core->hello_addr      = qdr_add_local_address_CT(core, 'L', "qdhello",     QD_TREATMENT_MULTICAST_FLOOD);
@@ -569,6 +572,8 @@ static void qdr_map_destination_CT(qdr_core_t *core, qdr_action_t *action, bool
 
         qd_iterator_t *iter = address->iterator;
         qdr_address_t *addr = 0;
+        const char prefix = (char) qd_iterator_octet(iter);
+        qd_iterator_reset(iter);
 
         qd_hash_retrieve(core->addr_hash, iter, (void**) &addr);
         if (!addr) {
@@ -578,6 +583,11 @@ static void qdr_map_destination_CT(qdr_core_t *core, qdr_action_t *action, bool
             DEQ_INSERT_TAIL(core->addrs, addr);
         }
 
+        if (QDR_IS_LINK_ROUTE(prefix)) {
+            // add the link route pattern to the wildcard address parse tree
+            qdr_link_route_map_pattern_CT(core, iter, addr);
+        }
+
         qdr_node_t *rnode = core->routers_by_mask_bit[router_maskbit];
         qd_bitmask_set_bit(addr->rnodes, router_maskbit);
         rnode->ref_count++;
@@ -613,7 +623,9 @@ static void qdr_unmap_destination_CT(qdr_core_t *core, qdr_action_t *action, boo
         qdr_node_t    *rnode = core->routers_by_mask_bit[router_maskbit];
         qd_iterator_t *iter  = address->iterator;
         qdr_address_t *addr  = 0;
+        const char prefix = (char) qd_iterator_octet(iter);
 
+        qd_iterator_reset(iter);
         qd_hash_retrieve(core->addr_hash, iter, (void**) &addr);
 
         if (!addr) {
@@ -621,6 +633,11 @@ static void qdr_unmap_destination_CT(qdr_core_t *core, qdr_action_t *action, boo
             break;
         }
 
+        if (QDR_IS_LINK_ROUTE(prefix)) {
+            // pull it from the wildcard address parse tree
+            qdr_link_route_unmap_pattern_CT(core, iter);
+        }
+
         qd_bitmask_clear_bit(addr->rnodes, router_maskbit);
         rnode->ref_count--;
         addr->cost_epoch--;

http://git-wip-us.apache.org/repos/asf/qpid-dispatch/blob/1bc362f2/src/router_core/router_core.c
----------------------------------------------------------------------
diff --git a/src/router_core/router_core.c b/src/router_core/router_core.c
index 5c1d020..3734c92 100644
--- a/src/router_core/router_core.c
+++ b/src/router_core/router_core.c
@@ -18,6 +18,7 @@
  */
 
 #include "router_core_private.h"
+#include "route_control.h"
 #include <stdio.h>
 #include <strings.h>
 
@@ -129,6 +130,11 @@ void qdr_core_free(qdr_core_t *core)
         }
     }
 
+    qdr_link_route_t *link_route = 0;
+    while ( (link_route = DEQ_HEAD(core->link_routes))) {
+        qdr_core_delete_link_route(core, link_route);
+    }
+
     qdr_address_t *addr = 0;
     while ( (addr = DEQ_HEAD(core->addrs)) ) {
         qdr_core_remove_address(core, addr);
@@ -139,6 +145,8 @@ void qdr_core_free(qdr_core_t *core)
     }
     qd_hash_free(core->addr_hash);
     qd_parse_tree_free(core->addr_parse_tree);
+    qd_parse_tree_free(core->link_route_tree[QD_INCOMING]);
+    qd_parse_tree_free(core->link_route_tree[QD_OUTGOING]);
     qd_hash_free(core->conn_id_hash);
     //TODO what about the actual connection identifier objects?
 
@@ -317,14 +325,24 @@ bool qdr_is_addr_treatment_multicast(qdr_address_t *addr)
     return false;
 }
 
+void qdr_core_delete_link_route(qdr_core_t *core, qdr_link_route_t *lr)
+{
+    DEQ_REMOVE(core->link_routes, lr);
+    free(lr->name);
+    free(lr->pattern);
+    free_qdr_link_route_t(lr);
+}
+
 void qdr_core_remove_address(qdr_core_t *core, qdr_address_t *addr)
 {
-    // Remove the address from the list and hash index
-    qd_hash_remove_by_handle(core->addr_hash, addr->hash_handle);
+    // Remove the address from the list, hash index, and parse tree
     DEQ_REMOVE(core->addrs, addr);
+    if (addr->hash_handle) {
+        qd_hash_remove_by_handle(core->addr_hash, addr->hash_handle);
+        qd_hash_handle_free(addr->hash_handle);
+    }
 
     // Free resources associated with this address
-    qd_hash_handle_free(addr->hash_handle);
     qd_bitmask_free(addr->rnodes);
     if (addr->treatment == QD_TREATMENT_ANYCAST_CLOSEST) {
         qd_bitmask_free(addr->closest_remotes);

http://git-wip-us.apache.org/repos/asf/qpid-dispatch/blob/1bc362f2/src/router_core/router_core_private.h
----------------------------------------------------------------------
diff --git a/src/router_core/router_core_private.h b/src/router_core/router_core_private.h
index 9535708..2f14b4c 100644
--- a/src/router_core/router_core_private.h
+++ b/src/router_core/router_core_private.h
@@ -445,6 +445,7 @@ struct qdr_address_t {
     qd_address_treatment_t     treatment;
     qdr_forwarder_t           *forwarder;
     int                        ref_count;     ///< Number of link-routes + auto-links referencing this address
+    int                        map_count;     ///< parse tree map/unmap operations for link route patterns
     bool                       block_deletion;
     bool                       local;
     uint32_t                   tracked_deliveries;
@@ -547,6 +548,18 @@ struct qdr_connection_t {
 ALLOC_DECLARE(qdr_connection_t);
 DEQ_DECLARE(qdr_connection_t, qdr_connection_list_t);
 
+// Address hash prefixes for link routes:
+//  'C' old style prefix address, incoming
+//  'D' old style prefix address, outgoing
+//  'E' link route pattern address, incoming
+//  'F' link route pattern address, outgoing
+#define QDR_IS_LINK_ROUTE_PREFIX(p) ((p) == 'C' || (p) == 'D')
+#define QDR_IS_LINK_ROUTE(p) ((p) == 'E' || (p) == 'F' || QDR_IS_LINK_ROUTE_PREFIX(p))
+#define QDR_LINK_ROUTE_DIR(p) (((p) == 'C' || (p) == 'E') ? QD_INCOMING : QD_OUTGOING)
+#define QDR_LINK_ROUTE_HASH(dir, is_prefix) \
+    (((dir) == QD_INCOMING)                 \
+     ? ((is_prefix) ? 'C' : 'E')            \
+     : ((is_prefix) ? 'D' : 'F'))
 
 struct qdr_link_route_t {
     DEQ_LINKS(qdr_link_route_t);
@@ -558,11 +571,13 @@ struct qdr_link_route_t {
     qdr_conn_identifier_t  *conn_id;
     qd_address_treatment_t  treatment;
     bool                    active;
+    bool                    is_prefix;
+    char                   *pattern;
 };
 
 ALLOC_DECLARE(qdr_link_route_t);
 DEQ_DECLARE(qdr_link_route_t, qdr_link_route_list_t);
-
+void qdr_core_delete_link_route(qdr_core_t *core, qdr_link_route_t *lr);
 
 typedef enum {
     QDR_AUTO_LINK_STATE_INACTIVE,
@@ -664,7 +679,8 @@ struct qdr_core_t {
     qd_hash_t                 *conn_id_hash;
     qdr_address_list_t         addrs;
     qd_hash_t                 *addr_hash;
-    qd_parse_node_t           *addr_parse_tree;
+    qd_parse_tree_t           *addr_parse_tree;
+    qd_parse_tree_t           *link_route_tree[2];   // QD_INCOMING, QD_OUTGOING
     qdr_address_t             *hello_addr;
     qdr_address_t             *router_addr_L;
     qdr_address_t             *routerma_addr_L;

http://git-wip-us.apache.org/repos/asf/qpid-dispatch/blob/1bc362f2/tests/parse_tree_tests.c
----------------------------------------------------------------------
diff --git a/tests/parse_tree_tests.c b/tests/parse_tree_tests.c
index 347f6df..f029f26 100644
--- a/tests/parse_tree_tests.c
+++ b/tests/parse_tree_tests.c
@@ -27,7 +27,7 @@ static char *test_add_remove(void *context)
 {
     qd_iterator_t *piter = qd_iterator_string("I.am.Sam", ITER_VIEW_ALL);
     qd_iterator_t *piter2 = qd_iterator_string("Sam.I.Am", ITER_VIEW_ALL);
-    qd_parse_node_t *node = qd_parse_tree_new();
+    qd_parse_tree_t *node = qd_parse_tree_new();
     void *payload;
 
     if (qd_parse_tree_remove_pattern(node, piter))
@@ -107,7 +107,7 @@ static char *check_normalize(const char *input,
     const char *patterns[1];
     void *payloads[1];
     visit_handle_t vh = {0, patterns, payloads};
-    qd_parse_node_t *node = qd_parse_tree_new();
+    qd_parse_tree_t *node = qd_parse_tree_new();
     qd_iterator_t *iter = qd_iterator_string(input, ITER_VIEW_ALL);
     void *payload;
 
@@ -172,7 +172,7 @@ static char *match_test(const char *pattern,
 {
     char *rc = NULL;
     qd_iterator_t *piter = qd_iterator_string(pattern, ITER_VIEW_ALL);
-    qd_parse_node_t *node = qd_parse_tree_new();
+    qd_parse_tree_t *node = qd_parse_tree_new();
     void *payload = (void *)"found";
 
     if (qd_parse_tree_add_pattern(node, piter, payload))
@@ -370,7 +370,7 @@ static char *test_multiple_matches(void *context)
     const char *_patterns[PCOUNT] = {NULL};
     void *_payloads[PCOUNT] = {NULL};
     visit_handle_t vh = {0, _patterns, _payloads};
-    qd_parse_node_t *node = qd_parse_tree_new();
+    qd_parse_tree_t *node = qd_parse_tree_new();
 
     // build the tree
     for (int i = 0; i < PCOUNT; i++) {

http://git-wip-us.apache.org/repos/asf/qpid-dispatch/blob/1bc362f2/tests/system_tests_link_routes.py
----------------------------------------------------------------------
diff --git a/tests/system_tests_link_routes.py b/tests/system_tests_link_routes.py
index c2a1abe..d8a1ec3 100644
--- a/tests/system_tests_link_routes.py
+++ b/tests/system_tests_link_routes.py
@@ -18,7 +18,7 @@
 #
 
 import unittest
-from time import sleep
+from time import sleep, time
 from subprocess import PIPE, STDOUT
 
 from system_test import TestCase, Qdrouterd, main_module, TIMEOUT, Process
@@ -31,24 +31,36 @@ from proton.utils import BlockingConnection, LinkDetached
 from system_tests_drain_support import DrainMessagesHandler, DrainOneMessageHandler, DrainNoMessagesHandler, DrainNoMoreMessagesHandler
 
 from qpid_dispatch.management.client import Node
+from qpid_dispatch.management.error import NotFoundStatus
 
 class LinkRouteTest(TestCase):
     """
     Tests the linkRoute property of the dispatch router.
 
-    Sets up 3 routers (one of which is acting as a broker(QDR.A)). 2 routers have linkRoute set to 'org.apache.'
+    Sets up 4 routers (two of which are acting as brokers (QDR.A, QDR.D)). The other two routers have linkRoutes
+    configured such that matching traffic will be directed to/from the 'fake' brokers.
+
     (please see configs in the setUpClass method to get a sense of how the routers and their connections are configured)
     The tests in this class send and receive messages across this network of routers to link routable addresses.
     Uses the Python Blocking API to send/receive messages. The blocking api plays neatly into the synchronous nature
     of system tests.
 
-        QDR.A acting broker
+        QDR.A acting broker #1
              +---------+         +---------+         +---------+     +-----------------+
              |         | <------ |         | <-----  |         |<----| blocking_sender |
              |  QDR.A  |         |  QDR.B  |         |  QDR.C  |     +-----------------+
              |         | ------> |         | ------> |         |     +-------------------+
              +---------+         +---------+         +---------+---->| blocking_receiver |
-                                                                     +-------------------+
+                                    ^  |                             +-------------------+
+                                    |  |
+                                    |  V
+                                 +---------+
+                                 |         |
+                                 |  QDR.D  |
+                                 |         |
+                                 +---------+
+                            QDR.D acting broker #2
+
     """
     @classmethod
     def get_router(cls, index):
@@ -72,6 +84,7 @@ class LinkRouteTest(TestCase):
         a_listener_port = cls.tester.get_port()
         b_listener_port = cls.tester.get_port()
         c_listener_port = cls.tester.get_port()
+        d_listener_port = cls.tester.get_port()
         test_tag_listener_port = cls.tester.get_port()
 
         router('A',
@@ -88,14 +101,25 @@ class LinkRouteTest(TestCase):
                    # Only inter router communication must happen on 'inter-router' connectors. This connector makes
                    # a connection from the router B's ephemeral port to c_listener_port
                    ('connector', {'name': 'routerC', 'role': 'inter-router', 'host': '0.0.0.0', 'port': c_listener_port}),
+                   # This is an on-demand connection made from QDR.B's ephemeral port to d_listener_port
+                   ('connector', {'name': 'routerD', 'role': 'route-container', 'host': '0.0.0.0', 'port': d_listener_port, 'saslMechanisms': 'ANONYMOUS'}),
 
                    #('linkRoute', {'prefix': 'org.apache', 'connection': 'broker', 'dir': 'in'}),
                    ('linkRoute', {'prefix': 'org.apache', 'containerId': 'QDR.A', 'dir': 'in'}),
                    ('linkRoute', {'prefix': 'org.apache', 'containerId': 'QDR.A', 'dir': 'out'}),
 
                    ('linkRoute', {'prefix': 'pulp.task', 'connection': 'test-tag', 'dir': 'in'}),
-                   ('linkRoute', {'prefix': 'pulp.task', 'connection': 'test-tag', 'dir': 'out'})
-                ]
+                   ('linkRoute', {'prefix': 'pulp.task', 'connection': 'test-tag', 'dir': 'out'}),
+
+                   # addresses matching pattern 'a.*.toA.#' route to QDR.A
+                   ('linkRoute', {'pattern': 'a.*.toA.#', 'containerId': 'QDR.A', 'dir': 'in'}),
+                   ('linkRoute', {'pattern': 'a.*.toA.#', 'containerId': 'QDR.A', 'dir': 'out'}),
+
+                   # addresses matching pattern 'a.*.toD.#' route to QDR.D
+                   ('linkRoute', {'pattern': 'a.*.toD.#', 'containerId': 'QDR.D', 'dir': 'in'}),
+                   ('linkRoute', {'pattern': 'a.*.toD.#', 'containerId': 'QDR.D', 'dir': 'out'})
+
+               ]
                )
         router('C',
                [
@@ -108,15 +132,28 @@ class LinkRouteTest(TestCase):
                    ('linkRoute', {'prefix': 'org.apache.', 'dir': 'out'}),
 
                    ('linkRoute', {'prefix': 'pulp.task', 'dir': 'in'}),
-                   ('linkRoute', {'prefix': 'pulp.task', 'dir': 'out'})
+                   ('linkRoute', {'prefix': 'pulp.task', 'dir': 'out'}),
+
+                   ('linkRoute', {'pattern': 'a.*.toA.#', 'dir': 'in'}),
+                   ('linkRoute', {'pattern': 'a.*.toA.#', 'dir': 'out'}),
+
+                   ('linkRoute', {'pattern': 'a.*.toD.#', 'dir': 'in'}),
+                   ('linkRoute', {'pattern': 'a.*.toD.#', 'dir': 'out'})
+
                 ]
                )
+        router('D',  # sink for QDR.D routes
+               [
+                   ('listener', {'role': 'normal', 'host': '0.0.0.0', 'port': d_listener_port, 'saslMechanisms': 'ANONYMOUS'}),
+               ])
 
-        # Wait for the routers to locate each other
+        # Wait for the routers to locate each other, and for route propagation
+        # to settle
         cls.routers[1].wait_router_connected('QDR.C')
         cls.routers[2].wait_router_connected('QDR.B')
+        cls.routers[2].wait_address("org.apache", remotes=1, delay=0.5)
 
-        # This is not a classic router network in the sense that one router is acting as a broker. We allow a little
+        # This is not a classic router network in the sense that QDR.A and D are acting as brokers. We allow a little
         # bit more time for the routers to stabilize.
         sleep(2)
 
@@ -202,16 +239,20 @@ class LinkRouteTest(TestCase):
 
     def test_bbb_qdstat_link_routes_routerB(self):
         """
-        Runs qdstat on router B to make sure that router B has two link routes, one 'in' and one 'out'
+        Runs qdstat on router B to make sure that router B has 4 link routes,
+        each having one 'in' and one 'out' entry
 
         """
         out = self.run_qdstat_linkRoute(self.routers[1].addresses[0])
+        for route in ['a.*.toA.#', 'a.*.toD.#', 'org.apache',  'pulp.task']:
+            self.assertTrue(route in out)
+
         out_list = out.split()
-        self.assertEqual(out_list.count('in'), 2)
-        self.assertEqual(out_list.count('out'), 2)
+        self.assertEqual(out_list.count('in'), 4)
+        self.assertEqual(out_list.count('out'), 4)
 
         parts = out.split("\n")
-        self.assertEqual(len(parts), 8)
+        self.assertEqual(len(parts), 12)
 
         out = self.run_qdstat_linkRoute(self.routers[1].addresses[0], args=['--limit=1'])
         parts = out.split("\n")
@@ -219,14 +260,15 @@ class LinkRouteTest(TestCase):
 
     def test_ccc_qdstat_link_routes_routerC(self):
         """
-        Runs qdstat on router C to make sure that router C has two link routes, one 'in' and one 'out'
+        Runs qdstat on router C to make sure that router C has 4 link routes,
+        each having one 'in' and one 'out' entry
 
         """
         out = self.run_qdstat_linkRoute(self.routers[2].addresses[0])
         out_list = out.split()
 
-        self.assertEqual(out_list.count('in'), 2)
-        self.assertEqual(out_list.count('out'), 2)
+        self.assertEqual(out_list.count('in'), 4)
+        self.assertEqual(out_list.count('out'), 4)
 
     def test_ddd_partial_link_route_match(self):
         """
@@ -357,6 +399,95 @@ class LinkRouteTest(TestCase):
 
         blocking_connection.close()
 
+    def _link_route_pattern_match(self, connect_host, include_host,
+                                  exclude_host, test_address):
+        """
+        This helper function ensures that messages sent to 'test_address' pass
+        through 'include_host', and are *not* routed to 'exclude_host'
+
+        """
+        hello_pattern = "Hello Pattern!"
+        route = 'M0' + test_address
+
+        # Connect to the two 'waypoints', ensure the route is not present on
+        # either
+
+        node_A = Node.connect(include_host, timeout=TIMEOUT)
+        node_B = Node.connect(exclude_host, timeout=TIMEOUT)
+
+        for node in [node_A, node_B]:
+            self.assertRaises(NotFoundStatus,
+                              node.read,
+                              type='org.apache.qpid.dispatch.router.address',
+                              name=route)
+
+        # Connect to 'connect_host' and send message to 'address'
+
+        blocking_connection = BlockingConnection(connect_host)
+        blocking_receiver = blocking_connection.create_receiver(address=test_address)
+        blocking_sender = blocking_connection.create_sender(address=test_address,
+                                                            options=AtMostOnce())
+        msg = Message(body=hello_pattern)
+        blocking_sender.send(msg)
+        received_message = blocking_receiver.receive()
+        self.assertEqual(hello_pattern, received_message.body)
+
+        # verify test_address is only present on include_host and not on exclude_host
+
+        self.assertRaises(NotFoundStatus,
+                          node_B.read,
+                          type='org.apache.qpid.dispatch.router.address',
+                          name=route)
+
+        self.assertEqual(1, node_A.read(type='org.apache.qpid.dispatch.router.address',
+                                        name=route).deliveriesIngress)
+        self.assertEqual(1, node_A.read(type='org.apache.qpid.dispatch.router.address',
+                                        name=route).deliveriesIngress)
+
+        # drop the connection and verify that test_address is no longer on include_host
+
+        blocking_connection.close()
+
+        timeout = time() + TIMEOUT
+        while True:
+            try:
+                node_A.read(type='org.apache.qpid.dispatch.router.address',
+                            name=route)
+                if time() > timeout:
+                    raise Exception("Expected route '%s' to expire!" % route)
+                sleep(0.1)
+            except NotFoundStatus:
+                break;
+
+        node_A.close()
+        node_B.close()
+
+    def test_link_route_pattern_match(self):
+        """
+        Verify the addresses match the proper patterns and are routed to the
+        proper 'waypoint' only
+        """
+        qdr_A = self.routers[0].addresses[0]
+        qdr_D = self.routers[3].addresses[0]
+        qdr_C = self.routers[2].addresses[0]
+
+        self._link_route_pattern_match(connect_host=qdr_C,
+                                       include_host=qdr_A,
+                                       exclude_host=qdr_D,
+                                       test_address='a.notD.toA')
+        self._link_route_pattern_match(connect_host=qdr_C,
+                                       include_host=qdr_D,
+                                       exclude_host=qdr_A,
+                                       test_address='a.notA.toD')
+        self._link_route_pattern_match(connect_host=qdr_C,
+                                       include_host=qdr_A,
+                                       exclude_host=qdr_D,
+                                       test_address='a.toD.toA.xyz')
+        self._link_route_pattern_match(connect_host=qdr_C,
+                                       include_host=qdr_D,
+                                       exclude_host=qdr_A,
+                                       test_address='a.toA.toD.abc')
+
     def test_custom_annotations_match(self):
         """
         The linkRoute on Routers C and B is set to org.apache.
@@ -438,23 +569,11 @@ class LinkRouteTest(TestCase):
         # First delete linkRoutes on QDR.B
         local_node = Node.connect(self.routers[1].addresses[0], timeout=TIMEOUT)
         result_list = local_node.query(type='org.apache.qpid.dispatch.router.config.linkRoute').results
+        self.assertEqual(8, len(result_list))
 
-        identity_1 = result_list[0][1]
-        identity_2 = result_list[1][1]
-        identity_3 = result_list[2][1]
-        identity_4 = result_list[3][1]
-
-        cmd = 'DELETE --type=linkRoute --identity=' + identity_1
-        self.run_qdmanage(cmd=cmd, address=self.routers[1].addresses[0])
-
-        cmd = 'DELETE --type=linkRoute --identity=' + identity_2
-        self.run_qdmanage(cmd=cmd, address=self.routers[1].addresses[0])
-
-        cmd = 'DELETE --type=linkRoute --identity=' + identity_3
-        self.run_qdmanage(cmd=cmd, address=self.routers[1].addresses[0])
-
-        cmd = 'DELETE --type=linkRoute --identity=' + identity_4
-        self.run_qdmanage(cmd=cmd, address=self.routers[1].addresses[0])
+        for rid in range(8):
+            cmd = 'DELETE --type=linkRoute --identity=' + result_list[rid][1]
+            self.run_qdmanage(cmd=cmd, address=self.routers[1].addresses[0])
 
         cmd = 'QUERY --type=linkRoute'
         out = self.run_qdmanage(cmd=cmd, address=self.routers[1].addresses[0])
@@ -472,23 +591,11 @@ class LinkRouteTest(TestCase):
         local_node = Node.connect(addr, timeout=TIMEOUT)
         result_list = local_node.query(type='org.apache.qpid.dispatch.router.config.linkRoute').results
 
-        identity_1 = result_list[0][1]
-        identity_2 = result_list[1][1]
-        identity_3 = result_list[2][1]
-        identity_4 = result_list[3][1]
-        identity_4 = result_list[3][1]
-
-        cmd = 'DELETE --type=linkRoute --identity=' + identity_1
-        self.run_qdmanage(cmd=cmd, address=addr)
-
-        cmd = 'DELETE --type=linkRoute --identity=' + identity_2
-        self.run_qdmanage(cmd=cmd, address=addr)
-
-        cmd = 'DELETE --type=linkRoute --identity=' + identity_3
-        self.run_qdmanage(cmd=cmd, address=addr)
-
-        cmd = 'DELETE --type=linkRoute --identity=' + identity_4
-        self.run_qdmanage(cmd=cmd, address=addr)
+        # QDR.C has 8 link routes configured, nuke 'em:
+        self.assertEqual(8, len(result_list))
+        for rid in range(8):
+            cmd = 'DELETE --type=linkRoute --identity=' + result_list[rid][1]
+            self.run_qdmanage(cmd=cmd, address=addr)
 
         cmd = 'QUERY --type=linkRoute'
         out = self.run_qdmanage(cmd=cmd, address=addr)

http://git-wip-us.apache.org/repos/asf/qpid-dispatch/blob/1bc362f2/tools/qdstat
----------------------------------------------------------------------
diff --git a/tools/qdstat b/tools/qdstat
index 790f955..2e08c2e 100755
--- a/tools/qdstat
+++ b/tools/qdstat
@@ -187,8 +187,8 @@ class BusManager(Node):
         if addr[0] == 'A' : return "area"
         if addr[0] == 'L' : return "local"
         if addr[0] == 'T' : return "topo"
-        if addr[0] == 'C' : return "link-in"
-        if addr[0] == 'D' : return "link-out"
+        if addr[0] in 'CE' : return "link-in"
+        if addr[0] in 'DF' : return "link-out"
         return "unknown: %s" % addr[0]
 
     def _addr_text(self, addr):
@@ -432,24 +432,24 @@ class BusManager(Node):
     def displayLinkRoutes(self):
         disp = Display(prefix="  ")
         heads = []
-        heads.append(Header("prefix"))
+        heads.append(Header("address"))
         heads.append(Header("dir"))
         heads.append(Header("distrib"))
         heads.append(Header("status"))
         rows = []
-        cols = ('prefix', 'dir', 'distribution', 'operStatus')
+        cols = ('prefix', 'dir', 'distribution', 'operStatus', 'pattern')
 
         link_routes = self.query('org.apache.qpid.dispatch.router.config.linkRoute', cols, limit=self.opts.limit)
 
         for link_route in link_routes:
             row = []
-            row.append(link_route.prefix)
+            row.append(link_route.prefix if link_route.prefix else link_route.pattern)
             row.append(link_route.dir)
             row.append(link_route.distribution)
             row.append(link_route.operStatus)
             rows.append(row)
         title = "Link Routes"
-        sorter = Sorter(heads, rows, 'prefix', 0, True)
+        sorter = Sorter(heads, rows, 'address', 0, True)
         dispRows = sorter.getSorted()
         disp.formattedTable(title, heads, dispRows)
 


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@qpid.apache.org
For additional commands, e-mail: commits-help@qpid.apache.org