You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@qpid.apache.org by or...@apache.org on 2018/12/18 18:23:54 UTC

[qpid-broker-j] 02/03: QPID-6948: [Broker-J] Introduce base classes for implementation of legacy management REST API and add 7.0 REST API support

This is an automated email from the ASF dual-hosted git repository.

orudyy pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/qpid-broker-j.git

commit 359b9a3fc513062aed2ef0e74edd7321e43eb76b
Author: Alex Rudyy <or...@apache.org>
AuthorDate: Tue Dec 18 17:19:59 2018 +0000

    QPID-6948: [Broker-J] Introduce base classes for implementation of legacy management REST API and add 7.0 REST API support
---
 .../AbstractLegacyConfiguredObjectController.java  | 560 +++++++++++++++++++++
 .../plugin/controller/CategoryController.java      |  78 +++
 .../controller/CategoryControllerFactory.java      |  48 ++
 .../controller/ControllerManagementResponse.java   |  82 +++
 .../controller/GenericCategoryController.java      | 266 ++++++++++
 .../controller/GenericLegacyConfiguredObject.java  | 195 +++++++
 .../plugin/controller/LegacyConfiguredObject.java  |  90 ++++
 .../LegacyConfiguredObjectToMapConverter.java      | 251 +++++++++
 .../controller/LegacyManagementController.java     |  44 ++
 .../plugin/controller/TypeController.java          |  39 ++
 .../plugin/controller/TypeControllerFactory.java   |  51 ++
 .../latest/LatestManagementControllerAdapter.java  | 489 ++++++++++++++++++
 .../v7_0/LegacyManagementController.java           |  80 +++
 .../v7_0/LegacyManagementControllerFactory.java    |  62 +++
 .../v7_0/category/ContainerController.java         |  80 +++
 .../v7_0/category/LegacyCategoryController.java    |  63 +++
 .../category/LegacyCategoryControllerFactory.java  | 154 ++++++
 ...stractLegacyConfiguredObjectControllerTest.java | 292 +++++++++++
 .../controller/GenericCategoryControllerTest.java  | 247 +++++++++
 .../GenericLegacyConfiguredObjectTest.java         | 212 ++++++++
 .../LatestManagementControllerAdapterTest.java     | 395 +++++++++++++++
 .../v7_0/LegacyManagementControllerTest.java       | 105 ++++
 .../v7_0/category/ContainerControllerTest.java     |  83 +++
 .../LegacyCategoryControllerFactoryTest.java       |  58 +++
 24 files changed, 4024 insertions(+)

