Code cleanup - PlatformMBeanServer, MBeanServerResource jmx-rest-api
authorhb
Thu, 28 Dec 2017 14:43:14 +0530
branchjmx-rest-api
changeset 55998 54779691e11f
parent 55997 f881344569d9
child 55999 2db04c2274fd
Code cleanup - PlatformMBeanServer, MBeanServerResource Added remaining HTTP methods to RESTresource
src/java.management.rest/share/classes/com/oracle/jmx/remote/rest/http/HttpResponse.java
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/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/javax/management/remote/rest/JmxRestAdapter.java
src/java.management.rest/share/classes/javax/management/remote/rest/PlatformRestAdapter.java
src/jdk.management.agent/share/classes/jdk/internal/agent/Agent.java
test/jdk/javax/management/remote/rest/RunRestAdapter.java
--- a/src/java.management.rest/share/classes/com/oracle/jmx/remote/rest/http/HttpResponse.java	Wed Dec 27 18:39:52 2017 +0530
+++ b/src/java.management.rest/share/classes/com/oracle/jmx/remote/rest/http/HttpResponse.java	Thu Dec 28 14:43:14 2017 +0530
@@ -51,17 +51,19 @@
 
     private final int code;
     private final String message;
-    private final String detail;
     private final String body;
 
     public HttpResponse(int code, String message) {
         this(code, message, "");
     }
 
+    public HttpResponse(String message) {
+        this(200,message,"");
+    }
+
     public HttpResponse(int code, String message, String detail) {
         this.code = code;
         this.message = message;
-        this.detail = detail;
 
         if (code != HttpURLConnection.HTTP_OK) {
             JSONObject jobj = new JSONObject();
--- a/src/java.management.rest/share/classes/com/oracle/jmx/remote/rest/http/HttpUtil.java	Wed Dec 27 18:39:52 2017 +0530
+++ b/src/java.management.rest/share/classes/com/oracle/jmx/remote/rest/http/HttpUtil.java	Thu Dec 28 14:43:14 2017 +0530
@@ -116,7 +116,8 @@
             int sp = auth.indexOf(' ');
             byte[] b = Base64.getDecoder().decode(auth.substring(sp + 1));
             String authCredentials = new String(b);
-            return authCredentials;
+            int colon = authCredentials.indexOf (':');
+            return authCredentials.substring(0,colon);
         }
         return "";
     }
--- a/src/java.management.rest/share/classes/com/oracle/jmx/remote/rest/http/MBeanServerCollectionResource.java	Wed Dec 27 18:39:52 2017 +0530
+++ b/src/java.management.rest/share/classes/com/oracle/jmx/remote/rest/http/MBeanServerCollectionResource.java	Thu Dec 28 14:43:14 2017 +0530
@@ -31,6 +31,8 @@
 import com.sun.net.httpserver.HttpServer;
 
 import javax.management.remote.rest.PlatformRestAdapter;
