001/* 002 * The contents of this file are subject to the terms of the Common Development and 003 * Distribution License (the License). You may not use this file except in compliance with the 004 * License. 005 * 006 * You can obtain a copy of the License at legal/CDDLv1.0.txt. See the License for the 007 * specific language governing permission and limitations under the License. 008 * 009 * When distributing Covered Software, include this CDDL Header Notice in each file and include 010 * the License file at legal/CDDLv1.0.txt. If applicable, add the following below the CDDL 011 * Header, with the fields enclosed by brackets [] replaced by your own identifying 012 * information: "Portions Copyright [year] [name of copyright owner]". 013 * 014 * Portions Copyright 2006-2007-2008 Sun Microsystems, Inc. 015 * Portions Copyright 2013-2016 ForgeRock AS. 016 */ 017package org.opends.server.config; 018 019import java.util.ArrayList; 020import java.util.Iterator; 021import java.util.List; 022import java.util.Map; 023import java.util.Set; 024import java.util.concurrent.CopyOnWriteArrayList; 025 026import javax.management.Attribute; 027import javax.management.AttributeList; 028import javax.management.AttributeNotFoundException; 029import javax.management.DynamicMBean; 030import javax.management.InvalidAttributeValueException; 031import javax.management.MBeanAttributeInfo; 032import javax.management.MBeanConstructorInfo; 033import javax.management.MBeanException; 034import javax.management.MBeanInfo; 035import javax.management.MBeanNotificationInfo; 036import javax.management.MBeanOperationInfo; 037import javax.management.MBeanServer; 038import javax.management.ObjectName; 039 040import org.forgerock.i18n.LocalizableMessage; 041import org.forgerock.i18n.slf4j.LocalizedLogger; 042import org.forgerock.opendj.ldap.ByteString; 043import org.forgerock.opendj.ldap.DN; 044import org.forgerock.opendj.ldap.ResultCode; 045import org.forgerock.opendj.ldap.SearchScope; 046import org.forgerock.opendj.ldap.schema.AttributeType; 047import org.forgerock.util.Utils; 048import org.opends.server.admin.std.server.MonitorProviderCfg; 049import org.opends.server.api.AlertGenerator; 050import org.opends.server.api.ClientConnection; 051import org.opends.server.api.DirectoryServerMBean; 052import org.opends.server.api.MonitorProvider; 053import org.opends.server.core.DirectoryServer; 054import org.opends.server.protocols.internal.InternalClientConnection; 055import org.opends.server.protocols.internal.InternalSearchOperation; 056import org.opends.server.protocols.internal.SearchRequest; 057import org.opends.server.protocols.jmx.Credential; 058import org.opends.server.protocols.jmx.JmxClientConnection; 059import org.opends.server.types.DirectoryException; 060 061import static org.opends.messages.ConfigMessages.*; 062import static org.opends.server.protocols.internal.Requests.*; 063import static org.opends.server.util.CollectionUtils.*; 064import static org.opends.server.util.ServerConstants.*; 065import static org.opends.server.util.StaticUtils.*; 066 067/** 068 * This class defines a JMX MBean that can be registered with the Directory 069 * Server to provide monitoring and statistical information, provide read and/or 070 * read-write access to the configuration, and provide notifications and alerts 071 * if a significant event or severe/fatal error occurs. 072 */ 073@org.opends.server.types.PublicAPI( 074 stability=org.opends.server.types.StabilityLevel.VOLATILE, 075 mayInstantiate=true, 076 mayExtend=false, 077 mayInvoke=true) 078public final class JMXMBean 079 implements DynamicMBean, DirectoryServerMBean 080{ 081 private static final LocalizedLogger logger = LocalizedLogger.getLoggerForThisClass(); 082 083 /** 084 * The fully-qualified name of this class. 085 */ 086 private static final String CLASS_NAME = "org.opends.server.config.JMXMBean"; 087 088 089 090 /** The set of alert generators for this MBean. */ 091 private List<AlertGenerator> alertGenerators; 092 093 /** The set of monitor providers for this MBean. */ 094 private List<MonitorProvider<? extends MonitorProviderCfg>> monitorProviders; 095 096 /** The DN of the configuration entry with which this MBean is associated. */ 097 private DN configEntryDN; 098 099 /** The object name for this MBean. */ 100 private ObjectName objectName; 101 102 103 /** 104 * Creates a JMX object name string based on a DN. 105 * 106 * @param configEntryDN The DN of the configuration entry with which 107 * this ObjectName is associated. 108 * 109 * @return The string representation of the JMX Object Name 110 * associated with the input DN. 111 */ 112 public static String getJmxName (DN configEntryDN) 113 { 114 try 115 { 116 String typeStr = null; 117 String dnString = configEntryDN.toString(); 118 if (dnString != null && dnString.length() != 0) 119 { 120 StringBuilder buffer = new StringBuilder(dnString.length()); 121 String rdns[] = dnString.replace(',', ';').split(";"); 122 for (int j = rdns.length - 1; j >= 0; j--) 123 { 124 int rdnIndex = rdns.length - j; 125 buffer.append(",Rdn").append(rdnIndex).append("=") ; 126 for (int i = 0; i < rdns[j].length(); i++) 127 { 128 char c = rdns[j].charAt(i); 129 if (isAlpha(c) || isDigit(c)) 130 { 131 buffer.append(c); 132 } else 133 { 134 switch (c) 135 { 136 case ' ': 137 buffer.append("_"); 138 break; 139 case '=': 140 buffer.append("-"); 141 } 142 } 143 } 144 } 145 146 typeStr = buffer.toString(); 147 } 148 149 return MBEAN_BASE_DOMAIN + ":" + "Name=rootDSE" + typeStr; 150 } catch (Exception e) 151 { 152 logger.traceException(e); 153 logger.error(ERR_CONFIG_JMX_CANNOT_REGISTER_MBEAN, configEntryDN, e); 154 return null; 155 } 156 } 157 158 /** 159 * Creates a new dynamic JMX MBean for use with the Directory Server. 160 * 161 * @param configEntryDN The DN of the configuration entry with which this 162 * MBean is associated. 163 */ 164 public JMXMBean(DN configEntryDN) 165 { 166 this.configEntryDN = configEntryDN; 167 168 alertGenerators = new CopyOnWriteArrayList<>(); 169 monitorProviders = new CopyOnWriteArrayList<>(); 170 171 MBeanServer mBeanServer = DirectoryServer.getJMXMBeanServer(); 172 if (mBeanServer != null) 173 { 174 try 175 { 176 objectName = new ObjectName(getJmxName(configEntryDN)) ; 177 178 try 179 { 180 if(mBeanServer.isRegistered(objectName)) 181 { 182 mBeanServer.unregisterMBean(objectName); 183 } 184 } 185 catch(Exception e) 186 { 187 logger.traceException(e); 188 } 189 190 mBeanServer.registerMBean(this, objectName); 191 192 } 193 catch (Exception e) 194 { 195 logger.traceException(e); 196 logger.error(ERR_CONFIG_JMX_CANNOT_REGISTER_MBEAN, configEntryDN, e); 197 } 198 } 199 } 200 201 202 203 /** 204 * Retrieves the JMX object name for this JMX MBean. 205 * 206 * @return The JMX object name for this JMX MBean. 207 */ 208 @Override 209 public ObjectName getObjectName() 210 { 211 return objectName; 212 } 213 214 215 216 /** 217 * Retrieves the set of alert generators for this JMX MBean. 218 * 219 * @return The set of alert generators for this JMX MBean. 220 */ 221 public List<AlertGenerator> getAlertGenerators() 222 { 223 return alertGenerators; 224 } 225 226 227 228 /** 229 * Adds the provided alert generator to the set of alert generators associated 230 * with this JMX MBean. 231 * 232 * @param generator The alert generator to add to the set of alert 233 * generators for this JMX MBean. 234 */ 235 public void addAlertGenerator(AlertGenerator generator) 236 { 237 synchronized (alertGenerators) 238 { 239 if (! alertGenerators.contains(generator)) 240 { 241 alertGenerators.add(generator); 242 } 243 } 244 } 245 246 247 248 /** 249 * Removes the provided alert generator from the set of alert generators 250 * associated with this JMX MBean. 251 * 252 * @param generator The alert generator to remove from the set of alert 253 * generators for this JMX MBean. 254 * 255 * @return <CODE>true</CODE> if the alert generator was removed, or 256 * <CODE>false</CODE> if it was not associated with this MBean. 257 */ 258 public boolean removeAlertGenerator(AlertGenerator generator) 259 { 260 synchronized (alertGenerators) 261 { 262 return alertGenerators.remove(generator); 263 } 264 } 265 266 /** 267 * Retrieves the set of monitor providers associated with this JMX MBean. 268 * 269 * @return The set of monitor providers associated with this JMX MBean. 270 */ 271 public List<MonitorProvider<? extends MonitorProviderCfg>> 272 getMonitorProviders() 273 { 274 return monitorProviders; 275 } 276 277 278 279 /** 280 * Adds the given monitor provider to the set of components associated with 281 * this JMX MBean. 282 * 283 * @param component The component to add to the set of monitor providers 284 * for this JMX MBean. 285 */ 286 public void addMonitorProvider(MonitorProvider<? extends MonitorProviderCfg> 287 component) 288 { 289 synchronized (monitorProviders) 290 { 291 if (! monitorProviders.contains(component)) 292 { 293 monitorProviders.add(component); 294 } 295 } 296 } 297 298 299 300 /** 301 * Removes the given monitor provider from the set of components associated 302 * with this JMX MBean. 303 * 304 * @param component The component to remove from the set of monitor 305 * providers for this JMX MBean. 306 * 307 * @return <CODE>true</CODE> if the specified component was successfully 308 * removed, or <CODE>false</CODE> if not. 309 */ 310 public boolean removeMonitorProvider(MonitorProvider<?> component) 311 { 312 synchronized (monitorProviders) 313 { 314 return monitorProviders.remove(component); 315 } 316 } 317 318 319 320 /** 321 * Retrieves the specified configuration attribute. 322 * 323 * @param name The name of the configuration attribute to retrieve. 324 * 325 * @return The specified configuration attribute, or <CODE>null</CODE> if 326 * there is no such attribute. 327 */ 328 private Attribute getJmxAttribute(String name) 329 { 330 // It's possible that this is a monitor attribute rather than a configurable 331 // one. Check all of those. 332 AttributeType attrType = DirectoryServer.getAttributeType(name); 333 for (MonitorProvider<? extends MonitorProviderCfg> monitor : monitorProviders) 334 { 335 for (org.opends.server.types.Attribute a : monitor.getMonitorData()) 336 { 337 if (attrType.equals(a.getAttributeDescription().getAttributeType())) 338 { 339 if (a.isEmpty()) 340 { 341 continue; 342 } 343 344 Iterator<ByteString> iterator = a.iterator(); 345 ByteString value = iterator.next(); 346 347 if (iterator.hasNext()) 348 { 349 List<String> stringValues = newArrayList(value.toString()); 350 while (iterator.hasNext()) 351 { 352 value = iterator.next(); 353 stringValues.add(value.toString()); 354 } 355 356 String[] valueArray = stringValues.toArray(new String[stringValues.size()]); 357 return new Attribute(name, valueArray); 358 } 359 else 360 { 361 return new Attribute(name, value.toString()); 362 } 363 } 364 } 365 } 366 367 return null; 368 } 369 370 371 372 /** 373 * Obtain the value of a specific attribute of the Dynamic MBean. 374 * 375 * @param attributeName The name of the attribute to be retrieved. 376 * 377 * @return The requested attribute. 378 * 379 * @throws AttributeNotFoundException If the specified attribute is not 380 * associated with this MBean. 381 */ 382 @Override 383 public Attribute getAttribute(String attributeName) 384 throws AttributeNotFoundException 385 { 386 // Get the jmx Client connection 387 ClientConnection clientConnection = getClientConnection(); 388 if (clientConnection == null) 389 { 390 return null; 391 } 392 393 // prepare the ldap search 394 try 395 { 396 // Perform the Ldap operation for 397 // - ACI Check 398 // - Loggin purpose 399 InternalSearchOperation op = searchMBeanConfigEntry(clientConnection); 400 // BUG : op may be null 401 ResultCode rc = op.getResultCode(); 402 if (rc != ResultCode.SUCCESS) { 403 LocalizableMessage message = ERR_CONFIG_JMX_CANNOT_GET_ATTRIBUTE. 404 get(attributeName, configEntryDN, op.getErrorMessage()); 405 throw new AttributeNotFoundException(message.toString()); 406 } 407 408 return getJmxAttribute(attributeName); 409 } 410 catch (AttributeNotFoundException e) 411 { 412 throw e; 413 } 414 catch (Exception e) 415 { 416 logger.traceException(e); 417 418 LocalizableMessage message = ERR_CONFIG_JMX_ATTR_NO_ATTR.get(configEntryDN, attributeName); 419 logger.error(message); 420 throw new AttributeNotFoundException(message.toString()); 421 } 422 } 423 424 /** 425 * Set the value of a specific attribute of the Dynamic MBean. In this case, 426 * it will always throw {@code InvalidAttributeValueException} because setting 427 * attribute values over JMX is currently not allowed. 428 * 429 * @param attribute The identification of the attribute to be set and the 430 * value it is to be set to. 431 * 432 * @throws AttributeNotFoundException If the specified attribute is not 433 * associated with this MBean. 434 * 435 * @throws InvalidAttributeValueException If the provided value is not 436 * acceptable for this MBean. 437 */ 438 @Override 439 public void setAttribute(javax.management.Attribute attribute) 440 throws AttributeNotFoundException, InvalidAttributeValueException 441 { 442 throw new InvalidAttributeValueException(); 443 } 444 445 /** 446 * Get the values of several attributes of the Dynamic MBean. 447 * 448 * @param attributes A list of the attributes to be retrieved. 449 * 450 * @return The list of attributes retrieved. 451 */ 452 @Override 453 public AttributeList getAttributes(String[] attributes) 454 { 455 // Get the jmx Client connection 456 ClientConnection clientConnection = getClientConnection(); 457 if (clientConnection == null) 458 { 459 return null; 460 } 461 462 // Perform the Ldap operation for 463 // - ACI Check 464 // - Loggin purpose 465 InternalSearchOperation op = searchMBeanConfigEntry(clientConnection); 466 if (op == null) 467 { 468 return null; 469 } 470 471 ResultCode rc = op.getResultCode(); 472 if (rc != ResultCode.SUCCESS) 473 { 474 return null; 475 } 476 477 478 AttributeList attrList = new AttributeList(attributes.length); 479 for (String name : attributes) 480 { 481 try 482 { 483 Attribute attr = getJmxAttribute(name); 484 if (attr != null) 485 { 486 attrList.add(attr); 487 continue; 488 } 489 } 490 catch (Exception e) 491 { 492 logger.traceException(e); 493 } 494 Attribute attr = getJmxAttribute(name); 495 if (attr != null) 496 { 497 attrList.add(attr); 498 } 499 } 500 501 return attrList; 502 } 503 504 private InternalSearchOperation searchMBeanConfigEntry(ClientConnection clientConnection) 505 { 506 SearchRequest request = newSearchRequest(configEntryDN, SearchScope.BASE_OBJECT); 507 if (clientConnection instanceof JmxClientConnection) { 508 return ((JmxClientConnection) clientConnection).processSearch(request); 509 } 510 else if (clientConnection instanceof InternalClientConnection) { 511 return ((InternalClientConnection) clientConnection).processSearch(request); 512 } 513 return null; 514 } 515 516 /** 517 * Sets the values of several attributes of the Dynamic MBean. 518 * 519 * @param attributes A list of attributes: The identification of the 520 * attributes to be set and the values they are to be set 521 * to. 522 * 523 * @return The list of attributes that were set with their new values. In 524 * this case, the list will always be empty because we do not support 525 * setting attribute values over JMX. 526 */ 527 @Override 528 public AttributeList setAttributes(AttributeList attributes) 529 { 530 return new AttributeList(); 531 } 532 533 534 535 /** 536 * Allows an action to be invoked on the Dynamic MBean. 537 * 538 * @param actionName The name of the action to be invoked. 539 * @param params An array containing the parameters to be set when the 540 * action is invoked. 541 * @param signature An array containing the signature of the action. The 542 * class objects will be loaded through the same class 543 * loader as the one used for loading the MBean on which 544 * action is invoked. 545 * 546 * @return The object returned by the action, which represents the result of 547 * invoking the action on the MBean specified. 548 * 549 * @throws MBeanException If a problem is encountered while invoking the 550 * method. 551 */ 552 @Override 553 public Object invoke(String actionName, Object[] params, String[] signature) 554 throws MBeanException 555 { 556 // If we've gotten here, then there is no such method so throw an exception. 557 StringBuilder buffer = new StringBuilder(); 558 buffer.append(actionName); 559 buffer.append("("); 560 Utils.joinAsString(", ", (Object[]) signature); 561 buffer.append(")"); 562 563 LocalizableMessage message = ERR_CONFIG_JMX_NO_METHOD.get(buffer, configEntryDN); 564 throw new MBeanException( 565 new DirectoryException(ResultCode.NO_SUCH_OPERATION, message)); 566 } 567 568 569 570 /** 571 * Provides the exposed attributes and actions of the Dynamic MBean using an 572 * MBeanInfo object. 573 * 574 * @return An instance of <CODE>MBeanInfo</CODE> allowing all attributes and 575 * actions exposed by this Dynamic MBean to be retrieved. 576 */ 577 @Override 578 public MBeanInfo getMBeanInfo() 579 { 580 ClientConnection clientConnection = getClientConnection(); 581 if (clientConnection == null) 582 { 583 return new MBeanInfo(CLASS_NAME, null, null, null, null, null); 584 } 585 586 List<MBeanAttributeInfo> attrs = new ArrayList<>(); 587 for (MonitorProvider<? extends MonitorProviderCfg> monitor : monitorProviders) 588 { 589 for (org.opends.server.types.Attribute a : monitor.getMonitorData()) 590 { 591 attrs.add(new MBeanAttributeInfo(a.getName(), String.class.getName(), 592 null, true, false, false)); 593 } 594 } 595 596 MBeanAttributeInfo[] mBeanAttributes = attrs.toArray(new MBeanAttributeInfo[attrs.size()]); 597 598 List<MBeanNotificationInfo> notifications = new ArrayList<>(); 599 for (AlertGenerator generator : alertGenerators) 600 { 601 String className = generator.getClassName(); 602 603 Map<String, String> alerts = generator.getAlerts(); 604 for (String type : alerts.keySet()) 605 { 606 String[] types = { type }; 607 String description = alerts.get(type); 608 notifications.add(new MBeanNotificationInfo(types, className, description)); 609 } 610 } 611 612 MBeanConstructorInfo[] mBeanConstructors = new MBeanConstructorInfo[0]; 613 MBeanOperationInfo[] mBeanOperations = new MBeanOperationInfo[0]; 614 615 MBeanNotificationInfo[] mBeanNotifications = new MBeanNotificationInfo[notifications.size()]; 616 notifications.toArray(mBeanNotifications); 617 618 return new MBeanInfo(CLASS_NAME, 619 "Configurable Attributes for " + configEntryDN, 620 mBeanAttributes, mBeanConstructors, mBeanOperations, 621 mBeanNotifications); 622 } 623 624 /** 625 * Get the client JMX connection to use. Returns null if an Exception is 626 * caught or if the AccessControlContext subject is null. 627 * 628 * @return The JmxClientConnection. 629 */ 630 private ClientConnection getClientConnection() 631 { 632 ClientConnection clientConnection = null; 633 java.security.AccessControlContext acc = java.security.AccessController 634 .getContext(); 635 try 636 { 637 javax.security.auth.Subject subject = javax.security.auth.Subject 638 .getSubject(acc); 639 if (subject != null) 640 { 641 Set<?> privateCreds = subject.getPrivateCredentials(Credential.class); 642 clientConnection = ((Credential) privateCreds.iterator().next()) 643 .getClientConnection(); 644 } 645 } 646 catch (Exception e) 647 { 648 } 649 return clientConnection; 650 } 651}