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}