+import java.io.UnsupportedEncodingException;
+import java.net.HttpURLConnection;
 import java.util.List;
 
 /**
@@ -39,10 +41,10 @@
  */
 public class MBeanServerCollectionResource implements RestResource {
 
-    private final List<JmxRestAdapter> restAdapters;
+    private final List<MBeanServerResource> restAdapters;
     private final int pageSize = 5;
 
-    public MBeanServerCollectionResource(List<JmxRestAdapter> adapters, HttpServer server) {
+    public MBeanServerCollectionResource(List<MBeanServerResource> adapters, HttpServer server) {
         this.restAdapters = adapters;
         server.createContext("/jmx/servers", this);
     }
@@ -51,13 +53,13 @@
     public HttpResponse doGet(HttpExchange exchange) {
         try {
             JSONObject _links = HttpUtil.getPaginationLinks(exchange, restAdapters, pageSize);
-            List<JmxRestAdapter> filteredList = HttpUtil.filterByPage(exchange, restAdapters, pageSize);
+            List<MBeanServerResource> filteredList = HttpUtil.filterByPage(exchange, restAdapters, pageSize);
             if (filteredList == null) {
                 return HttpResponse.OK;
             }
 
             final String path = PlatformRestAdapter.getDomain() +
-                    exchange.getRequestURI().getPath().replaceAll("\\/$", "");
+                    exchange.getRequestURI().getPath().replaceAll("/$", "");
 
             JSONObject root = new JSONObject();
             if (_links != null && !_links.isEmpty()) {
@@ -69,37 +71,17 @@
             JSONArray list = new JSONArray();
             filteredList.stream().map((adapter) -> {
                 JSONObject result = new JSONObject();
-                result.put("name", adapter.getAlias());
-                result.put("href", path + "/" + adapter.getAlias());
+                result.put("name", adapter.getContext());
+                result.put("href", path + "/" + adapter.getContext());
                 return result;
             }).forEachOrdered((result) -> {
                 list.add(result);
             });
-            root.put("items", list);
-            return new HttpResponse(200, root.toJsonString());
-        } catch (Exception ex) {
-            ex.printStackTrace();
-            return new HttpResponse(400, HttpResponse.getErrorMessage(ex));
+            root.put("mBeanServers", list);
+            return new HttpResponse(HttpURLConnection.HTTP_OK, root.toJsonString());
+        } catch (UnsupportedEncodingException e) {
+            return new HttpResponse(HttpResponse.BAD_REQUEST,
+                    HttpUtil.getRequestCharset(exchange) + " is not supported");
         }
     }
-
-    @Override
-    public HttpResponse doPut(HttpExchange exchange) {
-        return null;
-    }
-
-    @Override
-    public HttpResponse doPost(HttpExchange exchange) {
-        return null;
-    }
-
-    @Override
-    public HttpResponse doDelete(HttpExchange exchange) {
-        return null;
-    }
-
-    @Override
-    public HttpResponse doHead(HttpExchange exchange) {
-        return null;
-    }
 }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/java.management.rest/share/classes/com/oracle/jmx/remote/rest/http/MBeanServerResource.java	Thu Dec 28 14:43:14 2017 +0530
@@ -0,0 +1,329 @@
+/*
+ * Copyright (c) 2018, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package com.oracle.jmx.remote.rest.http;
+
+import com.oracle.jmx.remote.rest.json.JSONElement;
+import com.oracle.jmx.remote.rest.mapper.JSONMapper;
+import com.oracle.jmx.remote.rest.mapper.JSONMappingException;
+import com.oracle.jmx.remote.rest.mapper.JSONMappingFactory;
+import com.sun.jmx.remote.security.JMXPluggableAuthenticator;
+import com.sun.jmx.remote.security.JMXSubjectDomainCombiner;
+import com.sun.jmx.remote.security.SubjectDelegator;
+import com.sun.net.httpserver.BasicAuthenticator;
+import com.sun.net.httpserver.HttpContext;
+import com.sun.net.httpserver.HttpExchange;
+import com.sun.net.httpserver.HttpServer;
+
+import javax.management.JMX;
+import javax.management.MBeanServer;
+import javax.management.MBeanServerDelegate;
+import javax.management.MBeanServerDelegateMBean;
+import javax.management.remote.JMXAuthenticator;
+import javax.management.remote.rest.JmxRestAdapter;
+import javax.management.remote.rest.PlatformRestAdapter;
+import javax.security.auth.Subject;
+import java.io.IOException;
+import java.lang.reflect.InvocationHandler;
+import java.lang.reflect.Method;
+import java.lang.reflect.Proxy;
+import java.net.HttpURLConnection;
+import java.security.AccessControlContext;
+import java.security.AccessController;
+import java.security.PrivilegedAction;
+import java.util.Arrays;
+import java.util.LinkedHashMap;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.TimeUnit;
+
+
+public final class MBeanServerResource implements RestResource, JmxRestAdapter {
+
+    // Initialization parameters
+    private final HttpServer httpServer;
+    private final String contextStr;
+    private final Map<String, ?> env;
+    private final MBeanServer mbeanServer;
+
+    // Save the context to start/stop the adapter
+    private HttpContext httpContext;
+    private JMXAuthenticator authenticator = null;
+    private final MBeanServerDelegateMBean mBeanServerDelegateMBean;
+
+    // Save MBeanServer Proxy for a user
+    private final MBeanCollectionResource defaultMBeansResource;
+    // Use an expiring map that removes entries after the configured period lapses.
+    private final TimedMap<String, MBeanCollectionResource> proxyMBeanServers = new TimedMap<>(5*60);
+
+    private static int count = 0;
+    private boolean started = false;
+
+    public MBeanServerResource(HttpServer hServer, MBeanServer mbeanServer,
+                               String context, Map<String, ?> env) {
+        httpServer = hServer;
+        this.env = env;
+        this.mbeanServer = mbeanServer;
+
+        mBeanServerDelegateMBean = JMX.newMBeanProxy(mbeanServer,
+                MBeanServerDelegate.DELEGATE_NAME, MBeanServerDelegateMBean.class);
+
+        if (context == null || context.isEmpty()) {
+            contextStr = "server-" + count++;
+        } else {
+            contextStr = context;
+        }
+        // setup authentication
+        if (env.get("jmx.remote.x.authentication") != null) {
+            authenticator = (JMXAuthenticator) env.get("jmx.remote.authenticator");
+            if (authenticator == null) {
+                if (env.get("jmx.remote.x.password.file") != null
+                        || env.get("jmx.remote.x.login.config") != null) {
+                    authenticator = new JMXPluggableAuthenticator(env);
+                } else {
+                    throw new IllegalArgumentException
+                            ("Config error : Authentication is enabled with no authenticator");
+                }
+            }
+        }
+
+        if (env.get("jmx.remote.x.authentication") == null) {
+            defaultMBeansResource = new MBeanCollectionResource(mbeanServer);
+        } else {
+            defaultMBeansResource = null;
+        }
+    }
+
+    private MBeanServer getMBeanServerProxy(MBeanServer mbeaServer, Subject subject) {
+        return (MBeanServer) Proxy.newProxyInstance(MBeanServer.class.getClassLoader(),
+                new Class<?>[]{MBeanServer.class},
+                new AuthInvocationHandler(mbeaServer, subject));
+    }
+
+    @Override
+    public HttpResponse doGet(HttpExchange exchange) {
+        String selfUrl = getUrl();
+        Map<String, String> links = new LinkedHashMap<>();
+        links.put("mbeans", selfUrl + "/mbeans");
+
+        Map<String, Object> mBeanServerInfo = getMBeanServerInfo();
+        mBeanServerInfo.put("_links", links);
+
+        final JSONMapper typeMapper = JSONMappingFactory.INSTANCE.getTypeMapper(mBeanServerInfo);
+        if (typeMapper != null) {
+            try {
+                JSONElement jsonElement = typeMapper.toJsonValue(mBeanServerInfo);
+                return new HttpResponse(jsonElement.toJsonString());
+            } catch (JSONMappingException e) {
+                return HttpResponse.SERVER_ERROR;
+            }
+        } else {
+            return HttpResponse.SERVER_ERROR;
+        }
+    }
+
+    @Override
+    public void handle(HttpExchange exchange) throws IOException {
+        MBeanCollectionResource mBeansResource = defaultMBeansResource;
+        if (env.get("jmx.remote.x.authentication") != null) {
+            String authCredentials = HttpUtil.getCredentials(exchange);
+            // MBeanServer proxy should be populated in the authenticator
+            mBeansResource = proxyMBeanServers.get(authCredentials);
+            if (mBeansResource == null) {
+                throw new IllegalArgumentException("Invalid HTTP request Headers");
+            }
+        }
+
+        String path = exchange.getRequestURI().getPath();
+        String pathPrefix = httpContext.getPath();
+        // Route request to appropriate resource
+        if (path.matches(pathPrefix + "/?$")) {
+            RestResource.super.handle(exchange);
+        } else if (path.matches(pathPrefix + "/mbeans.*")) {
+            mBeansResource.handle(exchange);
+        } else {
+            HttpUtil.sendResponse(exchange, HttpResponse.REQUEST_NOT_FOUND);
+        }
+    }
+
+    public synchronized void start() {
+        if (!started) {
+            httpContext = httpServer.createContext("/jmx/servers/" + contextStr, this);
+            if (env.get("jmx.remote.x.authentication") != null) {
+                httpContext.setAuthenticator(new RestAuthenticator("jmx-rest"));
+            }
+            started = true;
+        }
+    }
+
+    @Override
+    public synchronized void stop() {
+        if (!started) {
+            throw new IllegalStateException("Rest Adapter not started yet");
+        }
+        httpServer.removeContext(httpContext);
+        started = false;
+    }
+
+    @Override
+    public String getUrl() {
+        return PlatformRestAdapter.getBaseURL() + "/" + contextStr;
+    }
+
+    @Override
+    public MBeanServer getMBeanServer() {
+        return mbeanServer;
+    }
+
+    public String getContext() {
+        return contextStr;
+    }
+
+    private class RestAuthenticator extends BasicAuthenticator {
+
+        RestAuthenticator(String realm) {
+            super(realm);
+        }
+
+        @Override
+        public boolean checkCredentials(String username, String password) {
+            if (proxyMBeanServers.containsKey(username)) {
+                return true;
+            } else {
+                Subject subject = null;
+                if (authenticator != null) {
+                    String[] credential = new String[]{username, password};
+                    try {
+                        subject = authenticator.authenticate(credential);
+                    } catch (SecurityException e) {
+                        return false;
+                    }
+                }
+                MBeanServer proxy = getMBeanServerProxy(mbeanServer, subject);
+                proxyMBeanServers.put(username, new MBeanCollectionResource(proxy));
+                return true;
+            }
+        }
+    }
+
+    Map<String, Object> getMBeanServerInfo() {
+        Map<String, Object> result = new LinkedHashMap<>();
+
+        result.put("id", mBeanServerDelegateMBean.getMBeanServerId());
+        result.put("context", contextStr);
+
+        result.put("defaultDomain", mbeanServer.getDefaultDomain());
+        result.put("mBeanCount", mbeanServer.getMBeanCount());
+        result.put("domains", Arrays.toString(mbeanServer.getDomains()));
+
+        result.put("specName", mBeanServerDelegateMBean.getSpecificationName());
+        result.put("specVersion", mBeanServerDelegateMBean.getSpecificationVersion());
+        result.put("specVendor", mBeanServerDelegateMBean.getSpecificationVendor());
+
+        result.put("implName", mBeanServerDelegateMBean.getImplementationName());
+        result.put("implVersion", mBeanServerDelegateMBean.getImplementationVersion());
+        result.put("implVendor", mBeanServerDelegateMBean.getImplementationVendor());
+
+        return result;
+    }
+
+    private class AuthInvocationHandler implements InvocationHandler {
+
+        private final MBeanServer mbeanServer;
+        private final AccessControlContext acc;
+
+        AuthInvocationHandler(MBeanServer server, Subject subject) {
+            this.mbeanServer = server;
+            if (subject == null) {
+                this.acc = null;
+            } else {
+                if (SubjectDelegator.checkRemoveCallerContext(subject)) {
+                    acc = JMXSubjectDomainCombiner.getDomainCombinerContext(subject);
+                } else {
+                    acc = JMXSubjectDomainCombiner.getContext(subject);
+                }
+            }
+        }
+
+        @Override
+        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
+            if (acc == null) {
+                return method.invoke(mbeanServer, args);
+            } else {
+                PrivilegedAction<Object> op = () -> {
+                    try {
+                        return method.invoke(mbeanServer, args);
+                    } catch (Exception ex) {
+                    }
+                    return null;
+                };
+                return AccessController.doPrivileged(op, acc);
+            }
+        }
+    }
+
+    /*
+    This is an expiring map that removes entries after the configured time period lapses.
+    This is required to re-authenticate the user after the timeout.
+     */
+    private class TimedMap<K,V> {
+
+        private ConcurrentHashMap<K,TimeStampedValue<V>>  permanentMap;
+        private long timeout = Long.MAX_VALUE;   // Timeout in seconds
+
+        private class TimeStampedValue<T> {
+            private T value;
+            private long insertTimeStamp;
+
+            TimeStampedValue(T value) {
+                this.value = value;
+                insertTimeStamp = TimeUnit.MILLISECONDS.toSeconds(System.currentTimeMillis());
+            }
+        }
+
+        public TimedMap(int seconds) {
+            this.timeout = seconds;
+        }
+
+        public boolean containsKey(K key) {
+            return permanentMap.containsKey(key);
+        }
+
+        public V get(K key) {
+            TimeStampedValue<V> vTimeStampedValue = permanentMap.get(key);
+            long current = TimeUnit.MILLISECONDS.toSeconds(System.currentTimeMillis());
+            if(current - vTimeStampedValue.insertTimeStamp > timeout) {
+                permanentMap.remove(key);
+                return null;
+            } else {
+                return vTimeStampedValue.value;
+            }
+        }
+
+        public void put(K key, V value) {
+            permanentMap.put(key, new TimeStampedValue<>(value));
+        }
+    }
+}
--- a/src/java.management.rest/share/classes/com/oracle/jmx/remote/rest/http/RestResource.java	Wed Dec 27 18:39:52 2017 +0530
+++ b/src/java.management.rest/share/classes/com/oracle/jmx/remote/rest/http/RestResource.java	Thu Dec 28 14:43:14 2017 +0530
@@ -54,13 +54,55 @@
             case "HEAD":
                 httpResponse = doHead(exchange);
                 break;
+            case "PATCH":
+                httpResponse = doPatch(exchange);
+                break;
+            case "CONNET":
+                httpResponse = doConnect(exchange);
+                break;
+            case "TRACE":
+                httpResponse = doTrace(exchange);
+                break;
+            case "OPTIONS":
+                httpResponse = doOptions(exchange);
+                break;
         }
         HttpUtil.sendResponse(exchange,httpResponse);
     }
 
