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}