1. URL decoding of URL jmx-rest-api
authorhb
Thu, 04 Jan 2018 14:39:04 +0530
branchjmx-rest-api
changeset 56006 352a4f213fc6
parent 56005 90cff2ac77b8
child 56007 d6cbabcaf518
1. URL decoding of URL 2. MBean info availble in MBeanCollection page 3. MBeanCollection post supports objectname filtering 4. Tests now use reflection instead of Methodhndles 3. couple of bug fixes
src/java.management.rest/share/classes/com/oracle/jmx/remote/rest/http/HttpUtil.java
src/java.management.rest/share/classes/com/oracle/jmx/remote/rest/http/MBeanCollectionResource.java
src/java.management.rest/share/classes/com/oracle/jmx/remote/rest/http/MBeanResource.java
src/java.management.rest/share/classes/com/oracle/jmx/remote/rest/http/MBeanServerCollectionResource.java
src/java.management.rest/share/classes/com/oracle/jmx/remote/rest/http/MBeanServerResource.java
src/java.management.rest/share/classes/com/oracle/jmx/remote/rest/http/RestResource.java
src/java.management.rest/share/classes/com/oracle/jmx/remote/rest/json/JSONPrimitive.java
src/java.management.rest/share/classes/com/oracle/jmx/remote/rest/json/parser/JsonParser.jj
test/jdk/javax/management/remote/rest/RestAdapterConfigTest.java
test/jdk/javax/management/remote/rest/RestAdapterPerfTest.java
test/jdk/javax/management/remote/rest/RestAdapterTest.java
--- a/src/java.management.rest/share/classes/com/oracle/jmx/remote/rest/http/HttpUtil.java	Tue Jan 02 15:03:52 2018 +0530
+++ b/src/java.management.rest/share/classes/com/oracle/jmx/remote/rest/http/HttpUtil.java	Thu Jan 04 14:39:04 2018 +0530
@@ -35,6 +35,7 @@
 import java.net.URL;
 import java.net.URLDecoder;
 import java.net.URLEncoder;
+import java.nio.charset.StandardCharsets;
 import java.util.Base64;
 import java.util.LinkedHashMap;
 import java.util.List;
