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 * Copyright 2006-2010 Sun Microsystems, Inc.
015 * Portions Copyright 2012-2016 ForgeRock AS.
016 */
017package org.opends.server.backends;
018
019import static org.forgerock.util.Reject.*;
020import static org.opends.messages.BackendMessages.*;
021import static org.opends.messages.ConfigMessages.*;
022import static org.opends.server.config.ConfigConstants.*;
023import static org.opends.server.util.CollectionUtils.*;
024import static org.opends.server.util.ServerConstants.*;
025import static org.opends.server.util.StaticUtils.*;
026
027import java.util.ArrayList;
028import java.util.Collection;
029import java.util.Collections;
030import java.util.HashMap;
031import java.util.LinkedHashMap;
032import java.util.List;
033import java.util.Map;
034import java.util.NavigableMap;
035import java.util.Set;
036import java.util.TreeMap;
037
038import org.forgerock.i18n.LocalizableMessage;
039import org.forgerock.i18n.slf4j.LocalizedLogger;
040import org.forgerock.opendj.config.server.ConfigChangeResult;
041import org.forgerock.opendj.config.server.ConfigException;
042import org.forgerock.opendj.ldap.AVA;
043import org.forgerock.opendj.ldap.ByteString;
044import org.forgerock.opendj.ldap.ConditionResult;
045import org.forgerock.opendj.ldap.DN;
046import org.forgerock.opendj.ldap.RDN;
047import org.forgerock.opendj.ldap.ResultCode;
048import org.forgerock.opendj.ldap.SearchScope;
049import org.forgerock.opendj.ldap.schema.AttributeType;
050import org.forgerock.util.Reject;
051import org.opends.server.admin.server.ConfigurationChangeListener;
052import org.opends.server.admin.std.server.MonitorBackendCfg;
053import org.opends.server.api.Backend;
054import org.opends.server.api.MonitorData;
055import org.opends.server.api.MonitorProvider;
056import org.opends.server.config.ConfigEntry;
057import org.opends.server.core.AddOperation;
058import org.opends.server.core.DeleteOperation;
059import org.opends.server.core.DirectoryServer;
060import org.opends.server.core.ModifyDNOperation;
061import org.opends.server.core.ModifyOperation;
062import org.opends.server.core.SearchOperation;
063import org.opends.server.core.ServerContext;
064import org.opends.server.types.Attribute;
065import org.opends.server.types.Attributes;
066import org.opends.server.types.BackupConfig;
067import org.opends.server.types.BackupDirectory;
068import org.opends.server.types.DirectoryException;
069import org.opends.server.types.Entry;
070import org.opends.server.types.IndexType;
071import org.opends.server.types.InitializationException;
072import org.opends.server.types.LDIFExportConfig;
073import org.opends.server.types.LDIFImportConfig;
074import org.opends.server.types.LDIFImportResult;
075import org.opends.server.types.ObjectClass;
076import org.opends.server.types.RestoreConfig;
077import org.opends.server.types.SearchFilter;
078import org.opends.server.util.DynamicConstants;
079import org.opends.server.util.LDIFWriter;
080import org.opends.server.util.TimeThread;
081
082/**
083 * This class defines a backend to hold Directory Server monitor entries. It
084 * will not actually store anything, but upon request will retrieve the
085 * requested monitor and dynamically generate the associated entry. It will also
086 * construct a base monitor entry with some useful server-wide data.
087 */
088public class MonitorBackend extends Backend<MonitorBackendCfg> implements
089    ConfigurationChangeListener<MonitorBackendCfg>
090{
091  private static final LocalizedLogger logger = LocalizedLogger.getLoggerForThisClass();
092
093  /** The set of user-defined attributes that will be included in the base monitor entry.   */
094  private ArrayList<Attribute> userDefinedAttributes;
095  /** The set of objectclasses that will be used in monitor entries. */
096  private final HashMap<ObjectClass, String> monitorObjectClasses = new LinkedHashMap<>(2);
097  /** The DN of the configuration entry for this backend. */
098  private DN configEntryDN;
099  /** The current configuration state. */
100  private MonitorBackendCfg currentConfig;
101  /** The DN for the base monitor entry. */
102  private DN baseMonitorDN;
103  /** The set of base DNs for this backend. */
104  private DN[] baseDNs;
105
106  /**
107   * Creates a new backend with the provided information. All backend
108   * implementations must implement a default constructor that use
109   * <CODE>super()</CODE> to invoke this constructor.
110   */
111  public MonitorBackend()
112  {
113    super();
114  }
115
116  /** {@inheritDoc} */
117  @Override
118  public void addEntry(final Entry entry, final AddOperation addOperation)
119      throws DirectoryException
120  {
121    throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM,
122        ERR_BACKEND_ADD_NOT_SUPPORTED.get(entry.getName(), getBackendID()));
123  }
124
125  /** {@inheritDoc} */
126  @Override
127  public ConfigChangeResult applyConfigurationChange(
128      final MonitorBackendCfg backendCfg)
129  {
130    final ConfigChangeResult ccr = new ConfigChangeResult();
131
132    // Check to see if there is a new set of user-defined attributes.
133    final ArrayList<Attribute> userAttrs = new ArrayList<>();
134    try
135    {
136      final ConfigEntry configEntry = DirectoryServer
137          .getConfigEntry(configEntryDN);
138      for (final List<Attribute> attrs : configEntry.getEntry()
139          .getUserAttributes().values())
140      {
141        for (final Attribute a : attrs)
142        {
143          if (!isMonitorConfigAttribute(a))
144          {
145            userAttrs.add(a);
146          }
147        }
148      }
149      for (final List<Attribute> attrs : configEntry.getEntry()
150          .getOperationalAttributes().values())
151      {
152        for (final Attribute a : attrs)
153        {
154          if (!isMonitorConfigAttribute(a))
155          {
156            userAttrs.add(a);
157          }
158        }
159      }
160    }
161    catch (final Exception e)
162    {
163      logger.traceException(e);
164
165      ccr.addMessage(ERR_CONFIG_BACKEND_ERROR_INTERACTING_WITH_BACKEND_ENTRY.get(
166          configEntryDN, stackTraceToSingleLineString(e)));
167      ccr.setResultCode(DirectoryServer.getServerErrorResultCode());
168    }
169
170    userDefinedAttributes = userAttrs;
171
172    ccr.addMessage(INFO_MONITOR_USING_NEW_USER_ATTRS.get());
173
174    currentConfig = backendCfg;
175    return ccr;
176  }
177
178  /** {@inheritDoc} */
179  @Override
180  public void configureBackend(final MonitorBackendCfg config, ServerContext serverContext)
181      throws ConfigException
182  {
183    Reject.ifNull(config);
184
185    final MonitorBackendCfg cfg = config;
186    final ConfigEntry configEntry = DirectoryServer.getConfigEntry(cfg.dn());
187
188    // Make sure that a configuration entry was provided. If not, then we will
189    // not be able to complete initialization.
190    if (configEntry == null)
191    {
192      final LocalizableMessage message = ERR_MONITOR_CONFIG_ENTRY_NULL.get();
193      throw new ConfigException(message);
194    }
195
196    configEntryDN = configEntry.getDN();
197
198    // Get the set of user-defined attributes for the configuration entry. Any
199    // attributes that we don't recognize will be included directly in the base
200    // monitor entry.
201    userDefinedAttributes = new ArrayList<>();
202    addAll(userDefinedAttributes, configEntry.getEntry().getUserAttributes().values());
203    addAll(userDefinedAttributes, configEntry.getEntry().getOperationalAttributes().values());
204
205    // Construct the set of objectclasses to include in the base monitor entry.
206    final ObjectClass topOC = DirectoryServer.getObjectClass(OC_TOP, true);
207    monitorObjectClasses.put(topOC, OC_TOP);
208
209    final ObjectClass monitorOC = DirectoryServer.getObjectClass(
210        OC_MONITOR_ENTRY, true);
211    monitorObjectClasses.put(monitorOC, OC_MONITOR_ENTRY);
212
213    // Create the set of base DNs that we will handle. In this case, it's just
214    // the DN of the base monitor entry.
215    try
216    {
217      baseMonitorDN = DN.valueOf(DN_MONITOR_ROOT);
218    }
219    catch (final Exception e)
220    {
221      logger.traceException(e);
222
223      final LocalizableMessage message = ERR_MONITOR_CANNOT_DECODE_MONITOR_ROOT_DN
224          .get(getExceptionMessage(e));
225      throw new ConfigException(message, e);
226    }
227
228    // FIXME -- Deal with this more correctly.
229    this.baseDNs = new DN[] { baseMonitorDN };
230
231    currentConfig = cfg;
232  }
233
234  private void addAll(ArrayList<Attribute> attributes, Collection<List<Attribute>> attributesToAdd)
235  {
236    for (final List<Attribute> attrs : attributesToAdd)
237    {
238      for (final Attribute a : attrs)
239      {
240        if (!isMonitorConfigAttribute(a))
241        {
242          attributes.add(a);
243        }
244      }
245    }
246  }
247
248  /** {@inheritDoc} */
249  @Override
250  public void createBackup(final BackupConfig backupConfig)
251      throws DirectoryException
252  {
253    throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM,
254        ERR_BACKEND_BACKUP_AND_RESTORE_NOT_SUPPORTED.get(getBackendID()));
255  }
256
257  /** {@inheritDoc} */
258  @Override
259  public void deleteEntry(final DN entryDN,
260      final DeleteOperation deleteOperation) throws DirectoryException
261  {
262    throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM,
263        ERR_BACKEND_DELETE_NOT_SUPPORTED.get(entryDN, getBackendID()));
264  }
265
266  /** {@inheritDoc} */
267  @Override
268  public boolean entryExists(final DN entryDN) throws DirectoryException
269  {
270    return getDIT().containsKey(entryDN);
271  }
272
273  /** {@inheritDoc} */
274  @Override
275  public void exportLDIF(final LDIFExportConfig exportConfig)
276      throws DirectoryException
277  {
278    // TODO export-ldif reports nonsense for upTime etc.
279
280    // Create the LDIF writer.
281    LDIFWriter ldifWriter;
282    try
283    {
284      ldifWriter = new LDIFWriter(exportConfig);
285    }
286    catch (final Exception e)
287    {
288      logger.traceException(e);
289
290      final LocalizableMessage message = ERR_ROOTDSE_UNABLE_TO_CREATE_LDIF_WRITER
291          .get(stackTraceToSingleLineString(e));
292      throw new DirectoryException(DirectoryServer.getServerErrorResultCode(), message);
293    }
294
295    // Write the base monitor entry to the LDIF.
296    try
297    {
298      ldifWriter.writeEntry(getBaseMonitorEntry());
299    }
300    catch (final Exception e)
301    {
302      logger.traceException(e);
303
304      close(ldifWriter);
305
306      final LocalizableMessage message = ERR_MONITOR_UNABLE_TO_EXPORT_BASE
307          .get(stackTraceToSingleLineString(e));
308      throw new DirectoryException(DirectoryServer.getServerErrorResultCode(), message);
309    }
310
311    // Get all the monitor providers, convert them to entries, and write them to
312    // LDIF.
313    for (final MonitorProvider<?> monitorProvider : DirectoryServer
314        .getMonitorProviders().values())
315    {
316      try
317      {
318        // TODO implementation of export is incomplete
319      }
320      catch (final Exception e)
321      {
322        logger.traceException(e);
323
324        close(ldifWriter);
325
326        final LocalizableMessage message = ERR_MONITOR_UNABLE_TO_EXPORT_PROVIDER_ENTRY
327            .get(monitorProvider.getMonitorInstanceName(), stackTraceToSingleLineString(e));
328        throw new DirectoryException(
329            DirectoryServer.getServerErrorResultCode(), message);
330      }
331    }
332
333    close(ldifWriter);
334  }
335
336  /** {@inheritDoc} */
337  @Override
338  public void closeBackend()
339  {
340    currentConfig.removeMonitorChangeListener(this);
341    try
342    {
343      DirectoryServer.deregisterBaseDN(baseMonitorDN);
344    }
345    catch (final Exception e)
346    {
347      logger.traceException(e);
348    }
349  }
350
351  /** {@inheritDoc} */
352  @Override
353  public DN[] getBaseDNs()
354  {
355    return baseDNs;
356  }
357
358  /** {@inheritDoc} */
359  @Override
360  public Entry getEntry(final DN entryDN) throws DirectoryException
361  {
362    // If the requested entry was null, then throw an exception.
363    if (entryDN == null)
364    {
365      throw new DirectoryException(DirectoryServer.getServerErrorResultCode(),
366          ERR_BACKEND_GET_ENTRY_NULL.get(getBackendID()));
367    }
368
369    // If the requested entry was the monitor base entry, then retrieve it
370    // without constructing the DIT.
371    if (entryDN.equals(baseMonitorDN))
372    {
373      return getBaseMonitorEntry();
374    }
375
376    // From now on we'll need the DIT.
377    final Map<DN, MonitorProvider<?>> dit = getDIT();
378    if (!dit.containsKey(entryDN))
379    {
380      throw new DirectoryException(ResultCode.NO_SUCH_OBJECT,
381          ERR_MONITOR_INVALID_BASE.get(entryDN, baseMonitorDN));
382    }
383
384    // The DN is associated with a valid monitor/glue entry.
385    return getEntry(entryDN, dit);
386  }
387
388  /** {@inheritDoc} */
389  @Override
390  public long getEntryCount()
391  {
392    return getDIT().size();
393  }
394
395  /** {@inheritDoc} */
396  @Override
397  public Set<String> getSupportedControls()
398  {
399    return Collections.emptySet();
400  }
401
402  /** {@inheritDoc} */
403  @Override
404  public Set<String> getSupportedFeatures()
405  {
406    return Collections.emptySet();
407  }
408
409  /** {@inheritDoc} */
410  @Override
411  public ConditionResult hasSubordinates(final DN entryDN)
412      throws DirectoryException
413  {
414    final NavigableMap<DN, MonitorProvider<?>> dit = getDIT();
415    if (dit.containsKey(entryDN))
416    {
417      final DN nextDN = dit.higherKey(entryDN);
418      return ConditionResult.valueOf(nextDN != null && nextDN.isSubordinateOrEqualTo(entryDN));
419    }
420    return ConditionResult.UNDEFINED;
421  }
422
423  /** {@inheritDoc} */
424  @Override
425  public LDIFImportResult importLDIF(final LDIFImportConfig importConfig, ServerContext serverContext)
426      throws DirectoryException
427  {
428    throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM,
429        ERR_BACKEND_IMPORT_NOT_SUPPORTED.get(getBackendID()));
430  }
431
432  /** {@inheritDoc} */
433  @Override
434  public void openBackend() throws ConfigException, InitializationException
435  {
436    // Register with the Directory Server as a configurable component.
437    currentConfig.addMonitorChangeListener(this);
438
439    // Register the monitor base as a private suffix.
440    try
441    {
442      DirectoryServer.registerBaseDN(baseMonitorDN, this, true);
443    }
444    catch (final Exception e)
445    {
446      logger.traceException(e);
447
448      final LocalizableMessage message = ERR_BACKEND_CANNOT_REGISTER_BASEDN.get(
449          baseMonitorDN, getExceptionMessage(e));
450      throw new InitializationException(message, e);
451    }
452  }
453
454  /** {@inheritDoc} */
455  @Override
456  public boolean isConfigurationChangeAcceptable(
457      final MonitorBackendCfg backendCfg,
458      final List<LocalizableMessage> unacceptableReasons)
459  {
460    // We'll pretty much accept anything here as long as it isn't one of our
461    // private attributes.
462    return true;
463  }
464
465  /** {@inheritDoc} */
466  @Override
467  public boolean isIndexed(final AttributeType attributeType,
468      final IndexType indexType)
469  {
470    // All searches in this backend will always be considered indexed.
471    return true;
472  }
473
474  /** {@inheritDoc} */
475  @Override
476  public long getNumberOfEntriesInBaseDN(final DN baseDN) throws DirectoryException {
477    checkNotNull(baseDN, "baseDN must not be null");
478    return getNumberOfSubordinates(baseDN, true) + 1;
479  }
480
481  /** {@inheritDoc} */
482  @Override
483  public long getNumberOfChildren(final DN parentDN) throws DirectoryException {
484    checkNotNull(parentDN, "parentDN must not be null");
485    return getNumberOfSubordinates(parentDN, false);
486  }
487
488  private long getNumberOfSubordinates(final DN entryDN, final boolean includeSubtree) throws DirectoryException
489  {
490    final NavigableMap<DN, MonitorProvider<?>> dit = getDIT();
491    if (!dit.containsKey(entryDN))
492    {
493      return -1L;
494    }
495    long count = 0;
496    final int childDNSize = entryDN.size() + 1;
497    for (final DN dn : dit.tailMap(entryDN, false).navigableKeySet())
498    {
499      if (!dn.isSubordinateOrEqualTo(entryDN))
500      {
501        break;
502      }
503      else if (includeSubtree || dn.size() == childDNSize)
504      {
505        count++;
506      }
507    }
508    return count;
509  }
510
511  /** {@inheritDoc} */
512  @Override
513  public void removeBackup(final BackupDirectory backupDirectory,
514      final String backupID) throws DirectoryException
515  {
516    throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM,
517        ERR_BACKEND_BACKUP_AND_RESTORE_NOT_SUPPORTED.get(getBackendID()));
518  }
519
520  /** {@inheritDoc} */
521  @Override
522  public void renameEntry(final DN currentDN, final Entry entry,
523      final ModifyDNOperation modifyDNOperation) throws DirectoryException
524  {
525    throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM,
526        ERR_BACKEND_MODIFY_DN_NOT_SUPPORTED.get(currentDN, getBackendID()));
527  }
528
529  /** {@inheritDoc} */
530  @Override
531  public void replaceEntry(final Entry oldEntry, final Entry newEntry,
532      final ModifyOperation modifyOperation) throws DirectoryException
533  {
534    throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM,
535        ERR_MONITOR_MODIFY_NOT_SUPPORTED.get(newEntry.getName(), configEntryDN));
536  }
537
538  /** {@inheritDoc} */
539  @Override
540  public void restoreBackup(final RestoreConfig restoreConfig)
541      throws DirectoryException
542  {
543    throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM,
544        ERR_BACKEND_BACKUP_AND_RESTORE_NOT_SUPPORTED.get(getBackendID()));
545  }
546
547  /** {@inheritDoc} */
548  @Override
549  public void search(final SearchOperation searchOperation)
550      throws DirectoryException
551  {
552    // Get the base DN, scope, and filter for the search.
553    final DN baseDN = searchOperation.getBaseDN();
554    final SearchScope scope = searchOperation.getScope();
555    final SearchFilter filter = searchOperation.getFilter();
556
557    // Compute the current monitor DIT.
558    final NavigableMap<DN, MonitorProvider<?>> dit = getDIT();
559
560    // Resolve the base entry and return no such object if it does not exist.
561    if (!dit.containsKey(baseDN))
562    {
563      // Not found, so find the nearest match.
564      DN matchedDN = baseDN.parent();
565      while (matchedDN != null)
566      {
567        if (dit.containsKey(matchedDN))
568        {
569          break;
570        }
571        matchedDN = matchedDN.parent();
572      }
573      final LocalizableMessage message = ERR_BACKEND_ENTRY_DOESNT_EXIST.get(baseDN, getBackendID());
574      throw new DirectoryException(ResultCode.NO_SUCH_OBJECT, message,
575          matchedDN, null);
576    }
577
578    // Walk through all entries and send the ones that match.
579    for (final Map.Entry<DN, MonitorProvider<?>> e : dit.tailMap(baseDN).entrySet())
580    {
581      final DN dn = e.getKey();
582      if (dn.isInScopeOf(baseDN, scope))
583      {
584        final Entry entry = getEntry(dn, dit);
585        if (filter.matchesEntry(entry))
586        {
587          searchOperation.returnEntry(entry, null);
588        }
589      }
590      else if (scope == SearchScope.BASE_OBJECT || !dn.isSubordinateOrEqualTo(baseDN))
591      {
592        // No more entries will be in scope.
593        break;
594      }
595    }
596  }
597
598  /** {@inheritDoc} */
599  @Override
600  public boolean supports(BackendOperation backendOperation)
601  {
602    // We can export all the monitor entries as a point-in-time snapshot.
603    // TODO implementation of export is incomplete
604    // TODO export-ldif reports nonsense for upTime etc.
605    return false;
606  }
607
608  /**
609   * Retrieves the base monitor entry for the Directory Server.
610   *
611   * @return The base monitor entry for the Directory Server.
612   */
613  private Entry getBaseMonitorEntry()
614  {
615    final ObjectClass extensibleObjectOC = DirectoryServer.getObjectClass(OC_EXTENSIBLE_OBJECT_LC, true);
616    final HashMap<ObjectClass, String> monitorClasses = newObjectClasses(extensibleObjectOC, OC_EXTENSIBLE_OBJECT);
617
618    final HashMap<AttributeType, List<Attribute>> monitorUserAttrs = new LinkedHashMap<>();
619    final HashMap<AttributeType, List<Attribute>> monitorOperationalAttrs = new LinkedHashMap<>();
620
621    put(monitorUserAttrs, Attributes.create(ATTR_COMMON_NAME, "monitor"));
622    put(monitorUserAttrs, Attributes.create(ATTR_PRODUCT_NAME, DynamicConstants.PRODUCT_NAME));
623    put(monitorUserAttrs, Attributes.create(ATTR_VENDOR_NAME, SERVER_VENDOR_NAME));
624    put(monitorUserAttrs, Attributes.create(ATTR_VENDOR_VERSION, DirectoryServer.getVersionString()));
625    put(monitorUserAttrs, Attributes.create(ATTR_START_TIME, DirectoryServer.getStartTimeUTC()));
626    put(monitorUserAttrs, Attributes.create(ATTR_CURRENT_TIME, TimeThread.getGMTTime()));
627    put(monitorUserAttrs, Attributes.create(ATTR_UP_TIME, getHumanReadableUpTime()));
628
629    // Add the number of connections currently established.
630    final long currentConns = DirectoryServer.getCurrentConnections();
631    put(monitorUserAttrs, Attributes.create(ATTR_CURRENT_CONNS, String.valueOf(currentConns)));
632
633    // Add the maximum number of connections established at one time.
634    final long maxConns = DirectoryServer.getMaxConnections();
635    put(monitorUserAttrs, Attributes.create(ATTR_MAX_CONNS, String.valueOf(maxConns)));
636
637    // Add the total number of connections the server has accepted.
638    final long totalConns = DirectoryServer.getTotalConnections();
639    put(monitorUserAttrs, Attributes.create(ATTR_TOTAL_CONNS, String.valueOf(totalConns)));
640
641    // Add all the user-defined attributes.
642    for (final Attribute a : userDefinedAttributes)
643    {
644      final AttributeType type = a.getAttributeDescription().getAttributeType();
645
646      final HashMap<AttributeType, List<Attribute>> attrsMap =
647          type.isOperational() ? monitorOperationalAttrs : monitorUserAttrs;
648      List<Attribute> attrs = attrsMap.get(type);
649      if (attrs == null)
650      {
651        attrs = new ArrayList<>();
652        attrsMap.put(type, attrs);
653      }
654      attrs.add(a);
655    }
656
657    return newEntry(baseMonitorDN, monitorClasses, monitorUserAttrs, monitorOperationalAttrs);
658  }
659
660  private String getHumanReadableUpTime()
661  {
662    long upSeconds = (System.currentTimeMillis() - DirectoryServer.getStartTime()) / 1000;
663    final long upDays = upSeconds / 86400;
664    upSeconds %= 86400;
665    final long upHours = upSeconds / 3600;
666    upSeconds %= 3600;
667    final long upMinutes = upSeconds / 60;
668    upSeconds %= 60;
669    return INFO_MONITOR_UPTIME.get(upDays, upHours, upMinutes, upSeconds).toString();
670  }
671
672  private void put(final HashMap<AttributeType, List<Attribute>> attrsMap, final Attribute attr)
673  {
674    attrsMap.put(attr.getAttributeDescription().getAttributeType(), newArrayList(attr));
675  }
676
677  /**
678   * Retrieves the branch monitor entry for the Directory Server.
679   *
680   * @param dn
681   *          to get.
682   * @return The branch monitor entry for the Directory Server.
683   */
684  private Entry getBranchMonitorEntry(final DN dn)
685  {
686    final ObjectClass monitorOC = DirectoryServer.getObjectClass(OC_MONITOR_BRANCH, true);
687    final HashMap<ObjectClass, String> monitorClasses = newObjectClasses(monitorOC, OC_MONITOR_BRANCH);
688
689    final HashMap<AttributeType, List<Attribute>> monitorUserAttrs = new LinkedHashMap<>();
690
691    final RDN rdn = dn.rdn();
692    if (rdn != null)
693    {
694      for (AVA ava : rdn)
695      {
696        final AttributeType attributeType = ava.getAttributeType();
697        final ByteString value = ava.getAttributeValue();
698        monitorUserAttrs.put(attributeType, Attributes.createAsList(attributeType, value));
699      }
700    }
701
702    return newEntry(dn, monitorClasses, monitorUserAttrs, null);
703  }
704
705  /**
706   * Returns a map containing records for each DN in the monitor backend's DIT.
707   * Each record maps the entry DN to the associated monitor provider, or
708   * {@code null} if the entry is a glue (branch) entry.
709   *
710   * @return A map containing records for each DN in the monitor backend's DIT.
711   */
712  private NavigableMap<DN, MonitorProvider<?>> getDIT()
713  {
714    final NavigableMap<DN, MonitorProvider<?>> dit = new TreeMap<>();
715    for (final MonitorProvider<?> monitorProvider : DirectoryServer.getMonitorProviders().values())
716    {
717      DN dn = DirectoryServer.getMonitorProviderDN(monitorProvider);
718      dit.put(dn, monitorProvider);
719
720      // Added glue records.
721      for (dn = dn.parent(); dn != null; dn = dn.parent())
722      {
723        if (dit.containsKey(dn))
724        {
725          break;
726        }
727        dit.put(dn, null);
728      }
729    }
730    return dit;
731  }
732
733
734
735  /**
736   * Creates the monitor entry having the specified DN.
737   *
738   * @param entryDN
739   *          The name of the monitor entry.
740   * @param dit
741   *          The monitor DIT.
742   * @return Returns the monitor entry having the specified DN.
743   */
744  private Entry getEntry(final DN entryDN,
745      final Map<DN, MonitorProvider<?>> dit)
746  {
747    // Get the monitor provider.
748    final MonitorProvider<?> monitorProvider = dit.get(entryDN);
749    if (monitorProvider != null)
750    {
751      return getMonitorEntry(entryDN, monitorProvider);
752    }
753    else if (entryDN.equals(baseMonitorDN))
754    {
755      // The monitor base entry needs special treatment.
756      return getBaseMonitorEntry();
757    }
758    else
759    {
760      // Create a generic glue branch entry.
761      return getBranchMonitorEntry(entryDN);
762    }
763  }
764
765
766
767  /**
768   * Generates and returns a monitor entry based on the contents of the provided
769   * monitor provider.
770   *
771   * @param entryDN
772   *          The DN to use for the entry.
773   * @param monitorProvider
774   *          The monitor provider to use to obtain the information for the
775   *          entry.
776   * @return The monitor entry generated from the information in the provided
777   *         monitor provider.
778   */
779  private Entry getMonitorEntry(final DN entryDN,
780      final MonitorProvider<?> monitorProvider)
781  {
782    final ObjectClass monitorOC = monitorProvider.getMonitorObjectClass();
783    final HashMap<ObjectClass, String> monitorClasses = newObjectClasses(monitorOC, monitorOC.getPrimaryName());
784
785    final MonitorData monitorAttrs = monitorProvider.getMonitorData();
786    final Map<AttributeType, List<Attribute>> attrMap = asMap(monitorAttrs);
787
788    // Make sure to include the RDN attribute.
789    final AVA ava = entryDN.rdn().getFirstAVA();
790    final AttributeType rdnType = ava.getAttributeType();
791    final ByteString rdnValue = ava.getAttributeValue();
792    attrMap.put(rdnType, Attributes.createAsList(rdnType, rdnValue));
793
794    return newEntry(entryDN, monitorClasses, attrMap, new HashMap<AttributeType, List<Attribute>>(0));
795  }
796
797  private Map<AttributeType, List<Attribute>> asMap(MonitorData monitorAttrs)
798  {
799    final Map<AttributeType, List<Attribute>> results = new LinkedHashMap<>(monitorAttrs.size() + 1);
800    for (final Attribute a : monitorAttrs)
801    {
802      final AttributeType type = a.getAttributeDescription().getAttributeType();
803
804      List<Attribute> attrs = results.get(type);
805      if (attrs == null)
806      {
807        attrs = new ArrayList<>();
808        results.put(type, attrs);
809      }
810      attrs.add(a);
811    }
812    return results;
813  }
814
815  private HashMap<ObjectClass, String> newObjectClasses(ObjectClass objectClass, String objectClassName)
816  {
817    final HashMap<ObjectClass, String> monitorClasses = new LinkedHashMap<>(monitorObjectClasses.size() + 1);
818    monitorClasses.putAll(monitorObjectClasses);
819    monitorClasses.put(objectClass, objectClassName);
820    return monitorClasses;
821  }
822
823  private Entry newEntry(final DN dn, final Map<ObjectClass, String> objectClasses,
824      final Map<AttributeType, List<Attribute>> userAttrs, Map<AttributeType, List<Attribute>> opAttrs)
825  {
826    final Entry e = new Entry(dn, objectClasses, userAttrs, opAttrs);
827    e.processVirtualAttributes();
828    return e;
829  }
830
831  /**
832   * Indicates whether the provided attribute is one that is used in the
833   * configuration of this backend.
834   *
835   * @param attribute
836   *          The attribute for which to make the determination.
837   * @return <CODE>true</CODE> if the provided attribute is one that is used in
838   *         the configuration of this backend, <CODE>false</CODE> if not.
839   */
840  private boolean isMonitorConfigAttribute(final Attribute attribute)
841  {
842    final AttributeType attrType = attribute.getAttributeDescription().getAttributeType();
843    return attrType.hasName(ATTR_COMMON_NAME)
844        || attrType.hasName(ATTR_BACKEND_ENABLED.toLowerCase())
845        || attrType.hasName(ATTR_BACKEND_CLASS.toLowerCase())
846        || attrType.hasName(ATTR_BACKEND_BASE_DN.toLowerCase())
847        || attrType.hasName(ATTR_BACKEND_ID.toLowerCase())
848        || attrType.hasName(ATTR_BACKEND_WRITABILITY_MODE.toLowerCase());
849  }
850
851}