-    public HttpResponse doGet(HttpExchange exchange);
-    public HttpResponse doPut(HttpExchange exchange);
-    public HttpResponse doPost(HttpExchange exchange);
-    public HttpResponse doDelete(HttpExchange exchange);
-    public HttpResponse doHead(HttpExchange exchange);
+    public default HttpResponse doGet(HttpExchange exchange) {
+        return HttpResponse.METHOD_NOT_ALLOWED;
+    }
+
+    public default HttpResponse doPut(HttpExchange exchange) {
+        return HttpResponse.METHOD_NOT_ALLOWED;
+    }
+
+    public default HttpResponse doPost(HttpExchange exchange) {
+        return HttpResponse.METHOD_NOT_ALLOWED;
+    }
+
+    public default HttpResponse doDelete(HttpExchange exchange) {
+        return HttpResponse.METHOD_NOT_ALLOWED;
+    }
+
+    public default HttpResponse doHead(HttpExchange exchange) {
+        return HttpResponse.METHOD_NOT_ALLOWED;
+    }
+
+    public default HttpResponse doConnect (HttpExchange exchange) {
+        return HttpResponse.METHOD_NOT_ALLOWED;
+    }
+
+    public default HttpResponse doOptions(HttpExchange exchange) {
+        return HttpResponse.METHOD_NOT_ALLOWED;
+    }
+
+    public default HttpResponse doTrace(HttpExchange exchange) {
+        return HttpResponse.METHOD_NOT_ALLOWED;
+    }
+
+    public default HttpResponse doPatch(HttpExchange exchange) {
+        return HttpResponse.METHOD_NOT_ALLOWED;
+    }
 }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/java.management.rest/share/classes/javax/management/remote/rest/JmxRestAdapter.java	Thu Dec 28 14:43:14 2017 +0530
