# HG changeset patch # User hb # Date 1514452394 -19800 # Node ID 54779691e11fc0513715b049043f70ebf3745905 # Parent f881344569d99737d57070243e55101ae1e23c61 Code cleanup - PlatformMBeanServer, MBeanServerResource Added remaining HTTP methods to RESTresource diff -r f881344569d9 -r 54779691e11f src/java.management.rest/share/classes/com/oracle/jmx/remote/rest/http/HttpResponse.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(); diff -r f881344569d9 -r 54779691e11f src/java.management.rest/share/classes/com/oracle/jmx/remote/rest/http/HttpUtil.java --- 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 ""; } diff -r f881344569d9 -r 54779691e11f src/java.management.rest/share/classes/com/oracle/jmx/remote/rest/http/MBeanServerCollectionResource.java --- 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 restAdapters; + private final List restAdapters; private final int pageSize = 5; - public MBeanServerCollectionResource(List adapters, HttpServer server) { + public MBeanServerCollectionResource(List 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 filteredList = HttpUtil.filterByPage(exchange, restAdapters, pageSize); + List 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; - } } diff -r f881344569d9 -r 54779691e11f src/java.management.rest/share/classes/com/oracle/jmx/remote/rest/http/MBeanServerResource.java --- /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 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 proxyMBeanServers = new TimedMap<>(5*60); + + private static int count = 0; + private boolean started = false; + + public MBeanServerResource(HttpServer hServer, MBeanServer mbeanServer, + String context, Map 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 links = new LinkedHashMap<>(); + links.put("mbeans", selfUrl + "/mbeans"); + + Map 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 getMBeanServerInfo() { + Map 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 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 { + + private ConcurrentHashMap> permanentMap; + private long timeout = Long.MAX_VALUE; // Timeout in seconds + + private class TimeStampedValue { + 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 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)); + } + } +} diff -r f881344569d9 -r 54779691e11f src/java.management.rest/share/classes/com/oracle/jmx/remote/rest/http/RestResource.java --- 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; + } } diff -r f881344569d9 -r 54779691e11f src/java.management.rest/share/classes/javax/management/remote/rest/JmxRestAdapter.java --- /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 diff -r f881344569d9 -r 54779691e11f src/java.management.rest/share/classes/javax/management/remote/rest/PlatformRestAdapter.java --- 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 env; - private static String portStr; - private static Properties props; - - private static List restAdapters = new CopyOnWriteArrayList<>(); + private static List 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. + *

+ * 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 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"; } } diff -r f881344569d9 -r 54779691e11f src/jdk.management.agent/share/classes/jdk/internal/agent/Agent.java --- 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(); diff -r f881344569d9 -r 54779691e11f test/jdk/javax/management/remote/rest/RunRestAdapter.java --- 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); } } }