Code cleanup - PlatformMBeanServer, MBeanServerResource
Added remaining HTTP methods to RESTresource
--- 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);
}
}
}