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