@@ -0,0 +1,14 @@
+package javax.management.remote.rest;
+
+import javax.management.MBeanServer;
+
+public interface JmxRestAdapter {
+
+    public void start();
+
+    public void stop();
+
+    public String getUrl();
+
+    public MBeanServer getMBeanServer();
+}
\ No newline at end of file
--- a/src/java.management.rest/share/classes/javax/management/remote/rest/PlatformRestAdapter.java	Wed Dec 27 18:39:52 2017 +0530
+++ b/src/java.management.rest/share/classes/javax/management/remote/rest/PlatformRestAdapter.java	Thu Dec 28 14:43:14 2017 +0530
@@ -25,15 +25,13 @@
 
 package javax.management.remote.rest;
 
-import javax.management.MBeanServerFactoryListener;
-
-import com.oracle.jmx.remote.rest.http.JmxRestAdapter;
 import com.oracle.jmx.remote.rest.http.MBeanServerCollectionResource;
+import com.oracle.jmx.remote.rest.http.MBeanServerResource;
 import com.sun.net.httpserver.HttpServer;
 import com.sun.net.httpserver.HttpsConfigurator;
 import com.sun.net.httpserver.HttpsServer;
 
-import javax.management.MBeanServer;
+import javax.management.*;
 import javax.net.ssl.KeyManagerFactory;
 import javax.net.ssl.SSLContext;
 import javax.net.ssl.TrustManagerFactory;
