mirror of https://github.com/OpenIdentityPlatform/OpenDJ.git

matthew_swift
10.15.2009 17f22d5021575f36f61e39b4343f3d9c2dcbf39e
Fix issue 3001: Performance bottleneck in GeneralizedTimeSyntax.format

Thanks to D.J. Hagberg of Millibits Consulting, Inc. for the code contribution.
3 files modified
310 ■■■■ changed files
opendj-sdk/opends/src/server/org/opends/server/schema/GeneralizedTimeSyntax.java 159 ●●●●● patch | view | raw | blame | history
opendj-sdk/opends/src/server/org/opends/server/util/TimeThread.java 15 ●●●●● patch | view | raw | blame | history
opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/schema/GeneralizedTimeSyntaxTest.java 136 ●●●●● patch | view | raw | blame | history
opendj-sdk/opends/src/server/org/opends/server/schema/GeneralizedTimeSyntax.java
@@ -22,13 +22,13 @@
 * CDDL HEADER END
 *
 *
 *      Copyright 2006-2008 Sun Microsystems, Inc.
 *      Copyright 2006-2009 Sun Microsystems, Inc.
 *      Portions Copyright 2009 D. J. Hagberg, Millibits Consulting, Inc.
 */
package org.opends.server.schema;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;
import java.util.GregorianCalendar;
@@ -70,19 +70,9 @@
   */
  private static final DebugTracer TRACER = getTracer();
  /**
   * The lock that will be used to provide threadsafe access to the date
   * formatter.
   */
  private static Object dateFormatLock;
  /**
   * The date formatter that will be used to convert dates into generalized time
   * values.  Note that all interaction with it must be synchronized.
   */
  private static SimpleDateFormat dateFormat;
  // UTC TimeZone is assumed to never change over JVM lifetime
  private static final TimeZone TIME_ZONE_UTC_OBJ =
      TimeZone.getTimeZone(TIME_ZONE_UTC);
