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