@@ -45,12 +43,17 @@
 import java.net.InetSocketAddress;
 import java.net.UnknownHostException;
 import java.security.KeyStore;
-import java.util.*;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Properties;
 import java.util.concurrent.CopyOnWriteArrayList;
 import java.util.concurrent.Executors;
 
-/** This is the root class that initializes the HTTPServer and
+/**
+ * This is the root class that initializes the HTTPServer and
  * REST adapter for platform mBeanServer.
+ *
  * @since 11
  */
 public class PlatformRestAdapter implements MBeanServerFactoryListener {
@@ -63,22 +66,35 @@
 
     // Save configuration to be used for other MBeanServers
     private static Map<String, Object> env;
-    private static String portStr;
-    private static Properties props;
-
-    private static List<JmxRestAdapter> restAdapters = new CopyOnWriteArrayList<>();
+    private static List<MBeanServerResource> restAdapters = new CopyOnWriteArrayList<>();
 
     private PlatformRestAdapter() {
     }
 
-    public static synchronized void init(String portStr, Properties props) throws IOException {
-        PlatformRestAdapter.portStr = portStr;
-        PlatformRestAdapter.props = props;
-
+    /**
+     * Starts the HTTP server with confiuration specified in properties.
+     * The configuration properties are Interface name/IP, port and SSL configuration
+     * By default the server binds to address '0.0.0.0' and port '0'. SSL is off by default. [TODO]The
+     * keyStore will be created one if not configured and the private key and a public certificate will
+     * be generated[/TODO].
+     * Below properties are used to configure the HTTP server.
+     * com.sun.management.jmxremote.rest.port
+     * com.sun.management.jmxremote.rest.host
+     * com.sun.management.jmxremote.ssl
+     * com.sun.management.jmxremote.ssl.config.file
+     * javax.net.ssl.keyStore
+     * javax.net.ssl.trustStore
+     * javax.net.ssl.keyStorePassword
+     * javax.net.ssl.trustStorePassword
+     * @param properties    Config properties for the HTTP server.
+     *                      If null or if any properties are not specified, default values will be assumed.
+     * @throws IOException  If the server could not be created
+     */
+    public static synchronized void init(Properties properties) throws IOException {
         if (httpServer == null) {
             final int port;
             try {
-                port = Integer.parseInt(portStr);
+                port = Integer.parseInt(properties.getProperty(PropertyNames.PORT, DefaultValues.PORT));
             } catch (NumberFormatException x) {
                 throw new IllegalArgumentException("Invalid string for port");
             }
@@ -86,73 +102,124 @@
                 throw new IllegalArgumentException("Invalid string for port");
             }
 
-            boolean useSSL = Boolean.parseBoolean((String) props.get("com.sun.management.jmxremote.ssl"));
+            String host = properties.getProperty(PropertyNames.HOST, DefaultValues.HOST);
+
+            boolean useSSL = Boolean.parseBoolean(properties.getProperty(
+                    PropertyNames.USE_SSL, DefaultValues.USE_SSL));
             if (useSSL) {
                 final String sslConfigFileName
-                        = props.getProperty(PropertyNames.SSL_CONFIG_FILE_NAME);
+                        = properties.getProperty(PropertyNames.SSL_CONFIG_FILE_NAME);
                 SSLContext ctx = getSSlContext(sslConfigFileName);
                 if (ctx != null) {
-                    HttpsServer server = HttpsServer.create(new InetSocketAddress("0.0.0.0", port), 0);
+                    HttpsServer server = HttpsServer.create(new InetSocketAddress(host, port), 0);
                     server.setHttpsConfigurator(new HttpsConfigurator(ctx));
                     httpServer = server;
                 } else {
-                    httpServer = HttpServer.create(new InetSocketAddress("0.0.0.0", port), 0);
+                    httpServer = HttpServer.create(new InetSocketAddress(host, port), 0);
                 }
             } else {
-                httpServer = HttpServer.create(new InetSocketAddress("0.0.0.0", port), 0);
+                httpServer = HttpServer.create(new InetSocketAddress(host, port), 0);
             }
 
-            // Initialize rest adapter
-			env = new HashMap<>();
-            // Do we use authentication?
-            final String useAuthenticationStr
-                    = props.getProperty(PropertyNames.USE_AUTHENTICATION,
-                            DefaultValues.USE_AUTHENTICATION);
-            final boolean useAuthentication
-                    = Boolean.valueOf(useAuthenticationStr);
-
-            String loginConfigName;
-            String passwordFileName;
-
-            if (useAuthentication) {
-                env.put("jmx.remote.x.authentication", Boolean.TRUE);
-                // Get non-default login configuration
-                loginConfigName
-                        = props.getProperty(PropertyNames.LOGIN_CONFIG_NAME);
-                env.put("jmx.remote.x.login.config", loginConfigName);
-
-                if (loginConfigName == null) {
-                    // Get password file
-                    passwordFileName
-                            = props.getProperty(PropertyNames.PASSWORD_FILE_NAME);
-                    env.put("jmx.remote.x.password.file", passwordFileName);
-                }
-            }
-
-            restAdapters.add(new JmxRestAdapter(httpServer, "platform", env, ManagementFactory.getPlatformMBeanServer()));
             new MBeanServerCollectionResource(restAdapters, httpServer);
             httpServer.setExecutor(Executors.newCachedThreadPool());
             httpServer.start();
+            startDefaultRestAdapter(properties);
         }
     }
 
+    private static void startDefaultRestAdapter(Properties properties) {
+        env = new HashMap<>();
+        // Do we use authentication?
+        final String useAuthenticationStr
+                = properties.getProperty(PropertyNames.USE_AUTHENTICATION,
+                DefaultValues.USE_AUTHENTICATION);
+        final boolean useAuthentication
+                = Boolean.valueOf(useAuthenticationStr);
+
+        String loginConfigName;
+        String passwordFileName;
+
+        if (useAuthentication) {
+            env.put("jmx.remote.x.authentication", Boolean.TRUE);
+            // Get non-default login configuration
+            loginConfigName
+                    = properties.getProperty(PropertyNames.LOGIN_CONFIG_NAME);
+            env.put("jmx.remote.x.login.config", loginConfigName);
+
+            if (loginConfigName == null) {
+                // Get password file
+                passwordFileName
+                        = properties.getProperty(PropertyNames.PASSWORD_FILE_NAME);
+                env.put("jmx.remote.x.password.file", passwordFileName);
+            }
+        }
+        MBeanServerResource adapter = new MBeanServerResource(httpServer, ManagementFactory.getPlatformMBeanServer(), "platform", env);
+        adapter.start();
+        restAdapters.add(adapter);
+    }
+
+    /**
+     * Wraps the mbeanServer in a REST adapter. The mBeanServer will be accessible over REST APIs
+     * at supplied context. env parameter configures authentication parameters for the MBeanServer.
+     *
+     * @param mbeanServer The MBeanServer to be wrapped in REST adapter
+     * @param context     The context in HTTP server under which this MBeanServer will be available over REST
+     *                    If it is null or empty, a context will be generated
+     * @param env         configures authemtication parameters for accessing the MBeanServer over this adapter
+     *                    If null, configuration from default rest adapter will be used.
+     *                    Below is the list of properties.
+     *                    <p>
+     *                    jmx.remote.x.authentication : enable/disable user authentication
+     *                    jmx.remote.authenticator :  Instance of a JMXAuthenticator
+     *                    jmx.remote.x.login.config : JAAS login conguration
+     *                    jmx.remote.x.password.file : file name for default JAAS login configuration
+     * @return an Instance of REST adapter that allows to start/stop the adapter
+     */
+    public static synchronized JmxRestAdapter newRestAdapter(MBeanServer mbeanServer, String context, Map<String, ?> env) {
+        if (httpServer == null) {
+            throw new IllegalStateException("Platform Adapter not initialized");
+        }
+
+        MBeanServerResource server = restAdapters.stream()
+                .filter(s -> areMBeanServersEqual(s.getMBeanServer(), mbeanServer))
+                .findFirst()
+                .get();
+        if (server == null) {
+            MBeanServerResource adapter = new MBeanServerResource(httpServer, mbeanServer, context, env);
+            adapter.start();
+            restAdapters.add(adapter);
+            return adapter;
+        } else {
+            throw new IllegalArgumentException("MBeanServer already registered at " + server.getUrl());
+        }
+    }
+
+    private static boolean areMBeanServersEqual(MBeanServer server1, MBeanServer server2) {
+        MBeanServerDelegateMBean bean1 = JMX.newMBeanProxy(server1, MBeanServerDelegate.DELEGATE_NAME, MBeanServerDelegateMBean.class);
+        MBeanServerDelegateMBean bean2 = JMX.newMBeanProxy(server2, MBeanServerDelegate.DELEGATE_NAME, MBeanServerDelegateMBean.class);
+        return bean1.getMBeanServerId().equalsIgnoreCase(bean2.getMBeanServerId());
+    }
+
     public synchronized static void stop() {
+        restAdapters.forEach(r -> r.stop());
         if (httpServer != null) {
             httpServer.stop(0);
             httpServer = null;
         }
     }
 
-    public synchronized static void start() throws IOException {
-        if(httpServer == null) {
-            PlatformRestAdapter.init(portStr,props);
-        }
-    }
-
+    /*
+    This auto addition of MBeanServer to rest adapter must be controlled by a system property
+    com.sun.management.jmxremote.mbeanserver.autoadd=true/false
+    This allows application MBeans to be be availble over REST without any changes to application code.
+     */
     @Override
     public void onMBeanServerCreated(MBeanServer mBeanServer) {
-        JmxRestAdapter restAdapter = new JmxRestAdapter(httpServer, "", env, mBeanServer);
-        restAdapters.add(restAdapter);
+        try {
+            newRestAdapter(mBeanServer, "", env);
+        } catch (IllegalArgumentException | IllegalStateException e) {
+        }
     }
 
     @Override
@@ -161,7 +228,7 @@
     }
 
     public static synchronized String getDomain() {
-        if(httpServer == null) {
+        if (httpServer == null) {
             throw new IllegalStateException("Platform rest adapter not initialized");
         }
         try {
@@ -175,17 +242,7 @@
     }
 
     public static synchronized String getBaseURL() {
-        if(httpServer == null) {
-            throw new IllegalStateException("Platform rest adapter not initialized");
-        }
-        try {
-            if (httpServer instanceof HttpsServer) {
-                return "https://" + InetAddress.getLocalHost().getHostName() + ":" + httpServer.getAddress().getPort() + "/jmx/servers";
-            }
-            return "http://" + InetAddress.getLocalHost().getHostName() + ":" + httpServer.getAddress().getPort() + "/jmx/servers";
-        } catch (UnknownHostException ex) {
-            return "http://localhost" + ":" + httpServer.getAddress().getPort() + "/jmx/servers";
-        }
+        return getDomain() + "/jmx/servers";
     }
 
     private static SSLContext getSSlContext(String sslConfigFileName) {
@@ -252,14 +309,10 @@
     static interface DefaultValues {
 
         public static final String PORT = "0";
-        public static final String CONFIG_FILE_NAME = "management.properties";
+        public static final String HOST = "0.0.0.0";
         public static final String USE_SSL = "false";
-        public static final String USE_LOCAL_ONLY = "true";
-        public static final String USE_REGISTRY_SSL = "false";
         public static final String USE_AUTHENTICATION = "false";
         public static final String PASSWORD_FILE_NAME = "jmxremote.password";
-        public static final String ACCESS_FILE_NAME = "jmxremote.access";
-        public static final String SSL_NEED_CLIENT_AUTH = "false";
     }
 
     /**
@@ -267,20 +320,12 @@
      */
     public static interface PropertyNames {
 
-        String PORT
+        public static final String PORT
                 = "com.sun.management.jmxremote.rest.port";
         public static final String HOST
                 = "com.sun.management.jmxremote.host";
         public static final String USE_SSL
                 = "com.sun.management.jmxremote.ssl";
-        public static final String USE_AUTHENTICATION
-                = "com.sun.management.jmxremote.authenticate";
-        public static final String PASSWORD_FILE_NAME
-                = "com.sun.management.jmxremote.password.file";
-        public static final String LOGIN_CONFIG_NAME
-                = "com.sun.management.jmxremote.login.config";
-        public static final String SSL_NEED_CLIENT_AUTH
-                = "com.sun.management.jmxremote.ssl.need.client.auth";
         public static final String SSL_CONFIG_FILE_NAME
                 = "com.sun.management.jmxremote.ssl.config.file";
         public static final String SSL_KEYSTORE_FILE
@@ -291,5 +336,11 @@
                 = "javax.net.ssl.keyStorePassword";
         public static final String SSL_TRUSTSTORE_PASSWORD
                 = "javax.net.ssl.trustStorePassword";
+        public static final String USE_AUTHENTICATION
+                = "com.sun.management.jmxremote.authenticate";
+        public static final String PASSWORD_FILE_NAME
+                = "com.sun.management.jmxremote.password.file";
+        public static final String LOGIN_CONFIG_NAME
+                = "com.sun.management.jmxremote.login.config";
     }
 }
--- a/src/jdk.management.agent/share/classes/jdk/internal/agent/Agent.java	Wed Dec 27 18:39:52 2017 +0530
+++ b/src/jdk.management.agent/share/classes/jdk/internal/agent/Agent.java	Thu Dec 28 14:43:14 2017 +0530
@@ -549,7 +549,7 @@
     private static void loadRestAdapter(Properties props) {
         try {
             if (props.get(REST_PORT) != null) {
-                PlatformRestAdapter.init((String) props.get(REST_PORT), props);
+                PlatformRestAdapter.init(props);
             }
         } catch (Throwable ex) {
             ex.printStackTrace();
--- a/test/jdk/javax/management/remote/rest/RunRestAdapter.java	Wed Dec 27 18:39:52 2017 +0530
+++ b/test/jdk/javax/management/remote/rest/RunRestAdapter.java	Thu Dec 28 14:43:14 2017 +0530
@@ -68,7 +68,7 @@
         Properties props = new Properties();
         props.setProperty("com.sun.management.jmxremote.ssl", "true");
         props.setProperty("com.sun.management.jmxremote.ssl.config.file", sslAgentConfig);
-        props.setProperty("com.sun.management.jmxremote.authenticate", "true");
+        props.setProperty("com.sun.management.jmxremote.authenticate", "false");
         props.setProperty("com.sun.management.jmxremote.rest.port", "8686");
 
         try (BufferedWriter writer = new BufferedWriter(new FileWriter(fileName))) {
@@ -93,7 +93,7 @@
         Properties props = new Properties();
         props.load(new FileInputStream(file));
         if (props.get("com.sun.management.jmxremote.rest.port") != null) {
-            PlatformRestAdapter.init((String) props.get("com.sun.management.jmxremote.rest.port"), props);
+            PlatformRestAdapter.init(props);
         }
     }
 }