@@ -91,16 +92,13 @@
 
     public static Map<String, String> getGetRequestQueryMap(HttpExchange ex)
             throws UnsupportedEncodingException {
-        String charset = getRequestCharset(ex);
         String query = ex.getRequestURI().getQuery();
-        if (charset != null && query != null) {
-            query = URLDecoder.decode(query, charset);
-        }
         Map<String, String> queryParams = new LinkedHashMap<>();
 
         if (query == null || query.isEmpty()) {
             return queryParams;
         }
+        query = URLDecoder.decode(query, StandardCharsets.UTF_8.displayName());
         String[] params = query.trim().split("&");
         for (String param : params) {
             int idx = param.indexOf('=');
@@ -148,7 +146,11 @@
         String msg = charset == null ? response.getBody() : URLEncoder.encode(response.getBody(), charset);
         byte[] bytes = msg.getBytes();
         Headers resHeaders = exchange.getResponseHeaders();
-        resHeaders.add("Content-Type", "application/json; charset=" + charset);
+        if(charset != null && !charset.isEmpty()) {
+            resHeaders.add("Content-Type", "application/json; charset=" + charset);
+        } else {
+            resHeaders.add("Content-Type", "application/json;");
+        }
 
         exchange.sendResponseHeaders(response.getCode(), bytes.length);
         try (OutputStream os = exchange.getResponseBody()) {
--- a/src/java.management.rest/share/classes/com/oracle/jmx/remote/rest/http/MBeanCollectionResource.java	Tue Jan 02 15:03:52 2018 +0530
+++ b/src/java.management.rest/share/classes/com/oracle/jmx/remote/rest/http/MBeanCollectionResource.java	Thu Jan 04 14:39:04 2018 +0530
@@ -25,8 +25,10 @@
 
 package com.oracle.jmx.remote.rest.http;
 
+import com.oracle.jmx.remote.rest.json.JSONArray;
 import com.oracle.jmx.remote.rest.json.JSONElement;
 import com.oracle.jmx.remote.rest.json.JSONObject;
+import com.oracle.jmx.remote.rest.json.JSONPrimitive;
 import com.oracle.jmx.remote.rest.json.parser.JSONParser;
 import com.oracle.jmx.remote.rest.json.parser.ParseException;
 import com.oracle.jmx.remote.rest.mapper.JSONMapper;
@@ -38,7 +40,8 @@
 import javax.management.remote.rest.PlatformRestAdapter;
 import java.io.IOException;
 import java.io.UnsupportedEncodingException;
-import java.net.HttpURLConnection;
+import java.net.URLDecoder;
+import java.nio.charset.StandardCharsets;
 import java.util.*;
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.CopyOnWriteArrayList;
@@ -134,7 +137,8 @@
 
     @Override
     public void handle(HttpExchange exchange) throws IOException {
-        String path = exchange.getRequestURI().getPath();
+        String path = URLDecoder.decode(exchange.getRequestURI().getPath(), StandardCharsets.UTF_8.displayName());
+
         if (path.matches(pathPrefix + "/?$")) {
             RestResource.super.handle(exchange);
         } else if (path.matches(pathPrefix + "/[^/]+/?.*")) {
@@ -161,27 +165,28 @@
 
     @Override
     public HttpResponse doGet(HttpExchange exchange) {
-        final String path = PlatformRestAdapter.getDomain()
-                + exchange.getRequestURI().getPath().replaceAll("/$", "");
         try {
+            final String path = PlatformRestAdapter.getDomain()
+                    + URLDecoder.decode(exchange.getRequestURI().getPath(), StandardCharsets.UTF_8.displayName())
+                    .replaceAll("/$", "");
             List<ObjectName> filteredMBeans = allowedMbeans;
             Map<String, String> queryMap = HttpUtil.getGetRequestQueryMap(exchange);
             String query = exchange.getRequestURI().getQuery();
-            if(query != null && queryMap.isEmpty()) {
+            if (query != null && queryMap.isEmpty()) {
                 return new HttpResponse(HttpResponse.BAD_REQUEST,
-                        "Invalid query params : Allowed query keys [query,page]");
-            }else if(query != null && !queryMap.isEmpty()) {
+                        "Invalid query params : Allowed query keys [objectname,page]");
+            } else if (query != null && !queryMap.isEmpty()) {
                 Map<String, String> newMap = new HashMap<>(queryMap);
-                newMap.remove("query");
+                newMap.remove("objectname");
                 newMap.remove("page");
-                if(!newMap.isEmpty()) { // Invalid query params
+                if (!newMap.isEmpty()) { // Invalid query params
                     return new HttpResponse(HttpResponse.BAD_REQUEST,
-                            "Invalid query params : Allowed query keys [query,page]");
+                            "Invalid query params : Allowed query keys [objectname,page]");
                 }
             }
-            if (queryMap.containsKey("query")) {        // Filter based on ObjectName query
+            if (queryMap.containsKey("objectname")) {        // Filter based on ObjectName query
                 Set<ObjectName> queryMBeans = mBeanServer
-                        .queryNames(new ObjectName(queryMap.get("query")), null);
+                        .queryNames(new ObjectName(queryMap.get("objectname")), null);
                 queryMBeans.retainAll(allowedMbeans);   // Intersection of two lists
                 filteredMBeans = new ArrayList<>(queryMBeans);
             }
@@ -190,14 +195,49 @@
             List<ObjectName> mbeanPage = HttpUtil.filterByPage(exchange, filteredMBeans, pageSize);
 
             List<Map<String, String>> items = new ArrayList<>(filteredMBeans.size());
-            mbeanPage.forEach(objectName -> {
-                Map<String, String> item = new LinkedHashMap<>(2);
+            for (ObjectName objectName : mbeanPage) {
+                Map<String, String> item = new LinkedHashMap<>();
                 item.put("name", objectName.toString());
+                MBeanResource mBeanResource = mBeanResourceMap.get(objectName.toString());
+                try {
+                    JSONObject mBeanInfo = mBeanResource.getMBeanInfo(mBeanServer, objectName);
+                    JSONElement element = mBeanInfo.get("descriptor");
+                    if (element != null) {
+                        JSONElement element1 = ((JSONObject) element).get("interfaceClassName");
+                        if (element1 != null) {
+                            String intfName = (String) ((JSONPrimitive) element1).getValue();
+                            item.put("interfaceClassName", intfName);
+                        }
+                    }
+                    element = mBeanInfo.get("className");
+                    if (element != null) {
+                        String className = (String) ((JSONPrimitive) element).getValue();
+                        item.put("className", className);
+                    }
+                    element = mBeanInfo.get("description");
+                    if (element != null) {
+                        String description = (String) ((JSONPrimitive) element).getValue();
+                        item.put("description", description);
+                    }
+                    element = mBeanInfo.get("attributeInfo");
+                    if(element != null) {
+                        item.put("attributeCount", ((JSONArray)element).size() + "");
+                    }
+                    element = mBeanInfo.get("operationInfo");
+                    if(element != null) {
+                        item.put("operationCount", ((JSONArray)element).size() + "");
+                    }
+
+                } catch (InstanceNotFoundException | IntrospectionException | ReflectionException e) {
+                }
+
                 String href = path + "/" + objectName.toString();
                 href = HttpUtil.escapeUrl(href);
                 item.put("href", href);
                 items.add(item);
-            });
+                String info = HttpUtil.escapeUrl(href + "/info");
+                item.put("info", info);
+            }
 
             Map<String, String> properties = new HashMap<>();
 
@@ -228,9 +268,9 @@
 
     @Override
     public HttpResponse doPost(HttpExchange exchange) {
-        String path = exchange.getRequestURI().getPath();
-        String reqBody = null;
         try {
+            String path = URLDecoder.decode(exchange.getRequestURI().getPath(),StandardCharsets.UTF_8.displayName());
+            String reqBody = null;
             if (path.matches(pathPrefix + "/?$")) { // POST to current URL
                 reqBody = HttpUtil.readRequestBody(exchange);
                 if (reqBody == null || reqBody.isEmpty()) { // No Parameters
--- a/src/java.management.rest/share/classes/com/oracle/jmx/remote/rest/http/MBeanResource.java	Tue Jan 02 15:03:52 2018 +0530
+++ b/src/java.management.rest/share/classes/com/oracle/jmx/remote/rest/http/MBeanResource.java	Thu Jan 04 14:39:04 2018 +0530
@@ -31,6 +31,7 @@
 import com.oracle.jmx.remote.rest.json.JSONPrimitive;
 import com.oracle.jmx.remote.rest.json.parser.JSONParser;
 import com.oracle.jmx.remote.rest.json.parser.ParseException;
+import com.oracle.jmx.remote.rest.json.parser.TokenMgrError;
 import com.oracle.jmx.remote.rest.mapper.JSONDataException;
 import com.oracle.jmx.remote.rest.mapper.JSONMapper;
 import com.oracle.jmx.remote.rest.mapper.JSONMappingException;
@@ -44,6 +45,8 @@
 import javax.management.remote.rest.PlatformRestAdapter;
 import java.io.IOException;
 import java.net.HttpURLConnection;
+import java.net.URLDecoder;
+import java.nio.charset.StandardCharsets;
 import java.util.*;
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
@@ -79,7 +82,7 @@
 
     @Override
     public void handle(HttpExchange exchange) throws IOException {
-        String path = exchange.getRequestURI().getPath();
+        String path = URLDecoder.decode(exchange.getRequestURI().getPath(),StandardCharsets.UTF_8.displayName());
         if (path.matches(pathPrefix + "/?$")) {
             RestResource.super.handle(exchange);
         } else if (path.matches(pathPrefix + "/info$")
@@ -98,16 +101,17 @@
         String path = PlatformRestAdapter.getDomain() +
                 exchange.getRequestURI().getPath().replaceAll("/$", "");
 
-        if (path.endsWith("info")) {
-            return doMBeanInfo();
-        }
+        try {
+            path = URLDecoder.decode(path, StandardCharsets.UTF_8.displayName());
+            if (path.endsWith("info")) {
+                return doMBeanInfo();
+            }
 
-        String infoPath = path + "/info";
+            String infoPath = path + "/info";
 
-        try {
             Map<String, Object> allAttributes = getAllAttributes();
             Map<String, String> _links = new LinkedHashMap<>();
-            _links.put("info", HttpUtil.escapeUrl(infoPath));
+            //_links.put("info", HttpUtil.escapeUrl(infoPath));
 
             MBeanOperationInfo[] opInfo = mBeanServer.getMBeanInfo(objectName).getOperations();
             JSONArray jarr = new JSONArray();
@@ -136,7 +140,6 @@
                 JSONObject jobj = new JSONObject();
                 jobj.put("attributes", jsonElement1);
                 jobj.put("operations", jarr);
-                jobj.put("_links", jsonElement2);
                 return new HttpResponse(jobj.toJsonString());
             } else {
                 return HttpResponse.SERVER_ERROR;
@@ -222,7 +225,7 @@
             }
         } catch (InstanceNotFoundException e) {
             // Should never happen
-        } catch (JSONDataException | ParseException e) {
+        } catch (JSONDataException | ParseException | TokenMgrError e) {
             return new HttpResponse(HttpURLConnection.HTTP_BAD_REQUEST, "Invalid JSON : " + reqBody, e.getMessage());
         } catch (IntrospectionException | JSONMappingException | MBeanException | ReflectionException | IOException e) {
             return new HttpResponse(HttpResponse.SERVER_ERROR, HttpResponse.getErrorMessage(e));
@@ -354,7 +357,7 @@
         return result;
     }
 
-    private JSONObject getMBeanInfo(MBeanServer mbeanServer, ObjectName mbean)
+    JSONObject getMBeanInfo(MBeanServer mbeanServer, ObjectName mbean)
             throws InstanceNotFoundException, IntrospectionException, ReflectionException {
 
         JSONObject jobj = new JSONObject();
--- a/src/java.management.rest/share/classes/com/oracle/jmx/remote/rest/http/MBeanServerCollectionResource.java	Tue Jan 02 15:03:52 2018 +0530
+++ b/src/java.management.rest/share/classes/com/oracle/jmx/remote/rest/http/MBeanServerCollectionResource.java	Thu Jan 04 14:39:04 2018 +0530
@@ -33,6 +33,8 @@
 import javax.management.remote.rest.PlatformRestAdapter;
 import java.io.UnsupportedEncodingException;
 import java.net.HttpURLConnection;
+import java.net.URLDecoder;
+import java.nio.charset.StandardCharsets;
 import java.util.List;
 
 /**
@@ -57,9 +59,16 @@
             if (filteredList == null) {
                 return HttpResponse.OK;
             }
-
-            final String path = PlatformRestAdapter.getDomain() +
-                    exchange.getRequestURI().getPath().replaceAll("/$", "");
+            String query = exchange.getRequestURI().getQuery();
+            if (query != null) {
+                return HttpResponse.BAD_REQUEST;
+            }
+            String exchangePath = URLDecoder.decode(exchange.getRequestURI().getPath(), StandardCharsets.UTF_8.displayName())
+                    .replaceAll("/$", "");
+            if (!exchangePath.equalsIgnoreCase("/jmx/servers")) {
+                return HttpResponse.REQUEST_NOT_FOUND;
+            }
+            final String path = PlatformRestAdapter.getDomain() + exchangePath;
 
             JSONObject root = new JSONObject();
             if (_links != null && !_links.isEmpty()) {
--- a/src/java.management.rest/share/classes/com/oracle/jmx/remote/rest/http/MBeanServerResource.java	Tue Jan 02 15:03:52 2018 +0530
+++ b/src/java.management.rest/share/classes/com/oracle/jmx/remote/rest/http/MBeanServerResource.java	Thu Jan 04 14:39:04 2018 +0530
@@ -50,6 +50,8 @@
 import java.lang.reflect.Method;
 import java.lang.reflect.Proxy;
 import java.net.HttpURLConnection;
+import java.net.URLDecoder;
+import java.nio.charset.StandardCharsets;
 import java.security.AccessControlContext;
 import java.security.AccessController;
 import java.security.PrivilegedAction;
@@ -157,7 +159,7 @@
             }
         }
 
-        String path = exchange.getRequestURI().getPath();
+        String path = URLDecoder.decode(exchange.getRequestURI().getPath(), StandardCharsets.UTF_8.displayName());
         String pathPrefix = httpContext.getPath();
         // Route request to appropriate resource
         if (path.matches(pathPrefix + "/?$")) {
--- a/src/java.management.rest/share/classes/com/oracle/jmx/remote/rest/http/RestResource.java	Tue Jan 02 15:03:52 2018 +0530
+++ b/src/java.management.rest/share/classes/com/oracle/jmx/remote/rest/http/RestResource.java	Thu Jan 04 14:39:04 2018 +0530
@@ -31,6 +31,7 @@
 import java.io.IOException;
 
 /**
+ *
  * @author harsha
  */
 public interface RestResource extends HttpHandler {
--- a/src/java.management.rest/share/classes/com/oracle/jmx/remote/rest/json/JSONPrimitive.java	Tue Jan 02 15:03:52 2018 +0530
+++ b/src/java.management.rest/share/classes/com/oracle/jmx/remote/rest/json/JSONPrimitive.java	Thu Jan 04 14:39:04 2018 +0530
@@ -22,6 +22,7 @@
  * or visit www.oracle.com if you need additional information or have any
  * questions.
  */
+
 package com.oracle.jmx.remote.rest.json;
 
 /**
--- a/src/java.management.rest/share/classes/com/oracle/jmx/remote/rest/json/parser/JsonParser.jj	Tue Jan 02 15:03:52 2018 +0530
+++ b/src/java.management.rest/share/classes/com/oracle/jmx/remote/rest/json/parser/JsonParser.jj	Thu Jan 04 14:39:04 2018 +0530
@@ -85,7 +85,7 @@
 |   < #DIGITS : (<DIGIT>)+>
 |   < #DIGIT: ["0"-"9"]>
 |   <QUOTED_STRING: "\"" ((~["\"","\\"]) | ("\\" ( ["n","t","b","r","f","\\","\""])))* "\"">
-|   <BOOL_LITERAL : "true" | "false">   
+|   <BOOL_LITERAL : "true" | "false">
 |   <NULL: "null">
 }
 
--- a/test/jdk/javax/management/remote/rest/RestAdapterConfigTest.java	Tue Jan 02 15:03:52 2018 +0530
+++ b/test/jdk/javax/management/remote/rest/RestAdapterConfigTest.java	Thu Jan 04 14:39:04 2018 +0530
@@ -8,13 +8,14 @@
 import javax.net.ssl.SSLContext;
 import javax.net.ssl.TrustManagerFactory;
 import java.io.*;
+import java.lang.reflect.Method;
 import java.net.InetAddress;
 import java.net.UnknownHostException;
 import java.security.KeyStore;
-import java.util.ArrayList;
-import java.util.List;
 import java.util.Properties;
-
+import java.util.Set;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
 
 /* @test
  * @summary Configuration test for rest adapter
@@ -33,20 +34,13 @@
     private static String sslClientConfig;
     private static String passwordFile;
     private static String configFile;
-    private static final List<Runnable> tasks = new ArrayList<>();
-    private static RestAdapterTest test = new RestAdapterTest();
+    private static RestAdapterTest restAdapterTest = new RestAdapterTest();
+    private static final Set<Method> tests;
 
     static {
-        tasks.add(test::testAllMBeanServers);
-        tasks.add(test::testAllMBeanInfo);
-        tasks.add(test::testAllMBeans);
-        tasks.add(test::testMBeanFiltering);
-        tasks.add(test::testMBeanGetAttributes);
-        tasks.add(test::testMBeanSetAttributes);
-        tasks.add(test::testMbeanNoArgOperations);
-        tasks.add(test::testAllMBeansBulkRequest);
-        tasks.add(test::testThreadMXBeanBulkRequest);
-        tasks.add(test::testThreadMXBeanThreadInfo);
+        tests = Stream.of(RestAdapterTest.class.getMethods())
+                .filter(a -> a.getName().startsWith("test"))
+                .collect(Collectors.toSet());
     }
 
     private void createAgentSslConfigFile(String fileName) throws IOException {
@@ -183,30 +177,38 @@
     @Test
     public void testHttpNoAuth() throws Exception {
         setupMgmtConfig(configFile, false, false);
-        test.setupServers();
-        tasks.forEach(Runnable::run);
-        test.tearDownServers();
+        restAdapterTest.setupServers();
+        for (Method m : tests) {
+            m.invoke(restAdapterTest);
+        }
+        restAdapterTest.tearDownServers();
     }
 
     public void testHttpsNoAuth() throws Exception {
         setupMgmtConfig(configFile, true, false);
-        test.setupServers();
-        tasks.forEach(Runnable::run);
-        test.tearDownServers();
+        restAdapterTest.setupServers();
+        for (Method m : tests) {
+            m.invoke(restAdapterTest);
+        }
+        restAdapterTest.tearDownServers();
     }
 
     public void testHttpAuth() throws Exception {
         setupMgmtConfig(configFile, false, true);
-        test.setupServers();
-        tasks.forEach(Runnable::run);
-        test.tearDownServers();
+        restAdapterTest.setupServers();
+        for (Method m : tests) {
+            m.invoke(restAdapterTest);
+        }
+        restAdapterTest.tearDownServers();
     }
 
     public void testHttpsAuth() throws Exception {
         setupMgmtConfig(configFile, true, true);
-        test.setupServers();
-        tasks.forEach(Runnable::run);
-        test.tearDownServers();
+        restAdapterTest.setupServers();
+        for (Method m : tests) {
+            m.invoke(restAdapterTest);
+        }
+        restAdapterTest.tearDownServers();
     }
 
     @AfterClass
@@ -221,4 +223,5 @@
         if (f.exists())
             f.delete();
     }
+
 }
--- a/test/jdk/javax/management/remote/rest/RestAdapterPerfTest.java	Tue Jan 02 15:03:52 2018 +0530
+++ b/test/jdk/javax/management/remote/rest/RestAdapterPerfTest.java	Thu Jan 04 14:39:04 2018 +0530
@@ -10,20 +10,17 @@
  * @run testng/othervm RestAdapterPerfTest
  */
 
+import java.lang.reflect.InvocationTargetException;
 import jdk.test.lib.Utils;
-import jdk.test.lib.process.ProcessTools;
-import java.util.ArrayList;
 import java.util.List;
 import java.util.Random;
 import java.util.concurrent.Executors;
 import java.util.concurrent.ThreadPoolExecutor;
 import java.util.concurrent.TimeUnit;
 import java.util.concurrent.atomic.AtomicInteger;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
 
-import org.testng.Assert;
-import org.testng.annotations.AfterClass;
-import org.testng.annotations.BeforeClass;
-import org.testng.annotations.DataProvider;
 import org.testng.annotations.Test;
 
 @Test
@@ -35,18 +32,14 @@
     @Test
     public void testMultipleClients() throws Exception {
         RestAdapterTest test = new RestAdapterTest();
-        List<Runnable> tasks = new ArrayList<>();
-
-        tasks.add(test::testAllMBeanServers);
-        tasks.add(test::testAllMBeanInfo);
-        tasks.add(test::testAllMBeans);
-        tasks.add(test::testMBeanFiltering);
-        tasks.add(test::testMBeanGetAttributes);
-        tasks.add(test::testMBeanSetAttributes);
-        tasks.add(test::testMbeanNoArgOperations);
-        tasks.add(test::testAllMBeansBulkRequest);
-        tasks.add(test::testThreadMXBeanBulkRequest);
-        tasks.add(test::testThreadMXBeanThreadInfo);
+        List<Runnable> tasks = Stream.of(RestAdapterTest.class.getMethods())
+                .filter(m -> m.getName().startsWith("test")).map(m -> (Runnable)() -> {
+                    try {
+                        m.invoke(test);
+                    } catch (IllegalAccessException e) {
+                    } catch (InvocationTargetException e) {
+                    }
+                }).collect(Collectors.toList());
 
         ThreadPoolExecutor es = (ThreadPoolExecutor) Executors.newFixedThreadPool(20);
         es.setThreadFactory((Runnable R) -> new Thread(R, "perf-" + count.getAndIncrement()));
--- a/test/jdk/javax/management/remote/rest/RestAdapterTest.java	Tue Jan 02 15:03:52 2018 +0530
+++ b/test/jdk/javax/management/remote/rest/RestAdapterTest.java	Thu Jan 04 14:39:04 2018 +0530
@@ -75,7 +75,7 @@
         try {
             MBeanServerConnection mBeanServer = connector.getMBeanServerConnection();
             Set<ObjectInstance> objectInstances = mBeanServer.queryMBeans(null, null);
-            return objectInstances.stream().map(a -> a.getObjectName().toString()).collect(Collectors.toSet());
+            return objectInstances.stream().map(a -> a.getObjectName().getCanonicalName()).collect(Collectors.toSet());
         } catch (Exception ex) {
             throw new RuntimeException(ex);
         }
@@ -149,6 +149,12 @@
                     JSONPrimitive jp = (JSONPrimitive) jobj.get("name");
                     String name = (String) jp.getValue();
                     mbeanNames.add(name);
+                    JSONPrimitive jhref = (JSONPrimitive) jobj.get("href");
+                    String href = (String) jhref.getValue();
+                    verifyHttpResponse(executeHttpRequest(href));
+                    JSONPrimitive jinfo = (JSONPrimitive) jobj.get("info");
+                    String info = (String) jinfo.getValue();
+                    verifyHttpResponse(executeHttpRequest(info));
                 }
 
                 JSONObject linkObj = (JSONObject) root.get("_links");
@@ -395,12 +401,12 @@
 
     public void testMBeanFiltering() {
         String url = restUrl + "/platform/mbeans?";
-        List<String> filtersOk = Arrays.asList("query=*:type=DiagnosticCommand,*",
-        "query=java.lang:*&page=2",
-        "query=java.lang:*&page=1",
-        "query=*:type=Diag*");
+        List<String> filtersOk = Arrays.asList("objectname=*:type=DiagnosticCommand,*",
+        "objectname=java.lang:*&page=2",
+        "objectname=java.lang:*&page=1",
+        "objectname=*:type=Diag*");
 
-        List<String> filtersKo = Arrays.asList("","*:type=DiagnosticCommand,*","query=java.lang:*&page=1&invalid=4");
+        List<String> filtersKo = Arrays.asList("","*:type=DiagnosticCommand,*","objectname=java.lang:*&page=1&invalid=4");
 
         for(String filter : filtersOk) {
             HttpResponse httpResponse = executeHttpRequest(url + filter);
@@ -458,7 +464,7 @@
             MBeanServerConnection mBeanServer = connector.getMBeanServerConnection();
             for (String name : mbeans) {
                 ObjectName objectName = new ObjectName(name);
-                String url = "/platform/mbeans/" + objectName.toString();
+                String url = "/platform/mbeans/" + objectName.getCanonicalName();
                 JSONObject attrMap = restGetAttributes(name);
                 MBeanAttributeInfo[] attrInfos = mBeanServer.getMBeanInfo(objectName).getAttributes();
                 Set<String> writableAttrs = Stream.of(attrInfos)
@@ -663,4 +669,12 @@
         HttpResponse httpResponse = executeHttpRequest(url, result.toJsonString(), true);
         verifyHttpResponse(httpResponse);
     }
+
+    @Test(priority = 6)
+    public void testMbeansQueryBulkRequest() {
+        String url = restUrl + "/platform/mbeans";
+        String request = "{\"?*:type=MemoryPool,*\":{\"attributes\":{\"get\":[\"Name\",\"Usage\"]},\"operations\":\"resetPeakUsage\"},\"java.lang:name=Compressed Class Space,type=MemoryPool\":{\"attributes\":{\"get\":[\"MemoryManagerNames\"]}}}";
+        HttpResponse httpResponse = executeHttpRequest(url, request, true);
+        verifyHttpResponse(httpResponse);
+    }
 }
\ No newline at end of file