001/*
002 * CDDL HEADER START
003 *
004 * The contents of this file are subject to the terms of the
005 * Common Development and Distribution License, Version 1.0 only
006 * (the "License").  You may not use this file except in compliance
007 * with the License.
008 *
009 * You can obtain a copy of the license at legal-notices/CDDLv1_0.txt
010 * or http://forgerock.org/license/CDDLv1.0.html.
011 * See the License for the specific language governing permissions
012 * and limitations under the License.
013 *
014 * When distributing Covered Code, include this CDDL HEADER in each
015 * file and include the License file at legal-notices/CDDLv1_0.txt.
016 * If applicable, add the following below this CDDL HEADER, with the
017 * fields enclosed by brackets "[]" replaced with your own identifying
018 * information:
019 *      Portions Copyright [yyyy] [name of copyright owner]
020 *
021 * CDDL HEADER END
022 *
023 *
024 *      Copyright 2006-2009 Sun Microsystems, Inc.
025 *      Portions Copyright 2011-2015 ForgeRock AS.
026 */
027package org.opends.server.util;
028
029import java.text.SimpleDateFormat;
030import java.util.Calendar;
031import java.util.Date;
032import java.util.GregorianCalendar;
033import java.util.List;
034import java.util.Map;
035import java.util.TimeZone;
036import java.util.concurrent.ConcurrentHashMap;
037import java.util.concurrent.CopyOnWriteArrayList;
038import java.util.concurrent.Executors;
039import java.util.concurrent.ScheduledExecutorService;
040import java.util.concurrent.ThreadFactory;
041import java.util.concurrent.TimeUnit;
042
043import org.opends.server.api.DirectoryThread;
044import org.forgerock.i18n.slf4j.LocalizedLogger;
045import org.opends.server.schema.GeneralizedTimeSyntax;
046
047/**
048 * This class provides an application-wide timing service. It provides
049 * the ability to retrieve the current time in various different formats
050 * and resolutions.
051 */
052@org.opends.server.types.PublicAPI(
053    stability = org.opends.server.types.StabilityLevel.UNCOMMITTED,
054    mayInstantiate = false,
055    mayExtend = false,
056    mayInvoke = true)
057public final class TimeThread
058{
059
060  /**
061   * Timer job.
062   */
063  private static final class TimeInfo implements Runnable
064  {
065
066    /** The calendar holding the current time. */
067    private GregorianCalendar calendar;
068
069    /** The date for this time thread. */
070    private Date date;
071
072    /** The timestamp for this time thread in the generalized time format. */
073    private String generalizedTime;
074
075    /** The timestamp for this time thread in GMT. */
076    private String gmtTimestamp;
077
078    /** The date formatter that will be used to obtain the GMT timestamp. */
079    private final SimpleDateFormat gmtTimestampFormatter;
080
081    /** The current time in HHmm form as an integer. */
082    private int hourAndMinute;
083
084    /** The timestamp for this time thread in the local time zone. */
085    private String localTimestamp;
086
087    /** The date formatter that will be used to obtain the local timestamp. */
088    private final SimpleDateFormat localTimestampFormatter;
089
090    /** The current time in nanoseconds. */
091    private volatile long nanoTime;
092
093    /** The current time in milliseconds since the epoch. */
094    private volatile long time;
095
096    /**
097     * A set of arbitrary formatters that should be maintained by this time
098     * thread.
099     */
100    private final List<SimpleDateFormat> userDefinedFormatters;
101
102    /**
103     * A set of arbitrary formatted times, mapped from format string to the
104     * corresponding formatted time representation.
105     */
106    private final Map<String, String> userDefinedTimeStrings;
107
108
109
110    /**
111     * Create a new job with the specified delay.
112     */
113    public TimeInfo()
114    {
115      userDefinedFormatters = new CopyOnWriteArrayList<>();
116      userDefinedTimeStrings = new ConcurrentHashMap<>();
117
118      TimeZone utcTimeZone = TimeZone.getTimeZone("UTC");
119
120      gmtTimestampFormatter = new SimpleDateFormat("yyyyMMddHHmmss'Z'");
121      gmtTimestampFormatter.setTimeZone(utcTimeZone);
122
123      localTimestampFormatter =
124          new SimpleDateFormat("dd/MMM/yyyy:HH:mm:ss Z");
125
126      // Populate initial values.
127      run();
128    }
129
130
131
132    /** {@inheritDoc} */
133    @Override
134    public void run()
135    {
136      try
137      {
138        calendar = new GregorianCalendar();
139        date = calendar.getTime();
140        time = date.getTime();
141        nanoTime = System.nanoTime();
142        generalizedTime = GeneralizedTimeSyntax.format(date);
143        localTimestamp = localTimestampFormatter.format(date);
144        gmtTimestamp = gmtTimestampFormatter.format(date);
145        hourAndMinute =
146            calendar.get(Calendar.HOUR_OF_DAY) * 100
147                + calendar.get(Calendar.MINUTE);
148
149        for (SimpleDateFormat format : userDefinedFormatters)
150        {
151          userDefinedTimeStrings.put(format.toPattern(), format.format(date));
152        }
153      }
154      catch (Exception e)
155      {
156        logger.traceException(e);
157      }
158    }
159  }
160
161  /**
162   * Thread factory used by the scheduled execution service.
163   */
164  private static final class TimeThreadFactory implements
165      ThreadFactory
166  {
167
168    /** {@inheritDoc} */
169    @Override
170    public Thread newThread(Runnable r)
171    {
172      Thread t = new DirectoryThread(r, "Time Thread");
173      t.setDaemon(true);
174      return t;
175    }
176
177  }
178
179
180
181  /** The singleton instance. */
182  private static TimeThread INSTANCE = new TimeThread();
183
184  /** The tracer object for the debug logger. */
185  private static final LocalizedLogger logger = LocalizedLogger.getLoggerForThisClass();
186
187
188
189  /**
190   * Retrieves a <CODE>Calendar</CODE> containing the time at the last
191   * update.
192   *
193   * @return A <CODE>Calendar</CODE> containing the time at the last
194   *         update.
195   * @throws IllegalStateException
196   *           If the time service has not been started.
197   */
198  public static Calendar getCalendar() throws IllegalStateException
199  {
200    checkState();
201    return INSTANCE.timeInfo.calendar;
202  }
203
204
205
206  /**
207   * Retrieves a <CODE>Date</CODE> containing the time at the last
208   * update.
209   *
210   * @return A <CODE>Date</CODE> containing the time at the last update.
211   * @throws IllegalStateException
212   *           If the time service has not been started.
213   */
214  public static Date getDate() throws IllegalStateException
215  {
216    checkState();
217    return INSTANCE.timeInfo.date;
218  }
219
220
221
222  /**
223   * Retrieves a string containing a normalized representation of the
224   * current time in a generalized time format. The timestamp will look
225   * like "20050101000000.000Z".
226   *
227   * @return A string containing a normalized representation of the
228   *         current time in a generalized time format.
229   * @throws IllegalStateException
230   *           If the time service has not been started.
231   */
232  public static String getGeneralizedTime() throws IllegalStateException
233  {
234    checkState();
235    return INSTANCE.timeInfo.generalizedTime;
236  }
237
238
239
240  /**
241   * Retrieves a string containing the current time in GMT. The
242   * timestamp will look like "20050101000000Z".
243   *
244   * @return A string containing the current time in GMT.
245   * @throws IllegalStateException
246   *           If the time service has not been started.
247   */
248  public static String getGMTTime() throws IllegalStateException
249  {
250    checkState();
251    return INSTANCE.timeInfo.gmtTimestamp;
252  }
253
254
255
256  /**
257   * Retrieves an integer containing the time in HHmm format at the last
258   * update. It will be calculated as "(hourOfDay*100) + minuteOfHour".
259   *
260   * @return An integer containing the time in HHmm format at the last
261   *         update.
262   * @throws IllegalStateException
263   *           If the time service has not been started.
264   */
265  public static int getHourAndMinute() throws IllegalStateException
266  {
267    checkState();
268    return INSTANCE.timeInfo.hourAndMinute;
269  }
270
271
272
273  /**
274   * Retrieves a string containing the current time in the local time
275   * zone. The timestamp format will look like
276   * "01/Jan/2005:00:00:00 -0600".
277   *
278   * @return A string containing the current time in the local time
279   *         zone.
280   * @throws IllegalStateException
281   *           If the time service has not been started.
282   */
283  public static String getLocalTime() throws IllegalStateException
284  {
285    checkState();
286    return INSTANCE.timeInfo.localTimestamp;
287  }
288
289
290
291  /**
292   * Retrieves the time in nanoseconds from the most precise available system
293   * timer. The value returned represents nanoseconds since some fixed but
294   * arbitrary time.
295   *
296   * @return The time in nanoseconds from some fixed but arbitrary time.
297   * @throws IllegalStateException
298   *           If the time service has not been started.
299   */
300  public static long getNanoTime() throws IllegalStateException
301  {
302    checkState();
303    return INSTANCE.timeInfo.nanoTime;
304  }
305
306
307
308  /**
309   * Retrieves the time in milliseconds since the epoch at the last
310   * update.
311   *
312   * @return The time in milliseconds since the epoch at the last
313   *         update.
314   * @throws IllegalStateException
315   *           If the time service has not been started.
316   */
317  public static long getTime() throws IllegalStateException
318  {
319    checkState();
320    return INSTANCE.timeInfo.time;
321  }
322
323
324
325  /**
326   * Retrieves the current time formatted using the given format string.
327   * <p>
328   * The first time this method is used with a given format string, it
329   * will be used to create a formatter that will generate the time
330   * string. That formatter will then be put into a list so that it will
331   * be maintained automatically for future use.
332   *
333   * @param formatString
334   *          The string that defines the format of the time string to
335   *          retrieve.
336   * @return The formatted time string.
337   * @throws IllegalArgumentException
338   *           If the provided format string is invalid.
339   * @throws IllegalStateException
340   *           If the time service has not been started.
341   */
342  public static String getUserDefinedTime(String formatString)
343      throws IllegalArgumentException, IllegalStateException
344  {
345    checkState();
346
347    String timeString =
348        INSTANCE.timeInfo.userDefinedTimeStrings.get(formatString);
349
350    if (timeString == null)
351    {
352      SimpleDateFormat formatter = new SimpleDateFormat(formatString);
353      timeString = formatter.format(INSTANCE.timeInfo.date);
354      INSTANCE.timeInfo.userDefinedTimeStrings.put(formatString,
355          timeString);
356      INSTANCE.timeInfo.userDefinedFormatters.add(formatter);
357    }
358
359    return timeString;
360  }
361
362
363
364  /**
365   * Removes the user-defined time formatter from this time thread so
366   * that it will no longer be maintained. This is a safe operation
367   * because if the same format string is used for multiple purposes
368   * then it will be added back the next time a time is requested with
369   * the given format.
370   *
371   * @param formatString
372   *          The format string for the date formatter to remove.
373   * @throws IllegalStateException
374   *           If the time service has not been started.
375   */
376  public static void removeUserDefinedFormatter(String formatString)
377    throws IllegalStateException
378  {
379    checkState();
380
381    INSTANCE.timeInfo.userDefinedFormatters.remove(new SimpleDateFormat(
382        formatString));
383    INSTANCE.timeInfo.userDefinedTimeStrings.remove(formatString);
384  }
385
386
387
388  /**
389   * Starts the time service if it has not already been started.
390   */
391  public static void start()
392  {
393    if (INSTANCE == null)
394    {
395      INSTANCE = new TimeThread();
396    }
397  }
398
399
400
401  /**
402   * Stops the time service if it has not already been stopped.
403   */
404  public static void stop()
405  {
406    if (INSTANCE != null)
407    {
408      INSTANCE.scheduler.shutdown();
409      INSTANCE = null;
410    }
411  }
412
413
414
415  /** Ensures that the time service has been started. */
416  private static void checkState() throws IllegalStateException
417  {
418    if (INSTANCE == null)
419    {
420      throw new IllegalStateException("Time service not started");
421    }
422  }
423
424
425
426  /** The scheduler. */
427  private final ScheduledExecutorService scheduler =
428      Executors.newSingleThreadScheduledExecutor(new TimeThreadFactory());
429
430  /** The current time information. */
431  private final TimeInfo timeInfo = new TimeInfo();
432
433
434
435  /**
436   * Creates a new instance of this time service and starts it.
437   */
438  private TimeThread()
439  {
440    this.scheduler.scheduleWithFixedDelay(timeInfo, 0, 200,
441        TimeUnit.MILLISECONDS);
442  }
443}