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}