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:53 UTC
[qpid-broker-j] 01/03: QPID-6948: [Broker-J] Introduce interfaces
for handling REST management requests and add pluggable mechanism for
supporting previous versions of REST API
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 d0a6a700c6b9b2f9ec1f1885bc4a73c796bd5f2b
Author: Alex Rudyy <or...@apache.org>
AuthorDate: Sat Dec 15 22:42:46 2018 +0000
QPID-6948: [Broker-J] Introduce interfaces for handling REST management requests and add pluggable mechanism for supporting previous versions of REST API
---
.../server/management/plugin/HttpManagement.java | 93 +-
.../management/plugin/ManagementController.java | 95 ++
.../plugin/ManagementControllerFactory.java | 47 +
.../management/plugin/ManagementException.java | 282 ++++++
.../management/plugin/ManagementRequest.java | 53 +
.../management/plugin/ManagementResponse.java | 34 +
.../qpid/server/management/plugin/RequestType.java | 29 +
.../server/management/plugin/ResponseType.java | 28 +
.../controller/AbstractManagementController.java | 204 ++++
.../plugin/controller/ConverterHelper.java | 154 +++
.../latest/LatestManagementController.java | 863 ++++++++++++++++
.../latest/LatestManagementControllerFactory.java | 56 ++
.../plugin/servlet/rest/RequestInfo.java | 7 +-
.../plugin/servlet/rest/RestServlet.java | 1062 ++++++--------------
.../servlet/rest/RestUserPreferenceHandler.java | 19 +-
.../latest/LatestManagementControllerTest.java | 809 +++++++++++++++
.../plugin/servlet/rest/RequestInfoParserTest.java | 23 +-
17 files changed, 3023 insertions(+), 835 deletions(-)
diff --git a/broker-plugins/management-http/src/main/java/org/apache/qpid/server/management/plugin/HttpManagement.java b/broker-plugins/management-http/src/main/java/org/apache/qpid/server/management/plugin/HttpManagement.java
index b591010..b76ab9d 100644
--- a/broker-plugins/management-http/src/main/java/org/apache/qpid/server/management/plugin/HttpManagement.java
+++ b/broker-plugins/management-http/src/main/java/org/apache/qpid/server/management/plugin/HttpManagement.java
@@ -25,6 +25,7 @@ import java.io.StringWriter;
import java.io.Writer;
import java.net.BindException;
import java.net.InetSocketAddress;
+import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
@@ -655,55 +656,59 @@ public class HttpManagement extends AbstractPluginAdapter<HttpManagement> implem
return factory;
}
- private void addRestServlet(ServletContextHandler root)
+ private void addRestServlet(final ServletContextHandler root)
{
- Set<Class<? extends ConfiguredObject>> categories = new HashSet<>(getModel().getSupportedCategories());
- final RestServlet restServlet = new RestServlet();
+ final Map<String, ManagementControllerFactory> factories = ManagementControllerFactory.loadFactories();
final ApiDocsServlet apiDocsServlet = new ApiDocsServlet();
-
- for (Class<? extends ConfiguredObject> category : categories)
- {
- String name = category.getSimpleName().toLowerCase();
-
- ServletHolder servletHolder = new ServletHolder(name, restServlet);
- servletHolder.getRegistration().setMultipartConfig(
- new MultipartConfigElement("",
- getContextValue(Long.class, MAX_HTTP_FILE_UPLOAD_SIZE_CONTEXT_NAME),
- -1L,
- getContextValue(Integer.class,
- MAX_HTTP_FILE_UPLOAD_SIZE_CONTEXT_NAME)));
-
- List<String> paths = Arrays.asList("/api/latest/" + name,
- "/api/v" + BrokerModel.MODEL_VERSION + "/" + name);
-
- for (String path : paths)
+ final List<String> supportedVersions = new ArrayList<>();
+ String currentVersion = BrokerModel.MODEL_VERSION;
+ ManagementController managementController = null;
+ ManagementControllerFactory factory;
+ do
+ {
+ factory = factories.get(currentVersion);
+ if (factory != null)
{
- root.addServlet(servletHolder, path + "/*");
+ managementController = factory.createManagementController(this, managementController);
+ final RestServlet managementServlet = new RestServlet();
+ final Collection<String> categories = managementController.getCategories();
+ for (String category : categories)
+ {
+ final String name = category.toLowerCase();
+ final String path = managementController.getCategoryMapping(name);
+ final ServletHolder servletHolder = new ServletHolder(path, managementServlet);
+ servletHolder.setInitParameter("qpid.controller.version", managementController.getVersion());
+
+ servletHolder.getRegistration().setMultipartConfig(
+ new MultipartConfigElement("",
+ getContextValue(Long.class,
+ MAX_HTTP_FILE_UPLOAD_SIZE_CONTEXT_NAME),
+ -1L,
+ getContextValue(Integer.class,
+ MAX_HTTP_FILE_UPLOAD_SIZE_CONTEXT_NAME)));
+
+ root.addServlet(servletHolder, path + (path.endsWith("/") ? "*" : "/*"));
+
+ if (BrokerModel.MODEL_VERSION.equals(managementController.getVersion()))
+ {
+ root.addServlet(servletHolder, "/api/latest/" + name + "/*");
+ ServletHolder docServletHolder = new ServletHolder(name + "docs", apiDocsServlet);
+ root.addServlet(docServletHolder, "/apidocs/latest/" + name + "/");
+ root.addServlet(docServletHolder, "/apidocs/v" + BrokerModel.MODEL_VERSION + "/" + name + "/");
+ root.addServlet(docServletHolder, "/apidocs/latest/" + name);
+ root.addServlet(docServletHolder, "/apidocs/v" + BrokerModel.MODEL_VERSION + "/" + name);
+ }
+ }
+ supportedVersions.add("v" + currentVersion);
+ currentVersion = factory.getPreviousVersion();
}
- ServletHolder docServletHolder = new ServletHolder(name + "docs", apiDocsServlet);
- root.addServlet(docServletHolder, "/apidocs/latest/" + name + "/");
- root.addServlet(docServletHolder, "/apidocs/v" + BrokerModel.MODEL_VERSION + "/" + name + "/");
- root.addServlet(docServletHolder, "/apidocs/latest/" + name);
- root.addServlet(docServletHolder, "/apidocs/v" + BrokerModel.MODEL_VERSION + "/" + name);
-
-
}
-
- final ServletHolder versionsServletHolder = new ServletHolder(new JsonValueServlet(getApiProperties()));
- root.addServlet(versionsServletHolder,"/api");
- root.addServlet(versionsServletHolder,"/api/");
-
- }
-
- private Map<String, Object> getApiProperties()
- {
- return Collections.singletonMap("supportedVersions", getSupportedRestApiVersions());
- }
-
- private List<String> getSupportedRestApiVersions()
- {
- // TODO - actually support multiple versions and add those versions to the list
- return Collections.singletonList(getLatestSupportedVersion());
+ while (factory != null);
+ root.getServletContext().setAttribute("qpid.controller.chain", managementController);
+ final Map<String, List<String>> supported = Collections.singletonMap("supportedVersions", supportedVersions);
+ final ServletHolder versionsServletHolder = new ServletHolder(new JsonValueServlet(supported));
+ root.addServlet(versionsServletHolder, "/api");
+ root.addServlet(versionsServletHolder, "/api/");
}
private String getLatestSupportedVersion()
diff --git a/broker-plugins/management-http/src/main/java/org/apache/qpid/server/management/plugin/ManagementController.java b/broker-plugins/management-http/src/main/java/org/apache/qpid/server/management/plugin/ManagementController.java
new file mode 100644
index 0000000..5c923c2
--- /dev/null
+++ b/broker-plugins/management-http/src/main/java/org/apache/qpid/server/management/plugin/ManagementController.java
@@ -0,0 +1,95 @@
+/*
+ *
+ * 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;
+
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.qpid.server.model.ConfiguredObject;
+
+public interface ManagementController
+{
+ String getVersion();
+
+ Collection<String> getCategories();
+
+ String getCategoryMapping(String category);
+
+ String getCategory(ConfiguredObject<?> managedObject);
+
+ Collection<String> getCategoryHierarchy(ConfiguredObject<?> root, String category);
+
+ ManagementController getNextVersionManagementController();
+
+ ManagementResponse handleGet(ManagementRequest request) throws ManagementException;
+
+ ManagementResponse handlePut(ManagementRequest request) throws ManagementException;
+
+ ManagementResponse handlePost(ManagementRequest request) throws ManagementException;
+
+ ManagementResponse handleDelete(ManagementRequest request) throws ManagementException;
+
+ Object get(ConfiguredObject<?> root,
+ String category,
+ List<String> path,
+ Map<String, List<String>> parameters) throws ManagementException;
+
+ Object createOrUpdate(ConfiguredObject<?> root,
+ String category,
+ List<String> path,
+ Map<String, Object> attributes,
+ boolean isPost) throws ManagementException;
+
+ int delete(ConfiguredObject<?> root,
+ String category,
+ List<String> path,
+ Map<String, List<String>> parameters) throws ManagementException;
+
+ ManagementResponse invoke(ConfiguredObject<?> root,
+ String category,
+ List<String> path,
+ String operation,
+ Map<String, Object> parameters,
+ boolean isPost,
+ final boolean isSecureOrAllowedOnInsecureChannel) throws ManagementException;
+
+ Object getPreferences(ConfiguredObject<?> root,
+ String category,
+ List<String> path,
+ Map<String, List<String>> parameters) throws ManagementException;
+
+ void setPreferences(ConfiguredObject<?> root,
+ String category,
+ List<String> path,
+ Object preferences,
+ Map<String, List<String>> parameters,
+ boolean isPost) throws ManagementException;
+
+ int deletePreferences(ConfiguredObject<?> root,
+ String category,
+ List<String> path,
+ Map<String, List<String>> parameters) throws ManagementException;
+
+ Object formatConfiguredObject(Object content,
+ Map<String, List<String>> parameters,
+ boolean isSecureOrAllowedOnInsecureChannel);
+}
diff --git a/broker-plugins/management-http/src/main/java/org/apache/qpid/server/management/plugin/ManagementControllerFactory.java b/broker-plugins/management-http/src/main/java/org/apache/qpid/server/management/plugin/ManagementControllerFactory.java
new file mode 100644
index 0000000..8e02513
--- /dev/null
+++ b/broker-plugins/management-http/src/main/java/org/apache/qpid/server/management/plugin/ManagementControllerFactory.java
@@ -0,0 +1,47 @@
+/*
+ *
+ * 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;
+
+import java.util.Map;
+import java.util.stream.Collectors;
+import java.util.stream.StreamSupport;
+
+import org.apache.qpid.server.plugin.Pluggable;
+import org.apache.qpid.server.plugin.QpidServiceLoader;
+
+public interface ManagementControllerFactory extends Pluggable
+{
+ String getVersion();
+
+ String getPreviousVersion();
+
+ ManagementController createManagementController(HttpManagementConfiguration<?> httpManagement,
+ ManagementController nextVersionManagementController);
+
+ static Map<String, ManagementControllerFactory> loadFactories()
+ {
+ final Iterable<ManagementControllerFactory> factories =
+ new QpidServiceLoader().atLeastOneInstanceOf(ManagementControllerFactory.class);
+
+ return StreamSupport.stream(factories.spliterator(), false)
+ .collect(Collectors.toMap(ManagementControllerFactory::getVersion, f -> f));
+ }
+}
diff --git a/broker-plugins/management-http/src/main/java/org/apache/qpid/server/management/plugin/ManagementException.java b/broker-plugins/management-http/src/main/java/org/apache/qpid/server/management/plugin/ManagementException.java
new file mode 100644
index 0000000..8e7b3c3
--- /dev/null
+++ b/broker-plugins/management-http/src/main/java/org/apache/qpid/server/management/plugin/ManagementException.java
@@ -0,0 +1,282 @@
+/*
+ *
+ * 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;
+
+import java.security.AccessController;
+import java.security.Principal;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.TreeSet;
+import java.util.stream.Collectors;
+
+import javax.security.auth.Subject;
+import javax.servlet.http.HttpServletResponse;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import org.apache.qpid.server.configuration.IllegalConfigurationException;
+import org.apache.qpid.server.management.plugin.controller.ConverterHelper;
+import org.apache.qpid.server.management.plugin.servlet.rest.NotFoundException;
+import org.apache.qpid.server.model.AbstractConfiguredObject;
+import org.apache.qpid.server.model.IllegalStateTransitionException;
+import org.apache.qpid.server.model.IntegrityViolationException;
+import org.apache.qpid.server.model.OperationTimeoutException;
+import org.apache.qpid.server.util.ExternalServiceException;
+import org.apache.qpid.server.util.ExternalServiceTimeoutException;
+
+public class ManagementException extends RuntimeException
+{
+ private static final int SC_UNPROCESSABLE_ENTITY = 422;
+
+ private static final Logger LOGGER = LoggerFactory.getLogger(ManagementException.class);
+
+ private final int _statusCode;
+ private final Map<String, String> _headers;
+
+ private ManagementException(final int statusCode,
+ final String message,
+ final Map<String, String> headers)
+ {
+ super(message);
+ _statusCode = statusCode;
+ _headers = headers;
+ }
+
+ private ManagementException(final int statusCode,
+ final String message,
+ final Throwable cause,
+ final Map<String, String> headers)
+ {
+ super(message, cause);
+ _statusCode = statusCode;
+ _headers = headers;
+ }
+
+ public int getStatusCode()
+ {
+ return _statusCode;
+ }
+
+ public Map<String, String> getHeaders()
+ {
+ return _headers;
+ }
+
+ public static ManagementException createNotFoundManagementException(final Exception e)
+ {
+ return new ManagementException(HttpServletResponse.SC_NOT_FOUND, e.getMessage(), e, null);
+ }
+
+ public static ManagementException createNotFoundManagementException(final String message)
+ {
+ return new ManagementException(HttpServletResponse.SC_NOT_FOUND, message, null);
+ }
+
+ public static ManagementException createGoneManagementException(final String message)
+ {
+ return new ManagementException(HttpServletResponse.SC_GONE, message, null);
+ }
+
+ public static ManagementException createUnprocessableManagementException(final String message)
+ {
+ return new ManagementException(SC_UNPROCESSABLE_ENTITY, message, null);
+ }
+
+ public static ManagementException createUnprocessableManagementException(final Exception e)
+ {
+ return new ManagementException(SC_UNPROCESSABLE_ENTITY, e.getMessage(), e, null);
+ }
+
+ private static ManagementException createConflictManagementException(final Exception e)
+ {
+ return new ManagementException(HttpServletResponse.SC_CONFLICT, e.getMessage(), e, null);
+ }
+
+
+ public static ManagementException createNotAllowedManagementException(final String message,
+ final Map<String, String> headers)
+ {
+ return new ManagementException(HttpServletResponse.SC_METHOD_NOT_ALLOWED, message, headers);
+ }
+
+ public static ManagementException createForbiddenManagementException(final String message)
+ {
+ return new ManagementException(HttpServletResponse.SC_FORBIDDEN, message, null);
+ }
+
+
+ public static ManagementException createForbiddenManagementException(final Exception e)
+ {
+ return new ManagementException(HttpServletResponse.SC_FORBIDDEN, e.getMessage(), e, null);
+ }
+
+
+ public static ManagementException createInternalServerErrorManagementException(final String message)
+ {
+ return new ManagementException(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, message, null);
+ }
+
+ public static ManagementException createInternalServerErrorManagementException(final String message,
+ final Exception e)
+ {
+ return new ManagementException(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, message, e, null);
+ }
+
+
+ private static ManagementException createBadGatewayManagementException(final String message,
+ final RuntimeException e)
+ {
+ return new ManagementException(HttpServletResponse.SC_BAD_GATEWAY, message, e, null);
+ }
+
+ private static ManagementException createGatewayTimeoutManagementException(final RuntimeException e)
+ {
+ return new ManagementException(HttpServletResponse.SC_GATEWAY_TIMEOUT, e.getMessage(), e, null);
+ }
+
+
+ public static ManagementException createBadRequestManagementException(final String message)
+ {
+ return new ManagementException(HttpServletResponse.SC_BAD_REQUEST, message, null);
+ }
+
+ public static ManagementException createBadRequestManagementException(final String message, final Throwable e)
+ {
+ return new ManagementException(HttpServletResponse.SC_BAD_REQUEST, message, e, null);
+ }
+
+
+ public static ManagementException toManagementException(final RuntimeException e,
+ final String categoryMapping,
+ final List<String> path)
+ {
+ if (e instanceof SecurityException)
+ {
+ LOGGER.debug("{}, sending {}", e.getClass().getName(), HttpServletResponse.SC_FORBIDDEN, e);
+ return createForbiddenManagementException(e);
+ }
+ else if (e instanceof AbstractConfiguredObject.DuplicateIdException
+ || e instanceof AbstractConfiguredObject.DuplicateNameException
+ || e instanceof IntegrityViolationException
+ || e instanceof IllegalStateTransitionException)
+ {
+ return createConflictManagementException(e);
+ }
+ else if (e instanceof NotFoundException)
+ {
+ if (LOGGER.isTraceEnabled())
+ {
+ LOGGER.trace(e.getClass().getSimpleName() + " processing request", e);
+ }
+ return createNotFoundManagementException(e);
+ }
+ else if (e instanceof IllegalConfigurationException || e instanceof IllegalArgumentException)
+ {
+ LOGGER.warn("{} processing request {} from user '{}': {}",
+ e.getClass().getSimpleName(),
+ getRequestURI(path, categoryMapping),
+ getRequestPrincipals(),
+ e.getMessage());
+ Throwable t = e;
+ int maxDepth = 10;
+ while ((t = t.getCause()) != null && maxDepth-- != 0)
+ {
+ LOGGER.warn("... caused by " + t.getClass().getSimpleName() + " : " + t.getMessage());
+ }
+ if (LOGGER.isDebugEnabled())
+ {
+ LOGGER.debug(e.getClass().getSimpleName() + " processing request", e);
+ }
+ return createUnprocessableManagementException(e);
+ }
+ else if (e instanceof OperationTimeoutException)
+ {
+ if (LOGGER.isDebugEnabled())
+ {
+ LOGGER.debug("Timeout during processing of request {} from user '{}'",
+ getRequestURI(path, categoryMapping),
+ getRequestPrincipals(),
+ e);
+ }
+ else
+ {
+ LOGGER.info("Timeout during processing of request {} from user '{}'",
+ getRequestURI(path, categoryMapping), getRequestPrincipals());
+ }
+ return createBadGatewayManagementException("Timeout occurred", e);
+ }
+
+ else if (e instanceof ExternalServiceTimeoutException)
+ {
+ LOGGER.warn("External request timeout ", e);
+ return createGatewayTimeoutManagementException(e);
+ }
+ else if (e instanceof ExternalServiceException)
+ {
+ LOGGER.warn("External request failed ", e);
+ return createBadGatewayManagementException(e.getMessage(), e);
+ }
+ else if (e instanceof ManagementException)
+ {
+ return (ManagementException)e;
+ }
+ else
+ {
+ LOGGER.warn("Unexpected Exception", e);
+ return createInternalServerErrorManagementException("Unexpected Exception", e);
+ }
+ }
+
+ public static ManagementException handleError(final Error e)
+ {
+ if (e instanceof NoClassDefFoundError)
+ {
+ LOGGER.warn("Unexpected exception processing request ", e);
+ return createBadRequestManagementException("Not found: " + e.getMessage(), e);
+ }
+ else
+ {
+ throw e;
+ }
+ }
+
+ public static String getRequestURI(final List<String> path, final String categoryMapping)
+ {
+ return categoryMapping + (categoryMapping.endsWith("/") ? "" : "/")
+ + path.stream().map(ConverterHelper::encode).collect(Collectors.joining("/"));
+ }
+
+ private static String getRequestPrincipals()
+ {
+ final Subject subject = Subject.getSubject(AccessController.getContext());
+ if (subject == null)
+ {
+ return null;
+ }
+ final Set<Principal> principalSet = subject.getPrincipals();
+ return String.join("/",
+ principalSet.stream()
+ .map(Principal::getName)
+ .collect(Collectors.toCollection(TreeSet::new)));
+ }
+}
diff --git a/broker-plugins/management-http/src/main/java/org/apache/qpid/server/management/plugin/ManagementRequest.java b/broker-plugins/management-http/src/main/java/org/apache/qpid/server/management/plugin/ManagementRequest.java
new file mode 100644
index 0000000..8b5c1ca
--- /dev/null
+++ b/broker-plugins/management-http/src/main/java/org/apache/qpid/server/management/plugin/ManagementRequest.java
@@ -0,0 +1,53 @@
+/*
+ *
+ * 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;
+
+import java.util.List;
+import java.util.Map;
+
+import org.apache.qpid.server.model.ConfiguredObject;
+
+public interface ManagementRequest
+{
+ String getMethod();
+
+ Map<String, List<String>> getParameters();
+
+ String getParameter(String name);
+
+ Map<String, Object> getParametersAsFlatMap();
+
+ Map<String, String> getHeaders();
+
+ List<String> getPath();
+
+ String getCategory();
+
+ ConfiguredObject<?> getRoot();
+
+ boolean isSecure();
+
+ boolean isConfidentialOperationAllowedOnInsecureChannel();
+
+ <T> T getBody(Class<T> type);
+
+ String getRequestURL();
+}
diff --git a/broker-plugins/management-http/src/main/java/org/apache/qpid/server/management/plugin/ManagementResponse.java b/broker-plugins/management-http/src/main/java/org/apache/qpid/server/management/plugin/ManagementResponse.java
new file mode 100644
index 0000000..a000619
--- /dev/null
+++ b/broker-plugins/management-http/src/main/java/org/apache/qpid/server/management/plugin/ManagementResponse.java
@@ -0,0 +1,34 @@
+/*
+ *
+ * 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;
+
+import java.util.Map;
+
+public interface ManagementResponse
+{
+ ResponseType getType();
+
+ Object getBody();
+
+ Map<String, String> getHeaders();
+
+ int getResponseCode();
+}
diff --git a/broker-plugins/management-http/src/main/java/org/apache/qpid/server/management/plugin/RequestType.java b/broker-plugins/management-http/src/main/java/org/apache/qpid/server/management/plugin/RequestType.java
new file mode 100644
index 0000000..29ca542
--- /dev/null
+++ b/broker-plugins/management-http/src/main/java/org/apache/qpid/server/management/plugin/RequestType.java
@@ -0,0 +1,29 @@
+/*
+ *
+ * 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;
+
+public enum RequestType
+{
+ OPERATION,
+ USER_PREFERENCES,
+ VISIBLE_PREFERENCES,
+ MODEL_OBJECT
+}
diff --git a/broker-plugins/management-http/src/main/java/org/apache/qpid/server/management/plugin/ResponseType.java b/broker-plugins/management-http/src/main/java/org/apache/qpid/server/management/plugin/ResponseType.java
new file mode 100644
index 0000000..8396075
--- /dev/null
+++ b/broker-plugins/management-http/src/main/java/org/apache/qpid/server/management/plugin/ResponseType.java
@@ -0,0 +1,28 @@
+/*
+ *
+ * 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;
+
+public enum ResponseType
+{
+ MODEL_OBJECT,
+ DATA,
+ EMPTY
+}
diff --git a/broker-plugins/management-http/src/main/java/org/apache/qpid/server/management/plugin/controller/AbstractManagementController.java b/broker-plugins/management-http/src/main/java/org/apache/qpid/server/management/plugin/controller/AbstractManagementController.java
new file mode 100644
index 0000000..3888869
--- /dev/null
+++ b/broker-plugins/management-http/src/main/java/org/apache/qpid/server/management/plugin/controller/AbstractManagementController.java
@@ -0,0 +1,204 @@
+/*
+ *
+ * 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.controller.ConverterHelper.encode;
+import static org.apache.qpid.server.management.plugin.ManagementException.createBadRequestManagementException;
+
+import java.util.Collection;
+import java.util.Collections;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+
+import javax.servlet.http.HttpServletResponse;
+
+import org.apache.qpid.server.management.plugin.ManagementController;
+import org.apache.qpid.server.management.plugin.ManagementException;
+import org.apache.qpid.server.management.plugin.RequestType;
+import org.apache.qpid.server.management.plugin.ManagementRequest;
+import org.apache.qpid.server.management.plugin.ManagementResponse;
+import org.apache.qpid.server.management.plugin.ResponseType;
+
+public abstract class AbstractManagementController implements ManagementController
+{
+ protected static final String USER_PREFERENCES = "userpreferences";
+ protected static final String VISIBLE_USER_PREFERENCES = "visiblepreferences";
+
+ public ManagementResponse handleGet(final ManagementRequest request) throws ManagementException
+ {
+ final RequestType type = getRequestType(request);
+ switch (type)
+ {
+ case OPERATION:
+ {
+ final Collection<String> hierarchy = getCategoryHierarchy(request.getRoot(), request.getCategory());
+ final List<String> operationPath = request.getPath().subList(0, hierarchy.size());
+ final String operationName = request.getPath().get(hierarchy.size());
+ return invoke(request.getRoot(),
+ request.getCategory(),
+ operationPath,
+ operationName,
+ request.getParametersAsFlatMap(),
+ false,
+ request.isSecure() || request.isConfidentialOperationAllowedOnInsecureChannel());
+ }
+ case MODEL_OBJECT:
+ {
+ final Object response = get(request.getRoot(),
+ request.getCategory(),
+ request.getPath(),
+ request.getParameters());
+
+ return new ControllerManagementResponse(ResponseType.MODEL_OBJECT, response);
+ }
+ case VISIBLE_PREFERENCES:
+ case USER_PREFERENCES:
+ {
+ final Object response = getPreferences(request.getRoot(),
+ request.getCategory(),
+ request.getPath(),
+ request.getParameters());
+ return new ControllerManagementResponse(ResponseType.DATA, response);
+ }
+ default:
+ {
+ throw createBadRequestManagementException(String.format("Unexpected request type '%s' for path '%s'",
+ type,
+ getCategoryMapping(request.getCategory())));
+ }
+ }
+
+
+ }
+
+ public ManagementResponse handlePut(final ManagementRequest request) throws ManagementException
+ {
+ return handlePostOrPut(request);
+ }
+
+ public ManagementResponse handlePost(final ManagementRequest request) throws ManagementException
+ {
+ return handlePostOrPut(request);
+ }
+
+ public ManagementResponse handleDelete(final ManagementRequest request) throws ManagementException
+ {
+ final RequestType type = getRequestType(request);
+ switch (type)
+ {
+ case MODEL_OBJECT:
+ {
+ delete(request.getRoot(),
+ request.getCategory(),
+ request.getPath(),
+ request.getParameters());
+ break;
+ }
+ case VISIBLE_PREFERENCES:
+ case USER_PREFERENCES:
+ {
+ deletePreferences(request.getRoot(),
+ request.getCategory(),
+ request.getPath(),
+ request.getParameters());
+ break;
+ }
+ default:
+ {
+ throw createBadRequestManagementException(String.format("Unexpected request type '%s' for path '%s'",
+ type,
+ getCategoryMapping(request.getCategory())));
+ }
+ }
+ return new ControllerManagementResponse(ResponseType.EMPTY, null);
+ }
+
+ private ManagementResponse handlePostOrPut(final ManagementRequest request) throws ManagementException
+ {
+ final RequestType type = getRequestType(request);
+ final Collection<String> hierarchy = getCategoryHierarchy(request.getRoot(), request.getCategory());
+ switch (type)
+ {
+ case OPERATION:
+ {
+ final List<String> operationPath = request.getPath().subList(0, hierarchy.size());
+ final String operationName = request.getPath().get(hierarchy.size());
+ @SuppressWarnings("unchecked")
+ final Map<String, Object> arguments = request.getBody(LinkedHashMap.class);
+ return invoke(request.getRoot(),
+ request.getCategory(),
+ operationPath,
+ operationName,
+ arguments,
+ true,
+ request.isSecure()
+ || request.isConfidentialOperationAllowedOnInsecureChannel());
+ }
+ case MODEL_OBJECT:
+ {
+ @SuppressWarnings("unchecked")
+ final Map<String, Object> attributes = request.getBody(LinkedHashMap.class);
+ final Object response = createOrUpdate(request.getRoot(),
+ request.getCategory(),
+ request.getPath(),
+ attributes,
+ "POST".equalsIgnoreCase(request.getMethod()));
+ int responseCode = HttpServletResponse.SC_OK;
+ ResponseType responseType = ResponseType.EMPTY;
+ Map<String, String> headers = Collections.emptyMap();
+ if (response != null)
+ {
+ responseCode = HttpServletResponse.SC_CREATED;
+ final StringBuilder requestURL = new StringBuilder(request.getRequestURL());
+ if (hierarchy.size() != request.getPath().size())
+ {
+ Object name = attributes.get("name");
+ requestURL.append("/").append(encode(String.valueOf(name)));
+ }
+ headers = Collections.singletonMap("Location", requestURL.toString());
+ responseType = ResponseType.MODEL_OBJECT;
+ }
+ return new ControllerManagementResponse(responseType, response, responseCode, headers);
+ }
+ case VISIBLE_PREFERENCES:
+ case USER_PREFERENCES:
+ {
+ setPreferences(request.getRoot(),
+ request.getCategory(),
+ request.getPath(),
+ request.getBody(Object.class),
+ request.getParameters(),
+ "POST".equalsIgnoreCase(request.getMethod()));
+ return new ControllerManagementResponse(ResponseType.EMPTY, null);
+ }
+ default:
+ {
+ throw createBadRequestManagementException(String.format("Unexpected request type '%s' for path '%s'",
+ type,
+ getCategoryMapping(request.getCategory())));
+ }
+ }
+
+ }
+
+ protected abstract RequestType getRequestType(ManagementRequest managementRequest) throws ManagementException;
+}
diff --git a/broker-plugins/management-http/src/main/java/org/apache/qpid/server/management/plugin/controller/ConverterHelper.java b/broker-plugins/management-http/src/main/java/org/apache/qpid/server/management/plugin/controller/ConverterHelper.java
new file mode 100644
index 0000000..b4fc180
--- /dev/null
+++ b/broker-plugins/management-http/src/main/java/org/apache/qpid/server/management/plugin/controller/ConverterHelper.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;
+
+import java.io.UnsupportedEncodingException;
+import java.net.URLEncoder;
+import java.nio.charset.StandardCharsets;
+import java.util.List;
+import java.util.Map;
+import java.util.regex.Pattern;
+
+import org.apache.qpid.server.management.plugin.HttpManagementUtil;
+import org.apache.qpid.server.management.plugin.ManagementException;
+
+public class ConverterHelper
+{
+
+ private static final Pattern CONTEXT_VARIABLE_PATTERN = Pattern.compile("\\$\\{[\\w+.\\-:]+}");
+
+ private ConverterHelper()
+ {
+ super();
+ }
+
+ public static long toLong(final Object attributeValue)
+ {
+ long value;
+ if (attributeValue instanceof Number)
+ {
+ value = ((Number) attributeValue).longValue();
+ }
+ else if (attributeValue instanceof String)
+ {
+ try
+ {
+ value = Long.parseLong((String) attributeValue);
+ }
+ catch (Exception e)
+ {
+ value = 0;
+ }
+ }
+ else
+ {
+ value = 0;
+ }
+ return value;
+ }
+
+ public static boolean toBoolean(final Object attributeValue)
+ {
+ boolean value;
+ if (attributeValue instanceof Boolean)
+ {
+ value = (Boolean) attributeValue;
+ }
+ else if (attributeValue instanceof String)
+ {
+ return Boolean.parseBoolean((String)attributeValue);
+
+ }
+ else
+ {
+ value = false;
+ }
+ return value;
+ }
+
+ public static int toInt(final Object value)
+ {
+ int result;
+ if (value instanceof Number)
+ {
+ result = ((Number) value).intValue();
+ }
+ else if (value instanceof String)
+ {
+ try
+ {
+ result = Integer.parseInt(String.valueOf(value));
+ }
+ catch (RuntimeException e)
+ {
+ result = 0;
+ }
+ }
+ else
+ {
+ result = 0;
+ }
+ return result;
+ }
+
+ public static boolean isContextVariable(final Object value)
+ {
+ return value != null && CONTEXT_VARIABLE_PATTERN.matcher(String.valueOf(value)).matches();
+ }
+
+ public static String encode (String value)
+ {
+ try
+ {
+ return URLEncoder.encode(value, StandardCharsets.UTF_8.displayName());
+ }
+ catch (UnsupportedEncodingException e)
+ {
+ throw ManagementException.createInternalServerErrorManagementException("UTF8 encoding is unsupported", e);
+ }
+ }
+
+ public static String getParameter(String name, Map<String, List<String>> parameters)
+ {
+ List<String> values = parameters.get(name);
+ return values == null || values.isEmpty() ? null : values.get(0);
+ }
+
+ public static int getIntParameterFromRequest(final Map<String, List<String>> parameters,
+ final String parameterName,
+ final int defaultValue)
+ {
+ int intValue = defaultValue;
+ final String stringValue = getParameter(parameterName, parameters);
+ if (stringValue != null)
+ {
+ try
+ {
+ intValue = Integer.parseInt(stringValue);
+ }
+ catch (NumberFormatException e)
+ {
+ // noop
+ }
+ }
+ return intValue;
+ }
+}
diff --git a/broker-plugins/management-http/src/main/java/org/apache/qpid/server/management/plugin/controller/latest/LatestManagementController.java b/broker-plugins/management-http/src/main/java/org/apache/qpid/server/management/plugin/controller/latest/LatestManagementController.java
new file mode 100644
index 0000000..18b457a
--- /dev/null
+++ b/broker-plugins/management-http/src/main/java/org/apache/qpid/server/management/plugin/controller/latest/LatestManagementController.java
@@ -0,0 +1,863 @@
+/*
+ *
+ * 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.DEFAULT_PREFERENCE_OPERATION_TIMEOUT;
+import static org.apache.qpid.server.management.plugin.HttpManagementConfiguration.PREFERENCE_OPERTAION_TIMEOUT_CONTEXT_NAME;
+import static org.apache.qpid.server.management.plugin.ManagementException.createBadRequestManagementException;
+import static org.apache.qpid.server.management.plugin.ManagementException.createForbiddenManagementException;
+import static org.apache.qpid.server.management.plugin.ManagementException.createNotAllowedManagementException;
+import static org.apache.qpid.server.management.plugin.ManagementException.createNotFoundManagementException;
+import static org.apache.qpid.server.management.plugin.controller.ConverterHelper.getParameter;
+import static org.apache.qpid.server.management.plugin.servlet.rest.AbstractServlet.CONTENT_DISPOSITION_ATTACHMENT_FILENAME_PARAM;
+import static org.apache.qpid.server.model.ConfiguredObjectTypeRegistry.returnsCollectionOfConfiguredObjects;
+
+import java.util.ArrayList;
+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 java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+import java.util.function.Predicate;
+import java.util.stream.Collectors;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import org.apache.qpid.server.management.plugin.HttpManagementConfiguration;
+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.management.plugin.ResponseType;
+import org.apache.qpid.server.management.plugin.controller.AbstractManagementController;
+import org.apache.qpid.server.management.plugin.controller.ControllerManagementResponse;
+import org.apache.qpid.server.management.plugin.controller.ConverterHelper;
+import org.apache.qpid.server.management.plugin.servlet.rest.ConfiguredObjectToMapConverter;
+import org.apache.qpid.server.management.plugin.servlet.rest.NotFoundException;
+import org.apache.qpid.server.management.plugin.servlet.rest.RequestInfo;
+import org.apache.qpid.server.management.plugin.servlet.rest.RestUserPreferenceHandler;
+import org.apache.qpid.server.model.AbstractConfigurationChangeListener;
+import org.apache.qpid.server.model.BrokerModel;
+import org.apache.qpid.server.model.ConfiguredObject;
+import org.apache.qpid.server.model.ConfiguredObjectFinder;
+import org.apache.qpid.server.model.ConfiguredObjectOperation;
+import org.apache.qpid.server.model.Model;
+import org.apache.qpid.server.model.State;
+import org.apache.qpid.server.model.preferences.UserPreferences;
+
+public class LatestManagementController extends AbstractManagementController
+{
+ private static final Logger LOGGER = LoggerFactory.getLogger(LatestManagementController.class);
+
+ 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 SORT_PARAM = "sort";
+ private static final String EXTRACT_INITIAL_CONFIG_PARAM = "extractInitialConfig";
+ private static final String EXCLUDE_INHERITED_CONTEXT_PARAM = "excludeInheritedContext";
+ private static final String SINGLETON_MODEL_OBJECT_RESPONSE_AS_LIST = "singletonModelObjectResponseAsList";
+ private static final Set<String> RESERVED_PARAMS =
+ new HashSet<>(Arrays.asList(DEPTH_PARAM,
+ SORT_PARAM,
+ OVERSIZE_PARAM,
+ ACTUALS_PARAM,
+ EXTRACT_INITIAL_CONFIG_PARAM,
+ CONTENT_DISPOSITION_ATTACHMENT_FILENAME_PARAM,
+ EXCLUDE_INHERITED_CONTEXT_PARAM,
+ SINGLETON_MODEL_OBJECT_RESPONSE_AS_LIST));
+
+ private static final int DEFAULT_DEPTH = 0;
+ private static final int DEFAULT_OVERSIZE = 120;
+
+
+ private final ConcurrentMap<ConfiguredObject<?>, ConfiguredObjectFinder> _configuredObjectFinders =
+ new ConcurrentHashMap<>();
+ private final Set<String> _supportedCategories;
+
+ private final ConfiguredObjectToMapConverter _objectConverter = new ConfiguredObjectToMapConverter();
+ private final RestUserPreferenceHandler _userPreferenceHandler;
+
+
+ LatestManagementController(final HttpManagementConfiguration<?> httpManagement)
+ {
+ final Long preferenceOperationTimeout =
+ httpManagement.getContextValue(Long.class, PREFERENCE_OPERTAION_TIMEOUT_CONTEXT_NAME);
+ _userPreferenceHandler = new RestUserPreferenceHandler(preferenceOperationTimeout == null
+ ? DEFAULT_PREFERENCE_OPERATION_TIMEOUT
+ : preferenceOperationTimeout);
+ _supportedCategories = Collections.unmodifiableSet(BrokerModel.getInstance()
+ .getSupportedCategories()
+ .stream()
+ .map(Class::getSimpleName)
+ .collect(Collectors.toSet()));
+ }
+
+ @Override
+ public String getVersion()
+ {
+ return BrokerModel.MODEL_VERSION;
+ }
+
+ @Override
+ public Set<String> getCategories()
+ {
+ return _supportedCategories;
+ }
+
+ @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 managedObject.getCategoryClass().getSimpleName();
+ }
+
+ @Override
+ public List<String> getCategoryHierarchy(final ConfiguredObject<?> root, final String category)
+ {
+ ConfiguredObjectFinder finder = getConfiguredObjectFinder(root);
+ Class<? extends ConfiguredObject>[] hierarchy = finder.getHierarchy(category.toLowerCase());
+ if (hierarchy == null)
+ {
+ return Collections.emptyList();
+ }
+ return Arrays.stream(hierarchy).map(Class::getSimpleName).collect(Collectors.toList());
+ }
+
+ @Override
+ public ManagementController getNextVersionManagementController()
+ {
+ return null;
+ }
+
+ @Override
+ protected RequestType getRequestType(final ManagementRequest request) throws ManagementException
+ {
+ final ConfiguredObject<?> root = request.getRoot();
+ if (root == null)
+ {
+ final String message =
+ String.format("No HTTP Management alias mapping found for '%s'", request.getRequestURL());
+ LOGGER.info(message);
+ throw createNotFoundManagementException(message);
+ }
+ final ConfiguredObjectFinder finder = getConfiguredObjectFinder(root);
+ final String category = request.getCategory();
+ final Class<? extends ConfiguredObject> configuredClass = getRequestCategoryClass(category, root.getModel());
+ final Class<? extends ConfiguredObject>[] hierarchy = finder.getHierarchy(configuredClass);
+ return getManagementRequestType(request.getMethod(), category, request.getPath(), hierarchy);
+ }
+
+ @Override
+ public Object get(final ConfiguredObject<?> root,
+ final String category,
+ final List<String> path,
+ final Map<String, List<String>> parameters) throws ManagementException
+ {
+ try
+ {
+ final Predicate<ConfiguredObject<?>> filterPredicate = buildFilterPredicates(parameters);
+ final boolean singleObjectRequest = isFullPath(root, path, category) && !hasFilter(parameters);
+ final Collection<ConfiguredObject<?>> allObjects = getTargetObjects(root, category, path, filterPredicate);
+ if (singleObjectRequest)
+ {
+ if (allObjects.isEmpty())
+ {
+ throw createNotFoundManagementException("Not Found");
+ }
+ else if (allObjects.size() != 1)
+ {
+ throw createBadRequestManagementException(String.format(
+ "Unexpected number of objects found [%d] for singleton request URI '%s'",
+ allObjects.size(), ManagementException.getRequestURI(path, getCategoryMapping(category))));
+ }
+ else
+ {
+ return allObjects.iterator().next();
+ }
+ }
+ else
+ {
+ return allObjects;
+ }
+ }
+ catch (RuntimeException e)
+ {
+ throw ManagementException.toManagementException(e, getCategoryMapping(category), path);
+ }
+ catch (Error e)
+ {
+ throw ManagementException.handleError(e);
+ }
+ }
+
+ @Override
+ public ConfiguredObject<?> createOrUpdate(final ConfiguredObject<?> root,
+ final String category,
+ final List<String> path,
+ final Map<String, Object> providedObject,
+ final boolean isPost) throws ManagementException
+ {
+ try
+ {
+ final List<String> hierarchy = getCategoryHierarchy(root, category);
+ if (path.isEmpty() && hierarchy.size() == 0)
+ {
+ root.setAttributes(providedObject);
+ return null;
+ }
+ final Class<? extends ConfiguredObject> categoryClass = getRequestCategoryClass(category, root.getModel());
+ ConfiguredObject theParent = root;
+ if (hierarchy.size() > 1)
+ {
+ final ConfiguredObjectFinder finder = getConfiguredObjectFinder(root);
+ theParent = finder.findObjectParentsFromPath(path, finder.getHierarchy(categoryClass), categoryClass);
+ }
+
+ final boolean isFullObjectURL = path.size() == hierarchy.size();
+ if (isFullObjectURL)
+ {
+ final String name = path.get(path.size() - 1);
+ final ConfiguredObject<?> configuredObject = theParent.getChildByName(categoryClass, name);
+ if (configuredObject != null)
+ {
+ configuredObject.setAttributes(providedObject);
+ return null;
+ }
+ else if (isPost)
+ {
+ throw createNotFoundManagementException(String.format("%s '%s' not found",
+ categoryClass.getSimpleName(),
+ name));
+ }
+ else
+ {
+ providedObject.put(ConfiguredObject.NAME, name);
+ }
+ }
+
+ return theParent.createChild(categoryClass, providedObject);
+ }
+ catch (RuntimeException e)
+ {
+ throw ManagementException.toManagementException(e, getCategoryMapping(category), path);
+ }
+ catch (Error e)
+ {
+ throw ManagementException.handleError(e);
+ }
+ }
+
+ @Override
+ public int delete(final ConfiguredObject<?> root,
+ final String category,
+ final List<String> names,
+ final Map<String, List<String>> parameters) throws ManagementException
+ {
+ int counter = 0;
+ try
+ {
+ final Predicate<ConfiguredObject<?>> filterPredicate = buildFilterPredicates(parameters);
+ final Collection<ConfiguredObject<?>> allObjects = getTargetObjects(root, category, names, filterPredicate);
+ if (allObjects.isEmpty())
+ {
+ throw createNotFoundManagementException("Not Found");
+ }
+
+ for (ConfiguredObject o : allObjects)
+ {
+ o.delete();
+ counter++;
+ }
+ }
+ catch (RuntimeException e)
+ {
+ throw ManagementException.toManagementException(e, getCategoryMapping(category), names);
+ }
+ catch (Error e)
+ {
+ throw ManagementException.handleError(e);
+ }
+ return counter;
+ }
+
+
+ @Override
+ @SuppressWarnings("unchecked")
+ public ManagementResponse invoke(final ConfiguredObject<?> root,
+ final String category,
+ final List<String> names,
+ final String operationName,
+ final Map<String, Object> operationArguments,
+ final boolean isPost,
+ final boolean isSecureOrAllowedOnInsecureChannel) throws ManagementException
+ {
+ ResponseType responseType = ResponseType.DATA;
+ Object returnValue;
+ try
+ {
+ final ConfiguredObject<?> target = getTarget(root, category, names);
+ final Map<String, ConfiguredObjectOperation<?>> availableOperations =
+ root.getModel().getTypeRegistry().getOperations(target.getClass());
+ final ConfiguredObjectOperation operation = availableOperations.get(operationName);
+ if (operation == null)
+ {
+ throw createNotFoundManagementException(String.format("No such operation '%s' in '%s'",
+ operationName,
+ category));
+ }
+
+ if (operation.isSecure(target, operationArguments) && !isSecureOrAllowedOnInsecureChannel)
+ {
+ throw createForbiddenManagementException(String.format(
+ "Operation '%s' can only be performed over a secure (HTTPS) connection",
+ operationName));
+ }
+
+ if (!isPost && !operation.isNonModifying())
+ {
+ throw createNotAllowedManagementException(String.format(
+ "Operation '%s' modifies the object so you must use POST.",
+ operationName), Collections.singletonMap("Allow", "POST"));
+ }
+
+ returnValue = operation.perform(target, operationArguments);
+
+ if (ConfiguredObject.class.isAssignableFrom(operation.getReturnType())
+ || returnsCollectionOfConfiguredObjects(operation))
+ {
+ responseType = ResponseType.MODEL_OBJECT;
+ }
+
+ }
+ catch (RuntimeException e)
+ {
+ throw ManagementException.toManagementException(e, getCategoryMapping(category), names);
+ }
+ catch (Error e)
+ {
+ throw ManagementException.handleError(e);
+ }
+ return new ControllerManagementResponse(responseType,returnValue);
+ }
+
+ @Override
+ public Object getPreferences(final ConfiguredObject<?> root,
+ final String category,
+ final List<String> path,
+ final Map<String, List<String>> parameters) throws ManagementException
+ {
+ Object responseObject;
+ try
+ {
+ final List<String> hierarchy = getCategoryHierarchy(root, category);
+ final Collection<ConfiguredObject<?>> allObjects = getTargetObjects(root, category, path, null);
+ if (allObjects.isEmpty() && isFullPath(root, path, category))
+ {
+ throw createNotFoundManagementException("Not Found");
+ }
+
+ final RequestInfo requestInfo = RequestInfo.createPreferencesRequestInfo(path.subList(0, hierarchy.size()),
+ path.subList(hierarchy.size() + 1,
+ path.size()),
+ parameters);
+
+ if (path.contains("*"))
+ {
+ List<Object> preferencesList = new ArrayList<>(allObjects.size());
+ responseObject = preferencesList;
+ for (ConfiguredObject<?> target : allObjects)
+ {
+ try
+ {
+ final UserPreferences userPreferences = target.getUserPreferences();
+ final Object preferences = _userPreferenceHandler.handleGET(userPreferences, requestInfo);
+ if (preferences == null
+ || (preferences instanceof Collection && ((Collection) preferences).isEmpty())
+ || (preferences instanceof Map && ((Map) preferences).isEmpty()))
+ {
+ continue;
+ }
+ preferencesList.add(preferences);
+ }
+ catch (NotFoundException e)
+ {
+ // The case where the preference's type and name is provided, but this particular object does not
+ // have a matching preference.
+ }
+ }
+ }
+ else
+ {
+ final ConfiguredObject<?> target = allObjects.iterator().next();
+ final UserPreferences userPreferences = target.getUserPreferences();
+ responseObject = _userPreferenceHandler.handleGET(userPreferences, requestInfo);
+ }
+ }
+ catch (RuntimeException e)
+ {
+ throw ManagementException.toManagementException(e, getCategoryMapping(category), path);
+ }
+ catch (Error e)
+ {
+ throw ManagementException.handleError(e);
+ }
+ return responseObject;
+ }
+
+ @Override
+ public void setPreferences(final ConfiguredObject<?> root,
+ final String category,
+ final List<String> path,
+ final Object providedObject,
+ final Map<String, List<String>> parameters,
+ final boolean isPost) throws ManagementException
+
+ {
+ try
+ {
+ final List<String> hierarchy = getCategoryHierarchy(root, category);
+ final RequestInfo requestInfo = RequestInfo.createPreferencesRequestInfo(path.subList(0, hierarchy.size()),
+ path.subList(hierarchy.size() + 1,
+ path.size()),
+ parameters);
+ final ConfiguredObject<?> target = getTarget(root, category, requestInfo.getModelParts());
+ if (isPost)
+ {
+ _userPreferenceHandler.handlePOST(target, requestInfo, providedObject);
+ }
+ else
+ {
+ _userPreferenceHandler.handlePUT(target, requestInfo, providedObject);
+ }
+ }
+ catch (RuntimeException e)
+ {
+ throw ManagementException.toManagementException(e, getCategoryMapping(category), path);
+ }
+ catch (Error e)
+ {
+ throw ManagementException.handleError(e);
+ }
+ }
+
+ @Override
+ public int deletePreferences(final ConfiguredObject<?> root,
+ final String category,
+ final List<String> names,
+ final Map<String, List<String>> parameters) throws ManagementException
+ {
+ int counter = 0;
+ try
+ {
+ final List<String> hierarchy = getCategoryHierarchy(root, category);
+ final RequestInfo requestInfo = RequestInfo.createPreferencesRequestInfo(names.subList(0, hierarchy.size()),
+ names.subList(hierarchy.size() + 1,
+ names.size()),
+ parameters);
+ final Collection<ConfiguredObject<?>> objects = getTargetObjects(root,
+ category,
+ requestInfo.getModelParts(),
+ buildFilterPredicates(parameters));
+ if (objects == null)
+ {
+ throw createNotFoundManagementException("Not Found");
+ }
+
+ //TODO: define format how to report the results for bulk delete, i.e. how to report individual errors and success statuses
+ if (objects.size() > 1)
+ {
+ throw createBadRequestManagementException("Deletion of user preferences using wildcards is unsupported");
+ }
+
+ for (ConfiguredObject o : objects)
+ {
+ _userPreferenceHandler.handleDELETE(o.getUserPreferences(), requestInfo);
+ counter++;
+ }
+ }
+ catch (RuntimeException e)
+ {
+ throw ManagementException.toManagementException(e, getCategoryMapping(category), names);
+ }
+ catch (Error e)
+ {
+ throw ManagementException.handleError(e);
+ }
+ return counter;
+ }
+
+ @Override
+ public Object formatConfiguredObject(final Object content,
+ final Map<String, List<String>> parameters,
+ final boolean isSecureOrAllowedOnInsecureChannel)
+ {
+
+ final int depth = ConverterHelper.getIntParameterFromRequest(parameters, DEPTH_PARAM, DEFAULT_DEPTH);
+ final int oversizeThreshold = ConverterHelper.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));
+
+ if (content instanceof ConfiguredObject)
+ {
+ Object object = convertObject(
+ (ConfiguredObject) 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 ConfiguredObject)
+ .map(ConfiguredObject.class::cast)
+ .map(o -> convertObject(
+ o,
+ depth,
+ actuals,
+ oversizeThreshold,
+ isSecureOrAllowedOnInsecureChannel,
+ excludeInheritedContext)).collect(Collectors.toSet());
+ if (!results.isEmpty())
+ {
+ return results;
+ }
+ }
+ return content;
+ }
+
+ private Map<String,Object> convertObject(final ConfiguredObject<?> configuredObject, final int depth,
+ final boolean actuals,
+ final int oversizeThreshold,
+ final boolean isSecureOrConfidentialOperationAllowedOnInsecureChannel,
+ final boolean excludeInheritedContext)
+ {
+ return _objectConverter.convertObjectToMap(configuredObject, configuredObject.getCategoryClass(),
+ new ConfiguredObjectToMapConverter.ConverterOptions(
+ depth,
+ actuals,
+ oversizeThreshold,
+ isSecureOrConfidentialOperationAllowedOnInsecureChannel,
+ excludeInheritedContext));
+ }
+
+ private boolean isFullPath(final ConfiguredObject root, final List<String> parts, final String category)
+ {
+ List<String> hierarchy = getCategoryHierarchy(root, category);
+ return parts.size() == hierarchy.size() && !parts.contains("*");
+ }
+
+ private ConfiguredObjectFinder getConfiguredObjectFinder(final ConfiguredObject<?> root)
+ {
+ ConfiguredObjectFinder finder = _configuredObjectFinders.get(root);
+ if (finder == null)
+ {
+ finder = new ConfiguredObjectFinder(root);
+ final ConfiguredObjectFinder existingValue = _configuredObjectFinders.putIfAbsent(root, finder);
+ if (existingValue != null)
+ {
+ finder = existingValue;
+ }
+ else
+ {
+ final AbstractConfigurationChangeListener deletionListener =
+ new AbstractConfigurationChangeListener()
+ {
+ @Override
+ public void stateChanged(final ConfiguredObject<?> object,
+ final State oldState,
+ final State newState)
+ {
+ if (newState == State.DELETED)
+ {
+ _configuredObjectFinders.remove(root);
+ }
+ }
+ };
+ root.addChangeListener(deletionListener);
+ if (root.getState() == State.DELETED)
+ {
+ _configuredObjectFinders.remove(root);
+ root.removeChangeListener(deletionListener);
+ }
+ }
+ }
+ return finder;
+ }
+
+ private Collection<ConfiguredObject<?>> getTargetObjects(final ConfiguredObject<?> root,
+ final String category,
+ final List<String> path,
+ final Predicate<ConfiguredObject<?>> filterPredicate)
+ {
+ final ConfiguredObjectFinder finder = getConfiguredObjectFinder(root);
+ final Class<? extends ConfiguredObject> configuredClass = getRequestCategoryClass(category, root.getModel());
+ Collection<ConfiguredObject<?>> targetObjects =
+ finder.findObjectsFromPath(path, finder.getHierarchy(configuredClass), true);
+
+ if (targetObjects == null)
+ {
+ targetObjects = Collections.emptySet();
+ }
+ else if (filterPredicate != null)
+ {
+ targetObjects = targetObjects.stream().filter(filterPredicate).collect(Collectors.toList());
+ }
+ return targetObjects;
+ }
+
+ private RequestType getManagementRequestType(final String method,
+ final String categoryName,
+ final List<String> parts,
+ final Class<? extends ConfiguredObject>[] hierarchy)
+ {
+ String servletPath = getCategoryMapping(categoryName);
+ if ("POST".equals(method))
+ {
+ return getPostRequestType(parts, hierarchy, servletPath);
+ }
+ else if ("PUT".equals(method))
+ {
+ return getPutRequestType(parts, hierarchy, servletPath);
+ }
+ else if ("GET".equals(method))
+ {
+ return getGetRequestType(parts, hierarchy, servletPath);
+ }
+ else if ("DELETE".equals(method))
+ {
+ return getDeleteRequestType(parts, hierarchy, servletPath);
+ }
+ else
+ {
+ throw createBadRequestManagementException(String.format("Unexpected method type '%s' for path '%s/%s'",
+ method,
+ servletPath,
+ String.join("/", parts)));
+ }
+ }
+
+ private RequestType getDeleteRequestType(final List<String> parts,
+ final Class<? extends ConfiguredObject>[] hierarchy,
+ final String servletPath)
+ {
+ if (parts.size() <= hierarchy.length)
+ {
+ return RequestType.MODEL_OBJECT;
+ }
+ else
+ {
+ if (USER_PREFERENCES.equals(parts.get(hierarchy.length)))
+ {
+ return RequestType.USER_PREFERENCES;
+ }
+ }
+ final String expectedPath = buildExpectedPath(servletPath, Arrays.asList(hierarchy));
+ throw createBadRequestManagementException(String.format(
+ "Invalid DELETE path '%s/%s'. Expected: '%s' or '%s/userpreferences[/<preference type>[/<preference name>]]'",
+ servletPath,
+ String.join("/", parts),
+ expectedPath,
+ expectedPath));
+ }
+
+ private RequestType getGetRequestType(final List<String> parts,
+ final Class<? extends ConfiguredObject>[] hierarchy,
+ final String servletPath)
+ {
+ if (parts.size() <= hierarchy.length)
+ {
+ return RequestType.MODEL_OBJECT;
+ }
+ else
+ {
+ if (USER_PREFERENCES.equals(parts.get(hierarchy.length)))
+ {
+ return RequestType.USER_PREFERENCES;
+ }
+ else if (VISIBLE_USER_PREFERENCES.equals(parts.get(hierarchy.length)))
+ {
+ return RequestType.VISIBLE_PREFERENCES;
+ }
+ else if (parts.size() == hierarchy.length + 1)
+ {
+ return RequestType.OPERATION;
+ }
+ }
+ final String expectedPath = buildExpectedPath(servletPath, Arrays.asList(hierarchy));
+ throw new IllegalArgumentException(String.format("Invalid GET path '%s/%s'. Expected: '%s[/<operation name>]'",
+ servletPath,
+ String.join("/", parts),
+ expectedPath));
+ }
+
+ private RequestType getPutRequestType(final List<String> parts,
+ final Class<? extends ConfiguredObject>[] hierarchy,
+ final String servletPath)
+ {
+ if (parts.size() == hierarchy.length || parts.size() == hierarchy.length - 1)
+ {
+ return RequestType.MODEL_OBJECT;
+ }
+ else if (parts.size() > hierarchy.length && USER_PREFERENCES.equals(parts.get(hierarchy.length)))
+ {
+ return RequestType.USER_PREFERENCES;
+ }
+ else
+ {
+ final String expectedPath = buildExpectedPath(servletPath, Arrays.asList(hierarchy));
+ throw createBadRequestManagementException(String.format("Invalid PUT path '%s/%s'. Expected: '%s'",
+ servletPath,
+ String.join("/", parts),
+ expectedPath));
+ }
+ }
+
+ private RequestType getPostRequestType(final List<String> parts,
+ final Class<? extends ConfiguredObject>[] hierarchy,
+ final String servletPath)
+ {
+ if (parts.size() == hierarchy.length || parts.size() == hierarchy.length - 1)
+ {
+ return RequestType.MODEL_OBJECT;
+ }
+ else if (parts.size() > hierarchy.length)
+ {
+ if (USER_PREFERENCES.equals(parts.get(hierarchy.length)))
+ {
+ return RequestType.USER_PREFERENCES;
+ }
+ else if (parts.size() == hierarchy.length + 1
+ && !VISIBLE_USER_PREFERENCES.equals(parts.get(hierarchy.length)))
+ {
+ return RequestType.OPERATION;
+ }
+ }
+ final List<Class<? extends ConfiguredObject>> hierarchyList = Arrays.asList(hierarchy);
+ final String expectedFullPath = buildExpectedPath(servletPath, hierarchyList);
+ final String expectedParentPath = buildExpectedPath(servletPath, hierarchyList.subList(0, hierarchy.length - 1));
+
+ throw createBadRequestManagementException(String.format(
+ "Invalid POST path '%s/%s'. Expected: '%s/<operation name>'"
+ + " or '%s'"
+ + " or '%s/userpreferences[/<preference type>]'",
+ servletPath,
+ String.join("/", parts),
+ expectedFullPath,
+ expectedParentPath,
+ expectedFullPath));
+ }
+
+ private Class<? extends ConfiguredObject> getRequestCategoryClass(final String categoryName,
+ final Model model)
+ {
+ for (Class<? extends ConfiguredObject> category : model.getSupportedCategories())
+ {
+ if (category.getSimpleName().toLowerCase().equals(categoryName.toLowerCase()))
+ {
+ return category;
+ }
+ }
+ throw createNotFoundManagementException(String.format("Category is not found for '%s'", categoryName));
+ }
+
+ private String buildExpectedPath(final String servletPath, final List<Class<? extends ConfiguredObject>> hierarchy)
+ {
+ final StringBuilder expectedPath = new StringBuilder(servletPath);
+ for (Class<? extends ConfiguredObject> part : hierarchy)
+ {
+ expectedPath.append("/<");
+ expectedPath.append(part.getSimpleName().toLowerCase());
+ expectedPath.append(" name>");
+ }
+ return expectedPath.toString();
+ }
+
+ private Predicate<ConfiguredObject<?>> buildFilterPredicates(final Map<String, List<String>> parameters)
+ {
+ return parameters.entrySet().stream()
+ .filter(entry -> !RESERVED_PARAMS.contains(entry.getKey()))
+ .map(entry -> {
+ final String paramName = entry.getKey();
+ final List<String> allowedValues = entry.getValue();
+ return (Predicate<ConfiguredObject<?>>) object -> {
+ Object value = object.getAttribute(paramName);
+ return allowedValues.contains(String.valueOf(value));
+ };
+ }).reduce(Predicate::and).orElse(t -> true);
+ }
+
+ private ConfiguredObject<?> getTarget(final ConfiguredObject<?> root,
+ final String category,
+ final List<String> names)
+ {
+ final Class<? extends ConfiguredObject> configuredClass = getRequestCategoryClass(category, root.getModel());
+ final ConfiguredObject<?> target;
+ final ConfiguredObjectFinder finder = getConfiguredObjectFinder(root);
+ final Class<? extends ConfiguredObject>[] hierarchy = finder.getHierarchy(configuredClass);
+ if (names.isEmpty() && hierarchy.length == 0)
+ {
+ target = root;
+ }
+ else
+ {
+ ConfiguredObject theParent = root;
+ if (hierarchy.length > 1)
+ {
+ theParent = finder.findObjectParentsFromPath(names, hierarchy, configuredClass);
+ }
+ final String name = names.get(names.size() - 1);
+ target = theParent.getChildByName(configuredClass, name);
+ if (target == null)
+ {
+
+ final String errorMessage = String.format("%s '%s' not found",
+ configuredClass.getSimpleName(),
+ String.join("/", names));
+ throw createNotFoundManagementException(errorMessage);
+ }
+ }
+ return target;
+ }
+
+ private boolean hasFilter(Map<String, List<String>> parameters)
+ {
+ return parameters.keySet().stream().anyMatch(parameter -> !RESERVED_PARAMS.contains(parameter));
+ }
+
+}
diff --git a/broker-plugins/management-http/src/main/java/org/apache/qpid/server/management/plugin/controller/latest/LatestManagementControllerFactory.java b/broker-plugins/management-http/src/main/java/org/apache/qpid/server/management/plugin/controller/latest/LatestManagementControllerFactory.java
new file mode 100644
index 0000000..82ba07e
--- /dev/null
+++ b/broker-plugins/management-http/src/main/java/org/apache/qpid/server/management/plugin/controller/latest/LatestManagementControllerFactory.java
@@ -0,0 +1,56 @@
+/*
+ *
+ * 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 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.model.BrokerModel;
+import org.apache.qpid.server.plugin.PluggableService;
+
+@PluggableService
+public class LatestManagementControllerFactory implements ManagementControllerFactory
+{
+ @Override
+ public String getType()
+ {
+ return "org.apache.qpid.server.management.plugin.model.latest";
+ }
+
+ @Override
+ public String getVersion()
+ {
+ return BrokerModel.MODEL_VERSION;
+ }
+
+ @Override
+ public String getPreviousVersion()
+ {
+ return "7.0";
+ }
+
+ @Override
+ public ManagementController createManagementController(final HttpManagementConfiguration<?> httpManagement,
+ final ManagementController nextVersionManagementController)
+ {
+ return new LatestManagementController(httpManagement);
+ }
+}
diff --git a/broker-plugins/management-http/src/main/java/org/apache/qpid/server/management/plugin/servlet/rest/RequestInfo.java b/broker-plugins/management-http/src/main/java/org/apache/qpid/server/management/plugin/servlet/rest/RequestInfo.java
index 69de651..cc67cd7 100644
--- a/broker-plugins/management-http/src/main/java/org/apache/qpid/server/management/plugin/servlet/rest/RequestInfo.java
+++ b/broker-plugins/management-http/src/main/java/org/apache/qpid/server/management/plugin/servlet/rest/RequestInfo.java
@@ -26,6 +26,8 @@ import java.util.Map;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
+import org.apache.qpid.server.management.plugin.RequestType;
+
public class RequestInfo
{
private final RequestType _type;
@@ -124,8 +126,5 @@ public class RequestInfo
return _hierarchySatisfied && !_hasWildcard;
}
- enum RequestType
- {
- OPERATION, USER_PREFERENCES, VISIBLE_PREFERENCES, MODEL_OBJECT
- }
+
}
diff --git a/broker-plugins/management-http/src/main/java/org/apache/qpid/server/management/plugin/servlet/rest/RestServlet.java b/broker-plugins/management-http/src/main/java/org/apache/qpid/server/management/plugin/servlet/rest/RestServlet.java
index 4e5e524..9a53d11 100644
--- a/broker-plugins/management-http/src/main/java/org/apache/qpid/server/management/plugin/servlet/rest/RestServlet.java
+++ b/broker-plugins/management-http/src/main/java/org/apache/qpid/server/management/plugin/servlet/rest/RestServlet.java
@@ -19,82 +19,51 @@
package org.apache.qpid.server.management.plugin.servlet.rest;
-import static org.apache.qpid.server.management.plugin.HttpManagementConfiguration.DEFAULT_PREFERENCE_OPERATION_TIMEOUT;
-import static org.apache.qpid.server.management.plugin.HttpManagementConfiguration.PREFERENCE_OPERTAION_TIMEOUT_CONTEXT_NAME;
-import static org.apache.qpid.server.management.plugin.HttpManagementUtil.ensureFilenameIsRfc2183;
-import static org.apache.qpid.server.model.ConfiguredObjectTypeRegistry.getCollectionMemberType;
-import static org.apache.qpid.server.model.ConfiguredObjectTypeRegistry.returnsCollectionOfConfiguredObjects;
-
import java.io.IOException;
-import java.lang.reflect.ParameterizedType;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.UnsupportedEncodingException;
+import java.net.URLDecoder;
+import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
-import java.util.HashSet;
-import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
-import java.util.Set;
+import java.util.stream.Collectors;
+import javax.servlet.ServletConfig;
+import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.Part;
-import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
-import com.google.common.base.Joiner;
-import com.google.common.base.Predicate;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
+import com.fasterxml.jackson.databind.SerializationFeature;
+import com.google.common.base.Strings;
-import org.apache.qpid.server.configuration.IllegalConfigurationException;
import org.apache.qpid.server.management.plugin.HttpManagementUtil;
-import org.apache.qpid.server.model.AbstractConfiguredObject;
+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.model.ConfiguredObject;
-import org.apache.qpid.server.model.ConfiguredObjectFinder;
-import org.apache.qpid.server.model.ConfiguredObjectOperation;
+import org.apache.qpid.server.model.ConfiguredObjectJacksonModule;
import org.apache.qpid.server.model.Content;
-import org.apache.qpid.server.model.IllegalStateTransitionException;
-import org.apache.qpid.server.model.IntegrityViolationException;
-import org.apache.qpid.server.model.Model;
-import org.apache.qpid.server.model.OperationTimeoutException;
-import org.apache.qpid.server.model.preferences.UserPreferences;
+import org.apache.qpid.server.model.port.HttpPort;
import org.apache.qpid.server.util.DataUrlUtils;
-import org.apache.qpid.server.util.ExternalServiceException;
-import org.apache.qpid.server.util.ExternalServiceTimeoutException;
-import org.apache.qpid.server.util.urlstreamhandler.data.Handler;
public class RestServlet extends AbstractServlet
{
private static final long serialVersionUID = 1L;
+ private static final String APPLICATION_JSON = "application/json";
- private static final Logger LOGGER = LoggerFactory.getLogger(RestServlet.class);
-
- public static final String DEPTH_PARAM = "depth";
- public static final String OVERSIZE_PARAM = "oversize";
- public static final String ACTUALS_PARAM = "actuals";
- public static final String SORT_PARAM = "sort";
- public static final String EXTRACT_INITIAL_CONFIG_PARAM = "extractInitialConfig";
- public static final String EXCLUDE_INHERITED_CONTEXT_PARAM = "excludeInheritedContext";
- private static final String SINGLETON_MODEL_OBJECT_RESPONSE_AS_LIST = "singletonModelObjectResponseAsList";
- public static final Set<String> RESERVED_PARAMS =
- new HashSet<>(Arrays.asList(DEPTH_PARAM,
- SORT_PARAM,
- OVERSIZE_PARAM,
- ACTUALS_PARAM,
- EXTRACT_INITIAL_CONFIG_PARAM,
- CONTENT_DISPOSITION_ATTACHMENT_FILENAME_PARAM,
- EXCLUDE_INHERITED_CONTEXT_PARAM,
- SINGLETON_MODEL_OBJECT_RESPONSE_AS_LIST));
- public static final int DEFAULT_DEPTH = 0;
- public static final int DEFAULT_OVERSIZE = 120;
-
- private transient final ConfiguredObjectToMapConverter _objectConverter = new ConfiguredObjectToMapConverter();
- private transient RestUserPreferenceHandler _userPreferenceHandler;
+ private transient ManagementController _managementController;
@SuppressWarnings("unused")
public RestServlet()
@@ -107,505 +76,178 @@ public class RestServlet extends AbstractServlet
{
super.init();
- Handler.register();
- Long preferenceOperationTimeout = getManagementConfiguration().getContextValue(Long.class, PREFERENCE_OPERTAION_TIMEOUT_CONTEXT_NAME);
- _userPreferenceHandler = new RestUserPreferenceHandler(preferenceOperationTimeout == null
- ? DEFAULT_PREFERENCE_OPERATION_TIMEOUT
- : preferenceOperationTimeout);
- }
-
-
- private Collection<ConfiguredObject<?>> getTargetObjects(final Class<? extends ConfiguredObject> configuredClass,
- final ConfiguredObjectFinder finder,
- RequestInfo requestInfo,
- List<Predicate<ConfiguredObject<?>>> filterPredicateList)
- {
- List<String> names = requestInfo.getModelParts();
+ final ServletConfig servletConfig = getServletConfig();
+ final ServletContext servletContext = servletConfig.getServletContext();
- Collection<ConfiguredObject<?>> targetObjects = finder.findObjectsFromPath(names, finder.getHierarchy(configuredClass), true);
+ final String modelVersion = servletConfig.getInitParameter("qpid.controller.version");
+ if (modelVersion == null)
+ {
+ throw new ServletException("Controller version is not specified");
+ }
- if (!(targetObjects == null || filterPredicateList.isEmpty()))
+ @SuppressWarnings("uncjecked")
+ ManagementController controller = (ManagementController) servletContext.getAttribute("qpid.controller.chain");
+ do
{
- Iterator<ConfiguredObject<?>> iter = targetObjects.iterator();
- while (iter.hasNext())
+ if (controller.getVersion().equals(modelVersion))
{
- ConfiguredObject obj = iter.next();
- for (Predicate<ConfiguredObject<?>> predicate : filterPredicateList)
- {
- if (!predicate.apply(obj))
- {
- iter.remove();
- break;
- }
- }
+ _managementController = controller;
+ break;
}
-
+ controller = controller.getNextVersionManagementController();
}
- return targetObjects;
- }
+ while (controller != null);
- private List<Predicate<ConfiguredObject<?>>> buildFilterPredicates(final HttpServletRequest request)
- {
- List<Predicate<ConfiguredObject<?>>> predicates = new ArrayList<>();
-
- for (final String paramName : Collections.list(request.getParameterNames()))
+ if (_managementController == null)
{
- if (!RESERVED_PARAMS.contains(paramName))
- {
- final List<String> allowedValues = Arrays.asList(request.getParameterValues(paramName));
-
- predicates.add(new Predicate<ConfiguredObject<?>>()
- {
- @Override
- public boolean apply(final ConfiguredObject<?> obj)
- {
- Object value = obj.getAttribute(paramName);
- return allowedValues.contains(String.valueOf(value));
- }
- });
- }
+ throw new ServletException("Controller is not found");
}
- return Collections.unmodifiableList(predicates);
}
@Override
- protected void doGet(HttpServletRequest request,
- HttpServletResponse response,
+ protected void doGet(final HttpServletRequest httpServletRequest,
+ final HttpServletResponse httpServletResponse,
final ConfiguredObject<?> managedObject)
- throws ServletException, IOException
+ throws IOException
{
- ConfiguredObjectFinder finder = getConfiguredObjectFinder(managedObject);
- Class<? extends ConfiguredObject> configuredClass = getConfiguredClass(request, managedObject);
- if(configuredClass == null)
- {
- sendError(response, HttpServletResponse.SC_NOT_FOUND);
- return;
- }
- final Class<? extends ConfiguredObject>[] hierarchy = finder.getHierarchy(configuredClass);
- if(hierarchy == null)
- {
- sendError(response, HttpServletResponse.SC_NOT_FOUND);
- return;
- }
-
- RequestInfoParser requestInfoParser = new RequestInfoParser(hierarchy);
-
- RequestInfo requestInfo = requestInfoParser.parse(request);
- switch (requestInfo.getType())
+ try
{
- case OPERATION:
- {
- doOperation(requestInfo, managedObject, configuredClass, finder, request, response);
- break;
- }
- case MODEL_OBJECT:
- {
- // TODO - sort special params, everything else should act as a filter
- String attachmentFilename = request.getParameter(CONTENT_DISPOSITION_ATTACHMENT_FILENAME_PARAM);
-
- if (attachmentFilename != null)
- {
- setContentDispositionHeaderIfNecessary(response, attachmentFilename);
- }
+ final ManagementRequest request = new ServletManagementRequest(managedObject, httpServletRequest);
+ final ManagementController controller = getManagementController();
+ final ManagementResponse response = controller.handleGet(request);
- List<Predicate<ConfiguredObject<?>>> filterPredicateList = buildFilterPredicates(request);
- Collection<ConfiguredObject<?>> allObjects =
- getTargetObjects(configuredClass, finder, requestInfo, filterPredicateList);
-
- boolean singleObjectRequest = requestInfo.isSingletonRequest() && filterPredicateList.isEmpty();
-
- if (allObjects == null || (allObjects.isEmpty() && singleObjectRequest))
- {
- sendJsonErrorResponse(request, response, HttpServletResponse.SC_NOT_FOUND, "Not Found");
- return;
- }
-
- int depth;
- boolean actuals;
- int oversizeThreshold;
- boolean excludeInheritedContext;
-
- depth = getIntParameterFromRequest(request, DEPTH_PARAM, DEFAULT_DEPTH);
- oversizeThreshold = getIntParameterFromRequest(request, OVERSIZE_PARAM, DEFAULT_OVERSIZE);
- actuals = getBooleanParameterFromRequest(request, ACTUALS_PARAM);
- String excludeInheritedContextParameter = request.getParameter(EXCLUDE_INHERITED_CONTEXT_PARAM);
-
- excludeInheritedContext = excludeInheritedContextParameter == null || Boolean.parseBoolean(
- excludeInheritedContextParameter);
-
- boolean responseAsList = Boolean.parseBoolean(request.getParameter(SINGLETON_MODEL_OBJECT_RESPONSE_AS_LIST));
- final Object responseObject;
- if (!responseAsList && singleObjectRequest)
- {
- if (allObjects.size() != 1)
- {
- throw new IllegalStateException(String.format(
- "Unexpected number of objects found [%d] for singleton request URI '%s'",
- allObjects.size(), request.getRequestURI()));
- }
- ConfiguredObject<?> singletonObject = allObjects.iterator().next();
- responseObject = _objectConverter.convertObjectToMap(singletonObject, configuredClass,
- new ConfiguredObjectToMapConverter
- .ConverterOptions(
- depth,
- actuals,
- oversizeThreshold,
- request.isSecure(),
- excludeInheritedContext));
- }
- else
- {
- final List<Object> outputList = new ArrayList<>();
- for (ConfiguredObject configuredObject : allObjects)
- {
-
- outputList.add(_objectConverter.convertObjectToMap(configuredObject, configuredClass,
- new ConfiguredObjectToMapConverter.ConverterOptions(
- depth,
- actuals,
- oversizeThreshold,
- request.isSecure(),
- excludeInheritedContext)));
- }
-
- responseObject = outputList;
- }
-
- boolean sendCachingHeaders = attachmentFilename == null;
- sendJsonResponse(responseObject,
- request,
- response,
- HttpServletResponse.SC_OK,
- sendCachingHeaders);
- break;
- }
- case VISIBLE_PREFERENCES:
- case USER_PREFERENCES:
- {
- doGetUserPreferences(managedObject, configuredClass, finder, requestInfo, request, response);
- break;
- }
-
- default:
- {
- throw new IllegalStateException(String.format("Unexpected request type '%s' for path '%s'",
- requestInfo.getType(),
- request.getPathInfo()));
- }
- }
- }
-
-
- private boolean isSingleObjectRequest(final RequestInfo requestInfo,
- final Class<? extends ConfiguredObject>[] hierarchy)
- {
- if (hierarchy.length > 0)
- {
- List<String> pathInfoElements = requestInfo.getModelParts();
- return pathInfoElements.size() == hierarchy.length;
+ sendResponse(request, response, httpServletRequest, httpServletResponse, controller);
}
-
- return false;
- }
-
- private Class<? extends ConfiguredObject> getConfiguredClass(HttpServletRequest request, ConfiguredObject<?> managedObject)
- {
- final String[] servletPathElements = request.getServletPath().split("/");
- String categoryName = servletPathElements[servletPathElements.length-1];
- Model model = managedObject.getModel();
- for(Class<? extends ConfiguredObject> category : model.getSupportedCategories())
+ catch (ManagementException e)
{
- if(category.getSimpleName().toLowerCase().equals(categoryName))
- {
- return category;
- }
+ sendResponse(e, httpServletRequest, httpServletResponse);
}
- return null;
- }
-
- @Override
- protected void doPut(HttpServletRequest request,
- HttpServletResponse response,
- final ConfiguredObject<?> managedObject)
- throws ServletException, IOException
- {
- performCreateOrUpdate(request, response, managedObject);
}
@Override
- protected void service(HttpServletRequest request, HttpServletResponse response)
- throws ServletException, IOException
+ protected void doPost(final HttpServletRequest httpServletRequest,
+ final HttpServletResponse httpServletResponse,
+ final ConfiguredObject<?> managedObject) throws IOException
{
try
{
- super.service(request, response);
+ final ManagementRequest request = new ServletManagementRequest(managedObject, httpServletRequest);
+ final ManagementController controller = getManagementController();
+ final ManagementResponse response = controller.handlePost(request);
+
+ sendResponse(request, response, httpServletRequest, httpServletResponse, controller);
}
- catch (Exception | NoClassDefFoundError e)
+ catch (ManagementException e)
{
- setResponseStatus(request, response, e);
+ sendResponse(e, httpServletRequest, httpServletResponse);
}
}
- private void performCreateOrUpdate(HttpServletRequest request,
- HttpServletResponse response,
- final ConfiguredObject<?> managedObject)
- throws IOException, ServletException
+ @Override
+ protected void doPut(final HttpServletRequest httpServletRequest,
+ final HttpServletResponse httpServletResponse,
+ final ConfiguredObject<?> managedObject) throws IOException
{
-
- ConfiguredObjectFinder finder = getConfiguredObjectFinder(managedObject);
- final Class<? extends ConfiguredObject> configuredClass = getConfiguredClass(request, managedObject);
- final Class<? extends ConfiguredObject>[] hierarchy = finder.getHierarchy(configuredClass);
- RequestInfoParser requestInfoParser = new RequestInfoParser(hierarchy);
-
- response.setContentType("application/json");
-
- RequestInfo requestInfo = requestInfoParser.parse(request);
- switch (requestInfo.getType())
+ try
{
- case MODEL_OBJECT:
- {
- List<String> names = requestInfo.getModelParts();
- boolean isFullObjectURL = names.size() == hierarchy.length;
- Map<String, Object> providedObject = getRequestProvidedObject(request, requestInfo);
- if (names.isEmpty() && hierarchy.length == 0)
- {
- managedObject.setAttributes(providedObject);
- response.setStatus(HttpServletResponse.SC_OK);
- return;
- }
-
- ConfiguredObject theParent = managedObject;
- Class<? extends ConfiguredObject> objClass = configuredClass;
- if (hierarchy.length > 1)
- {
+ final ManagementRequest request = new ServletManagementRequest(managedObject, httpServletRequest);
+ final ManagementController controller = getManagementController();
+ final ManagementResponse response = controller.handlePut(request);
- theParent = finder.findObjectParentsFromPath(names, hierarchy, configuredClass);
- }
-
- if (isFullObjectURL)
- {
- String name = names.get(names.size() - 1);
- ConfiguredObject<?> configuredObject = theParent.getChildByName(objClass, name);
-
- if (configuredObject != null)
- {
- configuredObject.setAttributes(providedObject);
- response.setStatus(HttpServletResponse.SC_OK);
- return;
- }
- else if ("POST".equalsIgnoreCase(request.getMethod()))
- {
- sendJsonErrorResponse(request, response,
- HttpServletResponse.SC_NOT_FOUND,
- String.format("%s '%s' not found", configuredClass.getSimpleName(), name));
- return;
- }
- else
- {
- providedObject.put(ConfiguredObject.NAME, name);
- }
- }
-
- ConfiguredObject<?> configuredObject = theParent.createChild(objClass, providedObject);
- StringBuffer requestURL = request.getRequestURL();
- if (!isFullObjectURL)
- {
- requestURL.append("/").append(configuredObject.getName());
- }
- response.setHeader("Location", requestURL.toString());
- response.setStatus(HttpServletResponse.SC_CREATED);
- break;
- }
- case OPERATION:
- {
- doOperation(requestInfo, managedObject, configuredClass, finder, request, response);
- break;
- }
- case USER_PREFERENCES:
- {
- doPostOrPutUserPreference(requestInfo, managedObject, configuredClass, finder, request, response);
- break;
- }
- default:
- {
- throw new IllegalStateException(String.format("Unexpected request type '%s' for path '%s'",
- requestInfo.getType(),
- request.getPathInfo()));
- }
+ sendResponse(request, response, httpServletRequest, httpServletResponse, controller);
+ }
+ catch (ManagementException e)
+ {
+ sendResponse(e, httpServletRequest, httpServletResponse);
}
}
- private void doGetUserPreferences(final ConfiguredObject<?> managedObject,
- final Class<? extends ConfiguredObject> configuredClass,
- final ConfiguredObjectFinder finder, final RequestInfo requestInfo,
- final HttpServletRequest request,
- final HttpServletResponse response) throws IOException, ServletException
+ @Override
+ protected void doDelete(final HttpServletRequest httpServletRequest,
+ final HttpServletResponse httpServletResponse,
+ final ConfiguredObject<?> managedObject) throws IOException
{
- Collection<ConfiguredObject<?>> allObjects = getTargetObjects(
- configuredClass,
- finder,
- requestInfo,
- Collections.<Predicate<ConfiguredObject<?>>>emptyList());
-
- if (allObjects == null || (allObjects.isEmpty() && isSingleObjectRequest(requestInfo, finder.getHierarchy(configuredClass))))
+ try
{
- sendJsonErrorResponse(request, response, HttpServletResponse.SC_NOT_FOUND, "Not Found");
- return;
- }
+ final ManagementRequest request = new ServletManagementRequest(managedObject, httpServletRequest);
+ final ManagementController controller = getManagementController();
+ final ManagementResponse response = controller.handleDelete(request);
- final Object responseObject;
- if (requestInfo.hasWildcard())
- {
- responseObject = new ArrayList<>(allObjects.size());
- for (ConfiguredObject<?> target : allObjects)
- {
- final UserPreferences userPreferences = target.getUserPreferences();
- try
- {
- final Object preferences = _userPreferenceHandler.handleGET(userPreferences, requestInfo);
- if (preferences == null || (preferences instanceof Collection
- && ((Collection) preferences).isEmpty()) || (preferences instanceof Map
- && ((Map) preferences).isEmpty()))
- {
- continue;
- }
- ((List<Object>) responseObject).add(preferences);
- }
- catch (NotFoundException e)
- {
- // The case where the preference's type and name is provided, but this particular object does not
- // have a matching preference.
- }
- }
+ sendResponse(request, response, httpServletRequest, httpServletResponse, controller);
}
- else
+ catch (ManagementException e)
{
- ConfiguredObject<?> target = allObjects.iterator().next();
- final UserPreferences userPreferences = target.getUserPreferences();
-
- responseObject = _userPreferenceHandler.handleGET(userPreferences, requestInfo);
+ sendResponse(e, httpServletRequest, httpServletResponse);
}
- sendJsonResponse(responseObject, request, response);
}
- private void doPostOrPutUserPreference(final RequestInfo requestInfo,
- final ConfiguredObject<?> managedObject,
- final Class<? extends ConfiguredObject> configuredClass,
- final ConfiguredObjectFinder finder,
- final HttpServletRequest request,
- final HttpServletResponse response) throws IOException, ServletException
+ private ManagementController getManagementController()
{
- ConfiguredObject<?> target = getTarget(requestInfo, managedObject, configuredClass, finder);
+ return _managementController;
+ }
- final Object providedObject = getRequestProvidedObject(request, requestInfo, Object.class);
- if ("POST".equals(request.getMethod()))
- {
- _userPreferenceHandler.handlePOST(target, requestInfo, providedObject);
- }
- else if ("PUT".equals(request.getMethod()))
- {
- _userPreferenceHandler.handlePUT(target, requestInfo, providedObject);
- }
- else
- {
- sendJsonErrorResponse(request,
- response,
- HttpServletResponse.SC_INTERNAL_SERVER_ERROR,
- "unexpected http method");
- }
+ private void sendResponse(final ManagementException managementException,
+ final HttpServletRequest request,
+ final HttpServletResponse response) throws IOException
+ {
+ setHeaders(response);
+ setExceptionHeaders(managementException, response);
+ response.setStatus(managementException.getStatusCode());
+ writeJsonResponse(Collections.singletonMap("errorMessage", managementException.getMessage()),
+ request,
+ response);
}
- private void doOperation(final RequestInfo requestInfo,
- final ConfiguredObject<?> managedObject,
- final Class<? extends ConfiguredObject> configuredClass,
- final ConfiguredObjectFinder finder,
- final HttpServletRequest request,
- final HttpServletResponse response) throws IOException, ServletException
+ private void setExceptionHeaders(final ManagementException managementException, final HttpServletResponse response)
{
- ConfiguredObject<?> target = getTarget(requestInfo, managedObject, configuredClass, finder);
- if (target == null)
+ Map<String, String> headers = managementException.getHeaders();
+ if (headers != null)
{
- return;
+ headers.forEach(response::setHeader);
}
- String operationName = requestInfo.getOperationName();
- final Map<String, ConfiguredObjectOperation<?>> availableOperations =
- managedObject.getModel().getTypeRegistry().getOperations(target.getClass());
- ConfiguredObjectOperation operation = availableOperations.get(operationName);
- Map<String, Object> operationArguments;
-
+ }
- String requestMethod = request.getMethod();
- if (operation == null)
+ private String toContentDispositionHeader(final String attachmentFilename)
+ {
+ String filenameRfc2183 = HttpManagementUtil.ensureFilenameIsRfc2183(attachmentFilename);
+ if (filenameRfc2183.length() > 0)
{
- sendJsonErrorResponse(request,
- response,
- HttpServletResponse.SC_NOT_FOUND,
- "No such operation: " + operationName);
- return;
+ return String.format("attachment; filename=\"%s\"", filenameRfc2183);
}
else
{
- switch (requestMethod)
- {
- case "GET":
- if (operation.isNonModifying())
- {
- operationArguments = getOperationArgumentsAsMap(request);
- operationArguments.keySet().removeAll(Arrays.asList(CONTENT_DISPOSITION_ATTACHMENT_FILENAME_PARAM));
- }
- else
- {
- response.addHeader("Allow", "POST");
- sendJsonErrorResponse(request,
- response,
- HttpServletResponse.SC_METHOD_NOT_ALLOWED,
- "Operation "
- + operationName
- + " modifies the object so you must use POST.");
- return;
- }
-
- break;
- case "POST":
- operationArguments = getRequestProvidedObject(request, requestInfo);
- break;
- default:
- response.addHeader("Allow", (operation.isNonModifying() ? "POST, GET" : "POST"));
- sendJsonErrorResponse(request,
- response,
- HttpServletResponse.SC_METHOD_NOT_ALLOWED,
- "Operation "
- + operationName
- + " does not support the "
- + requestMethod
- + " requestMethod.");
- return;
- }
+ // Agent will allow user to choose a name
+ return "attachment";
}
+ }
-
- if(operation.isSecure(target, operationArguments) && !(request.isSecure() || HttpManagementUtil.getPort(request).isAllowConfidentialOperationsOnInsecureChannels()))
+ private void sendResponse(final ManagementRequest managementRequest,
+ final ManagementResponse managementResponse,
+ final HttpServletRequest request,
+ final HttpServletResponse response,
+ final ManagementController controller) throws IOException
+ {
+ setHeaders(response);
+ Map<String, String> headers = managementResponse.getHeaders();
+ if (!headers.isEmpty())
{
- sendJsonErrorResponse(request,
- response,
- HttpServletResponse.SC_FORBIDDEN,
- "Operation '" + operationName + "' can only be performed over a secure (HTTPS) connection");
- return;
+ headers.forEach(response::setHeader);
}
- Object returnVal = operation.perform(target, operationArguments);
- String attachmentFilename = request.getParameter(CONTENT_DISPOSITION_ATTACHMENT_FILENAME_PARAM);
- if (attachmentFilename != null)
+ Map<String, List<String>> parameters = managementRequest.getParameters();
+ if (parameters.containsKey(CONTENT_DISPOSITION_ATTACHMENT_FILENAME_PARAM))
{
- setContentDispositionHeaderIfNecessary(response, attachmentFilename);
+ String attachmentFilename = managementRequest.getParameter(CONTENT_DISPOSITION_ATTACHMENT_FILENAME_PARAM);
+ response.setHeader(CONTENT_DISPOSITION, toContentDispositionHeader(attachmentFilename));
}
+ response.setStatus(managementResponse.getResponseCode());
- if(returnVal instanceof Content)
+ Object body = managementResponse.getBody();
+ if (body instanceof Content)
{
- Content content = (Content)returnVal;
+ Content content = (Content) body;
try
{
-
writeTypedContent(content, request, response);
}
finally
@@ -613,350 +255,242 @@ public class RestServlet extends AbstractServlet
content.release();
}
}
- else
+ else if (body != null)
{
- final ConfiguredObjectToMapConverter.ConverterOptions converterOptions =
- new ConfiguredObjectToMapConverter.ConverterOptions(DEFAULT_DEPTH,
- false,
- DEFAULT_OVERSIZE,
- request.isSecure(),
- true);
- if (ConfiguredObject.class.isAssignableFrom(operation.getReturnType()))
+ response.setContentType(APPLICATION_JSON);
+ Object data;
+ if (managementResponse.getType() == ResponseType.MODEL_OBJECT)
{
- returnVal = _objectConverter.convertObjectToMap((ConfiguredObject<?>) returnVal,
- operation.getReturnType(),
- converterOptions);
+ data = controller.formatConfiguredObject(
+ managementResponse.getBody(),
+ parameters,
+ managementRequest.isSecure()
+ || managementRequest.isConfidentialOperationAllowedOnInsecureChannel());
}
- else if (returnsCollectionOfConfiguredObjects(operation))
+ else
{
- List<Map<String, Object>> output = new ArrayList<>();
- for (Object configuredObject : (Collection)returnVal)
- {
- output.add(_objectConverter.convertObjectToMap((ConfiguredObject<?>) configuredObject,
- getCollectionMemberType((ParameterizedType) operation.getGenericReturnType()),
- converterOptions));
- }
- returnVal = output;
+ data = managementResponse.getBody();
}
- sendJsonResponse(returnVal, request, response);
+ writeJsonResponse(data, request, response);
}
}
- private ConfiguredObject<?> getTarget(final RequestInfo requestInfo,
- final ConfiguredObject<?> managedObject,
- final Class<? extends ConfiguredObject> configuredClass,
- final ConfiguredObjectFinder finder) throws IOException
+ private void setHeaders(final HttpServletResponse response)
{
- final ConfiguredObject<?> target;
- final List<String> names = requestInfo.getModelParts();
- final Class<? extends ConfiguredObject>[] hierarchy = finder.getHierarchy(configuredClass);
- if (names.isEmpty() && hierarchy.length == 0)
+ response.setHeader("Cache-Control", "no-cache");
+ response.setHeader("Pragma", "no-cache");
+ response.setDateHeader("Expires", 0);
+ response.setCharacterEncoding(StandardCharsets.UTF_8.name());
+ }
+
+ private void writeJsonResponse(final Object formattedResponse,
+ final HttpServletRequest request,
+ final HttpServletResponse response) throws IOException
+ {
+ try (OutputStream stream = HttpManagementUtil.getOutputStream(request,
+ response,
+ getManagementConfiguration()))
{
- target = managedObject;
+ ObjectMapper mapper = ConfiguredObjectJacksonModule.newObjectMapper(false);
+ mapper.configure(SerializationFeature.INDENT_OUTPUT, true);
+ mapper.writeValue(stream, formattedResponse);
}
- else
+ }
+
+ private static Map<String, List<String>> parseQueryString(String queryString)
+ {
+ if (Strings.isNullOrEmpty(queryString))
{
- ConfiguredObject theParent = managedObject;
- if (hierarchy.length > 1)
+ return Collections.emptyMap();
+ }
+ Map<String, List<String>> query = new LinkedHashMap<>();
+ final String[] pairs = queryString.split("&");
+ for (String pairString : pairs)
+ {
+ List<String> pair = new ArrayList<>(Arrays.asList(pairString.split("=")));
+ if (pair.size() == 1)
{
-
- ConfiguredObject parent =
- finder.findObjectParentsFromPath(names, hierarchy, configuredClass);
- theParent = parent;
+ pair.add(null);
}
- String name = names.get(names.size() - 1);
- target = theParent.getChildByName(configuredClass, name);
- if (target == null)
+ else if (pair.size() != 2)
{
+ throw new IllegalArgumentException(String.format("could not parse query string '%s'", queryString));
+ }
- final String errorMessage = String.format("%s '%s' not found",
- configuredClass.getSimpleName(),
- Joiner.on("/").join(names));
- throw new NotFoundException(errorMessage);
+ String key;
+ String value;
+ try
+ {
+ key = URLDecoder.decode(pair.get(0), "UTF-8");
+ value = pair.get(1) == null ? null : URLDecoder.decode(pair.get(1), "UTF-8");
+ }
+ catch (UnsupportedEncodingException e)
+ {
+ throw new RuntimeException(e);
+ }
+ if (!query.containsKey(key))
+ {
+ query.put(key, new ArrayList<>());
}
+ query.get(key).add(value);
}
- return target;
+ return query;
}
- private Map<String, Object> getOperationArgumentsAsMap(HttpServletRequest request)
+ private static class ServletManagementRequest implements ManagementRequest
{
- Map<String, Object> providedObject;
- providedObject = new HashMap<>();
- for (Map.Entry<String, String[]> entry : request.getParameterMap().entrySet())
+ private final HttpPort<?> _port;
+ private final HttpServletRequest _request;
+ private final Map<String, List<String>> _query;
+ private final List<String> _path;
+ private final ConfiguredObject<?> _root;
+ private final String _category;
+ private final Map<String, String> _headers;
+
+ ServletManagementRequest(final ConfiguredObject<?> root,
+ final HttpServletRequest request)
{
- String[] value = entry.getValue();
- if (value != null)
- {
- if(value.length > 1)
- {
- providedObject.put(entry.getKey(), Arrays.asList(value));
- }
- else
- {
- providedObject.put(entry.getKey(), value[0]);
- }
- }
+ _root = root;
+ _request = request;
+ _port = HttpManagementUtil.getPort(request);
+ _query = Collections.unmodifiableMap(parseQueryString(request.getQueryString()));
+ String pathInfo = _request.getPathInfo() == null ? "" : _request.getPathInfo();
+ String servletPath = request.getServletPath();
+ _path = Collections.unmodifiableList(HttpManagementUtil.getPathInfoElements(servletPath, pathInfo));
+ final String[] servletPathElements = servletPath.split("/");
+ _category = servletPathElements[servletPathElements.length - 1];
+ final Map<String, String> headers = Collections.list(request.getHeaderNames())
+ .stream()
+ .collect(Collectors.toMap(name -> name, request::getHeader));
+ _headers = Collections.unmodifiableMap(headers);
}
- return providedObject;
- }
- private Map<String, Object> getRequestProvidedObject(HttpServletRequest request, final RequestInfo requestInfo)
- throws IOException, ServletException
- {
- return getRequestProvidedObject(request, requestInfo, LinkedHashMap.class);
- }
+ public ConfiguredObject<?> getRoot()
+ {
+ return _root;
+ }
- private <T> T getRequestProvidedObject(HttpServletRequest request,
- final RequestInfo requestInfo,
- Class<T> expectedClass)
- throws IOException, ServletException
- {
- T providedObject;
+ public boolean isSecure()
+ {
+ return _request.isSecure();
+ }
- ArrayList<String> headers = Collections.list(request.getHeaderNames());
- ObjectMapper mapper = new ObjectMapper();
+ public boolean isConfidentialOperationAllowedOnInsecureChannel()
+ {
+ return _port.isAllowConfidentialOperationsOnInsecureChannels();
+ }
- if (headers.contains("Content-Type") && request.getHeader("Content-Type").startsWith("multipart/form-data"))
+ public List<String> getPath()
{
- providedObject = (T) new LinkedHashMap<>();
- Map<String, String> fileUploads = new HashMap<>();
- Collection<Part> parts = request.getParts();
- for (Part part : parts)
- {
- if ("data".equals(part.getName()) && "application/json".equals(part.getContentType()))
- {
- try
- {
- providedObject = (T) mapper.readValue(part.getInputStream(), LinkedHashMap.class);
- }
- catch (JsonProcessingException e)
- {
- throw new IllegalArgumentException("Cannot parse the operation body as json",e);
- }
+ return _path;
+ }
- }
- else
- {
- byte[] data = new byte[(int) part.getSize()];
- part.getInputStream().read(data);
- String inlineURL = DataUrlUtils.getDataUrlForBytes(data);
- fileUploads.put(part.getName(), inlineURL);
- }
- }
- ((Map<String, Object>) providedObject).putAll(fileUploads);
+ public String getMethod()
+ {
+ return _request.getMethod();
}
- else
+
+ public Map<String, List<String>> getParameters()
{
- try
- {
- providedObject = mapper.readValue(request.getInputStream(), expectedClass);
- }
- catch (JsonProcessingException e)
- {
- throw new IllegalArgumentException("Cannot parse the operation body as json",e);
- }
+ return Collections.unmodifiableMap(_query);
}
- return providedObject;
- }
- private void setResponseStatus(HttpServletRequest request, HttpServletResponse response, Throwable e)
- throws IOException
- {
- if (e instanceof SecurityException)
+ @Override
+ public String getParameter(final String name)
{
- LOGGER.debug("{}, sending {}", e.getClass().getName(), HttpServletResponse.SC_FORBIDDEN, e);
- response.setStatus(HttpServletResponse.SC_FORBIDDEN);
+ final List<String> values = _query.get(name);
+ return values == null || values.isEmpty() ? null : values.get(0);
}
- else
+
+ public Map<String, String> getHeaders()
{
- int responseCode = HttpServletResponse.SC_BAD_REQUEST;
- String message = e.getMessage();
- if (e instanceof AbstractConfiguredObject.DuplicateIdException
- || e instanceof AbstractConfiguredObject.DuplicateNameException
- || e instanceof IntegrityViolationException
- || e instanceof IllegalStateTransitionException)
- {
- responseCode = HttpServletResponse.SC_CONFLICT;
- }
- else if (e instanceof NotFoundException)
- {
- if (LOGGER.isTraceEnabled())
- {
- LOGGER.trace(e.getClass().getSimpleName() + " processing request", e);
- }
- responseCode = HttpServletResponse.SC_NOT_FOUND;
- }
- else if (e instanceof IllegalConfigurationException || e instanceof IllegalArgumentException)
- {
- LOGGER.warn("{} processing request {} from user '{}': {}",
- e.getClass().getSimpleName(),
- HttpManagementUtil.getRequestURL(request),
- HttpManagementUtil.getRequestPrincipals(request),
- message);
- Throwable t = e;
- int maxDepth = 10;
- while ((t = t.getCause()) != null && maxDepth-- != 0)
- {
- LOGGER.warn("... caused by " + t.getClass().getSimpleName() + " : " + t.getMessage());
- }
- if (LOGGER.isDebugEnabled())
- {
- LOGGER.debug(e.getClass().getSimpleName() + " processing request", e);
- }
- responseCode = SC_UNPROCESSABLE_ENTITY;
- }
- else if (e instanceof OperationTimeoutException)
- {
- message = "Timeout occurred";
- if (LOGGER.isDebugEnabled())
- {
- LOGGER.debug("Timeout during processing of request {} from user '{}'",
- HttpManagementUtil.getRequestURL(request),
- HttpManagementUtil.getRequestPrincipals(request),
- e);
- }
- else
- {
- LOGGER.info("Timeout during processing of request {} from user '{}'",
- HttpManagementUtil.getRequestURL(request),
- HttpManagementUtil.getRequestPrincipals(request));
- }
+ return _headers;
+ }
- responseCode = HttpServletResponse.SC_BAD_GATEWAY;
- }
- else if (e instanceof NoClassDefFoundError)
- {
- message = "Not found: " + message;
- LOGGER.warn("Unexpected exception processing request ", e);
- }
- else if (e instanceof ExternalServiceTimeoutException)
- {
- responseCode = HttpServletResponse.SC_GATEWAY_TIMEOUT;
- LOGGER.warn("External request timeout ", e);
- }
- else if (e instanceof ExternalServiceException)
+ @SuppressWarnings("unchecked")
+ public <T> T getBody(Class<T> type)
+ {
+ try
{
- responseCode = HttpServletResponse.SC_BAD_GATEWAY;
- LOGGER.warn("External request failed ", e);
+ return parse(type);
}
- else
+ catch (IOException | ServletException e)
{
- // This should not happen
- if (e instanceof RuntimeException)
- {
- throw (RuntimeException) e;
- }
- else if (e instanceof Error)
- {
- throw (Error) e;
- }
- else
- {
- throw new RuntimeException("Unexpected Exception", e);
- }
+ throw ManagementException.createBadRequestManagementException("Cannot parse body", e);
}
-
- sendJsonErrorResponse(request, response, responseCode, message);
}
- }
- @Override
- protected void doDelete(HttpServletRequest request,
- HttpServletResponse response,
- final ConfiguredObject<?> managedObject) throws ServletException, IOException
- {
- ConfiguredObjectFinder finder = getConfiguredObjectFinder(managedObject);
- Class<? extends ConfiguredObject> configuredClass = getConfiguredClass(request, managedObject);
- final Class<? extends ConfiguredObject>[] hierarchy = finder.getHierarchy(configuredClass);
- RequestInfoParser requestInfoParser = new RequestInfoParser(hierarchy);
-
- RequestInfo requestInfo = requestInfoParser.parse(request);
-
- Collection<ConfiguredObject<?>> allObjects = getTargetObjects(configuredClass, finder, requestInfo, buildFilterPredicates(request));
- if (allObjects == null)
+ @Override
+ public String getRequestURL()
{
- throw new NotFoundException("Not Found");
+ return _request.getRequestURL().toString();
}
- switch (requestInfo.getType())
+ @SuppressWarnings("unchecked")
+ private <T> T parse(Class<T> type) throws IOException, ServletException
{
- case MODEL_OBJECT:
- {
- for (ConfiguredObject o : allObjects)
- {
- o.delete();
- }
+ T providedObject;
+ final ObjectMapper mapper = new ObjectMapper();
- sendCachingHeadersOnResponse(response);
- response.setStatus(HttpServletResponse.SC_OK);
- break;
- }
- case USER_PREFERENCES:
+ if (_headers.containsKey("Content-Type") && _request.getHeader("Content-Type")
+ .startsWith("multipart/form-data"))
{
- //TODO: define format how to report the results for bulk delete, i.e. how to report individual errors and success statuses
- if (allObjects.size() > 1)
- {
- sendJsonErrorResponse(request,
- response,
- HttpServletResponse.SC_BAD_REQUEST,
- "Deletion of user preferences using wildcards is unsupported");
- return;
- }
- for (ConfiguredObject o : allObjects)
+ Map<String, Object> items = new LinkedHashMap<>();
+ Map<String, String> fileUploads = new HashMap<>();
+ Collection<Part> parts = _request.getParts();
+ for (Part part : parts)
{
- _userPreferenceHandler.handleDELETE(o.getUserPreferences(), requestInfo);
+ if ("data".equals(part.getName()) && "application/json".equals(part.getContentType()))
+ {
+ items = mapper.readValue(part.getInputStream(), LinkedHashMap.class);
+ }
+ else
+ {
+ byte[] data = new byte[(int) part.getSize()];
+ try (InputStream inputStream = part.getInputStream())
+ {
+ inputStream.read(data);
+ }
+ fileUploads.put(part.getName(), DataUrlUtils.getDataUrlForBytes(data));
+ }
}
- break;
- }
+ items.putAll(fileUploads);
- default:
+ providedObject = (T) items;
+ }
+ else
{
- sendJsonErrorResponse(request, response, HttpServletResponse.SC_BAD_REQUEST, "Unsupported delete call");
+ providedObject = mapper.readValue(_request.getInputStream(), type);
}
+ return providedObject;
}
- }
- @Override
- protected void doPost(HttpServletRequest request,
- HttpServletResponse response,
- final ConfiguredObject<?> managedObject) throws ServletException, IOException
- {
- performCreateOrUpdate(request, response, managedObject);
- }
-
-
- private int getIntParameterFromRequest(final HttpServletRequest request,
- final String paramName,
- final int defaultValue)
- {
- int intValue = defaultValue;
- final String stringValue = request.getParameter(paramName);
- if(stringValue!=null)
+ @Override
+ public Map<String, Object> getParametersAsFlatMap()
{
- try
+ final Map<String, Object> providedObject = new HashMap<>();
+ for (Map.Entry<String, List<String>> entry : _query.entrySet())
{
- intValue = Integer.parseInt(stringValue);
- }
- catch (NumberFormatException e)
- {
- LOGGER.warn("Could not parse " + stringValue + " as integer for parameter " + paramName);
+ final List<String> value = entry.getValue();
+ if (value != null)
+ {
+ if (value.size() == 1)
+ {
+ providedObject.put(entry.getKey(), value.get(0));
+ }
+ else
+ {
+ providedObject.put(entry.getKey(), value);
+ }
+ }
}
+ return providedObject;
}
- return intValue;
- }
- private boolean getBooleanParameterFromRequest(HttpServletRequest request, final String paramName)
- {
- return getBooleanParameterFromRequest(request, paramName, false);
- }
-
- private boolean getBooleanParameterFromRequest(HttpServletRequest request, final String paramName, final boolean defaultValue)
- {
- String value = request.getParameter(paramName);
- if (value == null)
+ @Override
+ public String getCategory()
{
- return defaultValue;
+ return _category;
}
- return Boolean.parseBoolean(value);
}
}
diff --git a/broker-plugins/management-http/src/main/java/org/apache/qpid/server/management/plugin/servlet/rest/RestUserPreferenceHandler.java b/broker-plugins/management-http/src/main/java/org/apache/qpid/server/management/plugin/servlet/rest/RestUserPreferenceHandler.java
index a0d4113..50c506c 100644
--- a/broker-plugins/management-http/src/main/java/org/apache/qpid/server/management/plugin/servlet/rest/RestUserPreferenceHandler.java
+++ b/broker-plugins/management-http/src/main/java/org/apache/qpid/server/management/plugin/servlet/rest/RestUserPreferenceHandler.java
@@ -19,12 +19,8 @@
package org.apache.qpid.server.management.plugin.servlet.rest;
-import java.security.AccessController;
-import java.security.Principal;
import java.util.ArrayList;
-import java.util.Collection;
import java.util.HashMap;
-import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
@@ -33,16 +29,15 @@ import java.util.Set;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
-import javax.security.auth.Subject;
-
import com.google.common.base.Joiner;
import com.google.common.util.concurrent.ListenableFuture;
-import org.apache.qpid.server.util.FutureHelper;
+import org.apache.qpid.server.management.plugin.RequestType;
import org.apache.qpid.server.model.ConfiguredObject;
import org.apache.qpid.server.model.preferences.Preference;
import org.apache.qpid.server.model.preferences.PreferenceFactory;
import org.apache.qpid.server.model.preferences.UserPreferences;
+import org.apache.qpid.server.util.FutureHelper;
public class RestUserPreferenceHandler
{
@@ -88,7 +83,7 @@ public class RestUserPreferenceHandler
awaitFuture(userPreferences.delete(type, name, id));
}
- void handlePUT(ConfiguredObject<?> target, RequestInfo requestInfo, Object providedObject)
+ public void handlePUT(ConfiguredObject<?> target, RequestInfo requestInfo, Object providedObject)
{
UserPreferences userPreferences = target.getUserPreferences();
if (userPreferences == null)
@@ -146,7 +141,7 @@ public class RestUserPreferenceHandler
}
}
- void handlePOST(ConfiguredObject<?> target, RequestInfo requestInfo, Object providedObject)
+ public void handlePOST(ConfiguredObject<?> target, RequestInfo requestInfo, Object providedObject)
{
UserPreferences userPreferences = target.getUserPreferences();
if (userPreferences == null)
@@ -185,7 +180,7 @@ public class RestUserPreferenceHandler
awaitFuture(userPreferences.updateOrAppend(preferences));
}
- Object handleGET(UserPreferences userPreferences, RequestInfo requestInfo)
+ public Object handleGET(UserPreferences userPreferences, RequestInfo requestInfo)
{
if (userPreferences == null)
{
@@ -197,11 +192,11 @@ public class RestUserPreferenceHandler
UUID id = getIdFromQueryParameters(queryParameters);
final ListenableFuture<Set<Preference>> allPreferencesFuture;
- if (requestInfo.getType() == RequestInfo.RequestType.USER_PREFERENCES)
+ if (requestInfo.getType() == RequestType.USER_PREFERENCES)
{
allPreferencesFuture = userPreferences.getPreferences();
}
- else if (requestInfo.getType() == RequestInfo.RequestType.VISIBLE_PREFERENCES)
+ else if (requestInfo.getType() == RequestType.VISIBLE_PREFERENCES)
{
allPreferencesFuture = userPreferences.getVisiblePreferences();
}
diff --git a/broker-plugins/management-http/src/test/java/org/apache/qpid/server/management/plugin/controller/latest/LatestManagementControllerTest.java b/broker-plugins/management-http/src/test/java/org/apache/qpid/server/management/plugin/controller/latest/LatestManagementControllerTest.java
new file mode 100644
index 0000000..39a22fa
--- /dev/null
+++ b/broker-plugins/management-http/src/test/java/org/apache/qpid/server/management/plugin/controller/latest/LatestManagementControllerTest.java
@@ -0,0 +1,809 @@
+/*
+ *
+ * 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.hamcrest.CoreMatchers.nullValue;
+import static org.junit.Assert.assertThat;
+import static org.junit.Assert.fail;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import java.security.PrivilegedAction;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.UUID;
+import java.util.concurrent.Future;
+import java.util.concurrent.TimeUnit;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+import javax.security.auth.Subject;
+
+import org.junit.Before;
+import org.junit.Test;
+
+import org.apache.qpid.server.management.plugin.HttpManagementConfiguration;
+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.AuthenticationProvider;
+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.model.preferences.GenericPreferenceValueFactory;
+import org.apache.qpid.server.model.preferences.Preference;
+import org.apache.qpid.server.model.preferences.PreferenceImpl;
+import org.apache.qpid.server.security.auth.AuthenticatedPrincipal;
+import org.apache.qpid.server.security.auth.UsernamePrincipal;
+import org.apache.qpid.server.virtualhost.QueueManagingVirtualHost;
+import org.apache.qpid.test.utils.UnitTestBase;
+
+public class LatestManagementControllerTest extends UnitTestBase
+{
+ private LatestManagementController _controller;
+
+ @Before
+ public void setUp()
+ {
+ final HttpManagementConfiguration<?> httpManagement = mock(HttpManagementConfiguration.class);
+ when(httpManagement.getContextValue(Long.class, PREFERENCE_OPERTAION_TIMEOUT_CONTEXT_NAME)).thenReturn(1000L);
+ _controller = new LatestManagementController(httpManagement);
+ }
+
+ @Test
+ public void getVersion()
+ {
+ assertThat(_controller.getVersion(), is(equalTo(BrokerModel.MODEL_VERSION)));
+ }
+
+ @Test
+ public void getCategories()
+ {
+ assertThat(_controller.getCategories(), is(equalTo(BrokerModel.getInstance()
+ .getSupportedCategories()
+ .stream()
+ .map(Class::getSimpleName)
+ .collect(Collectors.toSet()))));
+ }
+
+ @Test
+ public void getCategoryMapping()
+ {
+ assertThat(_controller.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(_controller.getCategory(object), is(equalTo(Broker.class.getSimpleName())));
+ }
+
+ @Test
+ public void getCategoryHierarchyForBrokerRootAndQueueCategory()
+ {
+ final Broker<?> object = BrokerTestHelper.createBrokerMock();
+ final Collection<String> expected = Arrays.asList("VirtualHostNode", "VirtualHost", "Queue");
+ assertThat(_controller.getCategoryHierarchy(object, "Queue"), is(equalTo(expected)));
+ }
+
+ @Test
+ public void getCategoryHierarchyForVirtualHostRootAndExchangeCategory() throws Exception
+ {
+ final QueueManagingVirtualHost<?> object = BrokerTestHelper.createVirtualHost("test", this);
+ final Collection<String> expected = Collections.singletonList("Exchange");
+ assertThat(_controller.getCategoryHierarchy(object, "Exchange"), is(equalTo(expected)));
+ }
+
+
+ @Test
+ public void getCategoryHierarchyForBrokerRootAndUnknownCategory()
+ {
+ final Broker<?> object = BrokerTestHelper.createBrokerMock();
+ final Collection<String> expected = Collections.emptyList();
+ assertThat(_controller.getCategoryHierarchy(object, "Binding"), is(equalTo(expected)));
+ }
+
+ @Test
+ public void getNextVersionManagementController()
+ {
+ assertThat(_controller.getNextVersionManagementController(), is(nullValue()));
+ }
+
+ @Test
+ public void getRequestTypeForGetAndModelObjectWithNotFullPath() throws Exception
+ {
+ final String hostName = "test";
+ final QueueManagingVirtualHost<?> virtualHost = createVirtualHostWithQueue(hostName, "foo", "bar");
+
+ final ManagementRequest request = mock(ManagementRequest.class);
+ when(request.getCategory()).thenReturn("queue");
+ doReturn(virtualHost.getBroker()).when(request).getRoot();
+ when(request.getPath()).thenReturn(Arrays.asList("*", hostName));
+ when(request.getParameters()).thenReturn(Collections.emptyMap());
+ when(request.getMethod()).thenReturn("GET");
+
+ final RequestType type = _controller.getRequestType(request);
+
+ assertThat(type, is(equalTo(RequestType.MODEL_OBJECT)));
+ }
+
+ @Test
+ public void getRequestTypeForGetAndModelObjectWithFullPath() throws Exception
+ {
+ final String hostName = "test";
+ final QueueManagingVirtualHost<?> virtualHost = createVirtualHostWithQueue(hostName, "foo", "bar");
+
+ final ManagementRequest request = mock(ManagementRequest.class);
+ when(request.getCategory()).thenReturn("queue");
+ doReturn(virtualHost.getBroker()).when(request).getRoot();
+ final List<String> path = Arrays.asList(virtualHost.getParent().getName(), hostName, "bar");
+ when(request.getPath()).thenReturn(path);
+ when(request.getParameters()).thenReturn(Collections.emptyMap());
+ when(request.getMethod()).thenReturn("GET");
+
+ final RequestType type = _controller.getRequestType(request);
+
+ assertThat(type, is(equalTo(RequestType.MODEL_OBJECT)));
+ }
+
+ @Test
+ public void getRequestTypeForGetAndUserPreferences() throws Exception
+ {
+ final String hostName = "test";
+ final QueueManagingVirtualHost<?> virtualHost = createVirtualHostWithQueue(hostName, "foo", "bar");
+
+ final ManagementRequest request = mock(ManagementRequest.class);
+ when(request.getCategory()).thenReturn("queue");
+ doReturn(virtualHost.getBroker()).when(request).getRoot();
+ List<String> path = Arrays.asList(virtualHost.getParent().getName(),
+ hostName,
+ "bar",
+ "userpreferences");
+ when(request.getPath()).thenReturn(path);
+ when(request.getParameters()).thenReturn(Collections.emptyMap());
+ when(request.getMethod()).thenReturn("GET");
+
+ final RequestType type = _controller.getRequestType(request);
+
+ assertThat(type, is(equalTo(RequestType.USER_PREFERENCES)));
+ }
+
+ @Test
+ public void getRequestTypeForGetAndVisiblePreferences() throws Exception
+ {
+ final String hostName = "test";
+ final QueueManagingVirtualHost<?> virtualHost = createVirtualHostWithQueue(hostName, "foo", "bar");
+
+ final ManagementRequest request = mock(ManagementRequest.class);
+ when(request.getCategory()).thenReturn("queue");
+ doReturn(virtualHost.getBroker()).when(request).getRoot();
+ List<String> path = Arrays.asList(virtualHost.getParent().getName(), hostName, "bar", "visiblepreferences");
+ when(request.getPath()).thenReturn(path);
+ when(request.getParameters()).thenReturn(Collections.emptyMap());
+ when(request.getMethod()).thenReturn("GET");
+
+ final RequestType type = _controller.getRequestType(request);
+
+ assertThat(type, is(equalTo(RequestType.VISIBLE_PREFERENCES)));
+ }
+
+ @Test
+ public void getForBrokerRootAndQueueSingletonPath() throws Exception
+ {
+ final String hostName = "test";
+ final QueueManagingVirtualHost<?> virtualHost = createVirtualHostWithQueue(hostName, "foo", "bar");
+ final String nodeName = virtualHost.getParent().getName();
+ final List<String> path = Arrays.asList(nodeName, hostName, "foo");
+
+ final Object object = _controller.get(virtualHost.getBroker(), "queue", path, Collections.emptyMap());
+ assertThat(object, is(notNullValue()));
+ assertThat(object, is(instanceOf(Queue.class)));
+
+ final Queue data = (Queue) object;
+ assertThat(data.getName(), is(equalTo("foo")));
+ }
+
+ @Test
+ public void getForBrokerRootAndQueuePathNoQueueName() throws Exception
+ {
+ final String hostName = "test";
+ final QueueManagingVirtualHost<?> virtualHost = createVirtualHostWithQueue(hostName, "foo", "bar");
+ final String nodeName = virtualHost.getParent().getName();
+ final List<String> path = Arrays.asList(nodeName, hostName);
+
+ final Object object = _controller.get(virtualHost.getBroker(), "queue", path, Collections.emptyMap());
+ assertThat(object, is(notNullValue()));
+ assertThat(object, is(instanceOf(Collection.class)));
+
+ final Collection<?> data = (Collection<?>) object;
+ final Iterator iterator = data.iterator();
+ final Object o = iterator.next();
+ final Object o2 = iterator.next();
+ assertThat(o, is(notNullValue()));
+ assertThat(o, is(instanceOf(Queue.class)));
+ assertThat(((Queue) o).getName(), is(equalTo("foo")));
+
+ assertThat(o2, is(notNullValue()));
+ assertThat(o2, is(instanceOf(Queue.class)));
+ assertThat(((Queue) o2).getName(), is(equalTo("bar")));
+ }
+
+ @Test
+ public void getForBrokerRootAndQueuePathWithWildCards() throws Exception
+ {
+ final String hostName = "test";
+ final QueueManagingVirtualHost<?> virtualHost = createVirtualHostWithQueue(hostName, "foo", "bar");
+ final List<String> path = Arrays.asList("*", hostName);
+
+ final Object object = _controller.get(virtualHost.getBroker(), "queue", path, Collections.emptyMap());
+ assertThat(object, is(notNullValue()));
+ assertThat(object, is(instanceOf(Collection.class)));
+
+ final Collection<?> data = (Collection<?>) object;
+ assertThat(data.size(), is(equalTo(2)));
+ final Iterator iterator = data.iterator();
+ final Object o = iterator.next();
+ final Object o2 = iterator.next();
+ assertThat(o, is(notNullValue()));
+ assertThat(o, is(instanceOf(Queue.class)));
+ assertThat(((Queue) o).getName(), is(equalTo("foo")));
+
+ assertThat(o2, is(notNullValue()));
+ assertThat(o2, is(instanceOf(Queue.class)));
+ assertThat(((Queue) o2).getName(), is(equalTo("bar")));
+ }
+
+ @Test
+ public void getForBrokerRootAndQueuePathWithFilter() throws Exception
+ {
+ final String hostName = "test";
+ final QueueManagingVirtualHost<?> virtualHost = createVirtualHostWithQueue(hostName, "foo", "bar", "bar2");
+ final List<String> path = Arrays.asList("*", hostName);
+
+ final Object object = _controller.get(virtualHost.getBroker(),
+ "queue",
+ path,
+ Collections.singletonMap(Queue.NAME, Arrays.asList("foo", "bar")));
+ assertThat(object, is(notNullValue()));
+ assertThat(object, is(instanceOf(Collection.class)));
+
+ final Collection<?> data = (Collection<?>) object;
+ assertThat(data.size(), is(equalTo(2)));
+ final Iterator iterator = data.iterator();
+ final Object o = iterator.next();
+ final Object o2 = iterator.next();
+ assertThat(o, is(notNullValue()));
+ assertThat(o, is(instanceOf(Queue.class)));
+ assertThat(((Queue) o).getName(), is(equalTo("foo")));
+
+ assertThat(o2, is(notNullValue()));
+ assertThat(o2, is(instanceOf(Queue.class)));
+ assertThat(((Queue) o2).getName(), is(equalTo("bar")));
+ }
+
+ @Test
+ public void createOrUpdateUsingPutAndFullPath() throws Exception
+ {
+ final String hostName = "test";
+ final QueueManagingVirtualHost<?> virtualHost = createVirtualHostWithQueue(hostName);
+ final List<String> path = Arrays.asList(virtualHost.getParent().getName(), hostName, "bar");
+
+ final Object object = _controller.createOrUpdate(virtualHost.getBroker(),
+ "queue",
+ path,
+ new HashMap<>(Collections.singletonMap(Queue.NAME, "bar")),
+ false);
+
+ assertThat(object, is(notNullValue()));
+ assertThat(object, is(instanceOf(Queue.class)));
+ assertThat(((Queue) object).getName(), is(equalTo("bar")));
+ }
+
+ @Test
+ public void createOrUpdateUsingPostAndFullPathForNonExisting() throws Exception
+ {
+ final String hostName = "test";
+ final QueueManagingVirtualHost<?> virtualHost = createVirtualHostWithQueue(hostName);
+ final List<String> path = Arrays.asList(virtualHost.getParent().getName(), hostName, "bar");
+
+ try
+ {
+ _controller.createOrUpdate(virtualHost.getBroker(),
+ "queue",
+ path,
+ new HashMap<>(Collections.singletonMap(Queue.NAME, "bar")),
+ true);
+ fail("Post update should fail for non existing");
+ }
+ catch (ManagementException e)
+ {
+ assertThat(e.getStatusCode(), is(equalTo(404)));
+ }
+ }
+
+ @Test
+ public void createOrUpdateUsingPostAndFullPathForExisting() throws Exception
+ {
+ final String hostName = "test";
+ final QueueManagingVirtualHost<?> virtualHost = createVirtualHostWithQueue(hostName, "bar");
+ final List<String> path = Arrays.asList(virtualHost.getParent().getName(), hostName, "bar");
+
+ final Object object = _controller.createOrUpdate(virtualHost.getBroker(),
+ "queue",
+ path,
+ new HashMap<>(Collections.singletonMap(Queue.NAME, "bar")),
+ true);
+
+ assertThat(object, is(nullValue()));
+ }
+
+ @Test
+ public void createOrUpdateUsingPostAndParentPath() throws Exception
+ {
+ final String hostName = "test";
+ final QueueManagingVirtualHost<?> virtualHost = createVirtualHostWithQueue(hostName);
+ final List<String> path = Arrays.asList(virtualHost.getParent().getName(), hostName);
+
+ final Object object = _controller.createOrUpdate(virtualHost.getBroker(),
+ "queue",
+ path,
+ new HashMap<>(Collections.singletonMap(Queue.NAME, "bar")),
+ true);
+
+ assertThat(object, is(notNullValue()));
+ assertThat(object, is(instanceOf(Queue.class)));
+ assertThat(((Queue) object).getName(), is(equalTo("bar")));
+ }
+
+ @Test
+ public void deleteUsingFullPath() throws Exception
+ {
+ final String hostName = "test";
+ final QueueManagingVirtualHost<?> virtualHost = createVirtualHostWithQueue(hostName, "foo", "bar");
+
+ List<String> path = Arrays.asList(virtualHost.getParent().getName(), hostName, "bar");
+
+ int count = _controller.delete(virtualHost.getBroker(), "queue", path, Collections.emptyMap());
+
+ assertThat(count, is(equalTo(1)));
+ }
+
+ @Test
+ public void deleteUsingFilter() throws Exception
+ {
+ final String hostName = "test";
+ final QueueManagingVirtualHost<?> virtualHost = createVirtualHostWithQueue(hostName, "foo", "bar");
+
+ List<String> path = Arrays.asList(virtualHost.getParent().getName(), hostName);
+
+ int count = _controller.delete(virtualHost.getBroker(),
+ "queue",
+ path,
+ Collections.singletonMap(Queue.NAME, Arrays.asList("foo", "bar", "bar2")));
+
+ assertThat(count, is(equalTo(2)));
+ }
+
+ @Test
+ public void deleteUsingWildcard() throws Exception
+ {
+ final String hostName = "test";
+ final QueueManagingVirtualHost<?> virtualHost = createVirtualHostWithQueue(hostName, "foo", "bar");
+
+ List<String> path = Arrays.asList(virtualHost.getParent().getName(), hostName, "*");
+
+ int count = _controller.delete(virtualHost.getBroker(), "queue", path, Collections.emptyMap());
+
+ assertThat(count, is(equalTo(2)));
+ }
+
+ @Test
+ public void invoke() throws Exception
+ {
+ final String hostName = "test";
+ final QueueManagingVirtualHost<?> virtualHost = createVirtualHostWithQueue(hostName, "foo", "bar");
+
+ 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 = _controller.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)));
+ }
+
+ @Test
+ public void getPreferences() throws Exception
+ {
+ final String hostName = "default";
+ final QueueManagingVirtualHost<?> virtualHost = createVirtualHostWithQueue(hostName);
+ final String preferencesType = "X-type-preference";
+ final Map<String, Object> preferenceValue = Collections.singletonMap("foo", "bar");
+ final Subject testSubject = createTestSubject();
+ final String prefernceName = "test";
+ createPreferences(testSubject, virtualHost, preferencesType, prefernceName, preferenceValue);
+
+ List<String> path = Arrays.asList(virtualHost.getParent().getName(), hostName, "userpreferences");
+ final Object preferences = Subject.doAs(testSubject, (PrivilegedAction<Object>) () ->
+ _controller.getPreferences(virtualHost.getBroker(), "virtualhost", path, Collections.emptyMap()));
+
+ assertPreference(preferencesType, prefernceName, preferenceValue, preferences);
+ }
+
+
+ @Test
+ public void setPreferences() throws Exception
+ {
+ final String hostName = "default";
+ final QueueManagingVirtualHost<?> virtualHost = createVirtualHostWithQueue(hostName);
+ final String preferencesType = "X-type";
+ final Map<String, Object> preferenceValue = Collections.singletonMap("foo", "bar");
+ final Subject testSubject = createTestSubject();
+ final String preferenceName = "pref";
+ final UUID id = createPreferences(testSubject, virtualHost, preferencesType, preferenceName, preferenceValue);
+ final List<String> path = Arrays.asList(virtualHost.getParent().getName(), hostName, "userpreferences");
+ final Map<String, Object> newValue = Collections.singletonMap("foo", "bar2");
+ final Map<String, Object> data = new HashMap<>();
+ data.put("id", id.toString());
+ data.put("name", preferenceName);
+ data.put("value", newValue);
+ final Map<String, List<Object>> modifiedPreferences = Collections.singletonMap(preferencesType,
+ Collections.singletonList(data));
+ Subject.doAs(testSubject, (PrivilegedAction<Void>) () -> {
+ _controller.setPreferences(virtualHost.getBroker(),
+ "virtualhost",
+ path,
+ modifiedPreferences,
+ Collections.emptyMap(),
+ true);
+ return null;
+ });
+ final Object preferences = Subject.doAs(testSubject, (PrivilegedAction<Object>) () ->
+ _controller.getPreferences(virtualHost.getBroker(), "virtualhost", path, Collections.emptyMap()));
+
+ assertPreference(preferencesType, preferenceName, newValue, preferences);
+ }
+
+ @Test
+ public void deletePreferences() throws Exception
+ {
+ final String hostName = "test";
+ final QueueManagingVirtualHost<?> virtualHost = createVirtualHostWithQueue(hostName);
+ final String preferencesType = "X-type";
+ final Map<String, Object> preferenceValue = Collections.singletonMap("foo", "bar");
+ final Subject testSubject = createTestSubject();
+ final String preferenceName = "pref";
+ createPreferences(testSubject, virtualHost, preferencesType, preferenceName, preferenceValue);
+
+ final List<String> path = Arrays.asList(virtualHost.getParent().getName(),
+ hostName,
+ "userpreferences",
+ preferencesType,
+ preferenceName);
+
+ Subject.doAs(testSubject, (PrivilegedAction<Void>) () -> {
+ _controller.deletePreferences(virtualHost.getBroker(),
+ "virtualhost",
+ path,
+ Collections.emptyMap());
+ return null;
+ });
+
+ final List<String> path2 = Arrays.asList(virtualHost.getParent().getName(), hostName, "userpreferences");
+
+ final Object preferences = Subject.doAs(testSubject, (PrivilegedAction<Object>) () ->
+ _controller.getPreferences(virtualHost.getBroker(), "virtualhost", path2, Collections.emptyMap()));
+ assertThat(preferences, is(notNullValue()));
+ assertThat(preferences, is(instanceOf(Map.class)));
+
+ final Map<?, ?> map = (Map<?, ?>) preferences;
+ assertThat(map.size(), is(equalTo(0)));
+ }
+
+ @Test
+ public void formatConfiguredObjectForSingletonResponse() throws Exception
+ {
+ final String hostName = "test";
+ final QueueManagingVirtualHost<?> virtualHost = createVirtualHostWithQueue(hostName, "foo", "bar");
+
+ final Object formatted = _controller.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(2)));
+ final Iterator<?> iterator = queueCollection.iterator();
+ final Object queue1 = iterator.next();
+ final Object queue2 = iterator.next();
+
+ assertThat(queue1, is(instanceOf(Map.class)));
+ assertThat(queue2, is(instanceOf(Map.class)));
+
+ final Map<?, ?> queueMap1 = (Map<?, ?>) queue1;
+ final Map<?, ?> queueMap2 = (Map<?, ?>) queue2;
+
+ assertThat(queueMap1.get(Queue.NAME), is(equalTo("bar")));
+ assertThat(queueMap2.get(Queue.NAME), is(equalTo("foo")));
+ }
+
+ @Test
+ public void formatConfiguredObjectForCollectionResponse() throws Exception
+ {
+ final String hostName = "test";
+ final QueueManagingVirtualHost<?> virtualHost = createVirtualHostWithQueue(hostName, "foo", "bar");
+
+ final Object formatted = _controller.formatConfiguredObject(Collections.singletonList(virtualHost),
+ Collections.singletonMap("depth",
+ Collections.singletonList(
+ "1")),
+ true);
+ assertThat(formatted, is(notNullValue()));
+ assertThat(formatted, is(instanceOf(Collection.class)));
+
+ final Collection<?> formattedCollection = (Collection<?>) formatted;
+ assertThat(formattedCollection.size(), is(equalTo(1)));
+
+ Object item = formattedCollection.iterator().next();
+ assertThat(item, is(instanceOf(Map.class)));
+
+ final Map<?, ?> data = (Map<?, ?>) item;
+ 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(2)));
+ final Iterator<?> iterator = queueCollection.iterator();
+ final Object queue1 = iterator.next();
+ final Object queue2 = iterator.next();
+
+ assertThat(queue1, is(instanceOf(Map.class)));
+ assertThat(queue2, is(instanceOf(Map.class)));
+
+ final Map<?, ?> queueMap1 = (Map<?, ?>) queue1;
+ final Map<?, ?> queueMap2 = (Map<?, ?>) queue2;
+
+ assertThat(queueMap1.get(Queue.NAME), is(equalTo("bar")));
+ assertThat(queueMap2.get(Queue.NAME), is(equalTo("foo")));
+ }
+
+ @Test
+ public void handleGetForBrokerRootAndQueueSingletonPath() throws Exception
+ {
+ final String hostName = "test";
+ final QueueManagingVirtualHost<?> virtualHost = createVirtualHostWithQueue(hostName, "foo", "bar");
+
+ final String nodeName = virtualHost.getParent().getName();
+ final ManagementRequest request = mock(ManagementRequest.class);
+ when(request.getCategory()).thenReturn("queue");
+ doReturn(virtualHost.getBroker()).when(request).getRoot();
+ when(request.getPath()).thenReturn(Arrays.asList(nodeName, hostName, "foo"));
+ when(request.getMethod()).thenReturn("GET");
+
+ final ManagementResponse response = _controller.handleGet(request);
+ assertThat(response, is(notNullValue()));
+ assertThat(response.getResponseCode(), is(equalTo(200)));
+ assertThat(response.getBody(), is(notNullValue()));
+ assertThat(response.getBody(), is(instanceOf(Queue.class)));
+
+ final Queue data = (Queue) response.getBody();
+ assertThat(data.getName(), is(equalTo("foo")));
+ }
+
+
+ @Test
+ public void handleGetForBrokerRootAndQueuePathWithoutQueueName() throws Exception
+ {
+ final String hostName = "test";
+ final QueueManagingVirtualHost<?> virtualHost = createVirtualHostWithQueue(hostName, "foo", "bar");
+
+ final String nodeName = virtualHost.getParent().getName();
+ final ManagementRequest request = mock(ManagementRequest.class);
+ when(request.getCategory()).thenReturn("queue");
+ doReturn(virtualHost.getBroker()).when(request).getRoot();
+ when(request.getPath()).thenReturn(Arrays.asList(nodeName, hostName));
+ when(request.getParameters()).thenReturn(Collections.emptyMap());
+ when(request.getMethod()).thenReturn("GET");
+
+ final ManagementResponse response = _controller.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(2)));
+
+ final Iterator iterator = data.iterator();
+ final Object object = iterator.next();
+ final Object object2 = iterator.next();
+ assertThat(object, is(notNullValue()));
+ assertThat(object, is(instanceOf(Queue.class)));
+ assertThat(((Queue) object).getName(), is(equalTo("foo")));
+
+ assertThat(object2, is(notNullValue()));
+ assertThat(object2, is(instanceOf(Queue.class)));
+ assertThat(((Queue) object2).getName(), is(equalTo("bar")));
+ }
+
+ @Test
+ public void handleGetForBrokerRootAndQueuePathWithFilter() throws Exception
+ {
+ final String hostName = "test";
+ final QueueManagingVirtualHost<?> virtualHost = createVirtualHostWithQueue(hostName, "foo", "bar");
+
+ final String nodeName = virtualHost.getParent().getName();
+ final ManagementRequest request = mock(ManagementRequest.class);
+ when(request.getCategory()).thenReturn("queue");
+ doReturn(virtualHost.getBroker()).when(request).getRoot();
+ when(request.getPath()).thenReturn(Arrays.asList(nodeName, hostName));
+ when(request.getParameters()).thenReturn(Collections.singletonMap("name", Collections.singletonList("bar")));
+ when(request.getMethod()).thenReturn("GET");
+
+ ManagementResponse response = _controller.handleGet(request);
+ assertThat(response, is(notNullValue()));
+ assertThat(response.getResponseCode(), is(equalTo(200)));
+ assertThat(response.getBody(), is(notNullValue()));
+ assertThat(response.getBody(), is(instanceOf(Collection.class)));
+
+ Collection data = (Collection) response.getBody();
+ assertThat(data.size(), is(equalTo(1)));
+
+ Object object = data.iterator().next();
+ assertThat(object, is(notNullValue()));
+ assertThat(object, is(instanceOf(Queue.class)));
+ assertThat(((Queue) object).getName(), is(equalTo("bar")));
+ }
+
+ private QueueManagingVirtualHost<?> createVirtualHostWithQueue(final String hostName, String... queueName)
+ 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);
+ Stream.of(queueName)
+ .forEach(n -> virtualHost.createChild(Queue.class, Collections.singletonMap(Queue.NAME, n)));
+ return virtualHost;
+ }
+
+
+ private UUID createPreferences(final Subject testSubject,
+ final QueueManagingVirtualHost<?> virtualHost,
+ final String preferenceType,
+ final String preferenceName,
+ final Map<String, Object> preferenceValue)
+ throws Exception
+ {
+ UUID uuid = UUID.randomUUID();
+ final Preference preference = new PreferenceImpl(virtualHost,
+ uuid,
+ preferenceName,
+ preferenceType,
+ "Some preference",
+ null,
+ new Date(),
+ new Date(),
+ null,
+ new GenericPreferenceValueFactory().createInstance(
+ preferenceValue));
+ final List<Preference> preferenceList = Collections.singletonList(preference);
+ final Future<Void> result = Subject.doAs(testSubject,
+ (PrivilegedAction<Future<Void>>) () -> virtualHost.getUserPreferences()
+ .updateOrAppend(
+ preferenceList));
+
+ result.get(2000L, TimeUnit.MILLISECONDS);
+ return uuid;
+ }
+
+ private Subject createTestSubject()
+ {
+ final AuthenticationProvider<?> provider = mock(AuthenticationProvider.class);
+ when(provider.getType()).thenReturn("type");
+ when(provider.getName()).thenReturn("name");
+
+ return new Subject(false,
+ Collections.singleton(new AuthenticatedPrincipal(new UsernamePrincipal("user", provider))),
+ Collections.emptySet(),
+ Collections.emptySet());
+ }
+
+ private void assertPreference(final String expectedType,
+ final String expectedName,
+ final Map<String, Object> expectedValue,
+ final Object preferences)
+ {
+ assertThat(preferences, is(notNullValue()));
+ assertThat(preferences, is(instanceOf(Map.class)));
+
+ final Map<?, ?> data = (Map<?, ?>) preferences;
+
+ final Object pt = data.get(expectedType);
+ assertThat(pt, is(notNullValue()));
+ assertThat(pt, is(instanceOf(Collection.class)));
+
+ final Collection<?> items = (Collection<?>) pt;
+ assertThat(items.size(), is(equalTo(1)));
+
+ final Object item = items.iterator().next();
+ assertThat(item, is(notNullValue()));
+ assertThat(item, is(instanceOf(Map.class)));
+
+ final Map<?, ?> map = (Map<?, ?>) item;
+
+ final Object value = map.get("value");
+ assertThat(value, is(notNullValue()));
+ assertThat(value, is(equalTo(expectedValue)));
+
+ final Object name = map.get("name");
+ assertThat(name, is(notNullValue()));
+ assertThat(name, is(equalTo(expectedName)));
+ }
+}
\ No newline at end of file
diff --git a/broker-plugins/management-http/src/test/java/org/apache/qpid/server/management/plugin/servlet/rest/RequestInfoParserTest.java b/broker-plugins/management-http/src/test/java/org/apache/qpid/server/management/plugin/servlet/rest/RequestInfoParserTest.java
index e18ff34..5ffafbb 100644
--- a/broker-plugins/management-http/src/test/java/org/apache/qpid/server/management/plugin/servlet/rest/RequestInfoParserTest.java
+++ b/broker-plugins/management-http/src/test/java/org/apache/qpid/server/management/plugin/servlet/rest/RequestInfoParserTest.java
@@ -36,6 +36,7 @@ import javax.servlet.http.HttpServletRequest;
import org.junit.Test;
+import org.apache.qpid.server.management.plugin.RequestType;
import org.apache.qpid.server.model.Queue;
import org.apache.qpid.server.model.VirtualHost;
import org.apache.qpid.server.model.VirtualHostNode;
@@ -54,7 +55,7 @@ public class RequestInfoParserTest extends UnitTestBase
RequestInfo info = parser.parse(_request);
- assertEquals("Unexpected request type", RequestInfo.RequestType.MODEL_OBJECT, info.getType());
+ assertEquals("Unexpected request type", RequestType.MODEL_OBJECT, info.getType());
assertEquals("Unexpected model parts", Collections.emptyList(), info.getModelParts());
}
@@ -71,7 +72,7 @@ public class RequestInfoParserTest extends UnitTestBase
RequestInfo info = parser.parse(_request);
- assertEquals("Unexpected request type", RequestInfo.RequestType.MODEL_OBJECT, info.getType());
+ assertEquals("Unexpected request type", RequestType.MODEL_OBJECT, info.getType());
assertEquals("Unexpected model parts", Arrays.asList(vhnName, vhName), info.getModelParts());
assertTrue("Expected exact object request", info.isSingletonRequest());
}
@@ -85,7 +86,7 @@ public class RequestInfoParserTest extends UnitTestBase
RequestInfo info = parser.parse(_request);
- assertEquals("Unexpected request type", RequestInfo.RequestType.MODEL_OBJECT, info.getType());
+ assertEquals("Unexpected request type", RequestType.MODEL_OBJECT, info.getType());
assertEquals("Unexpected model parts", Arrays.asList(vhnName), info.getModelParts());
assertFalse("Expected exact object request", info.isSingletonRequest());
}
@@ -126,7 +127,7 @@ public class RequestInfoParserTest extends UnitTestBase
RequestInfo info = parser.parse(_request);
- assertEquals("Unexpected request type", RequestInfo.RequestType.MODEL_OBJECT, info.getType());
+ assertEquals("Unexpected request type", RequestType.MODEL_OBJECT, info.getType());
assertEquals("Unexpected model parts", Arrays.asList(vhnName), info.getModelParts());
}
@@ -144,7 +145,7 @@ public class RequestInfoParserTest extends UnitTestBase
RequestInfo info = parser.parse(_request);
- assertEquals("Unexpected request type", RequestInfo.RequestType.MODEL_OBJECT, info.getType());
+ assertEquals("Unexpected request type", RequestType.MODEL_OBJECT, info.getType());
assertEquals("Unexpected model parts", Arrays.asList(vhnName, vhName), info.getModelParts());
}
@@ -203,7 +204,7 @@ public class RequestInfoParserTest extends UnitTestBase
RequestInfo info = parser.parse(_request);
- assertEquals("Unexpected request type", RequestInfo.RequestType.MODEL_OBJECT, info.getType());
+ assertEquals("Unexpected request type", RequestType.MODEL_OBJECT, info.getType());
assertEquals("Unexpected model parts", Arrays.asList(vhnName, vhName), info.getModelParts());
}
@@ -218,7 +219,7 @@ public class RequestInfoParserTest extends UnitTestBase
RequestInfo info = parser.parse(_request);
- assertEquals("Unexpected request type", RequestInfo.RequestType.MODEL_OBJECT, info.getType());
+ assertEquals("Unexpected request type", RequestType.MODEL_OBJECT, info.getType());
assertEquals("Unexpected model parts", Arrays.asList(vhnName, vhName), info.getModelParts());
}
@@ -271,7 +272,7 @@ public class RequestInfoParserTest extends UnitTestBase
RequestInfo info = parser.parse(_request);
- assertEquals("Unexpected request type", RequestInfo.RequestType.MODEL_OBJECT, info.getType());
+ assertEquals("Unexpected request type", RequestType.MODEL_OBJECT, info.getType());
assertEquals("Unexpected model parts", Arrays.asList(vhnName, vhName), info.getModelParts());
}
@@ -287,7 +288,7 @@ public class RequestInfoParserTest extends UnitTestBase
RequestInfo info = parser.parse(_request);
- assertEquals("Unexpected request type", RequestInfo.RequestType.MODEL_OBJECT, info.getType());
+ assertEquals("Unexpected request type", RequestType.MODEL_OBJECT, info.getType());
assertEquals("Unexpected model parts", Arrays.asList(vhnName, vhName), info.getModelParts());
}
@@ -320,7 +321,7 @@ public class RequestInfoParserTest extends UnitTestBase
RequestInfo info = parser.parse(_request);
- assertEquals("Unexpected request type", RequestInfo.RequestType.MODEL_OBJECT, info.getType());
+ assertEquals("Unexpected request type", RequestType.MODEL_OBJECT, info.getType());
assertEquals("Unexpected model parts", Arrays.asList(vhnName), info.getModelParts());
}
@@ -359,7 +360,7 @@ public class RequestInfoParserTest extends UnitTestBase
RequestInfo info = parser.parse(_request);
- assertEquals("Unexpected request type", RequestInfo.RequestType.OPERATION, info.getType());
+ assertEquals("Unexpected request type", RequestType.OPERATION, info.getType());
assertEquals("Unexpected model parts", Arrays.asList(vhnName), info.getModelParts());
assertEquals("Unexpected operation name", operationName, info.getOperationName());
assertTrue("Expected exact object request", info.isSingletonRequest());
---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@qpid.apache.org
For additional commands, e-mail: commits-help@qpid.apache.org