001/* 002 * Copyright 2008-2024 Ping Identity Corporation 003 * All Rights Reserved. 004 */ 005/* 006 * Copyright 2008-2024 Ping Identity Corporation 007 * 008 * Licensed under the Apache License, Version 2.0 (the "License"); 009 * you may not use this file except in compliance with the License. 010 * You may obtain a copy of the License at 011 * 012 * http://www.apache.org/licenses/LICENSE-2.0 013 * 014 * Unless required by applicable law or agreed to in writing, software 015 * distributed under the License is distributed on an "AS IS" BASIS, 016 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 017 * See the License for the specific language governing permissions and 018 * limitations under the License. 019 */ 020/* 021 * Copyright (C) 2008-2024 Ping Identity Corporation 022 * 023 * This program is free software; you can redistribute it and/or modify 024 * it under the terms of the GNU General Public License (GPLv2 only) 025 * or the terms of the GNU Lesser General Public License (LGPLv2.1 only) 026 * as published by the Free Software Foundation. 027 * 028 * This program is distributed in the hope that it will be useful, 029 * but WITHOUT ANY WARRANTY; without even the implied warranty of 030 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 031 * GNU General Public License for more details. 032 * 033 * You should have received a copy of the GNU General Public License 034 * along with this program; if not, see <http://www.gnu.org/licenses>. 035 */ 036package com.unboundid.ldap.matchingrules; 037 038 039 040import java.text.ParseException; 041import java.text.SimpleDateFormat; 042import java.util.Date; 043import java.util.TimeZone; 044 045import com.unboundid.asn1.ASN1OctetString; 046import com.unboundid.ldap.sdk.LDAPException; 047import com.unboundid.ldap.sdk.ResultCode; 048import com.unboundid.util.Debug; 049import com.unboundid.util.NotNull; 050import com.unboundid.util.Nullable; 051import com.unboundid.util.StaticUtils; 052import com.unboundid.util.ThreadSafety; 053import com.unboundid.util.ThreadSafetyLevel; 054 055import static com.unboundid.ldap.matchingrules.MatchingRuleMessages.*; 056 057 058 059/** 060 * This class provides an implementation of a matching rule that performs 061 * equality and ordering comparisons against values that should be timestamps 062 * in the generalized time syntax. Substring matching is not supported. 063 */ 064@ThreadSafety(level=ThreadSafetyLevel.COMPLETELY_THREADSAFE) 065public final class GeneralizedTimeMatchingRule 066 extends MatchingRule 067{ 068 /** 069 * The singleton instance that will be returned from the {@code getInstance} 070 * method. 071 */ 072 @NotNull private static final GeneralizedTimeMatchingRule INSTANCE = 073 new GeneralizedTimeMatchingRule(); 074 075 076 077 /** 078 * The date format that will be used for formatting generalized time values, 079 * assuming that the associated formatter is using the UTC time zone. 080 */ 081 @NotNull private static final String GENERALIZED_TIME_DATE_FORMAT = 082 "yyyyMMddHHmmss.SSS'Z'"; 083 084 085 086 /** 087 * A reference to the "UTC" time zone. 088 */ 089 @NotNull private static final TimeZone UTC_TIME_ZONE = 090 TimeZone.getTimeZone("UTC"); 091 092 093 094 /** 095 * The name for the generalizedTimeMatch equality matching rule. 096 */ 097 @NotNull public static final String EQUALITY_RULE_NAME = 098 "generalizedTimeMatch"; 099 100 101 102 /** 103 * The name for the generalizedTimeMatch equality matching rule, formatted in 104 * all lowercase characters. 105 */ 106 @NotNull static final String LOWER_EQUALITY_RULE_NAME = 107 StaticUtils.toLowerCase(EQUALITY_RULE_NAME); 108 109 110 111 /** 112 * The OID for the generalizedTimeMatch equality matching rule. 113 */ 114 @NotNull public static final String EQUALITY_RULE_OID = "2.5.13.27"; 115 116 117 118 /** 119 * The name for the generalizedTimeOrderingMatch ordering matching rule. 120 */ 121 @NotNull public static final String ORDERING_RULE_NAME = 122 "generalizedTimeOrderingMatch"; 123 124 125 126 /** 127 * The name for the generalizedTimeOrderingMatch ordering matching rule, 128 * formatted in all lowercase characters. 129 */ 130 @NotNull static final String LOWER_ORDERING_RULE_NAME = 131 StaticUtils.toLowerCase(ORDERING_RULE_NAME); 132 133 134 135 /** 136 * The OID for the generalizedTimeOrderingMatch ordering matching rule. 137 */ 138 @NotNull public static final String ORDERING_RULE_OID = "2.5.13.28"; 139 140 141 142 /** 143 * The serial version UID for this serializable class. 144 */ 145 private static final long serialVersionUID = -6317451154598148593L; 146 147 148 149 // The thread-local date formatter for this class. 150 @NotNull private static final ThreadLocal<SimpleDateFormat> dateFormat = 151 new ThreadLocal<>(); 152 153 154 155 /** 156 * Creates a new instance of this generalized time matching rule. 157 */ 158 public GeneralizedTimeMatchingRule() 159 { 160 // No implementation is required. 161 } 162 163 164 165 /** 166 * Retrieves a singleton instance of this matching rule. 167 * 168 * @return A singleton instance of this matching rule. 169 */ 170 @NotNull() 171 public static GeneralizedTimeMatchingRule getInstance() 172 { 173 return INSTANCE; 174 } 175 176 177 178 /** 179 * {@inheritDoc} 180 */ 181 @Override() 182 @NotNull() 183 public String getEqualityMatchingRuleName() 184 { 185 return EQUALITY_RULE_NAME; 186 } 187 188 189 190 /** 191 * {@inheritDoc} 192 */ 193 @Override() 194 @NotNull() 195 public String getEqualityMatchingRuleOID() 196 { 197 return EQUALITY_RULE_OID; 198 } 199 200 201 202 /** 203 * {@inheritDoc} 204 */ 205 @Override() 206 @NotNull() 207 public String getOrderingMatchingRuleName() 208 { 209 return ORDERING_RULE_NAME; 210 } 211 212 213 214 /** 215 * {@inheritDoc} 216 */ 217 @Override() 218 @NotNull() 219 public String getOrderingMatchingRuleOID() 220 { 221 return ORDERING_RULE_OID; 222 } 223 224 225 226 /** 227 * {@inheritDoc} 228 */ 229 @Override() 230 @Nullable() 231 public String getSubstringMatchingRuleName() 232 { 233 return null; 234 } 235 236 237 238 /** 239 * {@inheritDoc} 240 */ 241 @Override() 242 @Nullable() 243 public String getSubstringMatchingRuleOID() 244 { 245 return null; 246 } 247 248 249 250 /** 251 * {@inheritDoc} 252 */ 253 @Override() 254 public boolean valuesMatch(@NotNull final ASN1OctetString value1, 255 @NotNull final ASN1OctetString value2) 256 throws LDAPException 257 { 258 final Date d1; 259 try 260 { 261 d1 = StaticUtils.decodeGeneralizedTime(value1.stringValue()); 262 } 263 catch (final ParseException pe) 264 { 265 Debug.debugException(pe); 266 throw new LDAPException(ResultCode.INVALID_ATTRIBUTE_SYNTAX, 267 ERR_GENERALIZED_TIME_INVALID_VALUE.get(pe.getMessage()), pe); 268 } 269 270 final Date d2; 271 try 272 { 273 d2 = StaticUtils.decodeGeneralizedTime(value2.stringValue()); 274 } 275 catch (final ParseException pe) 276 { 277 Debug.debugException(pe); 278 throw new LDAPException(ResultCode.INVALID_ATTRIBUTE_SYNTAX, 279 ERR_GENERALIZED_TIME_INVALID_VALUE.get(pe.getMessage()), pe); 280 } 281 282 return d1.equals(d2); 283 } 284 285 286 287 /** 288 * {@inheritDoc} 289 */ 290 @Override() 291 public boolean matchesAnyValue(@NotNull final ASN1OctetString assertionValue, 292 @NotNull final ASN1OctetString[] attributeValues) 293 throws LDAPException 294 { 295 if ((assertionValue == null) || (attributeValues == null) || 296 (attributeValues.length == 0)) 297 { 298 return false; 299 } 300 301 final Date assertionValueDate; 302 try 303 { 304 assertionValueDate = 305 StaticUtils.decodeGeneralizedTime(assertionValue.stringValue()); 306 } 307 catch (final ParseException pe) 308 { 309 Debug.debugException(pe); 310 throw new LDAPException(ResultCode.INVALID_ATTRIBUTE_SYNTAX, 311 ERR_GENERALIZED_TIME_INVALID_VALUE.get(pe.getMessage()), pe); 312 } 313 314 for (final ASN1OctetString attributeValue : attributeValues) 315 { 316 try 317 { 318 if (assertionValueDate.equals( 319 StaticUtils.decodeGeneralizedTime(attributeValue.stringValue()))) 320 { 321 return true; 322 } 323 } 324 catch (final Exception e) 325 { 326 Debug.debugException(e); 327 } 328 } 329 330 return false; 331 } 332 333 334 335 /** 336 * {@inheritDoc} 337 */ 338 @Override() 339 public boolean matchesSubstring(@NotNull final ASN1OctetString value, 340 @Nullable final ASN1OctetString subInitial, 341 @Nullable final ASN1OctetString[] subAny, 342 @Nullable final ASN1OctetString subFinal) 343 throws LDAPException 344 { 345 throw new LDAPException(ResultCode.INAPPROPRIATE_MATCHING, 346 ERR_GENERALIZED_TIME_SUBSTRING_MATCHING_NOT_SUPPORTED.get()); 347 } 348 349 350 351 /** 352 * {@inheritDoc} 353 */ 354 @Override() 355 public int compareValues(@NotNull final ASN1OctetString value1, 356 @NotNull final ASN1OctetString value2) 357 throws LDAPException 358 { 359 final Date d1; 360 try 361 { 362 d1 = StaticUtils.decodeGeneralizedTime(value1.stringValue()); 363 } 364 catch (final ParseException pe) 365 { 366 Debug.debugException(pe); 367 throw new LDAPException(ResultCode.INVALID_ATTRIBUTE_SYNTAX, 368 ERR_GENERALIZED_TIME_INVALID_VALUE.get(pe.getMessage()), pe); 369 } 370 371 final Date d2; 372 try 373 { 374 d2 = StaticUtils.decodeGeneralizedTime(value2.stringValue()); 375 } 376 catch (final ParseException pe) 377 { 378 Debug.debugException(pe); 379 throw new LDAPException(ResultCode.INVALID_ATTRIBUTE_SYNTAX, 380 ERR_GENERALIZED_TIME_INVALID_VALUE.get(pe.getMessage()), pe); 381 } 382 383 return d1.compareTo(d2); 384 } 385 386 387 388 /** 389 * {@inheritDoc} 390 */ 391 @Override() 392 @NotNull() 393 public ASN1OctetString normalize(@NotNull final ASN1OctetString value) 394 throws LDAPException 395 { 396 final Date d; 397 try 398 { 399 d = StaticUtils.decodeGeneralizedTime(value.stringValue()); 400 } 401 catch (final ParseException pe) 402 { 403 Debug.debugException(pe); 404 throw new LDAPException(ResultCode.INVALID_ATTRIBUTE_SYNTAX, 405 ERR_GENERALIZED_TIME_INVALID_VALUE.get(pe.getMessage()), pe); 406 } 407 408 SimpleDateFormat f = dateFormat.get(); 409 if (f == null) 410 { 411 f = new SimpleDateFormat(GENERALIZED_TIME_DATE_FORMAT); 412 f.setTimeZone(UTC_TIME_ZONE); 413 dateFormat.set(f); 414 } 415 416 return new ASN1OctetString(f.format(d)); 417 } 418 419 420 421 /** 422 * {@inheritDoc} 423 */ 424 @Override() 425 @NotNull() 426 public ASN1OctetString normalizeSubstring( 427 @NotNull final ASN1OctetString value, 428 final byte substringType) 429 throws LDAPException 430 { 431 throw new LDAPException(ResultCode.INAPPROPRIATE_MATCHING, 432 ERR_GENERALIZED_TIME_SUBSTRING_MATCHING_NOT_SUPPORTED.get()); 433 } 434}