@@ -97,21 +87,6 @@
  /*
   * Create the date formatter that will be used to construct and parse
   * normalized generalized time values.
   */
  static
  {
    dateFormat = new SimpleDateFormat(DATE_FORMAT_GENERALIZED_TIME);
    dateFormat.setLenient(false);
    dateFormat.setTimeZone(TimeZone.getTimeZone("UTC"));
    dateFormatLock = new Object();
  }
  /**
   * Creates a new instance of this syntax.  Note that the only thing that
   * should be done here is to invoke the default constructor for the
@@ -128,6 +103,7 @@
  /**
   * {@inheritDoc}
   */
  @Override
  public void initializeSyntax(AttributeSyntaxCfg configuration)
         throws ConfigException
  {
@@ -163,6 +139,7 @@
   *
   * @return  The common name for this attribute syntax.
   */
  @Override
  public String getSyntaxName()
  {
    return SYNTAX_GENERALIZED_TIME_NAME;
@@ -175,6 +152,7 @@
   *
   * @return  The OID for this attribute syntax.
   */
  @Override
  public String getOID()
  {
    return SYNTAX_GENERALIZED_TIME_OID;
@@ -187,6 +165,7 @@
   *
   * @return  A description for this attribute syntax.
   */
  @Override
  public String getDescription()
  {
    return SYNTAX_GENERALIZED_TIME_DESCRIPTION;
@@ -202,6 +181,7 @@
   *          attributes with this syntax, or <CODE>null</CODE> if equality
   *          matches will not be allowed for this type by default.
   */
  @Override
  public EqualityMatchingRule getEqualityMatchingRule()
  {
    return defaultEqualityMatchingRule;
@@ -217,6 +197,7 @@
   *          attributes with this syntax, or <CODE>null</CODE> if ordering
   *          matches will not be allowed for this type by default.
   */
  @Override
  public OrderingMatchingRule getOrderingMatchingRule()
  {
    return defaultOrderingMatchingRule;
@@ -232,6 +213,7 @@
   *          attributes with this syntax, or <CODE>null</CODE> if substring
   *          matches will not be allowed for this type by default.
   */
  @Override
  public SubstringMatchingRule getSubstringMatchingRule()
  {
    return defaultSubstringMatchingRule;
@@ -247,6 +229,7 @@
   *          attributes with this syntax, or <CODE>null</CODE> if approximate
   *          matches will not be allowed for this type by default.
   */
  @Override
  public ApproximateMatchingRule getApproximateMatchingRule()
  {
    // Approximate matching will not be allowed by default.
@@ -267,6 +250,7 @@
   * @return  <CODE>true</CODE> if the provided value is acceptable for use with
   *          this syntax, or <CODE>false</CODE> if not.
   */
  @Override
  public boolean valueIsAcceptable(ByteSequence value,
                                   MessageBuilder invalidReason)
  {
@@ -293,10 +277,7 @@
   */
  public static String format(Date d)
  {
    synchronized (dateFormatLock)
    {
      return dateFormat.format(d);
    }
    return d == null ? null : format(d.getTime());
  }
@@ -310,12 +291,95 @@
   */
  public static String format(long t)
  {
    synchronized (dateFormatLock)
    {
      return dateFormat.format(new Date(t));
    }
  }
    // Generalized time has the format yyyyMMddHHmmss.SSS'Z'
    // Do this in a thread-safe non-synchronized fashion.
    // (Simple)DateFormat is neither fast nor thread-safe.
    StringBuilder sb = new StringBuilder(19);
    GregorianCalendar calendar = new GregorianCalendar(TIME_ZONE_UTC_OBJ);
    calendar.setLenient(false);
    calendar.setTimeInMillis(t);
    // Format the year yyyy.
    int n = calendar.get(Calendar.YEAR);
    if (n < 0)
    {
      throw new IllegalArgumentException("Year cannot be < 0:" + n);
    }
    else if (n < 10)
    {
      sb.append("000");
    }
    else if (n < 100)
    {
      sb.append("00");
    }
    else if (n < 1000)
    {
      sb.append("0");
    }
    sb.append(n);
    // Format the month MM.
    n = calendar.get(Calendar.MONTH) + 1;
    if (n < 10)
    {
      sb.append("0");
    }
    sb.append(n);
    // Format the day dd.
    n = calendar.get(Calendar.DAY_OF_MONTH);
    if (n < 10)
    {
      sb.append("0");
    }
    sb.append(n);
    // Format the hour HH.
    n = calendar.get(Calendar.HOUR_OF_DAY);
    if (n < 10)
    {
      sb.append("0");
    }
    sb.append(n);
    // Format the minute mm.
    n = calendar.get(Calendar.MINUTE);
    if (n < 10)
    {
      sb.append("0");
    }
    sb.append(n);
    // Format the seconds ss.
    n = calendar.get(Calendar.SECOND);
    if (n < 10)
    {
      sb.append("0");
    }
    sb.append(n);
    // Format the milli-seconds.
    sb.append('.');
    n = calendar.get(Calendar.MILLISECOND);
    if (n < 10)
    {
      sb.append("00");
    }
    else if (n < 100)
    {
      sb.append("0");
    }
    sb.append(n);
    // Format the timezone (always Z).
    sb.append('Z');
    return sb.toString();
  }
@@ -329,13 +393,7 @@
   */
  public static AttributeValue createGeneralizedTimeValue(long time)
  {
    String valueString;
    synchronized (dateFormatLock)
    {
      valueString = dateFormat.format(new Date(time));
    }
    String valueString = format(time);
    return AttributeValues.create(ByteString.valueOf(valueString),
        ByteString.valueOf(valueString));
  }
@@ -946,7 +1004,7 @@
          {
            GregorianCalendar calendar = new GregorianCalendar();
            calendar.setLenient(false);
            calendar.setTimeZone(TimeZone.getTimeZone(TIME_ZONE_UTC));
            calendar.setTimeZone(TIME_ZONE_UTC_OBJ);
            calendar.set(year, month, day, hour, minute, second);
            calendar.set(Calendar.MILLISECOND, 0);
            return calendar.getTimeInMillis();
@@ -1133,7 +1191,7 @@
          {
            GregorianCalendar calendar = new GregorianCalendar();
            calendar.setLenient(false);
            calendar.setTimeZone(TimeZone.getTimeZone(TIME_ZONE_UTC));
            calendar.setTimeZone(TIME_ZONE_UTC_OBJ);
            calendar.set(year, month, day, hour, minute, second);
            calendar.set(Calendar.MILLISECOND, 0);
            return calendar.getTimeInMillis();
@@ -1231,7 +1289,7 @@
          {
            GregorianCalendar calendar = new GregorianCalendar();
            calendar.setLenient(false);
            calendar.setTimeZone(TimeZone.getTimeZone(TIME_ZONE_UTC));
            calendar.setTimeZone(TIME_ZONE_UTC_OBJ);
            calendar.set(year, month, day, hour, minute, second);
            calendar.set(Calendar.MILLISECOND, 0);
            return calendar.getTimeInMillis();
@@ -1375,7 +1433,7 @@
                                         message);
          }
          timeZone = TimeZone.getTimeZone(TIME_ZONE_UTC);
          timeZone = TIME_ZONE_UTC_OBJ;
          break outerLoop;
        case '+':
@@ -1590,6 +1648,7 @@
  /**
   * {@inheritDoc}
   */
  @Override
  public boolean isBinary()
  {
    return false;
opendj-sdk/opends/src/server/org/opends/server/util/TimeThread.java
@@ -22,7 +22,7 @@
 * CDDL HEADER END
 *
 *
 *      Copyright 2006-2008 Sun Microsystems, Inc.
 *      Copyright 2006-2009 Sun Microsystems, Inc.
 */
package org.opends.server.util;
@@ -41,6 +41,7 @@
import static org.opends.server.loggers.debug.DebugLogger.*;
import org.opends.server.loggers.debug.DebugTracer;
import org.opends.server.schema.GeneralizedTimeSyntax;
import org.opends.server.types.DebugLogLevel;
import static org.opends.server.util.ServerConstants.*;
@@ -96,9 +97,6 @@
  // The current time in nanoseconds.
  private static volatile long nanoTime;
  // The date formatter that will be used to obtain the generalized time.
  private static SimpleDateFormat generalizedTimeFormatter;
  // The date formatter that will be used to obtain the local timestamp.
  private static SimpleDateFormat localTimestampFormatter;
@@ -130,10 +128,6 @@
    TimeZone utcTimeZone = TimeZone.getTimeZone(TIME_ZONE_UTC);
    generalizedTimeFormatter =
         new SimpleDateFormat(DATE_FORMAT_GENERALIZED_TIME);
    generalizedTimeFormatter.setTimeZone(utcTimeZone);
    gmtTimestampFormatter = new SimpleDateFormat(DATE_FORMAT_GMT_TIME);
    gmtTimestampFormatter.setTimeZone(utcTimeZone);
@@ -143,7 +137,7 @@
    date            = calendar.getTime();
    time            = date.getTime();
    nanoTime        = System.nanoTime();
    generalizedTime = generalizedTimeFormatter.format(date);
    generalizedTime = GeneralizedTimeSyntax.format(date);
    localTimestamp  = localTimestampFormatter.format(date);
    gmtTimestamp    = gmtTimestampFormatter.format(date);
    hourAndMinute   = (calendar.get(Calendar.HOUR_OF_DAY) * 100) +
@@ -158,6 +152,7 @@
   * Operates in a loop, getting the current time and then sleeping briefly
   * before checking again.
   */
  @Override
  public void run()
  {
    while (true)
@@ -168,7 +163,7 @@
        date            = calendar.getTime();
        time            = date.getTime();
        nanoTime        = System.nanoTime();
        generalizedTime = generalizedTimeFormatter.format(date);
        generalizedTime = GeneralizedTimeSyntax.format(date);
        localTimestamp  = localTimestampFormatter.format(date);
        gmtTimestamp    = gmtTimestampFormatter.format(date);
        hourAndMinute   = (calendar.get(Calendar.HOUR_OF_DAY) * 100) +
opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/schema/GeneralizedTimeSyntaxTest.java
@@ -22,12 +22,21 @@
 * CDDL HEADER END
 *
 *
 *      Copyright 2006-2008 Sun Microsystems, Inc.
 *      Copyright 2006-2009 Sun Microsystems, Inc.
 */
package org.opends.server.schema;
import static org.opends.server.util.ServerConstants.*;
import java.util.Calendar;
import java.util.Date;
import java.util.GregorianCalendar;
import java.util.TimeZone;
import org.opends.server.api.AttributeSyntax;
import org.testng.Assert;
import org.testng.annotations.DataProvider;
import org.testng.annotations.Test;
/**
 * Test the GeneralizedTimeSyntax.
@@ -80,4 +89,129 @@
    };
  }
  /**
   * Create data for format(...) tests.
   *
   * @return Returns test data.
   */
  @DataProvider(name="createFormatData")
  public Object[][] createFormatData()
  {
    return new Object [][] {
        // Note that Calendar months run from 0-11,
        // and that there was no such year as year 0 (1 BC -> 1 AD).
        {   1,  0,  1,  0,  0,  0,   0, "00010101000000.000Z"},
        {   9,  0,  1,  0,  0,  0,   0, "00090101000000.000Z"},
        {  10,  0,  1,  0,  0,  0,   0, "00100101000000.000Z"},
        {  99,  0,  1,  0,  0,  0,   0, "00990101000000.000Z"},
        { 100,  0,  1,  0,  0,  0,   0, "01000101000000.000Z"},
        { 999,  0,  1,  0,  0,  0,   0, "09990101000000.000Z"},
        {1000,  0,  1,  0,  0,  0,   0, "10000101000000.000Z"},
        {2000,  0,  1,  0,  0,  0,   0, "20000101000000.000Z"},
        {2099,  0,  1,  0,  0,  0,   0, "20990101000000.000Z"},
        {2000,  8,  1,  0,  0,  0,   0, "20000901000000.000Z"},
        {2000,  9,  1,  0,  0,  0,   0, "20001001000000.000Z"},
        {2000, 10,  1,  0,  0,  0,   0, "20001101000000.000Z"},
        {2000, 11,  1,  0,  0,  0,   0, "20001201000000.000Z"},
        {2000,  0,  9,  0,  0,  0,   0, "20000109000000.000Z"},
        {2000,  0, 10,  0,  0,  0,   0, "20000110000000.000Z"},
        {2000,  0, 19,  0,  0,  0,   0, "20000119000000.000Z"},
        {2000,  0, 20,  0,  0,  0,   0, "20000120000000.000Z"},
        {2000,  0, 29,  0,  0,  0,   0, "20000129000000.000Z"},
        {2000,  0, 30,  0,  0,  0,   0, "20000130000000.000Z"},
        {2000,  0, 31,  0,  0,  0,   0, "20000131000000.000Z"},
        {2000,  0,  1,  9,  0,  0,   0, "20000101090000.000Z"},
        {2000,  0,  1, 10,  0,  0,   0, "20000101100000.000Z"},
        {2000,  0,  1, 19,  0,  0,   0, "20000101190000.000Z"},
        {2000,  0,  1, 20,  0,  0,   0, "20000101200000.000Z"},
        {2000,  0,  1, 23,  0,  0,   0, "20000101230000.000Z"},
        {2000,  0,  1,  0,  9,  0,   0, "20000101000900.000Z"},
        {2000,  0,  1,  0, 10,  0,   0, "20000101001000.000Z"},
        {2000,  0,  1,  0, 59,  0,   0, "20000101005900.000Z"},
        {2000,  0,  1,  0,  0,  9,   0, "20000101000009.000Z"},
        {2000,  0,  1,  0,  0, 10,   0, "20000101000010.000Z"},
        {2000,  0,  1,  0,  0, 59,   0, "20000101000059.000Z"},
        {2000,  0,  1,  0,  0,  0,   9, "20000101000000.009Z"},
        {2000,  0,  1,  0,  0,  0,  10, "20000101000000.010Z"},
        {2000,  0,  1,  0,  0,  0,  99, "20000101000000.099Z"},
        {2000,  0,  1,  0,  0,  0, 100, "20000101000000.100Z"},
        {2000,  0,  1,  0,  0,  0, 999, "20000101000000.999Z"},
    };
  }
  /**
   * Tests {@link GeneralizedTimeSyntax#format(long)}.
   *
   * @param yyyy
   *          The year.
   * @param MM
   *          The month.
   * @param dd
   *          The day.
   * @param HH
   *          The hour.
   * @param mm
   *          The minute.
   * @param ss
   *          The second.
   * @param SSS
   *          The milli-seconds.
   * @param expected
   *          The expected generalized time formatted string.
   * @throws Exception
   *           If an unexpected error occurred.
   */
  @Test(dataProvider="createFormatData")
  public void testFormatLong(int yyyy, int MM, int dd, int HH, int mm,
      int ss, int SSS, String expected) throws Exception
  {
    Calendar calendar =
        new GregorianCalendar(TimeZone.getTimeZone(TIME_ZONE_UTC));
    calendar.set(yyyy, MM, dd, HH, mm, ss);
    calendar.set(Calendar.MILLISECOND, SSS);
    long time = calendar.getTimeInMillis();
    String actual = GeneralizedTimeSyntax.format(time);
    Assert.assertEquals(actual, expected);
  }
  /**
   * Tests {@link GeneralizedTimeSyntax#format(Date)}.
   *
   * @param yyyy
   *          The year.
   * @param MM
   *          The month.
   * @param dd
   *          The day.
   * @param HH
   *          The hour.
   * @param mm
   *          The minute.
   * @param ss
   *          The second.
   * @param SSS
   *          The milli-seconds.
   * @param expected
   *          The expected generalized time formatted string.
   * @throws Exception
   *           If an unexpected error occurred.
   */
  @Test(dataProvider="createFormatData")
  public void testFormatDate(int yyyy, int MM, int dd, int HH, int mm,
      int ss, int SSS, String expected) throws Exception
  {
    Calendar calendar =
        new GregorianCalendar(TimeZone.getTimeZone(TIME_ZONE_UTC));
    calendar.set(yyyy, MM, dd, HH, mm, ss);
    calendar.set(Calendar.MILLISECOND, SSS);
    Date time = new Date(calendar.getTimeInMillis());
    String actual = GeneralizedTimeSyntax.format(time);
    Assert.assertEquals(actual, expected);
  }
}