1 /*
   2  * Copyright (c) 2003, 2017, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.  Oracle designates this
   8  * particular file as subject to the "Classpath" exception as provided
   9  * by Oracle in the LICENSE file that accompanied this code.
  10  *
  11  * This code is distributed in the hope that it will be useful, but WITHOUT
  12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  14  * version 2 for more details (a copy is included in the LICENSE file that
  15  * accompanied this code).
  16  *
  17  * You should have received a copy of the GNU General Public License version
  18  * 2 along with this work; if not, write to the Free Software Foundation,
  19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  20  *
  21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  22  * or visit www.oracle.com if you need additional information or have any
  23  * questions.
  24  */
  25 package jdk.internal.agent;
  26 
  27 import java.io.BufferedInputStream;
  28 import java.io.File;
  29 import java.io.FileInputStream;
  30 import java.io.FileNotFoundException;
  31 import java.io.IOException;
  32 import java.io.InputStream;
  33 import java.lang.management.ManagementFactory;
  34 import java.lang.reflect.Method;
  35 import java.net.InetAddress;
  36 import java.net.MalformedURLException;
  37 import java.net.UnknownHostException;
  38 import java.security.AccessController;
  39 import java.security.PrivilegedAction;
  40 import java.text.MessageFormat;
  41 import java.util.HashMap;
  42 import java.util.Map;
  43 import java.util.MissingResourceException;
  44 import java.util.Properties;
  45 import java.util.ResourceBundle;
  46 import java.util.ServiceLoader;
  47 import java.util.function.Function;
  48 import java.util.function.Predicate;
  49 
  50 import javax.management.remote.JMXConnectorServer;
  51 import javax.management.remote.JMXServiceURL;
  52 
  53 import static jdk.internal.agent.AgentConfigurationError.*;
  54 import jdk.internal.agent.spi.AgentProvider;
  55 import jdk.internal.vm.VMSupport;
  56 import sun.management.jdp.JdpController;
  57 import sun.management.jdp.JdpException;
  58 import sun.management.jmxremote.ConnectorBootstrap;
  59 
  60 /**
  61  * This Agent is started by the VM when -Dcom.sun.management.snmp or
  62  * -Dcom.sun.management.jmxremote is set. This class will be loaded by the
  63  * system class loader. Also jmx framework could be started by jcmd
  64  */
  65 public class Agent {
  66     /**
  67      * Agent status collector strategy class
  68      */
  69     private static abstract class StatusCollector {
  70         protected static final Map<String, String> DEFAULT_PROPS = new HashMap<>();
  71 
  72         static {
  73             DEFAULT_PROPS.put(ConnectorBootstrap.PropertyNames.PORT,
  74                               ConnectorBootstrap.DefaultValues.PORT);
  75             DEFAULT_PROPS.put(ConnectorBootstrap.PropertyNames.USE_LOCAL_ONLY,
  76                               ConnectorBootstrap.DefaultValues.USE_LOCAL_ONLY);
  77             DEFAULT_PROPS.put(ConnectorBootstrap.PropertyNames.USE_AUTHENTICATION,
  78                               ConnectorBootstrap.DefaultValues.USE_AUTHENTICATION);
  79             DEFAULT_PROPS.put(ConnectorBootstrap.PropertyNames.USE_SSL,
  80                               ConnectorBootstrap.DefaultValues.USE_SSL);
  81             DEFAULT_PROPS.put(ConnectorBootstrap.PropertyNames.USE_REGISTRY_SSL,
  82                               ConnectorBootstrap.DefaultValues.USE_REGISTRY_SSL);
  83             DEFAULT_PROPS.put(ConnectorBootstrap.PropertyNames.SSL_NEED_CLIENT_AUTH,
  84                               ConnectorBootstrap.DefaultValues.SSL_NEED_CLIENT_AUTH);
  85             DEFAULT_PROPS.put(ConnectorBootstrap.PropertyNames.CONFIG_FILE_NAME,
  86                               ConnectorBootstrap.DefaultValues.CONFIG_FILE_NAME);
  87             DEFAULT_PROPS.put(ConnectorBootstrap.PropertyNames.PASSWORD_FILE_NAME,
  88                               ConnectorBootstrap.DefaultValues.PASSWORD_FILE_NAME);
  89             DEFAULT_PROPS.put(ConnectorBootstrap.PropertyNames.ACCESS_FILE_NAME,
  90                               ConnectorBootstrap.DefaultValues.ACCESS_FILE_NAME);
  91 
  92         }
  93 
  94         final protected StringBuilder sb = new StringBuilder();
  95         final public String collect() {
  96             Properties agentProps = VMSupport.getAgentProperties();
  97             String localConnAddr = (String)agentProps.get(LOCAL_CONNECTOR_ADDRESS_PROP);
  98             if (localConnAddr != null || jmxServer != null) {
  99                 addAgentStatus(true);
 100                 appendConnections(localConnAddr);
 101             } else {
 102                 addAgentStatus(false);
 103             }
 104             return sb.toString();
 105         }
 106 
 107         private void appendConnections(String localConnAddr) {
 108             appendConnectionsHeader();
 109             if (localConnAddr != null) {
 110                 try {
 111                     JMXServiceURL u = new JMXServiceURL(localConnAddr);
 112                     addConnection(false, u);
 113                 } catch (MalformedURLException e) {
 114                     // will never happen
 115                 }
 116 
 117             }
 118             if (jmxServer != null) {
 119                 addConnection(true, jmxServer.getAddress());
 120             }
 121             appendConnectionsFooter();
 122         }
 123 
 124         private void addConnection(boolean remote, JMXServiceURL u) {
 125             appendConnectionHeader(remote);
 126             addConnectionDetails(u);
 127             addConfigProperties();
 128             appendConnectionFooter(remote);
 129         }
 130 
 131         private void addConfigProperties() {
 132             appendConfigPropsHeader();
 133 
 134             Properties remoteProps = configProps != null ?
 135                                         configProps : getManagementProperties();
 136             Map<Object, Object> props = new HashMap<>(DEFAULT_PROPS);
 137 
 138             if (remoteProps == null) {
 139                 // local connector only
 140                 String loc_only = System.getProperty(
 141                     ConnectorBootstrap.PropertyNames.USE_LOCAL_ONLY
 142                 );
 143 
 144                 if (loc_only != null &&
 145                     !ConnectorBootstrap.DefaultValues.USE_LOCAL_ONLY.equals(loc_only)) {
 146                     props.put(
 147                         ConnectorBootstrap.PropertyNames.USE_LOCAL_ONLY,
 148                         loc_only
 149                     );
 150                 }
 151             } else {
 152                 props.putAll(remoteProps);
 153             }
 154 
 155             props.entrySet().stream()
 156                 .filter(preprocess(Map.Entry::getKey, StatusCollector::isManagementProp))
 157                 .forEach(this::addConfigProp);
 158 
 159             appendConfigPropsFooter();
 160         }
 161 
 162         private static boolean isManagementProp(Object pName) {
 163             return pName != null && pName.toString().startsWith("com.sun.management.");
 164         }
 165 
 166         private static <T, V> Predicate<T> preprocess(Function<T, V> f, Predicate<V> p) {
 167             return (T t) -> p.test(f.apply(t));
 168         }
 169 
 170         abstract protected void addAgentStatus(boolean enabled);
 171         abstract protected void appendConnectionsHeader();
 172         abstract protected void appendConnectionsFooter();
 173         abstract protected void addConnectionDetails(JMXServiceURL u);
 174         abstract protected void appendConnectionHeader(boolean remote);
 175         abstract protected void appendConnectionFooter(boolean remote);
 176         abstract protected void appendConfigPropsHeader();
 177         abstract protected void appendConfigPropsFooter();
 178         abstract protected void addConfigProp(Map.Entry<?, ?> prop);
 179     }
 180 
 181     /**
 182      * Free-text status collector strategy implementation
 183      */
 184     final private static class TextStatusCollector extends StatusCollector {
 185 
 186         @Override
 187         protected void addAgentStatus(boolean enabled) {
 188             sb.append("Agent: ").append(enabled ? "enabled" : "disabled").append('\n');
 189         }
 190 
 191         @Override
 192         protected void appendConnectionsHeader() {
 193             sb.append('\n');
 194         }
 195 
 196         @Override
 197         protected void addConnectionDetails(JMXServiceURL u) {
 198             sb.append("Protocol       : ").append(u.getProtocol()).append('\n')
 199               .append("Host           : ").append(u.getHost()).append('\n')
 200               .append("URL            : ").append(u).append('\n');
 201         }
 202 
 203         @Override
 204         protected void appendConnectionHeader(boolean remote) {
 205             sb.append("Connection Type: ").append(remote ? "remote" : "local").append('\n');
 206         }
 207 
 208         @Override
 209         protected void appendConfigPropsHeader() {
 210             sb.append("Properties     :\n");
 211         }
 212 
 213         @Override
 214         protected void addConfigProp(Map.Entry<?, ?> prop) {
 215             sb.append("  ").append(prop.getKey()).append(" = ")
 216               .append(prop.getValue());
 217             Object defVal = DEFAULT_PROPS.get(prop.getKey());
 218             if (defVal != null && defVal.equals(prop.getValue())) {
 219                 sb.append(" [default]");
 220             }
 221             sb.append("\n");
 222         }
 223 
 224         @Override
 225         protected void appendConnectionsFooter() {}
 226 
 227         @Override
 228         protected void appendConnectionFooter(boolean remote) {
 229             sb.append('\n');
 230         }
 231 
 232         @Override
 233         protected void appendConfigPropsFooter() {}
 234     }
 235 
 236     // management properties
 237 
 238     private static Properties mgmtProps;
 239     private static ResourceBundle messageRB;
 240     private static final String CONFIG_FILE =
 241             "com.sun.management.config.file";
 242     private static final String SNMP_PORT =
 243             "com.sun.management.snmp.port";
 244     private static final String JMXREMOTE =
 245             "com.sun.management.jmxremote";
 246     private static final String JMXREMOTE_PORT =
 247             "com.sun.management.jmxremote.port";
 248     private static final String RMI_PORT =
 249             "com.sun.management.jmxremote.rmi.port";
 250     private static final String ENABLE_THREAD_CONTENTION_MONITORING =
 251             "com.sun.management.enableThreadContentionMonitoring";
 252     private static final String LOCAL_CONNECTOR_ADDRESS_PROP =
 253             "com.sun.management.jmxremote.localConnectorAddress";
 254     private static final String SNMP_AGENT_NAME =
 255             "SnmpAgent";
 256 
 257     private static final String JDP_DEFAULT_ADDRESS = "224.0.23.178";
 258     private static final int JDP_DEFAULT_PORT = 7095;
 259 
 260     // The only active agent allowed
 261     private static JMXConnectorServer jmxServer = null;
 262     // The properties used to configure the server
 263     private static Properties configProps = null;
 264 
 265     // Parse string com.sun.management.prop=xxx,com.sun.management.prop=yyyy
 266     // and return property set if args is null or empty
 267     // return empty property set
 268     private static Properties parseString(String args) {
 269         Properties argProps = new Properties();
 270         if (args != null && !args.trim().equals("")) {
 271             for (String option : args.split(",")) {
 272                 String s[] = option.split("=", 2);
 273                 String name = s[0].trim();
 274                 String value = (s.length > 1) ? s[1].trim() : "";
 275 
 276                 if (!name.startsWith("com.sun.management.")) {
 277                     error(INVALID_OPTION, name);
 278                 }
 279 
 280                 argProps.setProperty(name, value);
 281             }
 282         }
 283 
 284         return argProps;
 285     }
 286 
 287     // invoked by -javaagent or -Dcom.sun.management.agent.class
 288     public static void premain(String args) throws Exception {
 289         agentmain(args);
 290     }
 291 
 292     // invoked by attach mechanism
 293     public static void agentmain(String args) throws Exception {
 294         if (args == null || args.length() == 0) {
 295             args = JMXREMOTE;           // default to local management
 296         }
 297 
 298         Properties arg_props = parseString(args);
 299 
 300         // Read properties from the config file
 301         Properties config_props = new Properties();
 302         String fname = arg_props.getProperty(CONFIG_FILE);
 303         readConfiguration(fname, config_props);
 304 
 305         // Arguments override config file
 306         config_props.putAll(arg_props);
 307         startAgent(config_props);
 308     }
 309 
 310     // jcmd ManagementAgent.start_local entry point
 311     // Also called due to command-line via startAgent()
 312     private static synchronized void startLocalManagementAgent() {
 313         Properties agentProps = VMSupport.getAgentProperties();
 314 
 315         // start local connector if not started
 316         if (agentProps.get(LOCAL_CONNECTOR_ADDRESS_PROP) == null) {
 317             JMXConnectorServer cs = ConnectorBootstrap.startLocalConnectorServer();
 318             String address = cs.getAddress().toString();
 319             // Add the local connector address to the agent properties
 320             agentProps.put(LOCAL_CONNECTOR_ADDRESS_PROP, address);
 321 
 322             try {
 323                 // export the address to the instrumentation buffer
 324                 ConnectorAddressLink.export(address);
 325             } catch (Exception x) {
 326                 // Connector server started but unable to export address
 327                 // to instrumentation buffer - non-fatal error.
 328                 warning(EXPORT_ADDRESS_FAILED, x.getMessage());
 329             }
 330         }
 331     }
 332 
 333     // jcmd ManagementAgent.start entry point
 334     // This method starts the remote JMX agent and starts neither
 335     // the local JMX agent nor the SNMP agent
 336     // @see #startLocalManagementAgent and also @see #startAgent.
 337     private static synchronized void startRemoteManagementAgent(String args) throws Exception {
 338         if (jmxServer != null) {
 339             throw new RuntimeException(getText(INVALID_STATE, "Agent already started"));
 340         }
 341 
 342         try {
 343             Properties argProps = parseString(args);
 344             configProps = new Properties();
 345 
 346             // Load the management properties from the config file
 347             // if config file is not specified readConfiguration implicitly
 348             // reads <java.home>/conf/management/management.properties
 349 
 350             String fname = System.getProperty(CONFIG_FILE);
 351             readConfiguration(fname, configProps);
 352 
 353             // management properties can be overridden by system properties
 354             // which take precedence
 355             Properties sysProps = System.getProperties();
 356             synchronized (sysProps) {
 357                 configProps.putAll(sysProps);
 358             }
 359 
 360             // if user specifies config file into command line for either
 361             // jcmd utilities or attach command it overrides properties set in
 362             // command line at the time of VM start
 363             String fnameUser = argProps.getProperty(CONFIG_FILE);
 364             if (fnameUser != null) {
 365                 readConfiguration(fnameUser, configProps);
 366             }
 367 
 368             // arguments specified in command line of jcmd utilities
 369             // override both system properties and one set by config file
 370             // specified in jcmd command line
 371             configProps.putAll(argProps);
 372 
 373             // jcmd doesn't allow to change ThreadContentionMonitoring, but user
 374             // can specify this property inside config file, so enable optional
 375             // monitoring functionality if this property is set
 376             final String enableThreadContentionMonitoring =
 377                     configProps.getProperty(ENABLE_THREAD_CONTENTION_MONITORING);
 378 
 379             if (enableThreadContentionMonitoring != null) {
 380                 ManagementFactory.getThreadMXBean().
 381                         setThreadContentionMonitoringEnabled(true);
 382             }
 383 
 384             String jmxremotePort = configProps.getProperty(JMXREMOTE_PORT);
 385             if (jmxremotePort != null) {
 386                 jmxServer = ConnectorBootstrap.
 387                         startRemoteConnectorServer(jmxremotePort, configProps);
 388 
 389                 startDiscoveryService(configProps);
 390             } else {
 391                 throw new AgentConfigurationError(INVALID_JMXREMOTE_PORT, "No port specified");
 392             }
 393         } catch (JdpException e) {
 394             error(e);
 395         } catch (AgentConfigurationError err) {
 396             error(err.getError(), err.getParams());
 397         }
 398     }
 399 
 400     private static synchronized void stopRemoteManagementAgent() throws Exception {
 401 
 402         JdpController.stopDiscoveryService();
 403 
 404         if (jmxServer != null) {
 405             ConnectorBootstrap.unexportRegistry();
 406             ConnectorAddressLink.unexportRemote();
 407 
 408             // Attempt to stop already stopped agent
 409             // Don't cause any errors.
 410             jmxServer.stop();
 411             jmxServer = null;
 412             configProps = null;
 413         }
 414     }
 415 
 416     private static synchronized String getManagementAgentStatus() throws Exception {
 417         return new TextStatusCollector().collect();
 418     }
 419 
 420     private static void startAgent(Properties props) throws Exception {
 421         String snmpPort = props.getProperty(SNMP_PORT);
 422         String jmxremote = props.getProperty(JMXREMOTE);
 423         String jmxremotePort = props.getProperty(JMXREMOTE_PORT);
 424 
 425         // Enable optional monitoring functionality if requested
 426         final String enableThreadContentionMonitoring =
 427                 props.getProperty(ENABLE_THREAD_CONTENTION_MONITORING);
 428         if (enableThreadContentionMonitoring != null) {
 429             ManagementFactory.getThreadMXBean().
 430                     setThreadContentionMonitoringEnabled(true);
 431         }
 432 
 433         try {
 434             if (snmpPort != null) {
 435                 loadSnmpAgent(props);
 436             }
 437 
 438             /*
 439              * If the jmxremote.port property is set then we start the
 440              * RMIConnectorServer for remote M&M.
 441              *
 442              * If the jmxremote or jmxremote.port properties are set then
 443              * we start a RMIConnectorServer for local M&M. The address
 444              * of this "local" server is exported as a counter to the jstat
 445              * instrumentation buffer.
 446              */
 447             if (jmxremote != null || jmxremotePort != null) {
 448                 if (jmxremotePort != null) {
 449                     jmxServer = ConnectorBootstrap.
 450                             startRemoteConnectorServer(jmxremotePort, props);
 451                     startDiscoveryService(props);
 452                 }
 453                 startLocalManagementAgent();
 454             }
 455 
 456         } catch (AgentConfigurationError e) {
 457             error(e.getError(), e.getParams());
 458         } catch (Exception e) {
 459             error(e);
 460         }
 461     }
 462 
 463     private static void startDiscoveryService(Properties props)
 464             throws IOException, JdpException {
 465         // Start discovery service if requested
 466         String discoveryPort = props.getProperty("com.sun.management.jdp.port");
 467         String discoveryAddress = props.getProperty("com.sun.management.jdp.address");
 468         String discoveryShouldStart = props.getProperty("com.sun.management.jmxremote.autodiscovery");
 469 
 470         // Decide whether we should start autodicovery service.
 471         // To start autodiscovery following conditions should be met:
 472         // autodiscovery==true OR (autodicovery==null AND jdp.port != NULL)
 473 
 474         boolean shouldStart = false;
 475         if (discoveryShouldStart == null){
 476             shouldStart = (discoveryPort != null);
 477         }
 478         else{
 479             try{
 480                shouldStart = Boolean.parseBoolean(discoveryShouldStart);
 481             } catch (NumberFormatException e) {
 482                 throw new AgentConfigurationError(AGENT_EXCEPTION, "Couldn't parse autodiscovery argument");
 483             }
 484         }
 485 
 486         if (shouldStart) {
 487             // port and address are required arguments and have no default values
 488             InetAddress address;
 489             try {
 490                 address = (discoveryAddress == null) ?
 491                         InetAddress.getByName(JDP_DEFAULT_ADDRESS) : InetAddress.getByName(discoveryAddress);
 492             } catch (UnknownHostException e) {
 493                 throw new AgentConfigurationError(AGENT_EXCEPTION, e, "Unable to broadcast to requested address");
 494             }
 495 
 496             int port = JDP_DEFAULT_PORT;
 497             if (discoveryPort != null) {
 498                try {
 499                   port = Integer.parseInt(discoveryPort);
 500                } catch (NumberFormatException e) {
 501                  throw new AgentConfigurationError(AGENT_EXCEPTION, "Couldn't parse JDP port argument");
 502                }
 503             }
 504 
 505             // Get service URL to broadcast it
 506             Map<String,String> remoteProps = ConnectorAddressLink.importRemoteFrom(0);
 507             String jmxUrlStr = remoteProps.get("sun.management.JMXConnectorServer.0.remoteAddress");
 508 
 509             String instanceName = props.getProperty("com.sun.management.jdp.name");
 510 
 511             JdpController.startDiscoveryService(address, port, instanceName, jmxUrlStr);
 512         }
 513     }
 514 
 515     public static Properties loadManagementProperties() {
 516         Properties props = new Properties();
 517 
 518         // Load the management properties from the config file
 519 
 520         String fname = System.getProperty(CONFIG_FILE);
 521         readConfiguration(fname, props);
 522 
 523         // management properties can be overridden by system properties
 524         // which take precedence
 525         Properties sysProps = System.getProperties();
 526         synchronized (sysProps) {
 527             props.putAll(sysProps);
 528         }
 529 
 530         return props;
 531     }
 532 
 533     public static synchronized Properties getManagementProperties() {
 534         if (mgmtProps == null) {
 535             String configFile = System.getProperty(CONFIG_FILE);
 536             String snmpPort = System.getProperty(SNMP_PORT);
 537             String jmxremote = System.getProperty(JMXREMOTE);
 538             String jmxremotePort = System.getProperty(JMXREMOTE_PORT);
 539 
 540             if (configFile == null && snmpPort == null
 541                     && jmxremote == null && jmxremotePort == null) {
 542                 // return if out-of-the-management option is not specified
 543                 return null;
 544             }
 545             mgmtProps = loadManagementProperties();
 546         }
 547         return mgmtProps;
 548     }
 549 
 550     private static void loadSnmpAgent(Properties props) {
 551         /*
 552          * Load the jdk.snmp service
 553          */
 554         AgentProvider provider = AccessController.doPrivileged(
 555             (PrivilegedAction<AgentProvider>) () -> {
 556                 for (AgentProvider aProvider : ServiceLoader.loadInstalled(AgentProvider.class)) {
 557                     if (aProvider.getName().equals(SNMP_AGENT_NAME))
 558                         return aProvider;
 559                 }
 560                 return null;
 561             },  null
 562         );
 563 
 564         if (provider != null) {
 565             provider.startAgent(props);
 566          } else { // snmp runtime doesn't exist - initialization fails
 567             throw new UnsupportedOperationException("Unsupported management property: " + SNMP_PORT);
 568         }
 569     }
 570 
 571     // read config file and initialize the properties
 572     private static void readConfiguration(String fname, Properties p) {
 573         if (fname == null) {
 574             String home = System.getProperty("java.home");
 575             if (home == null) {
 576                 throw new Error("Can't find java.home ??");
 577             }
 578             StringBuilder defaultFileName = new StringBuilder(home);
 579             defaultFileName.append(File.separator).append("conf");
 580             defaultFileName.append(File.separator).append("management");
 581             defaultFileName.append(File.separator).append("management.properties");
 582             // Set file name
 583             fname = defaultFileName.toString();
 584         }
 585         final File configFile = new File(fname);
 586         if (!configFile.exists()) {
 587             error(CONFIG_FILE_NOT_FOUND, fname);
 588         }
 589 
 590         InputStream in = null;
 591         try {
 592             in = new FileInputStream(configFile);
 593             BufferedInputStream bin = new BufferedInputStream(in);
 594             p.load(bin);
 595         } catch (FileNotFoundException e) {
 596             error(CONFIG_FILE_OPEN_FAILED, e.getMessage());
 597         } catch (IOException e) {
 598             error(CONFIG_FILE_OPEN_FAILED, e.getMessage());
 599         } catch (SecurityException e) {
 600             error(CONFIG_FILE_ACCESS_DENIED, fname);
 601         } finally {
 602             if (in != null) {
 603                 try {
 604                     in.close();
 605                 } catch (IOException e) {
 606                     error(CONFIG_FILE_CLOSE_FAILED, fname);
 607                 }
 608             }
 609         }
 610     }
 611 
 612     public static void startAgent() throws Exception {
 613         String prop = System.getProperty("com.sun.management.agent.class");
 614 
 615         // -Dcom.sun.management.agent.class not set so read management
 616         // properties and start agent
 617         if (prop == null) {
 618             // initialize management properties
 619             Properties props = getManagementProperties();
 620             if (props != null) {
 621                 startAgent(props);
 622             }
 623             return;
 624         }
 625 
 626         // -Dcom.sun.management.agent.class=<agent classname>:<agent args>
 627         String[] values = prop.split(":");
 628         if (values.length < 1 || values.length > 2) {
 629             error(AGENT_CLASS_INVALID, "\"" + prop + "\"");
 630         }
 631         String cname = values[0];
 632         String args = (values.length == 2 ? values[1] : null);
 633 
 634         if (cname == null || cname.length() == 0) {
 635             error(AGENT_CLASS_INVALID, "\"" + prop + "\"");
 636         }
 637 
 638         if (cname != null) {
 639             try {
 640                 // Instantiate the named class.
 641                 // invoke the premain(String args) method
 642                 Class<?> clz = ClassLoader.getSystemClassLoader().loadClass(cname);
 643                 Method premain = clz.getMethod("premain",
 644                         new Class<?>[]{String.class});
 645                 premain.invoke(null, /* static */
 646                         new Object[]{args});
 647             } catch (ClassNotFoundException ex) {
 648                 error(AGENT_CLASS_NOT_FOUND, "\"" + cname + "\"");
 649             } catch (NoSuchMethodException ex) {
 650                 error(AGENT_CLASS_PREMAIN_NOT_FOUND, "\"" + cname + "\"");
 651             } catch (SecurityException ex) {
 652                 error(AGENT_CLASS_ACCESS_DENIED);
 653             } catch (Exception ex) {
 654                 String msg = (ex.getCause() == null
 655                         ? ex.getMessage()
 656                         : ex.getCause().getMessage());
 657                 error(AGENT_CLASS_FAILED, msg);
 658             }
 659         }
 660     }
 661 
 662     public static void error(String key) {
 663         String keyText = getText(key);
 664         System.err.print(getText("agent.err.error") + ": " + keyText);
 665         throw new RuntimeException(keyText);
 666     }
 667 
 668     public static void error(String key, String[] params) {
 669         if (params == null || params.length == 0) {
 670             error(key);
 671         } else {
 672             StringBuilder message = new StringBuilder(params[0]);
 673             for (int i = 1; i < params.length; i++) {
 674                 message.append(' ').append(params[i]);
 675             }
 676             error(key, message.toString());
 677         }
 678     }
 679 
 680     public static void error(String key, String message) {
 681         String keyText = getText(key);
 682         System.err.print(getText("agent.err.error") + ": " + keyText);
 683         System.err.println(": " + message);
 684         throw new RuntimeException(keyText + ": " + message);
 685     }
 686 
 687     public static void error(Exception e) {
 688         e.printStackTrace();
 689         System.err.println(getText(AGENT_EXCEPTION) + ": " + e.toString());
 690         throw new RuntimeException(e);
 691     }
 692 
 693     public static void warning(String key, String message) {
 694         System.err.print(getText("agent.err.warning") + ": " + getText(key));
 695         System.err.println(": " + message);
 696     }
 697 
 698     private static void initResource() {
 699         try {
 700             messageRB =
 701                 ResourceBundle.getBundle("jdk.internal.agent.resources.agent");
 702         } catch (MissingResourceException e) {
 703             throw new Error("Fatal: Resource for management agent is missing");
 704         }
 705     }
 706 
 707     public static String getText(String key) {
 708         if (messageRB == null) {
 709             initResource();
 710         }
 711         try {
 712             return messageRB.getString(key);
 713         } catch (MissingResourceException e) {
 714             return "Missing management agent resource bundle: key = \"" + key + "\"";
 715         }
 716     }
 717 
 718     public static String getText(String key, String... args) {
 719         if (messageRB == null) {
 720             initResource();
 721         }
 722         String format = messageRB.getString(key);
 723         if (format == null) {
 724             format = "missing resource key: key = \"" + key + "\", "
 725                     + "arguments = \"{0}\", \"{1}\", \"{2}\"";
 726         }
 727         return MessageFormat.format(format, (Object[]) args);
 728     }
 729 }