diff --git a/broker-plugins/management-http/src/main/java/org/apache/qpid/server/management/plugin/controller/AbstractLegacyConfiguredObjectController.java b/broker-plugins/management-http/src/main/java/org/apache/qpid/server/management/plugin/controller/AbstractLegacyConfiguredObjectController.java
new file mode 100644
index 0000000..131beb8
--- /dev/null
+++ b/broker-plugins/management-http/src/main/java/org/apache/qpid/server/management/plugin/controller/AbstractLegacyConfiguredObjectController.java
@@ -0,0 +1,560 @@
+/*
+ *
+ * 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.
+ *
+ */
+package org.apache.qpid.server.management.plugin.controller;
+
+import static org.apache.qpid.server.management.plugin.ManagementException.createBadRequestManagementException;
+import static org.apache.qpid.server.management.plugin.ManagementException.createInternalServerErrorManagementException;
+import static org.apache.qpid.server.management.plugin.ManagementException.createNotFoundManagementException;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+import org.apache.qpid.server.management.plugin.ManagementController;
+import org.apache.qpid.server.management.plugin.ManagementException;
+import org.apache.qpid.server.management.plugin.ManagementRequest;
+import org.apache.qpid.server.management.plugin.ManagementResponse;
+import org.apache.qpid.server.management.plugin.RequestType;
+import org.apache.qpid.server.model.ConfiguredObject;
+
+public abstract class AbstractLegacyConfiguredObjectController extends AbstractManagementController
+        implements LegacyManagementController
+{
+    private final ManagementController _nextVersionManagementController;
+    private final String _modelVersion;
+
+    private final Map<String, String> _categoryNames = new HashMap<>();
+    private final Map<String, List<String>> _parents = new HashMap<>();
+    private final Map<String, List<String>> _children = new HashMap<>();
+
+    private final Map<String, CategoryController> _categoryConverters = new HashMap<>();
+    private final Map<String, Set<TypeController>> _typeControllers = new HashMap<>();
+    private volatile LegacyConfiguredObjectToMapConverter _legacyConfiguredObjectToMapConverter;
+
+    public AbstractLegacyConfiguredObjectController(final String modelVersion,
+                                                    final ManagementController nextVersionManagementController)
+    {
+        _modelVersion = modelVersion;
+        _nextVersionManagementController = nextVersionManagementController;
+    }
+
+    @Override
+    public String getVersion()
+    {
+        return _modelVersion;
+    }
+
+    @Override
+    public Collection<String> getCategories()
+    {
+        return Collections.unmodifiableCollection(_categoryNames.values());
+    }
+
+    @Override
+    public String getCategoryMapping(final String category)
+    {
+        return String.format("/api/v%s/%s/", getVersion(), category.toLowerCase());
+    }
+
+    @Override
+    public String getCategory(final ConfiguredObject<?> managedObject)
+    {
+        return _nextVersionManagementController.getCategory(managedObject);
+    }
+
+    @Override
+    public List<String> getCategoryHierarchy(final ConfiguredObject<?> root, final String categoryName)
+    {
+        return getCategoryHierarchy(getCategory(root), categoryName);
+    }
+
+    @Override
+    public ManagementController getNextVersionManagementController()
+    {
+        return _nextVersionManagementController;
+    }
+
+
+    @Override
+    public LegacyConfiguredObject createOrUpdate(final ConfiguredObject<?> root,
+                                                 final String category,
+                                                 final List<String> path,
+                                                 final Map<String, Object> attributes,
+                                                 final boolean isPost) throws ManagementException
+    {
+        return getCategoryController(category).createOrUpdate(root, path, attributes, isPost);
+    }
+
+
+    @Override
+    public Object get(final ConfiguredObject<?> root,
+                      final String category,
+                      final List<String> path,
+                      final Map<String, List<String>> parameters) throws ManagementException
+    {
+        return getCategoryController(category).get(root, path, convertQueryParameters(parameters));
+    }
+
+    @Override
+    public int delete(final ConfiguredObject<?> root,
+                      final String category,
+                      final List<String> path,
+                      final Map<String, List<String>> parameters) throws ManagementException
+    {
+        return getCategoryController(category).delete(root, path, convertQueryParameters(parameters));
+    }
+
+    @Override
+    public ManagementResponse invoke(final ConfiguredObject<?> root,
+                                     final String category,
+                                     final List<String> path,
+                                     final String operation,
+                                     final Map<String, Object> parameters,
+                                     final boolean isPost,
+                                     final boolean isSecureOrAllowedOnInsecureChannel) throws ManagementException
+    {
+        return getCategoryController(category).invoke(root, path, operation, parameters, isPost,
+                                                      isSecureOrAllowedOnInsecureChannel);
+    }
+
+    @Override
+    public Object getPreferences(final ConfiguredObject<?> root,
+                                 final String category,
+                                 final List<String> path,
+                                 final Map<String, List<String>> parameters) throws ManagementException
+    {
+        return getCategoryController(category).getPreferences(root, path, convertQueryParameters(parameters));
+    }
+
+    @Override
+    public void setPreferences(final ConfiguredObject<?> root,
+                               final String category,
+                               final List<String> path,
+                               final Object preferences,
+                               final Map<String, List<String>> parameters,
+                               final boolean isPost) throws ManagementException
+    {
+        getCategoryController(category).setPreferences(root, path, preferences, parameters, isPost);
+    }
+
+    @Override
+    public int deletePreferences(final ConfiguredObject<?> root,
+                                 final String category,
+                                 final List<String> path,
+                                 final Map<String, List<String>> parameters) throws ManagementException
+    {
+
+        return getCategoryController(category).deletePreferences(root, path, convertQueryParameters(parameters));
+    }
+
+    @Override
+    public CategoryController getCategoryController(final String category)
+    {
+        CategoryController converter = _categoryConverters.get(category.toLowerCase());
+        if (converter == null)
+        {
+            throw createInternalServerErrorManagementException(String.format("Converter for type '%s' cannot be found ",
+                                                                             category));
+        }
+        return converter;
+    }
+
+    @Override
+    public Set<TypeController> getTypeControllersByCategory(final String name)
+    {
+        Set<TypeController> typeControllers = _typeControllers.get(name.toLowerCase());
+        if (typeControllers == null)
+        {
+            return Collections.emptySet();
+        }
+        return Collections.unmodifiableSet(typeControllers);
+    }
+
+    @Override
+    public List<String> getCategoryHierarchy(final String rootCategory,
+                                             final String categoryName)
+    {
+        if (!_categoryNames.containsKey(rootCategory.toLowerCase()))
+        {
+            throw createInternalServerErrorManagementException(String.format("Unsupported root category '%s'",
+                                                                             rootCategory));
+        }
+        if (!_categoryNames.containsKey(categoryName.toLowerCase()))
+        {
+            throw createInternalServerErrorManagementException(String.format("Unsupported category '%s'",
+                                                                             categoryName));
+        }
+        final List<String> hierarchyList = new ArrayList<>();
+
+        String category = _categoryNames.get(categoryName.toLowerCase());
+        if (!category.equals(rootCategory))
+        {
+            Collection<String> parentCategories;
+
+            hierarchyList.add(category);
+
+            while (!(parentCategories = _parents.get(category)).contains(rootCategory))
+            {
+                hierarchyList.addAll(parentCategories);
+                category = parentCategories.iterator().next();
+            }
+
+            Collections.reverse(hierarchyList);
+        }
+        return Collections.unmodifiableList(hierarchyList);
+    }
+
+    @Override
+    public Collection<String> getChildrenCategories(String category)
+    {
+        List<String> children = _children.get(_categoryNames.get(category.toLowerCase()));
+        if (children == null)
+        {
+            return Collections.emptyList();
+        }
+        return Collections.unmodifiableList(children);
+    }
+
+    @Override
+    public Collection<String> getParentTypes(final String category)
+    {
+        return _parents.get(category);
+    }
+
+    public void initialize()
+    {
+        initialize(CategoryControllerFactory.findFactories(getVersion()),
+                   TypeControllerFactory.findFactories(getVersion()));
+    }
+
+    protected void initialize(final Set<CategoryControllerFactory> categoryFactories,
+                           final Set<TypeControllerFactory> typeFactories)
+    {
+        createTypeControllers(typeFactories);
+        createCategoryControllers(categoryFactories);
+        _legacyConfiguredObjectToMapConverter = new LegacyConfiguredObjectToMapConverter(this);
+    }
+
+
+    @Override
+    protected RequestType getRequestType(final ManagementRequest managementRequest) throws ManagementException
+    {
+        final List<String> path = managementRequest.getPath();
+        final String category = managementRequest.getCategory();
+        if (category == null)
+        {
+            throw createNotFoundManagementException(String.format("Category is not found for path '%s%s'",
+                                                                  getCategoryMapping(category),
+                                                                  buildPath(path)));
+        }
+
+        final List<String> hierarchy = getCategoryHierarchy(managementRequest.getRoot(), category);
+        return getManagementRequestType(managementRequest.getMethod(), category, path, hierarchy);
+    }
+
+    @Override
+    public LegacyConfiguredObject convertFromNextVersion(final LegacyConfiguredObject nextVersionObject)
+    {
+        return getCategoryController(nextVersionObject.getCategory()).convertFromNextVersion(nextVersionObject);
+    }
+
+    protected abstract Map<String, List<String>> convertQueryParameters(final Map<String, List<String>> parameters);
+
+    private void addRelationship(String parentCategory, String childCategory)
+    {
+        Collection<String> parents = _parents.computeIfAbsent(childCategory, k -> new ArrayList<>());
+        parents.add(parentCategory);
+
+        Collection<String> children = _children.computeIfAbsent(parentCategory, k -> new ArrayList<>());
+        children.add(childCategory);
+
+        _categoryNames.put(childCategory.toLowerCase(), childCategory);
+    }
+
+
+    private void createCategoryControllers(final Set<CategoryControllerFactory> factories)
+    {
+        factories.stream()
+                 .map(this::create)
+                 .flatMap(Collection::stream)
+                 .peek(this::register)
+                 .forEach(c -> {
+                     if (_categoryConverters.put(c.getCategory().toLowerCase(), c) != null)
+                     {
+                         throw new IllegalStateException(String.format(
+                                 "Category converter for category '%s' is already registered",
+                                 c.getCategory()));
+                     }
+                 });
+    }
+
+    private Set<CategoryController> create(final CategoryControllerFactory factory)
+    {
+        return factory.getSupportedCategories()
+                      .stream()
+                      .map(type -> factory.createController(type, this))
+                      .collect(Collectors.toSet());
+    }
+
+    private void register(final CategoryController converter)
+    {
+        String type = converter.getCategory();
+        for (String category : converter.getParentCategories())
+        {
+            addRelationship(category, type);
+        }
+        _categoryNames.put(type.toLowerCase(), type);
+    }
+
+    private void createTypeControllers(Set<TypeControllerFactory> typeFactories)
+    {
+        for (TypeControllerFactory factory : typeFactories)
+        {
+            TypeController controller = factory.createController(this);
+            Set<TypeController> categoryTypeConverters =
+                    _typeControllers.computeIfAbsent(factory.getCategory().toLowerCase(), k -> new HashSet<>());
+
+            categoryTypeConverters.add(controller);
+        }
+    }
+
+    protected LegacyConfiguredObjectToMapConverter getLegacyConfiguredObjectToMapConverter()
+    {
+        return _legacyConfiguredObjectToMapConverter;
+    }
+
+
+    private RequestType getManagementRequestType(final String method,
+                                                 final String category,
+                                                 final List<String> parts,
+                                                 final List<String> hierarchy)
+    {
+        if ("POST".equals(method))
+        {
+            return getPostRequestType(category, parts, hierarchy);
+        }
+        else if ("PUT".equals(method))
+        {
+            return getPutRequestType(category, parts, hierarchy);
+        }
+        else if ("GET".equals(method))
+        {
+            return getGetRequestType(category, parts, hierarchy);
+        }
+        else if ("DELETE".equals(method))
+        {
+            return getDeleteRequestType(category, parts, hierarchy);
+        }
+        else
+        {
+            throw createBadRequestManagementException(String.format("Unexpected method type '%s' for path '%s%s'",
+                                                                    method,
+                                                                    getCategoryMapping(category),
+                                                                    buildPath(parts)));
+        }
+    }
+
+    private RequestType getDeleteRequestType(final String category,
+                                             final List<String> parts,
+                                             final List<String> hierarchy)
+    {
+        if (parts.size() <= hierarchy.size())
+        {
+            return RequestType.MODEL_OBJECT;
+        }
+        else
+        {
+            if (USER_PREFERENCES.equals(parts.get(hierarchy.size())))
+            {
+                return RequestType.USER_PREFERENCES;
+            }
+        }
+        final String categoryMapping = getCategoryMapping(category);
+        final String expectedPath = buildExpectedPath(categoryMapping, hierarchy);
+        throw createBadRequestManagementException(String.format(
+                "Invalid DELETE path '%s%s'. Expected: '%s' or '%s/userpreferences[/<preference type>[/<preference name>]]'",
+                categoryMapping,
+                buildPath(parts),
+                expectedPath,
+                expectedPath));
+    }
+
+    private RequestType getGetRequestType(final String category,
+                                          final List<String> parts,
+                                          final List<String> hierarchy)
+    {
+        if (parts.size() <= hierarchy.size())
+        {
+            return RequestType.MODEL_OBJECT;
+        }
+        else
+        {
+            if (USER_PREFERENCES.equals(parts.get(hierarchy.size())))
+            {
+                return RequestType.USER_PREFERENCES;
+            }
+            else if (VISIBLE_USER_PREFERENCES.equals(parts.get(hierarchy.size())))
+            {
+                return RequestType.VISIBLE_PREFERENCES;
+            }
+            else if (parts.size() == hierarchy.size() + 1)
+            {
+                return RequestType.OPERATION;
+            }
+        }
+
+        final String categoryMapping = getCategoryMapping(category);
+        throw createBadRequestManagementException(String.format(
+                "Invalid GET path '%s%s'. Expected: '%s[/<operation name>]'",
+                categoryMapping,
+                buildPath(parts),
+                buildExpectedPath(categoryMapping, hierarchy)));
+    }
+
+    private RequestType getPutRequestType(final String category,
+                                          final List<String> parts,
+                                          final List<String> hierarchy)
+    {
+        if (parts.size() == hierarchy.size() || parts.size() == hierarchy.size() - 1)
+        {
+            return RequestType.MODEL_OBJECT;
+        }
+        else if (parts.size() > hierarchy.size() && USER_PREFERENCES.equals(parts.get(hierarchy.size())))
+        {
+            return RequestType.USER_PREFERENCES;
+        }
+        else
+        {
+            final String categoryMapping = getCategoryMapping(category);
+            throw createBadRequestManagementException(String.format("Invalid PUT path '%s%s'. Expected: '%s'",
+                                                                    categoryMapping, buildPath(parts),
+                                                                    buildExpectedPath(categoryMapping, hierarchy)));
+        }
+    }
+
+    private RequestType getPostRequestType(final String category,
+                                           final List<String> parts,
+                                           final List<String> hierarchy)
+    {
+        if (parts.size() == hierarchy.size() || parts.size() == hierarchy.size() - 1)
+        {
+            return RequestType.MODEL_OBJECT;
+        }
+        else if (parts.size() > hierarchy.size())
+        {
+            if (USER_PREFERENCES.equals(parts.get(hierarchy.size())))
+            {
+                return RequestType.USER_PREFERENCES;
+            }
+            else if (parts.size() == hierarchy.size() + 1
+                     && !VISIBLE_USER_PREFERENCES.equals(parts.get(hierarchy.size())))
+            {
+                return RequestType.OPERATION;
+            }
+        }
+
+        final String categoryMapping = getCategoryMapping(category);
+        final String expectedFullPath = buildExpectedPath(categoryMapping, hierarchy);
+        final String expectedParentPath =
+                buildExpectedPath(categoryMapping, hierarchy.subList(0, hierarchy.size() - 1));
+
+        throw createBadRequestManagementException(String.format(
+                "Invalid POST path '%s%s'. Expected: '%s/<operation name>'"
+                + " or '%s'"
+                + " or '%s/userpreferences[/<preference type>]'",
+                categoryMapping,
+                buildPath(parts),
+                expectedFullPath,
+                expectedParentPath,
+                expectedFullPath));
+    }
+
+    private String buildExpectedPath(final String servletPath, final List<String> hierarchy)
+    {
+        return hierarchy.stream()
+                        .map(h -> String.format("/<%s name>", h))
+                        .collect((Collectors.joining("", servletPath, "")));
+    }
+
+    private String buildPath(final List<String> path)
+    {
+        return path.isEmpty() ? "" : "/" + String.join("/", path);
+    }
+
+    protected Object formatConfiguredObject(final Object content,
+                                            final boolean isSecureOrAllowedOnInsecureChannel,
+                                            final int depth,
+                                            final int oversizeThreshold,
+                                            final boolean actuals,
+                                            final boolean excludeInheritedContext, final boolean responseAsList)
+    {
+        if (content instanceof LegacyConfiguredObject)
+        {
+            Object object = convertObject(
+                    (LegacyConfiguredObject) content,
+                    depth,
+                    actuals,
+                    oversizeThreshold,
+                    isSecureOrAllowedOnInsecureChannel,
+                    excludeInheritedContext);
+            return responseAsList ? Collections.singletonList(object) : object;
+        }
+        else if (content instanceof Collection)
+        {
+            Collection<Map<String, Object>> results = ((Collection<?>) content).stream()
+                                                                               .filter(o -> o instanceof LegacyConfiguredObject)
+                                                                               .map(LegacyConfiguredObject.class::cast)
+                                                                               .map(o -> convertObject(
+                                                                                       o,
+                                                                                       depth,
+                                                                                       actuals,
+                                                                                       oversizeThreshold,
+                                                                                       isSecureOrAllowedOnInsecureChannel,
+                                                                                       excludeInheritedContext))
+                                                                               .collect(Collectors.toSet());
+            if (!results.isEmpty())
+            {
+                return results;
+            }
+        }
+        return content;
+    }
+
+    protected Map<String, Object> convertObject(final LegacyConfiguredObject legacyConfiguredObjectObject,
+                                                final int depth,
+                                                final boolean actuals,
+                                                final int oversizeThreshold,
+                                                final boolean isSecureOrConfidentialOperationAllowedOnInsecureChannel,
+                                                final boolean excludeInheritedContext)
+    {
+        return getLegacyConfiguredObjectToMapConverter().convertManageableToMap(legacyConfiguredObjectObject,
+                                                                                depth,
+                                                                                actuals,
+                                                                                oversizeThreshold,
+                                                                                excludeInheritedContext);
+    }
+}
diff --git a/broker-plugins/management-http/src/main/java/org/apache/qpid/server/management/plugin/controller/CategoryController.java b/broker-plugins/management-http/src/main/java/org/apache/qpid/server/management/plugin/controller/CategoryController.java
new file mode 100644
index 0000000..9e2c011
--- /dev/null
+++ b/broker-plugins/management-http/src/main/java/org/apache/qpid/server/management/plugin/controller/CategoryController.java
@@ -0,0 +1,78 @@
+/*
+ *
+ * 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.
+ *
+ */
+package org.apache.qpid.server.management.plugin.controller;
+
+import java.util.List;
+import java.util.Map;
+
+import org.apache.qpid.server.management.plugin.ManagementResponse;
+import org.apache.qpid.server.management.plugin.ManagementController;
+import org.apache.qpid.server.management.plugin.ManagementException;
+import org.apache.qpid.server.model.ConfiguredObject;
+
+public interface CategoryController
+{
+    String getCategory();
+
+    String getNextVersionCategory();
+
+    String getDefaultType();
+
+    String[] getParentCategories();
+
+    LegacyManagementController getManagementController();
+
+    Object get(ConfiguredObject<?> root,
+               List<String> path,
+               Map<String, List<String>> parameters) throws ManagementException;
+
+    LegacyConfiguredObject createOrUpdate(ConfiguredObject<?> root,
+                                          List<String> path,
+                                          Map<String, Object> attributes,
+                                          boolean isPost) throws ManagementException;
+
+    int delete(ConfiguredObject<?> root,
+               List<String> path,
+               Map<String, List<String>> parameters) throws ManagementException;
+
+    ManagementResponse invoke(ConfiguredObject<?> root,
+                              List<String> path,
+                              String operation,
+                              Map<String, Object> parameters,
+                              boolean isPost,
+                              final boolean isSecure) throws ManagementException;
+
+    Object getPreferences(ConfiguredObject<?> root,
+                          List<String> path,
+                          Map<String, List<String>> parameters) throws ManagementException;
+
+    void setPreferences(ConfiguredObject<?> root,
+                        List<String> path,
+                        Object preferences,
+                        Map<String, List<String>> parameters,
+                        boolean isPost) throws ManagementException;
+
+    int deletePreferences(ConfiguredObject<?> root,
+                          List<String> path,
+                          Map<String, List<String>> parameters) throws ManagementException;
+
+    LegacyConfiguredObject convertFromNextVersion(LegacyConfiguredObject nextVersionObject);
+}
diff --git a/broker-plugins/management-http/src/main/java/org/apache/qpid/server/management/plugin/controller/CategoryControllerFactory.java b/broker-plugins/management-http/src/main/java/org/apache/qpid/server/management/plugin/controller/CategoryControllerFactory.java
new file mode 100644
index 0000000..552b2cd
--- /dev/null
+++ b/broker-plugins/management-http/src/main/java/org/apache/qpid/server/management/plugin/controller/CategoryControllerFactory.java
@@ -0,0 +1,48 @@
+/*
+ *
+ * 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.
+ *
+ */
+package org.apache.qpid.server.management.plugin.controller;
+
+import java.util.Set;
+import java.util.stream.Collectors;
+import java.util.stream.StreamSupport;
+
+import org.apache.qpid.server.management.plugin.ManagementController;
+import org.apache.qpid.server.plugin.Pluggable;
+import org.apache.qpid.server.plugin.QpidServiceLoader;
+
+public interface CategoryControllerFactory extends Pluggable
+{
+    CategoryController createController(String type, LegacyManagementController managementController);
+
+    Set<String> getSupportedCategories();
+
+    String getModelVersion();
+
+    static Set<CategoryControllerFactory> findFactories(String version)
+    {
+        final Iterable<CategoryControllerFactory> factories =
+                new QpidServiceLoader().atLeastOneInstanceOf(CategoryControllerFactory.class);
+
+        return StreamSupport.stream(factories.spliterator(), false)
+                            .filter(f -> version.equals(f.getModelVersion()))
+                            .collect(Collectors.toSet());
+    }
+}
diff --git a/broker-plugins/management-http/src/main/java/org/apache/qpid/server/management/plugin/controller/ControllerManagementResponse.java b/broker-plugins/management-http/src/main/java/org/apache/qpid/server/management/plugin/controller/ControllerManagementResponse.java
new file mode 100644
index 0000000..ff3061f
--- /dev/null
+++ b/broker-plugins/management-http/src/main/java/org/apache/qpid/server/management/plugin/controller/ControllerManagementResponse.java
@@ -0,0 +1,82 @@
+/*
+ *
+ * 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.
+ *
+ */
+package org.apache.qpid.server.management.plugin.controller;
+
+import java.util.Collections;
+import java.util.Map;
+
+import javax.servlet.http.HttpServletResponse;
+
+import org.apache.qpid.server.management.plugin.ManagementResponse;
+import org.apache.qpid.server.management.plugin.ResponseType;
+
+public class ControllerManagementResponse implements ManagementResponse
+{
+    private final ResponseType _type;
+    private final Object _body;
+    private final int _status;
+    private final Map<String, String> _headers;
+
+    public ControllerManagementResponse(final ResponseType type, final Object body)
+    {
+        this(type, body, HttpServletResponse.SC_OK, Collections.emptyMap());
+    }
+
+    public ControllerManagementResponse(final ResponseType type,
+                                        final Object body,
+                                        final int status,
+                                        final Map<String, String> headers)
+    {
+        _type = type;
+        _body = body;
+        _status = status;
+        _headers = headers;
+    }
+
+    @Override
+    public ResponseType getType()
+    {
+        return _type;
+    }
+
+    @Override
+    public Object getBody()
+    {
+        return _body;
+    }
+
+    public int getStatus()
+    {
+        return _status;
+    }
+
+    @Override
+    public Map<String, String> getHeaders()
+    {
+        return _headers;
+    }
+
+    @Override
+    public int getResponseCode()
+    {
+        return _status;
+    }
+}
diff --git a/broker-plugins/management-http/src/main/java/org/apache/qpid/server/management/plugin/controller/GenericCategoryController.java b/broker-plugins/management-http/src/main/java/org/apache/qpid/server/management/plugin/controller/GenericCategoryController.java
new file mode 100644
index 0000000..35ff45b
--- /dev/null
+++ b/broker-plugins/management-http/src/main/java/org/apache/qpid/server/management/plugin/controller/GenericCategoryController.java
@@ -0,0 +1,266 @@
+/*
+ *
+ * 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.
+ *
+ */
+package org.apache.qpid.server.management.plugin.controller;
+
+import static org.apache.qpid.server.management.plugin.ManagementException.createBadRequestManagementException;
+import static org.apache.qpid.server.management.plugin.ManagementException.createInternalServerErrorManagementException;
+
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+import org.apache.qpid.server.management.plugin.ManagementController;
+import org.apache.qpid.server.management.plugin.ManagementException;
+import org.apache.qpid.server.management.plugin.ManagementResponse;
+import org.apache.qpid.server.model.ConfiguredObject;
+
+public abstract class GenericCategoryController implements CategoryController
+{
+    private final String _name;
+    private final LegacyManagementController _managementController;
+    private final ManagementController _nextVersionManagementController;
+    private final String _defaultType;
+    private final Map<String, TypeController> _typeControllers;
+    private final Map<String, TypeController> _nextVersionTypeControllers;
+
+    protected GenericCategoryController(final LegacyManagementController managementController,
+                                        final ManagementController nextVersionManagementController,
+                                        final String name,
+                                        final String defaultType,
+                                        final Set<TypeController> typeControllers)
+    {
+        _name = name;
+        _managementController = managementController;
+        _nextVersionManagementController = nextVersionManagementController;
+        _defaultType = defaultType;
+        _typeControllers = typeControllers.stream().collect(Collectors.toMap(TypeController::getTypeName, c -> c));
+        _nextVersionTypeControllers =
+                typeControllers.stream().collect(Collectors.toMap(TypeController::getNextVersionTypeName, c -> c));
+    }
+
+    @Override
+    public String getCategory()
+    {
+        return _name;
+    }
+
+    @Override
+    public String getNextVersionCategory()
+    {
+        return _name;
+    }
+
+    @Override
+    public String getDefaultType()
+    {
+        return _defaultType;
+    }
+
+    @Override
+    public LegacyManagementController getManagementController()
+    {
+        return _managementController;
+    }
+
+    @Override
+    public Object get(final ConfiguredObject<?> root,
+                      final List<String> path,
+                      final Map<String, List<String>> parameters)
+            throws ManagementException
+    {
+        final Object content =
+                _nextVersionManagementController.get(root,
+                                                     getNextVersionCategory(),
+                                                     path,
+                                                     convertQueryParametersToNextVersion(parameters));
+        return convert(content);
+    }
+
+    @Override
+    public LegacyConfiguredObject createOrUpdate(ConfiguredObject<?> root,
+                                                 List<String> path,
+                                                 Map<String, Object> attributes,
+                                                 boolean isPost) throws ManagementException
+    {
+        final Map<String, Object> body = convertAttributesToNextVersion(root, path, attributes);
+        final Object configuredObject =
+                _nextVersionManagementController.createOrUpdate(root, getNextVersionCategory(), path, body, isPost);
+        if (configuredObject instanceof LegacyConfiguredObject)
+        {
+            LegacyConfiguredObject object = (LegacyConfiguredObject) configuredObject;
+            return convertFromNextVersion(object);
+        }
+        return null;
+    }
+
+    @Override
+    public LegacyConfiguredObject convertFromNextVersion(final LegacyConfiguredObject object)
+    {
+        TypeController controller = getTypeController(object);
+        if (controller != null)
+        {
+            return controller.convertFromNextVersion(object);
+        }
+        return convertNextVersionLegacyConfiguredObject(object);
+    }
+
+    @Override
+    public int delete(final ConfiguredObject<?> root,
+                      final List<String> path,
+                      final Map<String, List<String>> parameters) throws ManagementException
+    {
+        return _nextVersionManagementController.delete(root,
+                                                       getNextVersionCategory(),
+                                                       path,
+                                                       convertQueryParametersToNextVersion(parameters));
+    }
+
+    @Override
+    public ManagementResponse invoke(final ConfiguredObject<?> root,
+                                     final List<String> path,
+                                     final String operation,
+                                     final Map<String, Object> parameters,
+                                     final boolean isPost,
+                                     final boolean isSecure) throws ManagementException
+    {
+
+        Object result = get(root, path, Collections.emptyMap());
+        if (result instanceof LegacyConfiguredObject)
+        {
+            final LegacyConfiguredObject legacyConfiguredObject = (LegacyConfiguredObject) result;
+            return legacyConfiguredObject.invoke(operation, parameters, isSecure);
+        }
+        else
+        {
+            throw createBadRequestManagementException(String.format("Configured object %s/%s is not found",
+                                                                    getManagementController().getCategoryMapping(
+                                                                            getCategory()),
+                                                                    String.join("/", path)));
+        }
+    }
+
+    @Override
+    public Object getPreferences(final ConfiguredObject<?> root,
+                                 final List<String> path,
+                                 final Map<String, List<String>> parameters) throws ManagementException
+    {
+        return _nextVersionManagementController.getPreferences(root,
+                                                               getNextVersionCategory(),
+                                                               path,
+                                                               parameters);
+    }
+
+    @Override
+    @SuppressWarnings("unchecked")
+    public void setPreferences(final ConfiguredObject<?> root,
+                               final List<String> path,
+                               final Object preferences,
+                               final Map<String, List<String>> parameters,
+                               final boolean isPost) throws ManagementException
+    {
+        _nextVersionManagementController.setPreferences(root,
+                                                        getNextVersionCategory(),
+                                                        path,
+                                                        preferences,
+                                                        parameters,
+                                                        isPost);
+    }
+
+    @Override
+    public int deletePreferences(final ConfiguredObject<?> root,
+                                 final List<String> path,
+                                 final Map<String, List<String>> parameters) throws ManagementException
+    {
+        return _nextVersionManagementController.deletePreferences(root,
+                                                                  getNextVersionCategory(),
+                                                                  path,
+                                                                  parameters);
+    }
+
+    protected abstract LegacyConfiguredObject convertNextVersionLegacyConfiguredObject(final LegacyConfiguredObject object);
+
+    protected Map<String, List<String>> convertQueryParametersToNextVersion(final Map<String, List<String>> parameters)
+    {
+        return parameters;
+    }
+
+    protected ManagementController getNextVersionManagementController()
+    {
+        return _nextVersionManagementController;
+    }
+
+    protected Map<String, Object> convertAttributesToNextVersion(final ConfiguredObject<?> root,
+                                                                 final List<String> path,
+                                                                 final Map<String, Object> attributes)
+    {
+        TypeController typeController = getTypeController(attributes);
+        if (typeController != null)
+        {
+            return typeController.convertAttributesToNextVersion(root, path, attributes);
+        }
+        return attributes;
+    }
+
+
+    private Object convert(final Object content)
+    {
+        if (content instanceof LegacyConfiguredObject)
+        {
+            return convertFromNextVersion((LegacyConfiguredObject) content);
+        }
+        else if (content instanceof Collection)
+        {
+            final Collection<?> items = (Collection<?>) content;
+            return items.stream()
+                        .filter(LegacyConfiguredObject.class::isInstance)
+                        .map(LegacyConfiguredObject.class::cast)
+                        .map(this::convertFromNextVersion)
+                        .collect(Collectors.toList());
+        }
+        else
+        {
+            throw createInternalServerErrorManagementException("Unexpected data format from next version");
+        }
+    }
+
+    private TypeController getTypeController(final Map<String, Object> attributes)
+    {
+        String type = (String) attributes.get(LegacyConfiguredObject.TYPE);
+        if (type == null)
+        {
+            type = getDefaultType();
+        }
+        if (type != null)
+        {
+            return _typeControllers.get(type);
+        }
+        return null;
+    }
+
+    protected TypeController getTypeController(final LegacyConfiguredObject object)
+    {
+        String type = (String) object.getAttribute(LegacyConfiguredObject.TYPE);
+        return _nextVersionTypeControllers.get(type);
+    }
+}
diff --git a/broker-plugins/management-http/src/main/java/org/apache/qpid/server/management/plugin/controller/GenericLegacyConfiguredObject.java b/broker-plugins/management-http/src/main/java/org/apache/qpid/server/management/plugin/controller/GenericLegacyConfiguredObject.java
new file mode 100644
index 0000000..678cef0
--- /dev/null
+++ b/broker-plugins/management-http/src/main/java/org/apache/qpid/server/management/plugin/controller/GenericLegacyConfiguredObject.java
@@ -0,0 +1,195 @@
+/*
+ *
+ * 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.
+ *
+ */
+package org.apache.qpid.server.management.plugin.controller;
+
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Map;
+import java.util.Objects;
+import java.util.stream.Collectors;
+
+import org.apache.qpid.server.management.plugin.ManagementResponse;
+import org.apache.qpid.server.management.plugin.ResponseType;
+
+public class GenericLegacyConfiguredObject implements LegacyConfiguredObject
+{
+    private final LegacyManagementController _managementController;
+    private final LegacyConfiguredObject _nextVersionLegacyConfiguredObject;
+    private final String _category;
+
+    public GenericLegacyConfiguredObject(final LegacyManagementController managementController,
+                                         final LegacyConfiguredObject nextVersionLegacyConfiguredObject,
+                                         final String category)
+    {
+        _nextVersionLegacyConfiguredObject = nextVersionLegacyConfiguredObject;
+        _managementController = managementController;
+        _category = category;
+    }
+
+    @Override
+    public Collection<String> getAttributeNames()
+    {
+        return _nextVersionLegacyConfiguredObject.getAttributeNames();
+    }
+
+    @Override
+    public Object getAttribute(final String name)
+    {
+        return convertLegacyConfiguredObjectIfRequired(_nextVersionLegacyConfiguredObject.getAttribute(name));
+    }
+
+    @Override
+    public Object getActualAttribute(final String name)
+    {
+        return _nextVersionLegacyConfiguredObject.getActualAttribute(name);
+    }
+
+    @Override
+    public Collection<LegacyConfiguredObject> getChildren(final String category)
+    {
+        final Collection<LegacyConfiguredObject> children =
+                _nextVersionLegacyConfiguredObject.getChildren(category);
+        if (children != null)
+        {
+            return children.stream().map(_managementController::convertFromNextVersion).collect(Collectors.toSet());
+        }
+        return Collections.emptySet();
+    }
+
+    @Override
+    public String getCategory()
+    {
+        return _category;
+    }
+
+    @Override
+    public ManagementResponse invoke(final String operation,
+                                     final Map<String, Object> parameters,
+                                     final boolean isSecure)
+    {
+        ManagementResponse result = _nextVersionLegacyConfiguredObject.invoke(operation, parameters, isSecure);
+
+        return convertLegacyConfiguredObjectIfRequired(result);
+    }
+
+    @Override
+    public LegacyConfiguredObject getNextVersionConfiguredObject()
+    {
+        return _nextVersionLegacyConfiguredObject;
+    }
+
+    @Override
+    public LegacyConfiguredObject getParent(final String category)
+    {
+        LegacyConfiguredObject parent = _nextVersionLegacyConfiguredObject.getParent(category);
+        return _managementController.convertFromNextVersion(parent);
+    }
+
+    @Override
+    public boolean isSecureAttribute(final String name)
+    {
+        return _nextVersionLegacyConfiguredObject.isSecureAttribute(name);
+    }
+
+    @Override
+    public boolean isOversizedAttribute(final String name)
+    {
+        return _nextVersionLegacyConfiguredObject.isOversizedAttribute(name);
+    }
+
+    @Override
+    public String getContextValue(final String contextKey)
+    {
+        return _nextVersionLegacyConfiguredObject.getContextValue(contextKey);
+    }
+
+    @Override
+    public Map<String, Object> getStatistics()
+    {
+        return _nextVersionLegacyConfiguredObject.getStatistics();
+    }
+
+    @Override
+    public boolean equals(final Object o)
+    {
+        if (this == o)
+        {
+            return true;
+        }
+        if (o == null || getClass() != o.getClass())
+        {
+            return false;
+        }
+        final GenericLegacyConfiguredObject
+                that = (GenericLegacyConfiguredObject) o;
+        return Objects.equals(_nextVersionLegacyConfiguredObject, that._nextVersionLegacyConfiguredObject);
+    }
+
+    @Override
+    public int hashCode()
+    {
+        return Objects.hash(_nextVersionLegacyConfiguredObject);
+    }
+
+    public LegacyManagementController getManagementController()
+    {
+        return _managementController;
+    }
+
+    protected LegacyConfiguredObject getNextVersionLegacyConfiguredObject()
+    {
+        return _nextVersionLegacyConfiguredObject;
+    }
+
+    private Object convertLegacyConfiguredObjectIfRequired(final Object value)
+    {
+        if (value instanceof LegacyConfiguredObject)
+        {
+            return _managementController.convertFromNextVersion((LegacyConfiguredObject) value);
+        }
+        else if (value instanceof Collection)
+        {
+            Collection<?> collection = (Collection<?>) value;
+            if (collection.size() > 0 && collection.iterator().next() instanceof LegacyConfiguredObject)
+            {
+                return collection.stream()
+                                 .filter(o -> o instanceof LegacyConfiguredObject)
+                                 .map(LegacyConfiguredObject.class::cast)
+                                 .map(_managementController::convertFromNextVersion)
+                                 .collect(Collectors.toSet());
+            }
+        }
+        return value;
+    }
+
+    private ManagementResponse convertLegacyConfiguredObjectIfRequired(final ManagementResponse response)
+    {
+        if (response.getType() == ResponseType.MODEL_OBJECT)
+        {
+            Object body = convertLegacyConfiguredObjectIfRequired(response.getBody());
+            return new ControllerManagementResponse(response.getType(),
+                                                    body,
+                                                    response.getResponseCode(),
+                                                    response.getHeaders());
+        }
+        return response;
+    }
+}
diff --git a/broker-plugins/management-http/src/main/java/org/apache/qpid/server/management/plugin/controller/LegacyConfiguredObject.java b/broker-plugins/management-http/src/main/java/org/apache/qpid/server/management/plugin/controller/LegacyConfiguredObject.java
new file mode 100644
index 0000000..47d293d
--- /dev/null
+++ b/broker-plugins/management-http/src/main/java/org/apache/qpid/server/management/plugin/controller/LegacyConfiguredObject.java
@@ -0,0 +1,90 @@
+/*
+ *
+ * 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.
+ *
+ */
+
+package org.apache.qpid.server.management.plugin.controller;
+
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+
+import org.apache.qpid.server.management.plugin.ManagementResponse;
+
+public interface LegacyConfiguredObject
+{
+    String ID = "id";
+    String NAME = "name";
+    String TYPE = "type";
+    String DESCRIPTION = "description";
+    String DURABLE = "durable";
+    String CONTEXT = "context";
+    String LIFETIME_POLICY = "lifetimePolicy";
+    String STATE = "state";
+    String DESIRED_STATE = "desiredState";
+
+    String LAST_UPDATED_BY = "lastUpdatedBy";
+    String LAST_UPDATED_TIME = "lastUpdatedTime";
+    String CREATED_BY = "createdBy";
+    String CREATED_TIME = "createdTime";
+    String LAST_OPENED_TIME = "lastOpenedTime";
+
+    Set<String> AVAILABLE_ATTRIBUTES = Collections.unmodifiableSet(new HashSet<>(Arrays.asList(
+            ID,
+            NAME,
+            TYPE,
+            DESCRIPTION,
+            DURABLE,
+            CONTEXT,
+            LIFETIME_POLICY,
+            STATE,
+            DESIRED_STATE,
+            LAST_OPENED_TIME,
+            LAST_UPDATED_BY,
+            LAST_UPDATED_TIME,
+            CREATED_BY,
+            CREATED_TIME)));
+
+    Collection<String> getAttributeNames();
+
+    Object getAttribute(String name);
+
+    Map<String, Object> getStatistics();
+
+    Object getActualAttribute(String name);
+
+    boolean isSecureAttribute(String name);
+
+    boolean isOversizedAttribute(String name);
+
+    String getCategory();
+
+    Collection<LegacyConfiguredObject> getChildren(String category);
+
+    LegacyConfiguredObject getParent(String category);
+
+    String getContextValue(String contextKey);
+
+    ManagementResponse invoke(String operation, Map<String, Object> parameters, boolean isSecure);
+
+    LegacyConfiguredObject getNextVersionConfiguredObject();
+}
diff --git a/broker-plugins/management-http/src/main/java/org/apache/qpid/server/management/plugin/controller/LegacyConfiguredObjectToMapConverter.java b/broker-plugins/management-http/src/main/java/org/apache/qpid/server/management/plugin/controller/LegacyConfiguredObjectToMapConverter.java
new file mode 100644
index 0000000..f340359
--- /dev/null
+++ b/broker-plugins/management-http/src/main/java/org/apache/qpid/server/management/plugin/controller/LegacyConfiguredObjectToMapConverter.java
@@ -0,0 +1,251 @@
+/*
+ *
+ * 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.
+ *
+ */
+package org.apache.qpid.server.management.plugin.controller;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.HashMap;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.TreeMap;
+import java.util.stream.Collectors;
+
+import org.apache.qpid.server.model.Named;
+
+class LegacyConfiguredObjectToMapConverter
+{
+    private static final String STATISTICS_MAP_KEY = "statistics";
+    private static final String NAME = "name";
+    private static final String CONTEXT = "context";
+
+    private final LegacyManagementController _managementMetadata;
+
+    LegacyConfiguredObjectToMapConverter(final LegacyManagementController managementMetadata)
+    {
+        _managementMetadata = managementMetadata;
+    }
+
+    Map<String, Object> convertManageableToMap(final LegacyConfiguredObject legacyConfiguredObjectObject,
+                                               final int depth,
+                                               final boolean actuals,
+                                               final int oversizeThreshold,
+                                               final boolean excludeInheritedContext)
+    {
+        final Map<String, Object> object = new LinkedHashMap<>();
+
+        incorporateAttributesIntoMap(legacyConfiguredObjectObject,
+                                     object,
+                                     actuals,
+                                     excludeInheritedContext,
+                                     oversizeThreshold);
+        incorporateStatisticsIntoMap(legacyConfiguredObjectObject, object);
+
+        if (depth > 0)
+        {
+            incorporateChildrenIntoMap(legacyConfiguredObjectObject,
+                                       object,
+                                       depth,
+                                       actuals,
+                                       oversizeThreshold,
+                                       excludeInheritedContext);
+        }
+        return object;
+    }
+
+    private void incorporateAttributesIntoMap(
+            final LegacyConfiguredObject confObject,
+            final Map<String, Object> object,
+            final boolean useActualValues,
+            final boolean excludeInheritedContext,
+            final int oversizeThreshold)
+    {
+
+        for (String name : confObject.getAttributeNames())
+        {
+            Object value = useActualValues ? confObject.getActualAttribute(name) : confObject.getAttribute(name);
+            if (value instanceof LegacyConfiguredObject)
+            {
+                object.put(name, ((LegacyConfiguredObject) value).getAttribute(NAME));
+            }
+            else if (CONTEXT.equals(name))
+            {
+                Map<String, Object> contextValues = collectContext(confObject,
+                                                                   excludeInheritedContext,
+                                                                   useActualValues);
+
+                if (!contextValues.isEmpty())
+                {
+                    object.put(CONTEXT, contextValues);
+                }
+            }
+            else if (value instanceof Collection)
+            {
+                List<Object> converted = new ArrayList<>();
+                for (Object member : (Collection) value)
+                {
+                    if (member instanceof LegacyConfiguredObject)
+                    {
+                        converted.add(((LegacyConfiguredObject) member).getAttribute(NAME));
+                    }
+                    else
+                    {
+                        converted.add(member);
+                    }
+                }
+                object.put(name, converted);
+            }
+            else if (value instanceof Named)
+            {
+                object.put(name, ((Named) value).getName());
+            }
+            else if (value != null)
+            {
+                if (confObject.isSecureAttribute(name))
+                {
+                    value = confObject.getAttribute(name);
+                }
+
+                if (confObject.isOversizedAttribute(name) && !useActualValues)
+                {
+                    String valueString = String.valueOf(value);
+                    if (valueString.length() > oversizeThreshold)
+                    {
+                        object.put(name, String.valueOf(value).substring(0, oversizeThreshold - 4) + "...");
+                    }
+                    else
+                    {
+                        object.put(name, value);
+                    }
+                }
+                else
+                {
+                    object.put(name, value);
+                }
+            }
+        }
+    }
+
+    @SuppressWarnings("unchecked")
+    private Map<String, Object> collectContext(final LegacyConfiguredObject configuredObject,
+                                               final boolean excludeInheritedContext,
+                                               final boolean useActualValues)
+    {
+        final Map<String, Object> actualContext = new HashMap<>();
+        if (excludeInheritedContext)
+        {
+            final Object value = configuredObject.getActualAttribute(CONTEXT);
+            if (value instanceof Map)
+            {
+                actualContext.putAll((Map<String, String>) value);
+            }
+        }
+        else
+        {
+            actualContext.putAll(System.getenv());
+            actualContext.putAll((Map) System.getProperties());
+            collectInheritedActualContext(configuredObject, actualContext);
+        }
+
+        if (useActualValues)
+        {
+            return actualContext;
+        }
+        else
+        {
+            final Map<String, Object> effectiveContext = new HashMap<>();
+            for (String contextKey : actualContext.keySet())
+            {
+                effectiveContext.put(contextKey, configuredObject.getContextValue(contextKey));
+            }
+            return effectiveContext;
+        }
+    }
+
+    @SuppressWarnings("unchecked")
+    private void collectInheritedActualContext(final LegacyConfiguredObject confObject,
+                                               final Map<String, Object> contextValues)
+    {
+        final Collection<String> parents = _managementMetadata.getParentTypes(confObject.getCategory());
+        if (parents != null && !parents.isEmpty())
+        {
+            final LegacyConfiguredObject parent = confObject.getParent(parents.iterator().next());
+            if (parent != null)
+            {
+                collectInheritedActualContext(parent, contextValues);
+            }
+        }
+        final Object value = confObject.getActualAttribute(CONTEXT);
+        if (value instanceof Map)
+        {
+            contextValues.putAll((Map<String, Object>) value);
+        }
+    }
+
+    private void incorporateStatisticsIntoMap(final LegacyConfiguredObject confObject, final Map<String, Object> object)
+    {
+        final Map<String, Object> statMap = new TreeMap<>(confObject.getStatistics());
+        if (!statMap.isEmpty())
+        {
+            object.put(STATISTICS_MAP_KEY, statMap);
+        }
+    }
+
+    private void incorporateChildrenIntoMap(
+            final LegacyConfiguredObject confObject,
+            final Map<String, Object> object,
+            final int depth,
+            final boolean actuals,
+            final int oversizeThreshold,
+            final boolean excludeInheritedContext)
+    {
+        Collection<String> childTypes = _managementMetadata.getChildrenCategories(confObject.getCategory());
+        if (childTypes != null && !childTypes.isEmpty())
+        {
+            List<String> types = new ArrayList<>(childTypes);
+            Collections.sort(types);
+
+            for (String childType : types)
+            {
+                Collection<LegacyConfiguredObject> children = confObject.getChildren(childType);
+                if (children != null && !children.isEmpty())
+                {
+                    List<LegacyConfiguredObject> sortedChildren = new ArrayList<>(children);
+                    sortedChildren.sort(Comparator.comparing(o -> ((String) o.getAttribute(NAME))));
+
+                    List<Map<String, Object>> childObjects = sortedChildren.stream()
+                                                                           .sorted(Comparator.comparing(o -> ((String) o.getAttribute(NAME))))
+                                                                           .map(child -> convertManageableToMap(child,
+                                                                                                                depth
+                                                                                                                - 1,
+                                                                                                                actuals,
+                                                                                                                oversizeThreshold,
+                                                                                                                excludeInheritedContext))
+                                                                           .collect(Collectors.toList());
+                    String childTypeSingular = childType.toLowerCase();
+                    object.put(childTypeSingular + (childTypeSingular.endsWith("s") ? "es" : "s"), childObjects);
+                }
+            }
+        }
+    }
+}
diff --git a/broker-plugins/management-http/src/main/java/org/apache/qpid/server/management/plugin/controller/LegacyManagementController.java b/broker-plugins/management-http/src/main/java/org/apache/qpid/server/management/plugin/controller/LegacyManagementController.java
new file mode 100644
index 0000000..4c9f4206
--- /dev/null
+++ b/broker-plugins/management-http/src/main/java/org/apache/qpid/server/management/plugin/controller/LegacyManagementController.java
@@ -0,0 +1,44 @@
+/*
+ *
+ * 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.
+ *
+ */
+
+package org.apache.qpid.server.management.plugin.controller;
+
+import java.util.Collection;
+import java.util.Set;
+
+import org.apache.qpid.server.management.plugin.ManagementController;
+
+public interface LegacyManagementController extends ManagementController
+{
+    Collection<String> getParentTypes(String category);
+
+    Collection<String> getChildrenCategories(String category);
+
+    Collection<String> getCategories();
+
+    Collection<String> getCategoryHierarchy(String rootCategory, String category);
+
+    CategoryController getCategoryController(String category);
+
+    Set<TypeController> getTypeControllersByCategory(String name);
+
+    LegacyConfiguredObject convertFromNextVersion(LegacyConfiguredObject nextVersionObject);
+}
diff --git a/broker-plugins/management-http/src/main/java/org/apache/qpid/server/management/plugin/controller/TypeController.java b/broker-plugins/management-http/src/main/java/org/apache/qpid/server/management/plugin/controller/TypeController.java
new file mode 100644
index 0000000..cce2eb7
--- /dev/null
+++ b/broker-plugins/management-http/src/main/java/org/apache/qpid/server/management/plugin/controller/TypeController.java
@@ -0,0 +1,39 @@
+/*
+ *
+ * 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.
+ *
+ */
+package org.apache.qpid.server.management.plugin.controller;
+
+import java.util.List;
+import java.util.Map;
+
+import org.apache.qpid.server.model.ConfiguredObject;
+
+public interface TypeController
+{
+    String getTypeName();
+
+    String getNextVersionTypeName();
+
+    Map<String, Object> convertAttributesToNextVersion(ConfiguredObject<?> root,
+                                                       List<String> path,
+                                                       Map<String, Object> attributes);
+
+    LegacyConfiguredObject convertFromNextVersion(LegacyConfiguredObject object);
+}
diff --git a/broker-plugins/management-http/src/main/java/org/apache/qpid/server/management/plugin/controller/TypeControllerFactory.java b/broker-plugins/management-http/src/main/java/org/apache/qpid/server/management/plugin/controller/TypeControllerFactory.java
new file mode 100644
index 0000000..2d3ec05
--- /dev/null
+++ b/broker-plugins/management-http/src/main/java/org/apache/qpid/server/management/plugin/controller/TypeControllerFactory.java
@@ -0,0 +1,51 @@
+/*
+ *
+ * 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.
+ *
+ */
+package org.apache.qpid.server.management.plugin.controller;
+
+import java.util.Set;
+import java.util.stream.Collectors;
+import java.util.stream.StreamSupport;
+
+import org.apache.qpid.server.management.plugin.ManagementController;
+import org.apache.qpid.server.plugin.Pluggable;
+import org.apache.qpid.server.plugin.QpidServiceLoader;
+
+public interface TypeControllerFactory extends Pluggable
+{
+    TypeController createController(ManagementController managementController);
+
+    String getCategory();
+
+    String getNextVersionCategory();
+
+    String getModelVersion();
+
+    static Set<TypeControllerFactory> findFactories(String version)
+    {
+        final Iterable<TypeControllerFactory> factories =
+                new QpidServiceLoader().instancesOf(TypeControllerFactory.class);
+
+        return StreamSupport.stream(factories.spliterator(), false)
+                            .filter(f -> version.equals(f.getModelVersion()))
+                            .collect(Collectors.toSet());
+    }
+
+}
diff --git a/broker-plugins/management-http/src/main/java/org/apache/qpid/server/management/plugin/controller/latest/LatestManagementControllerAdapter.java b/broker-plugins/management-http/src/main/java/org/apache/qpid/server/management/plugin/controller/latest/LatestManagementControllerAdapter.java
new file mode 100644
index 0000000..e0da225
--- /dev/null
+++ b/broker-plugins/management-http/src/main/java/org/apache/qpid/server/management/plugin/controller/latest/LatestManagementControllerAdapter.java
@@ -0,0 +1,489 @@
+/*
+ *
+ * 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.
+ *
+ */
+package org.apache.qpid.server.management.plugin.controller.latest;
+
+import static org.apache.qpid.server.management.plugin.ManagementException.createNotFoundManagementException;
+import static org.apache.qpid.server.model.ConfiguredObjectTypeRegistry.getCollectionMemberType;
+import static org.apache.qpid.server.model.ConfiguredObjectTypeRegistry.returnsCollectionOfConfiguredObjects;
+
+import java.lang.reflect.ParameterizedType;
+import java.lang.reflect.Type;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.stream.Collectors;
+
+import org.apache.qpid.server.management.plugin.ManagementController;
+import org.apache.qpid.server.management.plugin.ManagementException;
+import org.apache.qpid.server.management.plugin.ManagementRequest;
+import org.apache.qpid.server.management.plugin.ManagementResponse;
+import org.apache.qpid.server.management.plugin.ResponseType;
+import org.apache.qpid.server.management.plugin.controller.ControllerManagementResponse;
+import org.apache.qpid.server.management.plugin.controller.LegacyConfiguredObject;
+import org.apache.qpid.server.model.ConfiguredObject;
+import org.apache.qpid.server.model.ConfiguredObjectAttribute;
+import org.apache.qpid.server.model.ConfiguredObjectOperation;
+import org.apache.qpid.server.model.Model;
+
+public class LatestManagementControllerAdapter implements ManagementController
+{
+    private ManagementController _latestManagementController;
+
+    public LatestManagementControllerAdapter(final ManagementController latestManagementController)
+    {
+        _latestManagementController = latestManagementController;
+    }
+
+    @Override
+    public String getVersion()
+    {
+        return _latestManagementController.getVersion();
+    }
+
+    @Override
+    public Collection<String> getCategories()
+    {
+        return _latestManagementController.getCategories();
+    }
+
+    @Override
+    public String getCategoryMapping(final String category)
+    {
+        return _latestManagementController.getCategoryMapping(category);
+    }
+
+    @Override
+    public String getCategory(final ConfiguredObject<?> managedObject)
+    {
+        return _latestManagementController.getCategory(managedObject);
+    }
+
+    @Override
+    public Collection<String> getCategoryHierarchy(final ConfiguredObject<?> root, final String category)
+    {
+        return _latestManagementController.getCategoryHierarchy(root, category);
+    }
+
+    @Override
+    public ManagementController getNextVersionManagementController()
+    {
+        return _latestManagementController;
+    }
+
+    @Override
+    public ManagementResponse handleGet(final ManagementRequest request) throws ManagementException
+    {
+        ManagementResponse response = _latestManagementController.handleGet(request);
+        return new ControllerManagementResponse(response.getType(),
+                                                convertResponseObject(response.getBody()),
+                                                response.getResponseCode(),
+                                                response.getHeaders());
+    }
+
+    @Override
+    public ManagementResponse handlePut(final ManagementRequest request) throws ManagementException
+    {
+        ManagementResponse response = _latestManagementController.handlePut(request);
+        return new ControllerManagementResponse(response.getType(),
+                                                convertResponseObject(response.getBody()),
+                                                response.getResponseCode(),
+                                                response.getHeaders());
+    }
+
+    @Override
+    public ManagementResponse handlePost(final ManagementRequest request) throws ManagementException
+    {
+        ManagementResponse response = _latestManagementController.handlePost(request);
+        return new ControllerManagementResponse(response.getType(),
+                                                convertResponseObject(response.getBody()),
+                                                response.getResponseCode(),
+                                                response.getHeaders());
+    }
+
+    @Override
+    public ManagementResponse handleDelete(final ManagementRequest request) throws ManagementException
+    {
+        ManagementResponse response = _latestManagementController.handleDelete(request);
+        return new ControllerManagementResponse(response.getType(),
+                                                convertResponseObject(response.getBody()),
+                                                response.getResponseCode(),
+                                                response.getHeaders());
+    }
+
+
+    @Override
+    public Object get(final ConfiguredObject<?> root,
+                      final String category,
+                      final List<String> path,
+                      final Map<String, List<String>> parameters) throws ManagementException
+    {
+        Object result = _latestManagementController.get(root, category, path, parameters);
+        return convertResponseObject(result);
+    }
+
+
+    @Override
+    public Object createOrUpdate(final ConfiguredObject<?> root,
+                                 final String category,
+                                 final List<String> path,
+                                 final Map<String, Object> attributes,
+                                 final boolean isPost) throws ManagementException
+    {
+        Object result = _latestManagementController.createOrUpdate(root, category, path, attributes, isPost);
+        if (result instanceof ConfiguredObject)
+        {
+            return new LegacyConfiguredObjectObject((ConfiguredObject) result);
+        }
+        return null;
+    }
+
+    @Override
+    public int delete(final ConfiguredObject<?> root,
+                      final String category,
+                      final List<String> path,
+                      final Map<String, List<String>> parameters) throws ManagementException
+    {
+        return _latestManagementController.delete(root, category, path, parameters);
+    }
+
+    @Override
+    public ManagementResponse invoke(final ConfiguredObject<?> root,
+                                     final String category,
+                                     final List<String> path,
+                                     final String operationName,
+                                     final Map<String, Object> parameters,
+                                     final boolean isPost,
+                                     final boolean isSecureOrAllowedOnInsecureChannel) throws ManagementException
+    {
+        ManagementResponse response =
+                _latestManagementController.invoke(root, category, path, operationName, parameters, isPost,
+                                                   isSecureOrAllowedOnInsecureChannel);
+        if (response.getType() == ResponseType.MODEL_OBJECT)
+        {
+            Object result = response.getBody();
+
+            if (result instanceof ConfiguredObject)
+            {
+                result = new LegacyConfiguredObjectObject((ConfiguredObject<?>) result);
+            }
+            else if (result instanceof Collection)
+            {
+                result = ((Collection<?>) result).stream()
+                                                 .map(o -> new LegacyConfiguredObjectObject((ConfiguredObject<?>) o))
+                                                 .collect(Collectors.toSet());
+            }
+            return new ControllerManagementResponse(ResponseType.MODEL_OBJECT, result);
+        }
+        return response;
+    }
+
+    @Override
+    public Object getPreferences(final ConfiguredObject<?> root,
+                                 final String category,
+                                 final List<String> path,
+                                 final Map<String, List<String>> parameters) throws ManagementException
+    {
+        return _latestManagementController.getPreferences(root, category, path, parameters);
+    }
+
+    @Override
+    public void setPreferences(final ConfiguredObject<?> root,
+                               final String category,
+                               final List<String> path,
+                               final Object preferences,
+                               final Map<String, List<String>> parameters,
+                               final boolean isPost) throws ManagementException
+    {
+        _latestManagementController.setPreferences(root, category, path, preferences, parameters, isPost);
+    }
+
+    @Override
+    public int deletePreferences(final ConfiguredObject<?> root,
+                                 final String category,
+                                 final List<String> path,
+                                 final Map<String, List<String>> parameters) throws ManagementException
+    {
+        return _latestManagementController.delete(root, category, path, parameters);
+    }
+
+    @Override
+    public Object formatConfiguredObject(final Object data,
+                                         final Map<String, List<String>> parameters,
+                                         final boolean isSecureOrAllowedOnInsecureChannel)
+    {
+        Object content = data;
+        if (content instanceof LegacyConfiguredObjectObject)
+        {
+
+            content = ((LegacyConfiguredObjectObject) data).getConfiguredObject();
+        }
+        else if (data instanceof Collection)
+        {
+            content = ((Collection<?>) data).stream()
+                                            .filter(o -> o instanceof LegacyConfiguredObjectObject)
+                                            .map(LegacyConfiguredObjectObject.class::cast)
+                                            .map(LegacyConfiguredObjectObject::getConfiguredObject)
+                                            .collect(Collectors.toSet());
+        }
+
+        return _latestManagementController.formatConfiguredObject(content,
+                                                                  parameters,
+                                                                  isSecureOrAllowedOnInsecureChannel);
+    }
+
+    private Class<? extends ConfiguredObject> getRequestCategoryClass(final String categoryName,
+                                                                      final Model model)
+    {
+        for (Class<? extends ConfiguredObject> category : model.getSupportedCategories())
+        {
+            if (category.getSimpleName().toLowerCase().equals(categoryName))
+            {
+                return category;
+            }
+        }
+        throw createNotFoundManagementException(String.format("Category is not found for '%s'", categoryName));
+    }
+
+    private Object convertResponseObject(final Object result)
+    {
+        if (result instanceof ConfiguredObject)
+        {
+            return new LegacyConfiguredObjectObject((ConfiguredObject) result);
+        }
+        else if (result instanceof Collection)
+        {
+            return ((Collection<?>) result).stream().filter(o -> o instanceof ConfiguredObject)
+                                           .map(ConfiguredObject.class::cast)
+                                           .map(o -> new LegacyConfiguredObjectObject((ConfiguredObject<?>) o))
+                                           .collect(Collectors.toSet());
+        }
+        return result;
+    }
+
+    private class LegacyConfiguredObjectObject implements LegacyConfiguredObject
+    {
+        private final Map<String, Object> _actualAttributes;
+        private ConfiguredObject<?> _configuredObject;
+
+        LegacyConfiguredObjectObject(final ConfiguredObject<?> configuredObject)
+        {
+            _configuredObject = configuredObject;
+            _actualAttributes = configuredObject.getActualAttributes();
+        }
+
+        @Override
+        public Collection<String> getAttributeNames()
+        {
+            return _configuredObject.getAttributeNames();
+        }
+
+        @Override
+        public Object getAttribute(final String name)
+        {
+            return convertIntoLegacyIfRequired(name, _configuredObject.getAttribute(name));
+        }
+
+        @Override
+        public Object getActualAttribute(final String name)
+        {
+            return _actualAttributes.get(name);
+        }
+
+        @Override
+        public Collection<LegacyConfiguredObject> getChildren(final String category)
+        {
+            Class<? extends ConfiguredObject> categoryClass =
+                    getRequestCategoryClass(category.toLowerCase(), _configuredObject.getModel());
+            return _configuredObject.getChildren(categoryClass)
+                                    .stream()
+                                    .map(LegacyConfiguredObjectObject::new)
+                                    .collect(Collectors.toSet());
+        }
+
+        @Override
+        public String getCategory()
+        {
+            return _configuredObject.getCategoryClass().getSimpleName();
+        }
+
+        @Override
+        @SuppressWarnings("unchecked")
+        public ManagementResponse invoke(final String operationName,
+                                         final Map<String, Object> parameters,
+                                         final boolean isSecure)
+        {
+            try
+            {
+                final Model model = _configuredObject.getModel();
+                final Map<String, ConfiguredObjectOperation<?>> availableOperations =
+                        model.getTypeRegistry().getOperations(_configuredObject.getClass());
+                final ConfiguredObjectOperation operation = availableOperations.get(operationName);
+                if (operation == null)
+                {
+                    throw createNotFoundManagementException(String.format("No such operation as '%s' in '%s'",
+                                                                          operationName,
+                                                                          getCategory()));
+                }
+
+                Object returnValue = operation.perform(_configuredObject, parameters);
+
+                final ResponseType responseType;
+                if (ConfiguredObject.class.isAssignableFrom(operation.getReturnType()))
+                {
+                    returnValue = new LegacyConfiguredObjectObject((ConfiguredObject<?>) returnValue);
+                    responseType = ResponseType.MODEL_OBJECT;
+                }
+                else if (returnsCollectionOfConfiguredObjects(operation))
+                {
+                    returnValue = ((Collection) returnValue).stream()
+                                                            .map(o -> new LegacyConfiguredObjectObject((ConfiguredObject<?>) o))
+                                                            .collect(Collectors.toSet());
+                    responseType = ResponseType.MODEL_OBJECT;
+                }
+                else
+                {
+                    responseType = ResponseType.DATA;
+                }
+                return new ControllerManagementResponse(responseType, returnValue);
+            }
+            catch (RuntimeException e)
+            {
+                throw ManagementException.toManagementException(e,
+                                                                getCategoryMapping(getCategory()),
+                                                                Collections.emptyList());
+            }
+            catch (Error e)
+            {
+                throw ManagementException.handleError(e);
+            }
+        }
+
+        @Override
+        public LegacyConfiguredObject getNextVersionConfiguredObject()
+        {
+            return null;
+        }
+
+        @Override
+        public LegacyConfiguredObject getParent(final String category)
+        {
+            ConfiguredObject<?> parent = _configuredObject.getParent();
+            if (category != null && !parent.getCategoryClass().getSimpleName().equalsIgnoreCase(category))
+            {
+                throw new IllegalArgumentException(String.format(
+                        "ConfiguredObject of category '%s' has no parent of category %s",
+                        getCategory(),
+                        category));
+            }
+            return new LegacyConfiguredObjectObject(parent);
+        }
+
+        @Override
+        public boolean isSecureAttribute(final String name)
+        {
+            ConfiguredObjectAttribute<?, ?> objectAttribute = getConfiguredObjectAttribute(name);
+            return objectAttribute != null && objectAttribute.isSecure();
+        }
+
+        @Override
+        public boolean isOversizedAttribute(final String name)
+        {
+            ConfiguredObjectAttribute<?, ?> objectAttribute = getConfiguredObjectAttribute(name);
+            return objectAttribute != null && objectAttribute.isOversized();
+        }
+
+        @Override
+        public String getContextValue(final String contextKey)
+        {
+            return _configuredObject.getContextValue(String.class, contextKey);
+        }
+
+        @Override
+        public Map<String, Object> getStatistics()
+        {
+            return _configuredObject.getStatistics();
+        }
+
+        @Override
+        public boolean equals(final Object o)
+        {
+            if (this == o)
+            {
+                return true;
+            }
+            if (o == null || getClass() != o.getClass())
+            {
+                return false;
+            }
+            final LegacyConfiguredObjectObject object = (LegacyConfiguredObjectObject) o;
+            return Objects.equals(_configuredObject, object._configuredObject);
+        }
+
+        @Override
+        public int hashCode()
+        {
+            return _configuredObject.hashCode();
+        }
+
+        ConfiguredObject<?> getConfiguredObject()
+        {
+            return _configuredObject;
+        }
+
+        private Object convertIntoLegacyIfRequired(final String name, final Object value)
+        {
+            if (value != null)
+            {
+                ConfiguredObjectAttribute<?, ?> attribute = getConfiguredObjectAttribute(name);
+
+                final Class<?> type = attribute.getType();
+                final Type genericType = attribute.getGenericType();
+                if (ConfiguredObject.class.isAssignableFrom(type))
+                {
+                    return new LegacyConfiguredObjectObject((ConfiguredObject<?>) value);
+                }
+                else if (Collection.class.isAssignableFrom(type)
+                         && genericType instanceof ParameterizedType
+                         && ConfiguredObject.class.isAssignableFrom(getCollectionMemberType((ParameterizedType) genericType)))
+                {
+                    Collection<?> collection = (Collection<?>) value;
+                    return collection.stream()
+                                     .filter(o -> o instanceof LegacyConfiguredObjectObject)
+                                     .map(LegacyConfiguredObjectObject.class::cast)
+                                     .map(co -> new LegacyConfiguredObjectObject((ConfiguredObject<?>) co))
+                                     .collect(Collectors.toSet());
+                }
+            }
+            return value;
+        }
+
+
+        private ConfiguredObjectAttribute<?, ?> getConfiguredObjectAttribute(final String name)
+        {
+            return _configuredObject.getModel()
+                                    .getTypeRegistry()
+                                    .getAttributeTypes(_configuredObject.getClass())
+                                    .get(name);
+        }
+    }
+}
diff --git a/broker-plugins/management-http/src/main/java/org/apache/qpid/server/management/plugin/controller/v7_0/LegacyManagementController.java b/broker-plugins/management-http/src/main/java/org/apache/qpid/server/management/plugin/controller/v7_0/LegacyManagementController.java
new file mode 100644
index 0000000..44ddf03
--- /dev/null
+++ b/broker-plugins/management-http/src/main/java/org/apache/qpid/server/management/plugin/controller/v7_0/LegacyManagementController.java
@@ -0,0 +1,80 @@
+/*
+ *
+ * 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.
+ *
+ */
+package org.apache.qpid.server.management.plugin.controller.v7_0;
+
+import static org.apache.qpid.server.management.plugin.controller.ConverterHelper.getIntParameterFromRequest;
+import static org.apache.qpid.server.management.plugin.controller.ConverterHelper.getParameter;
+
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
+
+import org.apache.qpid.server.management.plugin.ManagementController;
+import org.apache.qpid.server.management.plugin.controller.AbstractLegacyConfiguredObjectController;
+import org.apache.qpid.server.management.plugin.controller.LegacyConfiguredObject;
+
+public class LegacyManagementController extends AbstractLegacyConfiguredObjectController
+{
+    private static final String DEPTH_PARAM = "depth";
+    private static final String OVERSIZE_PARAM = "oversize";
+    private static final String ACTUALS_PARAM = "actuals";
+    private static final String EXCLUDE_INHERITED_CONTEXT_PARAM = "excludeInheritedContext";
+    private static final String SINGLETON_MODEL_OBJECT_RESPONSE_AS_LIST = "singletonModelObjectResponseAsList";
+
+    private static final int DEFAULT_DEPTH = 0;
+    private static final int DEFAULT_OVERSIZE = 120;
+
+    public LegacyManagementController(final ManagementController nextVersionManagementController)
+    {
+        super(LegacyManagementControllerFactory.MODEL_VERSION, nextVersionManagementController);
+    }
+
+    @Override
+    protected Map<String, List<String>> convertQueryParameters(final Map<String, List<String>> parameters)
+    {
+        return parameters;
+    }
+
+    @Override
+    public Object formatConfiguredObject(final Object content,
+                                         final Map<String, List<String>> parameters,
+                                         final boolean isSecureOrAllowedOnInsecureChannel)
+    {
+        final int depth = getIntParameterFromRequest(parameters, DEPTH_PARAM, DEFAULT_DEPTH);
+        final int oversizeThreshold = getIntParameterFromRequest(parameters, OVERSIZE_PARAM, DEFAULT_OVERSIZE);
+        final boolean actuals = Boolean.parseBoolean(getParameter(ACTUALS_PARAM, parameters));
+        final String excludeInheritedContextParameter = getParameter(EXCLUDE_INHERITED_CONTEXT_PARAM, parameters);
+        final boolean excludeInheritedContext = excludeInheritedContextParameter == null
+                                                || Boolean.parseBoolean(excludeInheritedContextParameter);
+        final boolean responseAsList =
+                Boolean.parseBoolean(getParameter(SINGLETON_MODEL_OBJECT_RESPONSE_AS_LIST, parameters));
+
+        return formatConfiguredObject(content,
+                                      isSecureOrAllowedOnInsecureChannel,
+                                      depth,
+                                      oversizeThreshold,
+                                      actuals,
+                                      excludeInheritedContext,
+                                      responseAsList);
+    }
+}
diff --git a/broker-plugins/management-http/src/main/java/org/apache/qpid/server/management/plugin/controller/v7_0/LegacyManagementControllerFactory.java b/broker-plugins/management-http/src/main/java/org/apache/qpid/server/management/plugin/controller/v7_0/LegacyManagementControllerFactory.java
new file mode 100644
index 0000000..1b26e92
--- /dev/null
+++ b/broker-plugins/management-http/src/main/java/org/apache/qpid/server/management/plugin/controller/v7_0/LegacyManagementControllerFactory.java
@@ -0,0 +1,62 @@
+/*
+ *
+ * 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.
+ *
+ */
+package org.apache.qpid.server.management.plugin.controller.v7_0;
+
+import org.apache.qpid.server.management.plugin.HttpManagementConfiguration;
+import org.apache.qpid.server.management.plugin.ManagementController;
+import org.apache.qpid.server.management.plugin.ManagementControllerFactory;
+import org.apache.qpid.server.management.plugin.controller.CategoryControllerFactory;
+import org.apache.qpid.server.management.plugin.controller.TypeControllerFactory;
+import org.apache.qpid.server.plugin.PluggableService;
+
+@PluggableService
+public class LegacyManagementControllerFactory implements ManagementControllerFactory
+{
+    public static final String MODEL_VERSION = "7.0";
+
+    @Override
+    public String getType()
+    {
+        return "org.apache.qpid.server.management.plugin.model.v7_0";
+    }
+
+    @Override
+    public String getVersion()
+    {
+        return MODEL_VERSION;
+    }
+
+    @Override
+    public String getPreviousVersion()
+    {
+        return "6.1";
+    }
+
+    @Override
+    public ManagementController createManagementController(final HttpManagementConfiguration<?> httpManagement,
+                                                           final ManagementController nextVersionManagementController)
+    {
+
+        LegacyManagementController controller = new LegacyManagementController(nextVersionManagementController);
+        controller.initialize();
+        return controller;
+    }
+}
diff --git a/broker-plugins/management-http/src/main/java/org/apache/qpid/server/management/plugin/controller/v7_0/category/ContainerController.java b/broker-plugins/management-http/src/main/java/org/apache/qpid/server/management/plugin/controller/v7_0/category/ContainerController.java
new file mode 100644
index 0000000..44a34e5
--- /dev/null
+++ b/broker-plugins/management-http/src/main/java/org/apache/qpid/server/management/plugin/controller/v7_0/category/ContainerController.java
@@ -0,0 +1,80 @@
+/*
+ *
+ * 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.
+ *
+ */
+package org.apache.qpid.server.management.plugin.controller.v7_0.category;
+
+
+import java.util.Set;
+
+import org.apache.qpid.server.management.plugin.controller.GenericLegacyConfiguredObject;
+import org.apache.qpid.server.management.plugin.controller.LegacyConfiguredObject;
+import org.apache.qpid.server.management.plugin.controller.LegacyManagementController;
+import org.apache.qpid.server.management.plugin.controller.TypeController;
+
+
+class ContainerController extends LegacyCategoryController
+{
+    ContainerController(final LegacyManagementController legacyManagementController,
+                        final String type,
+                        final String parentType,
+                        final String defaultType,
+                        final Set<TypeController> typeControllers)
+    {
+        super(legacyManagementController, type, parentType, defaultType, typeControllers);
+    }
+
+    @Override
+    protected LegacyConfiguredObject convertNextVersionLegacyConfiguredObject(final LegacyConfiguredObject object)
+    {
+        return new LegacyContainer(getManagementController(), object, getCategory());
+    }
+
+    static class LegacyContainer extends GenericLegacyConfiguredObject
+    {
+        private static final String MODEL_VERSION = "modelVersion";
+
+        LegacyContainer(final LegacyManagementController managementController,
+                        final LegacyConfiguredObject nextVersionLegacyConfiguredObject,
+                        final String category)
+        {
+            super(managementController, nextVersionLegacyConfiguredObject, category);
+        }
+
+        @Override
+        public Object getAttribute(final String name)
+        {
+            if (MODEL_VERSION.equals(name))
+            {
+                return getManagementController().getVersion();
+            }
+            return super.getAttribute(name);
+        }
+
+        @Override
+        public Object getActualAttribute(final String name)
+        {
+            if (MODEL_VERSION.equals(name))
+            {
+                return getManagementController().getVersion();
+            }
+            return super.getActualAttribute(name);
+        }
+    }
+}
diff --git a/broker-plugins/management-http/src/main/java/org/apache/qpid/server/management/plugin/controller/v7_0/category/LegacyCategoryController.java b/broker-plugins/management-http/src/main/java/org/apache/qpid/server/management/plugin/controller/v7_0/category/LegacyCategoryController.java
new file mode 100644
index 0000000..82ace6d
--- /dev/null
+++ b/broker-plugins/management-http/src/main/java/org/apache/qpid/server/management/plugin/controller/v7_0/category/LegacyCategoryController.java
@@ -0,0 +1,63 @@
+/*
+ *
+ * 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.
+ *
+ */
+package org.apache.qpid.server.management.plugin.controller.v7_0.category;
+
+import java.util.Set;
+
+import org.apache.qpid.server.management.plugin.controller.GenericCategoryController;
+import org.apache.qpid.server.management.plugin.controller.GenericLegacyConfiguredObject;
+import org.apache.qpid.server.management.plugin.controller.LegacyConfiguredObject;
+import org.apache.qpid.server.management.plugin.controller.LegacyManagementController;
+import org.apache.qpid.server.management.plugin.controller.TypeController;
+import org.apache.qpid.server.management.plugin.controller.latest.LatestManagementControllerAdapter;
+
+public class LegacyCategoryController extends GenericCategoryController
+{
+    private final String _parentCategory;
+
+    LegacyCategoryController(final LegacyManagementController managementController,
+                             final String name,
+                             final String parentCategory,
+                             final String defaultType,
+                             final Set<TypeController> typeControllers)
+    {
+        super(managementController,
+              new LatestManagementControllerAdapter(managementController.getNextVersionManagementController()),
+              name,
+              defaultType,
+              typeControllers);
+        _parentCategory = parentCategory;
+    }
+
+    @Override
+    public String[] getParentCategories()
+    {
+        return new String[]{_parentCategory};
+    }
+
+    @Override
+    protected LegacyConfiguredObject convertNextVersionLegacyConfiguredObject(final LegacyConfiguredObject object)
+    {
+        return new GenericLegacyConfiguredObject(getManagementController(),
+                                                 object,
+                                                 getCategory());
+    }
+}
diff --git a/broker-plugins/management-http/src/main/java/org/apache/qpid/server/management/plugin/controller/v7_0/category/LegacyCategoryControllerFactory.java b/broker-plugins/management-http/src/main/java/org/apache/qpid/server/management/plugin/controller/v7_0/category/LegacyCategoryControllerFactory.java
new file mode 100644
index 0000000..3f07cc3
--- /dev/null
+++ b/broker-plugins/management-http/src/main/java/org/apache/qpid/server/management/plugin/controller/v7_0/category/LegacyCategoryControllerFactory.java
@@ -0,0 +1,154 @@
+/*
+ *
+ * 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.
+ *
+ */
+package org.apache.qpid.server.management.plugin.controller.v7_0.category;
+
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Set;
+
+import org.apache.qpid.server.management.plugin.controller.CategoryController;
+import org.apache.qpid.server.management.plugin.controller.CategoryControllerFactory;
+import org.apache.qpid.server.management.plugin.controller.LegacyManagementController;
+import org.apache.qpid.server.management.plugin.controller.v7_0.LegacyManagementControllerFactory;
+import org.apache.qpid.server.plugin.PluggableService;
+
+@PluggableService
+public class LegacyCategoryControllerFactory implements CategoryControllerFactory
+{
+    static final String CATEGORY_BROKER = "Broker";
+    private static final String CATEGORY_BROKER_LOGGER = "BrokerLogger";
+    private static final String CATEGORY_BROKER_LOG_INCLUSION_RULE = "BrokerLogInclusionRule";
+    private static final String CATEGORY_AUTHENTICATION_PROVIDER = "AuthenticationProvider";
+    private static final String CATEGORY_USER = "User";
+    private static final String CATEGORY_ACCESS_CONTROL_PROVIDER = "AccessControlProvider";
+    private static final String CATEGORY_PLUGIN = "Plugin";
+    private static final String CATEGORY_TRUST_STORE = "TrustStore";
+    private static final String CATEGORY_KEY_STORE = "KeyStore";
+    private static final String CATEGORY_PORT = "Port";
+    private static final String CATEGORY_VIRTUAL_HOST_ALIAS = "VirtualHostAlias";
+    private static final String CATEGORY_GROUP_PROVIDER = "GroupProvider";
+    private static final String CATEGORY_GROUP = "Group";
+    private static final String CATEGORY_GROUP_MEMBER = "GroupMember";
+    private static final String CATEGORY_VIRTUAL_HOST_NODE = "VirtualHostNode";
+    private static final String CATEGORY_REMOTE_REPLICATION_NODE = "RemoteReplicationNode";
+    static final String CATEGORY_VIRTUAL_HOST = "VirtualHost";
+    private static final String CATEGORY_VIRTUAL_HOST_LOGGER = "VirtualHostLogger";
+    private static final String CATEGORY_VIRTUAL_HOST_LOG_INCLUSION_RULE = "VirtualHostLogInclusionRule";
+    private static final String CATEGORY_VIRTUAL_HOST_ACCESS_CONTROL_PROVIDER = "VirtualHostAccessControlProvider";
+    private static final String CATEGORY_EXCHANGE = "Exchange";
+    private static final String CATEGORY_QUEUE = "Queue";
+    private static final String CATEGORY_CONSUMER = "Consumer";
+    private static final String CATEGORY_CONNECTION = "Connection";
+    private static final String CATEGORY_SESSION = "Session";
+    private static final String CATEGORY_SYSTEM_CONFIG = "SystemConfig";
+    static final Map<String, String> SUPPORTED_CATEGORIES =
+            Collections.unmodifiableMap(new HashMap<String, String>()
+            {
+                {
+                    put(CATEGORY_BROKER_LOGGER, CATEGORY_BROKER);
+                    put(CATEGORY_BROKER_LOG_INCLUSION_RULE, CATEGORY_BROKER_LOGGER);
+                    put(CATEGORY_AUTHENTICATION_PROVIDER, CATEGORY_BROKER);
+                    put(CATEGORY_USER, CATEGORY_AUTHENTICATION_PROVIDER);
+                    put(CATEGORY_ACCESS_CONTROL_PROVIDER, CATEGORY_BROKER);
+                    put(CATEGORY_PLUGIN, CATEGORY_BROKER);
+                    put(CATEGORY_TRUST_STORE, CATEGORY_BROKER);
+                    put(CATEGORY_KEY_STORE, CATEGORY_BROKER);
+                    put(CATEGORY_PORT, CATEGORY_BROKER);
+                    put(CATEGORY_VIRTUAL_HOST_ALIAS, CATEGORY_PORT);
+                    put(CATEGORY_GROUP_PROVIDER, CATEGORY_BROKER);
+                    put(CATEGORY_GROUP, CATEGORY_GROUP_PROVIDER);
+                    put(CATEGORY_GROUP_MEMBER, CATEGORY_GROUP);
+                    put(CATEGORY_VIRTUAL_HOST_NODE, CATEGORY_BROKER);
+                    put(CATEGORY_REMOTE_REPLICATION_NODE, CATEGORY_VIRTUAL_HOST_NODE);
+                    put(CATEGORY_VIRTUAL_HOST, CATEGORY_VIRTUAL_HOST_NODE);
+                    put(CATEGORY_VIRTUAL_HOST_LOGGER, CATEGORY_VIRTUAL_HOST);
+                    put(CATEGORY_VIRTUAL_HOST_LOG_INCLUSION_RULE, CATEGORY_VIRTUAL_HOST_LOGGER);
+                    put(CATEGORY_VIRTUAL_HOST_ACCESS_CONTROL_PROVIDER, CATEGORY_VIRTUAL_HOST);
+                    put(CATEGORY_EXCHANGE, CATEGORY_VIRTUAL_HOST);
+                    put(CATEGORY_QUEUE, CATEGORY_VIRTUAL_HOST);
+                    put(CATEGORY_CONSUMER, CATEGORY_QUEUE);
+                    put(CATEGORY_CONNECTION, CATEGORY_VIRTUAL_HOST);
+                    put(CATEGORY_SESSION, CATEGORY_CONNECTION);
+                    put(CATEGORY_BROKER, CATEGORY_SYSTEM_CONFIG);
+                }
+            });
+
+    private static final Map<String, String> DEFAULT_TYPES = Collections.unmodifiableMap(new HashMap<String, String>()
+    {
+        {
+            put(CATEGORY_BROKER_LOGGER, "Broker");
+            put(CATEGORY_TRUST_STORE, "FileTrustStore");
+            put(CATEGORY_KEY_STORE, "FileKeyStore");
+            put(CATEGORY_GROUP, "ManagedGroup");
+            put(CATEGORY_GROUP_MEMBER, "ManagedGroupMember");
+            put(CATEGORY_VIRTUAL_HOST, "ProvidedStore");
+            put(CATEGORY_QUEUE, "standard");
+        }
+    });
+
+    @Override
+    public CategoryController createController(final String type,
+                                               final LegacyManagementController legacyManagementController)
+    {
+        if (SUPPORTED_CATEGORIES.containsKey(type))
+        {
+            if (CATEGORY_VIRTUAL_HOST.equals(type) || CATEGORY_BROKER.equals(type))
+            {
+                return new ContainerController(legacyManagementController,
+                                               type,
+                                               SUPPORTED_CATEGORIES.get(type),
+                                               DEFAULT_TYPES.get(type),
+                                               legacyManagementController.getTypeControllersByCategory(type));
+            }
+            else
+            {
+                return new LegacyCategoryController(legacyManagementController,
+                                                    type,
+                                                    SUPPORTED_CATEGORIES.get(type),
+                                                    DEFAULT_TYPES.get(type),
+                                                    legacyManagementController.getTypeControllersByCategory(type));
+            }
+        }
+        else
+        {
+            throw new IllegalArgumentException(String.format("Unsupported type '%s'", type));
+        }
+    }
+
+    @Override
+    public Set<String> getSupportedCategories()
+    {
+        return SUPPORTED_CATEGORIES.keySet();
+    }
+
+    @Override
+    public String getModelVersion()
+    {
+        return LegacyManagementControllerFactory.MODEL_VERSION;
+    }
+
+    @Override
+    public String getType()
+    {
+        return LegacyCategoryControllerFactory.class.getName();
+    }
+}
diff --git a/broker-plugins/management-http/src/test/java/org/apache/qpid/server/management/plugin/controller/AbstractLegacyConfiguredObjectControllerTest.java b/broker-plugins/management-http/src/test/java/org/apache/qpid/server/management/plugin/controller/AbstractLegacyConfiguredObjectControllerTest.java
new file mode 100644
index 0000000..8df9aea
--- /dev/null
+++ b/broker-plugins/management-http/src/test/java/org/apache/qpid/server/management/plugin/controller/AbstractLegacyConfiguredObjectControllerTest.java
@@ -0,0 +1,292 @@
+/*
+ *
+ * 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.
+ *
+ */
+package org.apache.qpid.server.management.plugin.controller;
+
+import static org.hamcrest.CoreMatchers.equalTo;
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.CoreMatchers.notNullValue;
+import static org.junit.Assert.assertThat;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import org.junit.Before;
+import org.junit.Test;
+
+import org.apache.qpid.server.management.plugin.ManagementController;
+import org.apache.qpid.server.management.plugin.ManagementRequest;
+import org.apache.qpid.server.management.plugin.ManagementResponse;
+import org.apache.qpid.server.management.plugin.RequestType;
+import org.apache.qpid.server.management.plugin.ResponseType;
+import org.apache.qpid.server.model.ConfiguredObject;
+import org.apache.qpid.test.utils.UnitTestBase;
+
+public class AbstractLegacyConfiguredObjectControllerTest extends UnitTestBase
+{
+
+    private static final String TEST_VERSION = "testVersion";
+    private static final String TEST_CATEGORY = "testCategory";
+    private static final String TEST_CATEGORY_2 = "testCategory2";
+    private static final String TEST_TYPE = "testType";
+    private ManagementController _nextVersionManagementController;
+    private AbstractLegacyConfiguredObjectController _controller;
+    private CategoryController _categoryController2;
+    private ConfiguredObject<?> _root;
+    private TypeController _typeController;
+
+    @Before
+    public void setUp()
+    {
+        _nextVersionManagementController = mock(ManagementController.class);
+        _controller = new AbstractLegacyConfiguredObjectController(TEST_VERSION, _nextVersionManagementController)
+        {
+            @Override
+            public Object formatConfiguredObject(final Object content,
+                                                 final Map<String, List<String>> parameters,
+                                                 final boolean isSecureOrAllowedOnInsecureChannel)
+            {
+                return null;
+            }
+
+            @Override
+            protected Map<String, List<String>> convertQueryParameters(final Map<String, List<String>> parameters)
+            {
+                return parameters;
+            }
+        };
+        _root = mock(ConfiguredObject.class);
+        final CategoryControllerFactory categoryFactory = mock(CategoryControllerFactory.class);
+        final TypeControllerFactory typeFactory = mock(TypeControllerFactory.class);
+        final CategoryController categoryController = mock(CategoryController.class);
+        when(categoryController.getParentCategories()).thenReturn(new String[0]);
+        when(categoryController.getCategory()).thenReturn(TEST_CATEGORY);
+        _categoryController2 = mock(CategoryController.class);
+        when(_categoryController2.getCategory()).thenReturn(TEST_CATEGORY_2);
+        when(_categoryController2.getParentCategories()).thenReturn(new String[]{TEST_CATEGORY});
+
+        when(categoryFactory.getModelVersion()).thenReturn(TEST_VERSION);
+        when(categoryFactory.getSupportedCategories()).thenReturn(new HashSet<>(Arrays.asList(TEST_CATEGORY,
+                                                                                              TEST_CATEGORY_2)));
+        when(categoryFactory.createController(TEST_CATEGORY, _controller)).thenReturn(categoryController);
+        when(categoryFactory.createController(TEST_CATEGORY_2, _controller)).thenReturn(_categoryController2);
+        when(typeFactory.getCategory()).thenReturn(TEST_CATEGORY);
+        when(typeFactory.getModelVersion()).thenReturn(TEST_VERSION);
+        _typeController = mock(TypeController.class);
+        when(_typeController.getTypeName()).thenReturn(TEST_TYPE);
+        when(typeFactory.createController(_controller)).thenReturn(_typeController);
+        _controller.initialize(Collections.singleton(categoryFactory), Collections.singleton(typeFactory));
+    }
+
+    @Test
+    public void getVersion()
+    {
+        assertThat(_controller.getVersion(), is(equalTo(TEST_VERSION)));
+    }
+
+    @Test
+    public void getCategories()
+    {
+        Collection<String> categories = _controller.getCategories();
+        Set<String> expected = new HashSet<>(Arrays.asList(TEST_CATEGORY, TEST_CATEGORY_2));
+        assertThat(new HashSet<>(categories), is(equalTo(expected)));
+    }
+
+    @Test
+    public void getCategoryMapping()
+    {
+        assertThat(_controller.getCategoryMapping(TEST_CATEGORY), is(equalTo(
+                String.format("/api/v%s/%s/", TEST_VERSION, TEST_CATEGORY.toLowerCase()))));
+    }
+
+    @Test
+    public void getCategory()
+    {
+        final ConfiguredObject<?> managementObject = mock(ConfiguredObject.class);
+        when(_nextVersionManagementController.getCategory(managementObject)).thenReturn(TEST_CATEGORY);
+        String category = _controller.getCategory(managementObject);
+        assertThat(category, is(equalTo(TEST_CATEGORY)));
+    }
+
+    @Test
+    public void getCategoryHierarchy()
+    {
+        when(_nextVersionManagementController.getCategory(_root)).thenReturn(TEST_CATEGORY);
+        final List<String> hierarchy = _controller.getCategoryHierarchy(_root, TEST_CATEGORY_2);
+
+        final Set<String> expected = Collections.singleton(TEST_CATEGORY_2);
+        assertThat(new HashSet<>(hierarchy), is(equalTo(expected)));
+    }
+
+    @Test
+    public void getNextVersionManagementController()
+    {
+        assertThat(_controller.getNextVersionManagementController(), is(equalTo(_nextVersionManagementController)));
+    }
+
+    @Test
+    public void createOrUpdate()
+    {
+        final List<String> path = Collections.singletonList("test");
+        final Map<String, Object> attributes = Collections.singletonMap("name", "test");
+        final LegacyConfiguredObject object = mock(LegacyConfiguredObject.class);
+        when(_categoryController2.createOrUpdate(_root, path, attributes, false)).thenReturn(object);
+        final LegacyConfiguredObject result =
+                _controller.createOrUpdate(_root, TEST_CATEGORY_2, path, attributes, false);
+        assertThat(result, is(equalTo(object)));
+    }
+
+    @Test
+    public void get()
+    {
+        final List<String> path = Collections.singletonList("test");
+        final LegacyConfiguredObject object = mock(LegacyConfiguredObject.class);
+        final Map<String, List<String>> parameters =
+                Collections.singletonMap("name", Collections.singletonList("test"));
+        when(_categoryController2.get(_root, path, parameters)).thenReturn(object);
+        final Object result = _controller.get(_root, TEST_CATEGORY_2, path, parameters);
+        assertThat(result, is(equalTo(object)));
+    }
+
+    @Test
+    public void delete()
+    {
+        final List<String> path = Collections.singletonList("test");
+        final Map<String, List<String>> parameters =
+                Collections.singletonMap("name", Collections.singletonList("test"));
+        _controller.delete(_root, TEST_CATEGORY_2, path, parameters);
+        verify(_categoryController2).delete(_root, path, parameters);
+    }
+
+    @Test
+    public void invoke()
+    {
+        final List<String> path = Collections.singletonList("test");
+        final Map<String, Object> parameters = Collections.singletonMap("name", "test");
+        final Object operationResult = mock(Object.class);
+
+        final String operationName = "testOperation";
+
+        final ManagementResponse managementResponse =
+                new ControllerManagementResponse(ResponseType.DATA, operationResult);
+        when(_categoryController2.invoke(_root, path, operationName, parameters, true, true)).thenReturn(
+                managementResponse);
+        final ManagementResponse result =
+                _controller.invoke(_root, TEST_CATEGORY_2, path, operationName, parameters, true, true);
+
+        assertThat(result, is(notNullValue()));
+        assertThat(result.getResponseCode(), is(equalTo(200)));
+        assertThat(result.getBody(), is(equalTo(operationResult)));
+        verify(_categoryController2).invoke(_root, path, operationName, parameters, true, true);
+    }
+
+    @Test
+    public void getPreferences()
+    {
+        final List<String> path = Arrays.asList("test", "preferences");
+        final Map<String, List<String>> parameters =
+                Collections.singletonMap("name", Collections.singletonList("test"));
+        final Object prefs = mock(Object.class);
+        when(_categoryController2.getPreferences(_root, path, parameters)).thenReturn(prefs);
+        final Object preferences = _controller.getPreferences(_root, TEST_CATEGORY_2, path, parameters);
+        assertThat(preferences, is(equalTo(prefs)));
+        verify(_categoryController2).getPreferences(_root, path, parameters);
+    }
+
+    @Test
+    public void setPreferences()
+    {
+        final List<String> path = Arrays.asList("test", "preferences");
+        final Map<String, List<String>> parameters =
+                Collections.singletonMap("name", Collections.singletonList("test"));
+        final Object prefs = mock(Object.class);
+        _controller.setPreferences(_root, TEST_CATEGORY_2, path, prefs, parameters, true);
+        verify(_categoryController2).setPreferences(_root, path, prefs, parameters, true);
+    }
+
+    @Test
+    public void deletePreferences()
+    {
+        final List<String> path = Arrays.asList("test", "preferences");
+        final Map<String, List<String>> parameters =
+                Collections.singletonMap("name", Collections.singletonList("test"));
+        _controller.deletePreferences(_root, TEST_CATEGORY_2, path, parameters);
+        verify(_categoryController2).deletePreferences(_root, path, parameters);
+    }
+
+    @Test
+    public void getCategoryController()
+    {
+        assertThat(_controller.getCategoryController(TEST_CATEGORY_2), is(equalTo(_categoryController2)));
+    }
+
+    @Test
+    public void getTypeControllersByCategory()
+    {
+        final Set<TypeController> typeControllers = _controller.getTypeControllersByCategory(TEST_CATEGORY);
+        assertThat(typeControllers, is(equalTo(Collections.singleton(_typeController))));
+    }
+
+    @Test
+    public void getChildrenCategories()
+    {
+        final Collection<String> childrenCategories = _controller.getChildrenCategories(TEST_CATEGORY);
+        assertThat(new HashSet<>(childrenCategories), is(equalTo(Collections.singleton(TEST_CATEGORY_2))));
+    }
+
+    @Test
+    public void getParentTypes()
+    {
+        final Collection<String> childrenCategories = _controller.getParentTypes(TEST_CATEGORY_2);
+        assertThat(new HashSet<>(childrenCategories), is(equalTo(Collections.singleton(TEST_CATEGORY))));
+    }
+
+    @Test
+    public void getNextVersionLegacyConfiguredObjectConverter()
+    {
+        assertThat(_controller.getNextVersionManagementController(), is(equalTo(_nextVersionManagementController)));
+    }
+
+    @Test
+    public void getRequestType()
+    {
+        final Map<String, List<String>> parameters =
+                Collections.singletonMap("name", Collections.singletonList("test"));
+        final ManagementRequest managementRequest = mock(ManagementRequest.class);
+        doReturn(_root).when(managementRequest).getRoot();
+        when(managementRequest.getMethod()).thenReturn("GET");
+        when(managementRequest.getParameters()).thenReturn(parameters);
+        when(managementRequest.getPath()).thenReturn(Collections.singletonList("test"));
+        when(managementRequest.getCategory()).thenReturn(TEST_CATEGORY_2);
+        when(_controller.getCategory(_root)).thenReturn(TEST_CATEGORY);
+
+        final RequestType requestType = _controller.getRequestType(managementRequest);
+        assertThat(requestType, is(equalTo(RequestType.MODEL_OBJECT)));
+    }
+}
diff --git a/broker-plugins/management-http/src/test/java/org/apache/qpid/server/management/plugin/controller/GenericCategoryControllerTest.java b/broker-plugins/management-http/src/test/java/org/apache/qpid/server/management/plugin/controller/GenericCategoryControllerTest.java
new file mode 100644
index 0000000..1f336ae
--- /dev/null
+++ b/broker-plugins/management-http/src/test/java/org/apache/qpid/server/management/plugin/controller/GenericCategoryControllerTest.java
@@ -0,0 +1,247 @@
+/*
+ *
+ * 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.
+ *
+ */
+package org.apache.qpid.server.management.plugin.controller;
+
+import static org.hamcrest.CoreMatchers.equalTo;
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.CoreMatchers.notNullValue;
+import static org.junit.Assert.assertThat;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+
+import org.junit.Before;
+import org.junit.Test;
+
+import org.apache.qpid.server.management.plugin.ManagementResponse;
+import org.apache.qpid.server.management.plugin.ResponseType;
+import org.apache.qpid.server.model.ConfiguredObject;
+import org.apache.qpid.test.utils.UnitTestBase;
+
+public class GenericCategoryControllerTest extends UnitTestBase
+{
+    private static final String TEST_CATEGORY = "testCategory";
+    private static final String DEFAULT_TYPE = "defaultType";
+    private LegacyManagementController _managementController;
+    private LegacyManagementController _nextVersionManagementController;
+    private GenericCategoryController _controller;
+    private ConfiguredObject _root;
+    private TypeController _typeController;
+    private LegacyConfiguredObject _converted;
+
+    @Before
+    public void setUp()
+    {
+        _managementController = mock(LegacyManagementController.class);
+        _nextVersionManagementController = mock(LegacyManagementController.class);
+        _typeController = mock(TypeController.class);
+        when(_typeController.getTypeName()).thenReturn(DEFAULT_TYPE);
+        when(_typeController.getNextVersionTypeName()).thenReturn(DEFAULT_TYPE);
+        _converted = mock(LegacyConfiguredObject.class);
+        _controller = new GenericCategoryController(_managementController,
+                                                    _nextVersionManagementController,
+                                                    TEST_CATEGORY,
+                                                    DEFAULT_TYPE,
+                                                    Collections.singleton(_typeController))
+        {
+            @Override
+            protected LegacyConfiguredObject convertNextVersionLegacyConfiguredObject(final LegacyConfiguredObject object)
+            {
+                return _converted;
+            }
+
+            @Override
+            public String[] getParentCategories()
+            {
+                return new String[0];
+            }
+        };
+        _root = mock(ConfiguredObject.class);
+    }
+
+    @Test
+    public void getCategory()
+    {
+        assertThat(_controller.getCategory(), is(equalTo(TEST_CATEGORY)));
+    }
+
+    @Test
+    public void getDefaultType()
+    {
+        assertThat(_controller.getDefaultType(), is(equalTo(DEFAULT_TYPE)));
+    }
+
+    @Test
+    public void getManagementController()
+    {
+        assertThat(_controller.getManagementController(), is(equalTo(_managementController)));
+    }
+
+    @Test
+    public void get()
+    {
+        final List<String> path = Arrays.asList("test1", "test2");
+        final Map<String, List<String>> parameters =
+                Collections.singletonMap("testParam", Collections.singletonList("testValue"));
+        final LegacyConfiguredObject result = mock(LegacyConfiguredObject.class);
+        when(result.getAttribute(LegacyConfiguredObject.TYPE)).thenReturn(DEFAULT_TYPE);
+        final LegacyConfiguredObject convertedResult = mock(LegacyConfiguredObject.class);
+        when(_nextVersionManagementController.get(_root, TEST_CATEGORY, path, parameters)).thenReturn(result);
+        when(_typeController.convertFromNextVersion(result)).thenReturn(convertedResult);
+        final Object readResult = _controller.get(_root, path, parameters);
+        assertThat(readResult, is(equalTo(convertedResult)));
+        verify(_nextVersionManagementController).get(_root, TEST_CATEGORY, path, parameters);
+    }
+
+    @Test
+    public void createOrUpdate()
+    {
+        final List<String> path = Arrays.asList("test1", "test2");
+        final Map<String, Object> attributes = Collections.singletonMap("test", "testValue");
+        final LegacyConfiguredObject result = mock(LegacyConfiguredObject.class);
+        final LegacyConfiguredObject convertedResult = mock(LegacyConfiguredObject.class);
+        when(result.getAttribute(LegacyConfiguredObject.TYPE)).thenReturn(DEFAULT_TYPE);
+
+        when(_typeController.convertAttributesToNextVersion(_root, path, attributes)).thenReturn(attributes);
+        when(_typeController.convertFromNextVersion(result)).thenReturn(convertedResult);
+        when(_nextVersionManagementController.createOrUpdate(_root, TEST_CATEGORY, path, attributes, false))
+                .thenReturn(result);
+        when(_typeController.convertFromNextVersion(result)).thenReturn(convertedResult);
+        final Object createResult = _controller.createOrUpdate(_root, path, attributes, false);
+        assertThat(createResult, is(equalTo(convertedResult)));
+        verify(_nextVersionManagementController).createOrUpdate(_root, TEST_CATEGORY, path, attributes, false);
+    }
+
+    @Test
+    public void delete()
+    {
+        final List<String> path = Arrays.asList("test1", "test2");
+        final Map<String, List<String>> parameters =
+                Collections.singletonMap("testParam", Collections.singletonList("testValue"));
+
+        final int result = 1;
+        when(_nextVersionManagementController.delete(_root, TEST_CATEGORY, path, parameters)).thenReturn(result);
+        final Object deleteResult = _controller.delete(_root, path, parameters);
+        assertThat(deleteResult, is(equalTo(result)));
+        verify(_nextVersionManagementController).delete(_root, TEST_CATEGORY, path, parameters);
+    }
+
+    @Test
+    public void invoke()
+    {
+        final List<String> path = Arrays.asList("test1", "test2");
+        final String operationName = "testOperation";
+        final Map<String, Object> operationParameters = Collections.singletonMap("testParam", "testValue");
+
+        final LegacyConfiguredObject result = mock(LegacyConfiguredObject.class);
+        when(result.getAttribute(LegacyConfiguredObject.TYPE)).thenReturn(DEFAULT_TYPE);
+        final LegacyConfiguredObject convertedResult = mock(LegacyConfiguredObject.class);
+        when(_nextVersionManagementController.get(eq(_root),
+                                                  eq(TEST_CATEGORY),
+                                                  eq(path),
+                                                  eq(Collections.emptyMap()))).thenReturn(result);
+        when(_typeController.convertFromNextVersion(result)).thenReturn(convertedResult);
+
+
+        final Object operationValue = "testValue";
+        final ManagementResponse operationResult = new ControllerManagementResponse(ResponseType.DATA, operationValue);
+        when(convertedResult.invoke(operationName, operationParameters, true)).thenReturn(operationResult);
+        final ManagementResponse response =
+                _controller.invoke(_root, path, operationName, operationParameters, true, true);
+        assertThat(response, is(notNullValue()));
+        assertThat(response.getResponseCode(), is(equalTo(200)));
+        assertThat(response.getBody(), is(equalTo(operationValue)));
+        verify(_nextVersionManagementController).get(_root, TEST_CATEGORY, path, Collections.emptyMap());
+        verify(convertedResult).invoke(operationName, operationParameters, true);
+    }
+
+    @Test
+    public void getPreferences()
+    {
+        final List<String> path = Arrays.asList("test1", "test2");
+        final Map<String, List<String>> parameters =
+                Collections.singletonMap("testParam", Collections.singletonList("testValue"));
+
+        final Object result = mock(Object.class);
+        when(_nextVersionManagementController.getPreferences(_root,
+                                                             TEST_CATEGORY,
+                                                             path,
+                                                             parameters)).thenReturn(result);
+
+        final Object preferences = _controller.getPreferences(_root, path, parameters);
+        assertThat(preferences, is(equalTo(result)));
+        verify(_nextVersionManagementController).getPreferences(_root, TEST_CATEGORY, path, parameters);
+    }
+
+    @Test
+    public void setPreferences()
+    {
+        final List<String> path = Arrays.asList("test1", "test2");
+        final Map<String, List<String>> parameters =
+                Collections.singletonMap("testParam", Collections.singletonList("testValue"));
+
+        final Object preferences = mock(Object.class);
+        _controller.setPreferences(_root, path, preferences, parameters, true);
+
+        verify(_nextVersionManagementController).setPreferences(_root,
+                                                                TEST_CATEGORY,
+                                                                path,
+                                                                preferences,
+                                                                parameters,
+                                                                true);
+    }
+
+    @Test
+    public void deletePreferences()
+    {
+        final List<String> path = Arrays.asList("test1", "test2");
+        final Map<String, List<String>> parameters =
+                Collections.singletonMap("testParam", Collections.singletonList("testValue"));
+
+        _controller.deletePreferences(_root, path, parameters);
+
+        verify(_nextVersionManagementController).deletePreferences(_root, TEST_CATEGORY, path, parameters);
+    }
+
+    @Test
+    public void getNextVersionManagementController()
+    {
+        assertThat(_controller.getNextVersionManagementController(), is(equalTo(_nextVersionManagementController)));
+    }
+
+    @Test
+    public void convertAttributesToNextVersion()
+    {
+        final List<String> path = Arrays.asList("test1", "test2");
+        final Map<String, Object> attributes = Collections.singletonMap("testParam", "testValue");
+        final Map<String, Object> convertedAttributes = Collections.singletonMap("testParam", "testValue2");
+
+        when(_typeController.convertAttributesToNextVersion(_root, path, attributes)).thenReturn(convertedAttributes);
+        final Map<String, Object> converted = _controller.convertAttributesToNextVersion(_root, path, attributes);
+        assertThat(converted, is(equalTo(convertedAttributes)));
+    }
+}
diff --git a/broker-plugins/management-http/src/test/java/org/apache/qpid/server/management/plugin/controller/GenericLegacyConfiguredObjectTest.java b/broker-plugins/management-http/src/test/java/org/apache/qpid/server/management/plugin/controller/GenericLegacyConfiguredObjectTest.java
new file mode 100644
index 0000000..b55a1f2
--- /dev/null
+++ b/broker-plugins/management-http/src/test/java/org/apache/qpid/server/management/plugin/controller/GenericLegacyConfiguredObjectTest.java
@@ -0,0 +1,212 @@
+/*
+ *
+ * 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.
+ *
+ */
+package org.apache.qpid.server.management.plugin.controller;
+
+import static org.hamcrest.CoreMatchers.equalTo;
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.CoreMatchers.notNullValue;
+import static org.junit.Assert.assertThat;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Map;
+
+import org.junit.Before;
+import org.junit.Test;
+
+import org.apache.qpid.server.management.plugin.ManagementResponse;
+import org.apache.qpid.server.management.plugin.ResponseType;
+import org.apache.qpid.test.utils.UnitTestBase;
+
+public class GenericLegacyConfiguredObjectTest extends UnitTestBase
+{
+
+    private GenericLegacyConfiguredObject _object;
+    private LegacyConfiguredObject _nextVersionLegacyConfiguredObject;
+    private static final String CATEGORY = "testCategory";
+    private LegacyManagementController _managementController;
+
+    @Before
+    public void setUp()
+    {
+        _managementController = mock(LegacyManagementController.class);
+        _nextVersionLegacyConfiguredObject = mock(LegacyConfiguredObject.class);
+
+        _object = new GenericLegacyConfiguredObject(_managementController,
+                                                    _nextVersionLegacyConfiguredObject,
+                                                    CATEGORY);
+    }
+
+    @Test
+    public void getAttributeNames()
+    {
+        final Collection<String> attributeNames = Arrays.asList("foo", "bar", "test");
+        when(_nextVersionLegacyConfiguredObject.getAttributeNames()).thenReturn(attributeNames);
+        Collection<String> names = _object.getAttributeNames();
+
+        assertThat(names, is(equalTo(attributeNames)));
+        verify(_nextVersionLegacyConfiguredObject).getAttributeNames();
+    }
+
+    @Test
+    public void getAttribute()
+    {
+        final String attributeName = "name";
+        final String attributeValue = "test";
+
+        when(_nextVersionLegacyConfiguredObject.getAttribute(attributeName)).thenReturn(attributeValue);
+        final Object value = _object.getAttribute(attributeName);
+
+        assertThat(value, is(equalTo(attributeValue)));
+        verify(_nextVersionLegacyConfiguredObject).getAttribute(attributeName);
+    }
+
+    @Test
+    public void getActualAttribute()
+    {
+        final String attributeName = "name";
+        final String attributeValue = "test";
+
+        when(_nextVersionLegacyConfiguredObject.getActualAttribute(attributeName)).thenReturn(attributeValue);
+        final Object value = _object.getActualAttribute(attributeName);
+
+        assertThat(value, is(equalTo(attributeValue)));
+        verify(_nextVersionLegacyConfiguredObject).getActualAttribute(attributeName);
+    }
+
+    @Test
+    public void getChildren()
+    {
+        final String childrenCategory = "testChildrenCategory";
+
+        final LegacyConfiguredObject child = mock(LegacyConfiguredObject.class);
+        final Collection<LegacyConfiguredObject> children = Collections.singleton(child);
+        when(_nextVersionLegacyConfiguredObject.getChildren(childrenCategory)).thenReturn(children);
+        final LegacyConfiguredObject converted = mock(LegacyConfiguredObject.class);
+        when(_managementController.convertFromNextVersion(child)).thenReturn(converted);
+        final Collection<LegacyConfiguredObject> value = _object.getChildren(childrenCategory);
+
+        assertThat(value.size(), is(equalTo(1)));
+        final LegacyConfiguredObject convertedChild = value.iterator().next();
+        assertThat(convertedChild, is(equalTo(converted)));
+        verify(_nextVersionLegacyConfiguredObject).getChildren(childrenCategory);
+        verify(_managementController).convertFromNextVersion(child);
+    }
+
+    @Test
+    public void getCategory()
+    {
+        assertThat(_object.getCategory(), is(equalTo(CATEGORY)));
+    }
+
+    @Test
+    public void invoke()
+    {
+        final String operationName = "testOperation";
+        final Map<String, Object> operationArguments = Collections.singletonMap("arg", "argValue");
+        final String operationResult = "testOperationResult";
+        final ControllerManagementResponse managementResponse = new ControllerManagementResponse(
+                ResponseType.DATA, operationResult);
+        when(_nextVersionLegacyConfiguredObject.invoke(operationName,
+                                                       operationArguments,
+                                                       true)).thenReturn(managementResponse);
+
+        final ManagementResponse result =
+                _object.invoke(operationName, operationArguments, true);
+
+        assertThat(result, is(notNullValue()));
+        assertThat(result.getResponseCode(), is(equalTo(200)));
+        assertThat(result.getBody(), is(equalTo(operationResult)));
+    }
+
+    @Test
+    public void getNextVersionConfiguredObject()
+    {
+        assertThat(_object.getNextVersionConfiguredObject(), is(equalTo(_nextVersionLegacyConfiguredObject)));
+    }
+
+    @Test
+    public void getParent()
+    {
+        final String parentCategory = "testParentCategory";
+        final LegacyConfiguredObject nextVersionParent = mock(LegacyConfiguredObject.class);
+        final LegacyConfiguredObject nextVersionParentConverted = mock(LegacyConfiguredObject.class);
+        when(_nextVersionLegacyConfiguredObject.getParent(parentCategory)).thenReturn(nextVersionParent);
+        when(_managementController.convertFromNextVersion(nextVersionParent)).thenReturn(
+                nextVersionParentConverted);
+        final LegacyConfiguredObject parent = _object.getParent(parentCategory);
+        assertThat(parent, is(equalTo(nextVersionParentConverted)));
+        verify(_nextVersionLegacyConfiguredObject).getParent(parentCategory);
+    }
+
+    @Test
+    public void isSecureAttribute()
+    {
+        final String attributeName = "testAttribute";
+        when(_nextVersionLegacyConfiguredObject.isSecureAttribute(attributeName)).thenReturn(true);
+        assertThat(_object.isSecureAttribute(attributeName), is(equalTo(true)));
+        verify(_nextVersionLegacyConfiguredObject).isSecureAttribute(attributeName);
+    }
+
+    @Test
+    public void isOversizedAttribute()
+    {
+        final String attributeName = "testAttribute";
+        when(_nextVersionLegacyConfiguredObject.isOversizedAttribute(attributeName)).thenReturn(true);
+        assertThat(_object.isOversizedAttribute(attributeName), is(equalTo(true)));
+        verify(_nextVersionLegacyConfiguredObject).isOversizedAttribute(attributeName);
+    }
+
+    @Test
+    public void getContextValue()
+    {
+        final String contextName = "testContext";
+        final String contextValue = "testValue";
+        when(_nextVersionLegacyConfiguredObject.getContextValue(contextName)).thenReturn(contextValue);
+        assertThat(_object.getContextValue(contextName), is(equalTo(contextValue)));
+        verify(_nextVersionLegacyConfiguredObject).getContextValue(contextName);
+    }
+
+    @Test
+    public void getStatistics()
+    {
+        Map<String, Object> stats = Collections.singletonMap("testStat", "statValue");
+        when(_nextVersionLegacyConfiguredObject.getStatistics()).thenReturn(stats);
+        assertThat(_object.getStatistics(), is(equalTo(stats)));
+        verify(_nextVersionLegacyConfiguredObject).getStatistics();
+    }
+
+    @Test
+    public void getManagementController()
+    {
+        assertThat(_object.getManagementController(), is(equalTo(_managementController)));
+    }
+
+    @Test
+    public void getNextVersionLegacyConfiguredObject()
+    {
+        assertThat(_object.getNextVersionConfiguredObject(), is(equalTo(_nextVersionLegacyConfiguredObject)));
+    }
+}
diff --git a/broker-plugins/management-http/src/test/java/org/apache/qpid/server/management/plugin/controller/latest/LatestManagementControllerAdapterTest.java b/broker-plugins/management-http/src/test/java/org/apache/qpid/server/management/plugin/controller/latest/LatestManagementControllerAdapterTest.java
new file mode 100644
index 0000000..aede511
--- /dev/null
+++ b/broker-plugins/management-http/src/test/java/org/apache/qpid/server/management/plugin/controller/latest/LatestManagementControllerAdapterTest.java
@@ -0,0 +1,395 @@
+/*
+ *
+ * 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.
+ *
+ */
+package org.apache.qpid.server.management.plugin.controller.latest;
+
+import static org.apache.qpid.server.management.plugin.HttpManagementConfiguration.PREFERENCE_OPERTAION_TIMEOUT_CONTEXT_NAME;
+import static org.hamcrest.CoreMatchers.equalTo;
+import static org.hamcrest.CoreMatchers.instanceOf;
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.CoreMatchers.notNullValue;
+import static org.junit.Assert.assertThat;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
+
+import org.junit.Before;
+import org.junit.Test;
+
+import org.apache.qpid.server.management.plugin.HttpManagementConfiguration;
+import org.apache.qpid.server.management.plugin.ManagementController;
+import org.apache.qpid.server.management.plugin.ManagementRequest;
+import org.apache.qpid.server.management.plugin.ManagementResponse;
+import org.apache.qpid.server.management.plugin.controller.LegacyConfiguredObject;
+import org.apache.qpid.server.model.Broker;
+import org.apache.qpid.server.model.BrokerModel;
+import org.apache.qpid.server.model.BrokerTestHelper;
+import org.apache.qpid.server.model.ConfiguredObject;
+import org.apache.qpid.server.model.Queue;
+import org.apache.qpid.server.model.VirtualHost;
+import org.apache.qpid.server.model.VirtualHostNode;
+import org.apache.qpid.server.virtualhost.QueueManagingVirtualHost;
+import org.apache.qpid.test.utils.UnitTestBase;
+
+public class LatestManagementControllerAdapterTest extends UnitTestBase
+{
+    private LatestManagementControllerAdapter _adapter;
+
+    @Before
+    public void setUp()
+    {
+        final HttpManagementConfiguration<?> httpManagement = mock(HttpManagementConfiguration.class);
+        when(httpManagement.getContextValue(Long.class, PREFERENCE_OPERTAION_TIMEOUT_CONTEXT_NAME)).thenReturn(1000L);
+        final ManagementController managementController = new LatestManagementController(httpManagement);
+        when(managementController.getVersion()).thenReturn(BrokerModel.MODEL_VERSION);
+        _adapter = new LatestManagementControllerAdapter(managementController);
+    }
+
+    @Test
+    public void getVersion()
+    {
+        assertThat(_adapter.getVersion(), is(equalTo(BrokerModel.MODEL_VERSION)));
+    }
+
+    @Test
+    public void getCategories()
+    {
+        assertThat(_adapter.getCategories(), is(equalTo(BrokerModel.getInstance()
+                                                                   .getSupportedCategories()
+                                                                   .stream()
+                                                                   .map(Class::getSimpleName)
+                                                                   .collect(Collectors.toSet()))));
+    }
+
+    @Test
+    public void getCategoryMapping()
+    {
+        assertThat(_adapter.getCategoryMapping("foo"),
+                   is(equalTo(String.format("/api/v%s/%s/", BrokerModel.MODEL_VERSION, "foo"))));
+    }
+
+    @Test
+    public void getCategory()
+    {
+        final ConfiguredObject<?> object = mock(ConfiguredObject.class);
+        doReturn(Broker.class).when(object).getCategoryClass();
+        assertThat(_adapter.getCategory(object), is(equalTo(Broker.class.getSimpleName())));
+    }
+
+    @Test
+    public void getCategoryHierarchy()
+    {
+        final Broker<?> object = BrokerTestHelper.createBrokerMock();
+        final Collection<String> expected = Arrays.asList("VirtualHostNode", "VirtualHost", "Queue");
+        assertThat(_adapter.getCategoryHierarchy(object, "Queue"), is(equalTo(expected)));
+    }
+
+    @Test
+    public void handleGet() throws Exception
+    {
+        final String hostName = "test";
+        final String queueName = "foo";
+
+        final QueueManagingVirtualHost<?> virtualHost = createTestVirtualHost(hostName);
+        virtualHost.createChild(Queue.class, Collections.singletonMap(Queue.NAME, queueName));
+
+        final String nodeName = virtualHost.getParent().getName();
+        final ManagementRequest request = mockManagementRequest(virtualHost.getBroker(),
+                                                                "GET",
+                                                                "queue",
+                                                                Arrays.asList(nodeName, hostName),
+                                                                Collections.singletonMap("name",
+                                                                                         Collections.singletonList("foo")));
+
+        final ManagementResponse response = _adapter.handleGet(request);
+        assertThat(response, is(notNullValue()));
+        assertThat(response.getResponseCode(), is(equalTo(200)));
+        assertThat(response.getBody(), is(notNullValue()));
+        assertThat(response.getBody(), is(instanceOf(Collection.class)));
+
+        final Collection data = (Collection) response.getBody();
+        assertThat(data.size(), is(equalTo(1)));
+
+        final Object object = data.iterator().next();
+        assertThat(object, is(notNullValue()));
+        assertThat(object, is(instanceOf(LegacyConfiguredObject.class)));
+        assertThat(((LegacyConfiguredObject) object).getAttribute(LegacyConfiguredObject.NAME), is(equalTo("foo")));
+    }
+
+
+    @Test
+    public void handlePut() throws Exception
+    {
+        final String hostName = "test";
+        final String queueName = "foo";
+
+        final QueueManagingVirtualHost<?> virtualHost = createTestVirtualHost(hostName);
+        final String nodeName = virtualHost.getParent().getName();
+        final Broker root = virtualHost.getBroker();
+
+        final ManagementRequest request =
+                mockManagementRequest(root, "PUT", "queue", Arrays.asList(nodeName, hostName), Collections.emptyMap());
+
+        when(request.getBody(LinkedHashMap.class)).thenReturn(new LinkedHashMap<String, Object>(Collections.singletonMap(
+                "name",
+                queueName)));
+        when(request.getRequestURL()).thenReturn("test");
+
+        final ManagementResponse response = _adapter.handlePut(request);
+        assertThat(response, is(notNullValue()));
+        assertThat(response.getResponseCode(), is(equalTo(201)));
+        assertThat(response.getBody(), is(notNullValue()));
+        assertThat(response.getBody(), is(instanceOf(LegacyConfiguredObject.class)));
+
+        final LegacyConfiguredObject object = (LegacyConfiguredObject) response.getBody();
+        assertThat(object.getAttribute(LegacyConfiguredObject.NAME), is(equalTo("foo")));
+    }
+
+    @Test
+    public void handlePost() throws Exception
+    {
+        final String hostName = "test";
+        final String queueName = "foo";
+
+        final QueueManagingVirtualHost<?> virtualHost = createTestVirtualHost(hostName);
+        final String nodeName = virtualHost.getParent().getName();
+        final Broker root = virtualHost.getBroker();
+
+        final ManagementRequest request =
+                mockManagementRequest(root, "POST", "queue", Arrays.asList(nodeName, hostName), Collections.emptyMap());
+
+        when(request.getBody(LinkedHashMap.class)).thenReturn(new LinkedHashMap<String, Object>(Collections.singletonMap(
+                "name",
+                queueName)));
+        when(request.getRequestURL()).thenReturn("test");
+
+        final ManagementResponse response = _adapter.handlePut(request);
+        assertThat(response, is(notNullValue()));
+        assertThat(response.getResponseCode(), is(equalTo(201)));
+        assertThat(response.getBody(), is(notNullValue()));
+        assertThat(response.getBody(), is(instanceOf(LegacyConfiguredObject.class)));
+
+        final LegacyConfiguredObject object = (LegacyConfiguredObject) response.getBody();
+        assertThat(object.getAttribute(LegacyConfiguredObject.NAME), is(equalTo("foo")));
+    }
+
+    @Test
+    public void handleDelete() throws Exception
+    {
+        final String hostName = "test";
+        final String queueName = "foo";
+
+        final QueueManagingVirtualHost<?> virtualHost = createTestVirtualHost(hostName);
+        virtualHost.createChild(Queue.class, Collections.singletonMap(Queue.NAME, queueName));
+        assertThat(virtualHost.getQueueCount(), is(equalTo(1L)));
+
+        final String nodeName = virtualHost.getParent().getName();
+        final ManagementRequest request = mockManagementRequest(virtualHost.getBroker(),
+                                                                "DELETE",
+                                                                "queue",
+                                                                Arrays.asList(nodeName, hostName),
+                                                                Collections.singletonMap("name",
+                                                                                         Collections.singletonList("foo")));
+        final ManagementResponse response = _adapter.handleDelete(request);
+        assertThat(response, is(notNullValue()));
+        assertThat(response.getResponseCode(), is(equalTo(200)));
+        assertThat(virtualHost.getQueueCount(), is(equalTo(0L)));
+    }
+
+    @Test
+    public void get() throws Exception
+    {
+        final String hostName = "test";
+        final String queueName = "foo";
+
+        final QueueManagingVirtualHost<?> virtualHost = createTestVirtualHost(hostName);
+        virtualHost.createChild(Queue.class, Collections.singletonMap(Queue.NAME, queueName));
+
+        final String nodeName = virtualHost.getParent().getName();
+
+        final Object response = _adapter.get(virtualHost.getBroker(), "queue", Arrays.asList(nodeName, hostName),
+                                             Collections.singletonMap("name",
+                                                                      Collections.singletonList("foo")));
+        assertThat(response, is(instanceOf(Collection.class)));
+
+        final Collection data = (Collection) response;
+        assertThat(data.size(), is(equalTo(1)));
+
+        final Object object = data.iterator().next();
+        assertThat(object, is(notNullValue()));
+        assertThat(object, is(instanceOf(LegacyConfiguredObject.class)));
+        assertThat(((LegacyConfiguredObject) object).getAttribute(LegacyConfiguredObject.NAME), is(equalTo("foo")));
+    }
+
+    @Test
+    public void createOrUpdate() throws Exception
+    {
+        final String hostName = "test";
+        final String queueName = "foo";
+
+        final QueueManagingVirtualHost<?> virtualHost = createTestVirtualHost(hostName);
+        final String nodeName = virtualHost.getParent().getName();
+        final Broker root = virtualHost.getBroker();
+
+        final ManagementRequest request =
+                mockManagementRequest(root, "POST", "queue", Arrays.asList(nodeName, hostName), Collections.emptyMap());
+
+        when(request.getBody(LinkedHashMap.class)).thenReturn(new LinkedHashMap<String, Object>(Collections.singletonMap(
+                "name",
+                queueName)));
+        when(request.getRequestURL()).thenReturn("test");
+
+        final Object response = _adapter.createOrUpdate(virtualHost.getBroker(),
+                                                        "queue",
+                                                        Arrays.asList(nodeName, hostName),
+                                                        Collections.singletonMap("name", queueName),
+                                                        true);
+        assertThat(response, is(instanceOf(LegacyConfiguredObject.class)));
+
+        final LegacyConfiguredObject object = (LegacyConfiguredObject) response;
+        assertThat(object.getAttribute(LegacyConfiguredObject.NAME), is(equalTo(queueName)));
+    }
+
+    @Test
+    public void delete() throws Exception
+    {
+        final String hostName = "test";
+        final String queueName = "foo";
+
+        final QueueManagingVirtualHost<?> virtualHost = createTestVirtualHost(hostName);
+        virtualHost.createChild(Queue.class, Collections.singletonMap(Queue.NAME, queueName));
+        assertThat(virtualHost.getQueueCount(), is(equalTo(1L)));
+
+        final String nodeName = virtualHost.getParent().getName();
+
+        _adapter.delete(virtualHost.getBroker(),
+                        "queue",
+                        Arrays.asList(nodeName, hostName, queueName),
+                        Collections.emptyMap());
+
+        assertThat(virtualHost.getQueueCount(), is(equalTo(0L)));
+    }
+
+    @Test
+    public void invoke() throws Exception
+    {
+        final String hostName = "test";
+        final String queueName = "foo";
+
+        final QueueManagingVirtualHost<?> virtualHost = createTestVirtualHost(hostName);
+        Queue queue = virtualHost.createChild(Queue.class, Collections.singletonMap(Queue.NAME, queueName));
+        assertThat(virtualHost.getQueueCount(), is(equalTo(1L)));
+
+        List<String> path = Arrays.asList(virtualHost.getParent().getName(), hostName);
+
+        Map<String, Object> message = new HashMap<>();
+        message.put("address", "foo");
+        message.put("persistent", "false");
+        message.put("content", "Test Content");
+        message.put("mimeType", "text/plain");
+        ManagementResponse response = _adapter.invoke(virtualHost.getBroker(),
+                                                         "virtualhost",
+                                                         path,
+                                                         "publishMessage",
+                                                         Collections.singletonMap("message", message),
+                                                         true,
+                                                         true);
+
+        assertThat(response, is(notNullValue()));
+        assertThat(response.getResponseCode(), is(equalTo(200)));
+        Object body = response.getBody();
+        assertThat(body, is(instanceOf(Number.class)));
+        assertThat(((Number) body).intValue(), is(equalTo(1)));
+        assertThat(queue.getQueueDepthMessages(), is(equalTo(1)));
+    }
+
+    @Test
+    public void formatConfiguredObject() throws Exception
+    {
+        final String hostName = "test";
+        final QueueManagingVirtualHost<?> virtualHost = createTestVirtualHost(hostName);
+        final String queueName = "foo";
+        virtualHost.createChild(Queue.class, Collections.singletonMap(Queue.NAME, queueName));
+        assertThat(virtualHost.getQueueCount(), is(equalTo(1L)));
+
+        final Object formatted = _adapter.formatConfiguredObject(virtualHost,
+                                                                    Collections.singletonMap("depth",
+                                                                                             Collections.singletonList(
+                                                                                                     "1")),
+                                                                    true);
+        assertThat(formatted, is(notNullValue()));
+        assertThat(formatted, is(instanceOf(Map.class)));
+
+        final Map<?, ?> data = (Map<?, ?>) formatted;
+        assertThat(data.get(VirtualHost.NAME), is(equalTo(hostName)));
+        final Object queues = data.get("queues");
+        assertThat(queues, is(notNullValue()));
+        assertThat(queues, is(instanceOf(Collection.class)));
+
+        final Collection<?> queueCollection = (Collection<?>) queues;
+
+        assertThat(queueCollection.size(), is(equalTo(1)));
+        final Iterator<?> iterator = queueCollection.iterator();
+        final Object queue1 = iterator.next();
+
+        assertThat(queue1, is(instanceOf(Map.class)));
+
+        final Map<?, ?> queueMap1 = (Map<?, ?>) queue1;
+
+        assertThat(queueMap1.get(Queue.NAME), is(equalTo("foo")));
+    }
+
+
+    private QueueManagingVirtualHost<?> createTestVirtualHost(final String hostName) throws Exception
+    {
+        final QueueManagingVirtualHost<?> virtualHost = BrokerTestHelper.createVirtualHost(hostName, this);
+        final Broker root = virtualHost.getBroker();
+        final ConfiguredObject<?> virtualHostNode = virtualHost.getParent();
+        when(root.getChildren(VirtualHostNode.class)).thenReturn(Collections.singletonList(virtualHostNode));
+        when(virtualHostNode.getChildren(VirtualHost.class)).thenReturn(Collections.singletonList(virtualHost));
+        when(virtualHostNode.getChildByName(VirtualHost.class, hostName)).thenReturn(virtualHost);
+        return virtualHost;
+    }
+
+    private ManagementRequest mockManagementRequest(ConfiguredObject<?> root,
+                                                    String method,
+                                                    String category,
+                                                    List<String> path,
+                                                    Map<String, List<String>> parameters)
+    {
+        final ManagementRequest request = mock(ManagementRequest.class);
+        when(request.getCategory()).thenReturn(category);
+        doReturn(root).when(request).getRoot();
+        when(request.getPath()).thenReturn(path);
+        when(request.getParameters()).thenReturn(parameters);
+        when(request.getMethod()).thenReturn(method);
+        return request;
+    }
+}
diff --git a/broker-plugins/management-http/src/test/java/org/apache/qpid/server/management/plugin/controller/v7_0/LegacyManagementControllerTest.java b/broker-plugins/management-http/src/test/java/org/apache/qpid/server/management/plugin/controller/v7_0/LegacyManagementControllerTest.java
new file mode 100644
index 0000000..8c7c378
--- /dev/null
+++ b/broker-plugins/management-http/src/test/java/org/apache/qpid/server/management/plugin/controller/v7_0/LegacyManagementControllerTest.java
@@ -0,0 +1,105 @@
+/*
+ *
+ * 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.
+ *
+ */
+package org.apache.qpid.server.management.plugin.controller.v7_0;
+
+import static org.hamcrest.CoreMatchers.equalTo;
+import static org.hamcrest.CoreMatchers.instanceOf;
+import static org.hamcrest.CoreMatchers.is;
+import static org.junit.Assert.assertThat;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+
+import org.junit.Before;
+import org.junit.Test;
+
+import org.apache.qpid.server.management.plugin.ManagementController;
+import org.apache.qpid.server.management.plugin.controller.LegacyConfiguredObject;
+import org.apache.qpid.test.utils.UnitTestBase;
+
+public class LegacyManagementControllerTest extends UnitTestBase
+{
+    private LegacyManagementController _controller;
+
+    @Before
+    public void setUp()
+    {
+        final ManagementController nextVersionManagementController = mock(ManagementController.class);
+        _controller = new LegacyManagementController(nextVersionManagementController);
+        _controller.initialize();
+    }
+
+    @Test
+    public void convertQueryParameters()
+    {
+        final Map<String, List<String>> parameters = Collections.singletonMap("depth", Collections.singletonList("1"));
+        final Map<String, List<String>> converted = _controller.convertQueryParameters(parameters);
+        assertThat(converted, is(equalTo(parameters)));
+    }
+
+    @Test
+    public void formatConfiguredObject()
+    {
+        final String objectName = "test-object";
+        final String hostName = "test-vhn";
+        final Map<String, List<String>> parameters = Collections.singletonMap("depth", Collections.singletonList("1"));
+        final LegacyConfiguredObject object = mock(LegacyConfiguredObject.class);
+        final LegacyConfiguredObject vhn = mock(LegacyConfiguredObject.class);
+        when(object.getAttributeNames()).thenReturn(Arrays.asList(LegacyConfiguredObject.NAME,
+                                                                  LegacyConfiguredObject.TYPE));
+        when(object.getAttribute(LegacyConfiguredObject.NAME)).thenReturn(objectName);
+        when(object.getAttribute(LegacyConfiguredObject.TYPE)).thenReturn("Broker");
+        when(object.getCategory()).thenReturn("Broker");
+        when(object.getChildren("VirtualHostNode")).thenReturn(Collections.singletonList(vhn));
+        when(vhn.getAttributeNames()).thenReturn(Arrays.asList(LegacyConfiguredObject.NAME,
+                                                                  LegacyConfiguredObject.TYPE));
+        when(vhn.getAttribute(LegacyConfiguredObject.NAME)).thenReturn(hostName);
+        when(vhn.getAttribute(LegacyConfiguredObject.TYPE)).thenReturn("VirtualHostNode");
+        when(vhn.getCategory()).thenReturn("VirtualHostNode");
+
+        Object data = _controller.formatConfiguredObject(object, parameters, true);
+
+        assertThat(data, is(instanceOf(Map.class)));
+        Map<?, ?> formatted = (Map<?, ?>) data;
+
+        assertThat(formatted.get(LegacyConfiguredObject.NAME), is(equalTo(objectName)));
+        assertThat(formatted.get(LegacyConfiguredObject.TYPE), is(equalTo("Broker")));
+
+        Object vhns = formatted.get("virtualhostnodes");
+        assertThat(vhns, is(instanceOf(Collection.class)));
+
+        Collection<?> nodes = (Collection<?>)vhns;
+
+        assertThat(nodes.size(), is(equalTo(1)));
+
+        Object node = nodes.iterator().next();
+        assertThat(node, is(instanceOf(Map.class)));
+        Map<?, ?> formattedNode = (Map<?, ?>) node;
+
+        assertThat(formattedNode.get(LegacyConfiguredObject.NAME), is(equalTo(hostName)));
+        assertThat(formattedNode.get(LegacyConfiguredObject.TYPE), is(equalTo("VirtualHostNode")));
+    }
+}
diff --git a/broker-plugins/management-http/src/test/java/org/apache/qpid/server/management/plugin/controller/v7_0/category/ContainerControllerTest.java b/broker-plugins/management-http/src/test/java/org/apache/qpid/server/management/plugin/controller/v7_0/category/ContainerControllerTest.java
new file mode 100644
index 0000000..896d41b
--- /dev/null
+++ b/broker-plugins/management-http/src/test/java/org/apache/qpid/server/management/plugin/controller/v7_0/category/ContainerControllerTest.java
@@ -0,0 +1,83 @@
+/*
+ *
+ * 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.
+ *
+ */
+package org.apache.qpid.server.management.plugin.controller.v7_0.category;
+
+
+import static org.hamcrest.CoreMatchers.equalTo;
+import static org.hamcrest.CoreMatchers.is;
+import static org.junit.Assert.assertThat;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import org.junit.Before;
+import org.junit.Test;
+
+import org.apache.qpid.server.management.plugin.controller.CategoryController;
+import org.apache.qpid.server.management.plugin.controller.LegacyConfiguredObject;
+import org.apache.qpid.server.management.plugin.controller.LegacyManagementController;
+import org.apache.qpid.test.utils.UnitTestBase;
+
+public class ContainerControllerTest extends UnitTestBase
+{
+    private LegacyCategoryControllerFactory _factory;
+    private LegacyManagementController _nextVersionManagementController;
+    private String MODEL_VERSION = "7.0";
+
+    @Before
+    public void setUp()
+    {
+        _nextVersionManagementController = mock(LegacyManagementController.class);
+        when(_nextVersionManagementController.getVersion()).thenReturn(MODEL_VERSION);
+        _factory = new LegacyCategoryControllerFactory();
+    }
+
+    @Test
+    public void convertNextVersionBrokerConfiguredObject()
+    {
+        final CategoryController controller =
+                _factory.createController("Broker", _nextVersionManagementController);
+
+        assertThat(controller.getCategory(), is(equalTo("Broker")));
+
+        final LegacyConfiguredObject object = mock(LegacyConfiguredObject.class);
+        when(object.getAttribute("modelVersion")).thenReturn("foo");
+        final LegacyConfiguredObject converted = controller.convertFromNextVersion(object);
+
+        Object modelVersion = converted.getAttribute("modelVersion");
+        assertThat(modelVersion, is(equalTo(MODEL_VERSION)));
+    }
+
+    @Test
+    public void convertNextVersionVirtualHostConfiguredObject()
+    {
+        final CategoryController controller =
+                _factory.createController("VirtualHost", _nextVersionManagementController);
+
+        assertThat(controller.getCategory(), is(equalTo("VirtualHost")));
+
+        final LegacyConfiguredObject object = mock(LegacyConfiguredObject.class);
+        when(object.getAttribute("modelVersion")).thenReturn("foo");
+        final LegacyConfiguredObject converted = controller.convertFromNextVersion(object);
+
+        Object modelVersion = converted.getAttribute("modelVersion");
+        assertThat(modelVersion, is(equalTo(MODEL_VERSION)));
+    }
+}
diff --git a/broker-plugins/management-http/src/test/java/org/apache/qpid/server/management/plugin/controller/v7_0/category/LegacyCategoryControllerFactoryTest.java b/broker-plugins/management-http/src/test/java/org/apache/qpid/server/management/plugin/controller/v7_0/category/LegacyCategoryControllerFactoryTest.java
new file mode 100644
index 0000000..3964d2e
--- /dev/null
+++ b/broker-plugins/management-http/src/test/java/org/apache/qpid/server/management/plugin/controller/v7_0/category/LegacyCategoryControllerFactoryTest.java
@@ -0,0 +1,58 @@
+/*
+ *
+ * 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.
+ *
+ */
+package org.apache.qpid.server.management.plugin.controller.v7_0.category;
+
+import static org.apache.qpid.server.management.plugin.controller.v7_0.category.LegacyCategoryControllerFactory.SUPPORTED_CATEGORIES;
+import static org.hamcrest.CoreMatchers.equalTo;
+import static org.hamcrest.CoreMatchers.is;
+import static org.junit.Assert.assertThat;
+import static org.mockito.Mockito.mock;
+
+import org.junit.Before;
+import org.junit.Test;
+
+import org.apache.qpid.server.management.plugin.ManagementController;
+import org.apache.qpid.server.management.plugin.controller.CategoryController;
+import org.apache.qpid.server.management.plugin.controller.LegacyManagementController;
+import org.apache.qpid.test.utils.UnitTestBase;
+
+public class LegacyCategoryControllerFactoryTest extends UnitTestBase
+{
+    private LegacyCategoryControllerFactory _factory;
+    private LegacyManagementController _nextVersionManagementController;
+
+    @Before
+    public void setUp()
+    {
+        _nextVersionManagementController = mock(LegacyManagementController.class);
+        _factory = new LegacyCategoryControllerFactory();
+    }
+
+    @Test
+    public void createController()
+    {
+        SUPPORTED_CATEGORIES.keySet().forEach(category-> {
+            final CategoryController controller =
+                    _factory.createController(category, _nextVersionManagementController);
+            assertThat(controller.getCategory(), is(equalTo(category)));
+        });
+    }
+}


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