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