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}