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 2016 ForgeRock AS.
015 */
016package org.opends.server.api;
017
018import static org.opends.server.core.DirectoryServer.*;
019
020import java.lang.reflect.Method;
021import java.util.ArrayList;
022import java.util.Calendar;
023import java.util.Collection;
024import java.util.Date;
025import java.util.Iterator;
026import java.util.List;
027import java.util.UUID;
028
029import org.forgerock.opendj.ldap.ByteString;
030import org.forgerock.opendj.ldap.DN;
031import org.forgerock.opendj.ldap.GeneralizedTime;
032import org.forgerock.opendj.ldap.schema.AttributeType;
033import org.forgerock.opendj.ldap.schema.CoreSchema;
034import org.forgerock.opendj.ldap.schema.Syntax;
035import org.opends.server.types.Attribute;
036import org.opends.server.types.Attribute.RemoveOnceSwitchingAttributes;
037import org.opends.server.types.AttributeBuilder;
038import org.opends.server.types.Attributes;
039import org.opends.server.types.PublicAPI;
040import org.opends.server.types.StabilityLevel;
041
042/**
043 * This class is used to hold monitoring data, i.e. a list of attributes. It provides convenient
044 * methods to easily build such data.
045 * <p>
046 * <strong>Note:</strong> <br>
047 * Creating monitor entries may become a lot easier once we've migrated to the SDK Entry class:
048 *
049 * <pre>
050 * Entry entry = ...;
051 * entry.addAttribute("stringStat", "aString")
052 *       .addAttribute("integerStat", 12345)
053 *       .addAttribute("dnStat", DN.valueOf("dc=aDN");
054 * </pre>
055 *
056 * We could also envisage an annotation based approach where we determine the monitor content from
057 * annotated fields/methods in an object.
058 */
059@PublicAPI(stability = StabilityLevel.PRIVATE)
060public final class MonitorData implements Iterable<Attribute>
061{
062  private final List<Attribute> attrs;
063
064  /** Constructor to use when the number of attributes to create is unknown. */
065  public MonitorData()
066  {
067    attrs = new ArrayList<>();
068  }
069
070  /**
071   * Constructor that accepts the number of attributes to create.
072   *
073   * @param expectedAttributesCount
074   *          number of attributes that will be added
075   */
076  public MonitorData(int expectedAttributesCount)
077  {
078    attrs = new ArrayList<>(expectedAttributesCount);
079  }
080
081  /**
082   * Adds an attribute with the provided name and value.
083   *
084   * @param attrName
085   *          the attribute name
086   * @param attrValue
087   *          the attribute value
088   */
089  public void add(String attrName, Object attrValue)
090  {
091    Syntax syntax;
092    if (attrValue instanceof String
093        || attrValue instanceof ByteString
094        || attrValue instanceof Float
095        || attrValue instanceof Double)
096    {
097      // coming first because they are the most common types
098      syntax = CoreSchema.getDirectoryStringSyntax();
099    }
100    else if (attrValue instanceof Number)
101    {
102      syntax = CoreSchema.getIntegerSyntax();
103    }
104    else if (attrValue instanceof Boolean)
105    {
106      syntax = CoreSchema.getBooleanSyntax();
107    }
108    else if (attrValue instanceof DN)
109    {
110      syntax = CoreSchema.getDNSyntax();
111    }
112    else if (attrValue instanceof Date)
113    {
114      syntax = CoreSchema.getGeneralizedTimeSyntax();
115      attrValue = GeneralizedTime.valueOf((Date) attrValue);
116    }
117    else if (attrValue instanceof Calendar)
118    {
119      syntax = CoreSchema.getGeneralizedTimeSyntax();
120      attrValue = GeneralizedTime.valueOf((Calendar) attrValue);
121    }
122    else if (attrValue instanceof UUID)
123    {
124      syntax = CoreSchema.getUUIDSyntax();
125    }
126    else
127    {
128      syntax = CoreSchema.getDirectoryStringSyntax();
129    }
130    add(attrName, syntax, attrValue);
131  }
132
133  private void add(String attrName, Syntax syntax, Object attrValue)
134  {
135    AttributeType attrType = getSchema().getAttributeType(attrName, syntax);
136    attrs.add(Attributes.create(attrType, String.valueOf(attrValue)));
137  }
138
139  /**
140   * Adds an attribute with the provided name and value if the value is not null.
141   *
142   * @param attrName
143   *          the attribute name
144   * @param attrValue
145   *          the attribute value
146   */
147  public void addIfNotNull(String attrName, Object attrValue)
148  {
149    if (attrValue != null)
150    {
151      add(attrName, attrValue);
152    }
153  }
154
155  /**
156   * Adds an attribute with the provided name and values.
157   *
158   * @param attrName
159   *          the attribute name
160   * @param attrValues
161   *          the attribute values
162   */
163  @RemoveOnceSwitchingAttributes(comment = "once using the non immutable SDK's Attribute class, "
164      + "we can incrementally build an attribute by using the add(String attrName, Object attrValue) method")
165  public void add(String attrName, Collection<?> attrValues)
166  {
167    AttributeBuilder builder = new AttributeBuilder(attrName);
168    builder.addAllStrings(attrValues);
169    attrs.add(builder.toAttribute());
170  }
171
172  /**
173   * Adds all the properties from the provided bean as attributes, prepending the provided prefix.
174   *
175   * @param bean
176   *          the bean from which to read the properties
177   * @param attributesPrefix
178   *          the prefix to prepend to the attributes read from the bean
179   * @throws ReflectiveOperationException
180   *           if a problem occurs while reading the properties of the bean
181   */
182  public void addBean(Object bean, String attributesPrefix) throws ReflectiveOperationException
183  {
184    for (Method method : bean.getClass().getMethods())
185    {
186      if (method.getName().startsWith("get"))
187      {
188        Class<?> returnType = method.getReturnType();
189        if (returnType.equals(int.class) || returnType.equals(long.class) || returnType.equals(String.class))
190        {
191          addStatAttribute(attributesPrefix, bean, method, 3);
192        }
193      }
194      else if (method.getName().startsWith("is") && method.getReturnType().equals(boolean.class))
195      {
196        addStatAttribute(attributesPrefix, bean, method, 2);
197      }
198    }
199  }
200
201  private void addStatAttribute(String attrPrefix, Object stats, Method method, int skipNameLen)
202      throws ReflectiveOperationException
203  {
204    String attrName = attrPrefix + method.getName().substring(skipNameLen);
205    add(attrName, method.invoke(stats));
206  }
207
208  @Override
209  public Iterator<Attribute> iterator()
210  {
211    return attrs.iterator();
212  }
213
214  /**
215   * Returns the number of attributes.
216   *
217   * @return the number of attributes
218   */
219  public int size()
220  {
221    return attrs.size();
222  }
223
224  @Override
225  public String toString()
226  {
227    return getClass().getName() + attrs;
228  }
229}