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 010 * trunk/opends/resource/legal-notices/OpenDS.LICENSE 011 * or https://OpenDS.dev.java.net/OpenDS.LICENSE. 012 * See the License for the specific language governing permissions 013 * and limitations under the License. 014 * 015 * When distributing Covered Code, include this CDDL HEADER in each 016 * file and include the License file at 017 * trunk/opends/resource/legal-notices/OpenDS.LICENSE. If applicable, 018 * add the following below this CDDL HEADER, with the fields enclosed 019 * by brackets "[]" replaced with your own identifying information: 020 * Portions Copyright [yyyy] [name of copyright owner] 021 * 022 * CDDL HEADER END 023 * 024 * 025 * Copyright 2006-2008 Sun Microsystems, Inc. 026 */ 027 package org.opends.server.schema; 028 029 030 031 import java.text.SimpleDateFormat; 032 import java.util.Calendar; 033 import java.util.Date; 034 import java.util.GregorianCalendar; 035 import java.util.TimeZone; 036 037 import org.opends.messages.Message; 038 import org.opends.messages.MessageBuilder; 039 import org.opends.server.admin.std.server.AttributeSyntaxCfg; 040 import org.opends.server.api.ApproximateMatchingRule; 041 import org.opends.server.api.AttributeSyntax; 042 import org.opends.server.api.EqualityMatchingRule; 043 import org.opends.server.api.OrderingMatchingRule; 044 import org.opends.server.api.SubstringMatchingRule; 045 import org.opends.server.config.ConfigException; 046 import org.opends.server.core.DirectoryServer; 047 import org.opends.server.loggers.debug.DebugTracer; 048 import org.opends.server.protocols.asn1.ASN1OctetString; 049 import org.opends.server.types.AttributeValue; 050 import org.opends.server.types.ByteString; 051 import org.opends.server.types.DebugLogLevel; 052 import org.opends.server.types.DirectoryException; 053 import org.opends.server.types.ResultCode; 054 055 import static org.opends.messages.SchemaMessages.*; 056 import static org.opends.server.loggers.ErrorLogger.*; 057 import static org.opends.server.loggers.debug.DebugLogger.*; 058 import static org.opends.server.schema.SchemaConstants.*; 059 import static org.opends.server.util.ServerConstants.*; 060 061 062 063 /** 064 * This class defines the generalized time attribute syntax, which is a way of 065 * representing time in a form like "YYYYMMDDhhmmssZ". The actual form is 066 * somewhat flexible, and may omit the minute and second information, or may 067 * include sub-second information. It may also replace "Z" with a time zone 068 * offset like "-0500" for representing values that are not in UTC. 069 */ 070 public class GeneralizedTimeSyntax 071 extends AttributeSyntax<AttributeSyntaxCfg> 072 { 073 /** 074 * The tracer object for the debug logger. 075 */ 076 private static final DebugTracer TRACER = getTracer(); 077 078 /** 079 * The lock that will be used to provide threadsafe access to the date 080 * formatter. 081 */ 082 private static Object dateFormatLock; 083 084 085 086 /** 087 * The date formatter that will be used to convert dates into generalized time 088 * values. Note that all interaction with it must be synchronized. 089 */ 090 private static SimpleDateFormat dateFormat; 091 092 093 094 // The default equality matching rule for this syntax. 095 private EqualityMatchingRule defaultEqualityMatchingRule; 096 097 // The default ordering matching rule for this syntax. 098 private OrderingMatchingRule defaultOrderingMatchingRule; 099 100 // The default substring matching rule for this syntax. 101 private SubstringMatchingRule defaultSubstringMatchingRule; 102 103 104 105 /* 106 * Create the date formatter that will be used to construct and parse 107 * normalized generalized time values. 108 */ 109 static 110 { 111 dateFormat = new SimpleDateFormat(DATE_FORMAT_GENERALIZED_TIME); 112 dateFormat.setLenient(false); 113 dateFormat.setTimeZone(TimeZone.getTimeZone("UTC")); 114 115 dateFormatLock = new Object(); 116 } 117 118 119 120 /** 121 * Creates a new instance of this syntax. Note that the only thing that 122 * should be done here is to invoke the default constructor for the 123 * superclass. All initialization should be performed in the 124 * <CODE>initializeSyntax</CODE> method. 125 */ 126 public GeneralizedTimeSyntax() 127 { 128 super(); 129 } 130 131 132 133 /** 134 * {@inheritDoc} 135 */ 136 public void initializeSyntax(AttributeSyntaxCfg configuration) 137 throws ConfigException 138 { 139 defaultEqualityMatchingRule = 140 DirectoryServer.getEqualityMatchingRule(EMR_GENERALIZED_TIME_OID); 141 if (defaultEqualityMatchingRule == null) 142 { 143 logError(ERR_ATTR_SYNTAX_UNKNOWN_EQUALITY_MATCHING_RULE.get( 144 EMR_GENERALIZED_TIME_OID, SYNTAX_GENERALIZED_TIME_NAME)); 145 } 146 147 defaultOrderingMatchingRule = 148 DirectoryServer.getOrderingMatchingRule(OMR_GENERALIZED_TIME_OID); 149 if (defaultOrderingMatchingRule == null) 150 { 151 logError(ERR_ATTR_SYNTAX_UNKNOWN_ORDERING_MATCHING_RULE.get( 152 OMR_GENERALIZED_TIME_OID, SYNTAX_GENERALIZED_TIME_NAME)); 153 } 154 155 defaultSubstringMatchingRule = 156 DirectoryServer.getSubstringMatchingRule(SMR_CASE_IGNORE_OID); 157 if (defaultSubstringMatchingRule == null) 158 { 159 logError(ERR_ATTR_SYNTAX_UNKNOWN_SUBSTRING_MATCHING_RULE.get( 160 SMR_CASE_IGNORE_OID, SYNTAX_GENERALIZED_TIME_NAME)); 161 } 162 } 163 164 165 166 /** 167 * Retrieves the common name for this attribute syntax. 168 * 169 * @return The common name for this attribute syntax. 170 */ 171 public String getSyntaxName() 172 { 173 return SYNTAX_GENERALIZED_TIME_NAME; 174 } 175 176 177 178 /** 179 * Retrieves the OID for this attribute syntax. 180 * 181 * @return The OID for this attribute syntax. 182 */ 183 public String getOID() 184 { 185 return SYNTAX_GENERALIZED_TIME_OID; 186 } 187 188 189 190 /** 191 * Retrieves a description for this attribute syntax. 192 * 193 * @return A description for this attribute syntax. 194 */ 195 public String getDescription() 196 { 197 return SYNTAX_GENERALIZED_TIME_DESCRIPTION; 198 } 199 200 201 202 /** 203 * Retrieves the default equality matching rule that will be used for 204 * attributes with this syntax. 205 * 206 * @return The default equality matching rule that will be used for 207 * attributes with this syntax, or <CODE>null</CODE> if equality 208 * matches will not be allowed for this type by default. 209 */ 210 public EqualityMatchingRule getEqualityMatchingRule() 211 { 212 return defaultEqualityMatchingRule; 213 } 214 215 216 217 /** 218 * Retrieves the default ordering matching rule that will be used for 219 * attributes with this syntax. 220 * 221 * @return The default ordering matching rule that will be used for 222 * attributes with this syntax, or <CODE>null</CODE> if ordering 223 * matches will not be allowed for this type by default. 224 */ 225 public OrderingMatchingRule getOrderingMatchingRule() 226 { 227 return defaultOrderingMatchingRule; 228 } 229 230 231 232 /** 233 * Retrieves the default substring matching rule that will be used for 234 * attributes with this syntax. 235 * 236 * @return The default substring matching rule that will be used for 237 * attributes with this syntax, or <CODE>null</CODE> if substring 238 * matches will not be allowed for this type by default. 239 */ 240 public SubstringMatchingRule getSubstringMatchingRule() 241 { 242 return defaultSubstringMatchingRule; 243 } 244 245 246 247 /** 248 * Retrieves the default approximate matching rule that will be used for 249 * attributes with this syntax. 250 * 251 * @return The default approximate matching rule that will be used for 252 * attributes with this syntax, or <CODE>null</CODE> if approximate 253 * matches will not be allowed for this type by default. 254 */ 255 public ApproximateMatchingRule getApproximateMatchingRule() 256 { 257 // Approximate matching will not be allowed by default. 258 return null; 259 } 260 261 262 263 /** 264 * Indicates whether the provided value is acceptable for use in an attribute 265 * with this syntax. If it is not, then the reason may be appended to the 266 * provided buffer. 267 * 268 * @param value The value for which to make the determination. 269 * @param invalidReason The buffer to which the invalid reason should be 270 * appended. 271 * 272 * @return <CODE>true</CODE> if the provided value is acceptable for use with 273 * this syntax, or <CODE>false</CODE> if not. 274 */ 275 public boolean valueIsAcceptable(ByteString value, 276 MessageBuilder invalidReason) 277 { 278 try 279 { 280 decodeGeneralizedTimeValue(value); 281 return true; 282 } 283 catch (DirectoryException de) 284 { 285 invalidReason.append(de.getMessageObject()); 286 return false; 287 } 288 } 289 290 291 292 /** 293 * Retrieves the generalized time representation of the provided date. 294 * 295 * @param d The date to retrieve in generalized time form. 296 * 297 * @return The generalized time representation of the provided date. 298 */ 299 public static String format(Date d) 300 { 301 synchronized (dateFormatLock) 302 { 303 return dateFormat.format(d); 304 } 305 } 306 307 308 309 /** 310 * Retrieves the generalized time representation of the provided date. 311 * 312 * @param t The timestamp to retrieve in generalized time form. 313 * 314 * @return The generalized time representation of the provided date. 315 */ 316 public static String format(long t) 317 { 318 synchronized (dateFormatLock) 319 { 320 return dateFormat.format(new Date(t)); 321 } 322 } 323 324 325 326 327 /** 328 * Retrieves an attribute value containing a generalized time representation 329 * of the provided date. 330 * 331 * @param time The time for which to retrieve the generalized time value. 332 * 333 * @return The attribute value created from the date. 334 */ 335 public static AttributeValue createGeneralizedTimeValue(long time) 336 { 337 String valueString; 338 339 synchronized (dateFormatLock) 340 { 341 valueString = dateFormat.format(new Date(time)); 342 } 343 344 return new AttributeValue(new ASN1OctetString(valueString), 345 new ASN1OctetString(valueString)); 346 } 347 348 349 350 /** 351 * Decodes the provided normalized value as a generalized time value and 352 * retrieves a timestamp containing its representation. 353 * 354 * @param value The normalized value to decode using the generalized time 355 * syntax. 356 * 357 * @return The timestamp created from the provided generalized time value. 358 * 359 * @throws DirectoryException If the provided value cannot be parsed as a 360 * valid generalized time string. 361 */ 362 public static long decodeGeneralizedTimeValue(ByteString value) 363 throws DirectoryException 364 { 365 int year = 0; 366 int month = 0; 367 int day = 0; 368 int hour = 0; 369 int minute = 0; 370 int second = 0; 371 372 373 // Get the value as a string and verify that it is at least long enough for 374 // "YYYYMMDDhhZ", which is the shortest allowed value. 375 String valueString = value.stringValue().toUpperCase(); 376 int length = valueString.length(); 377 if (length < 11) 378 { 379 Message message = 380 WARN_ATTR_SYNTAX_GENERALIZED_TIME_TOO_SHORT.get(valueString); 381 throw new DirectoryException( 382 ResultCode.INVALID_ATTRIBUTE_SYNTAX, message); 383 } 384 385 386 // The first four characters are the century and year, and they must be 387 // numeric digits between 0 and 9. 388 for (int i=0; i < 4; i++) 389 { 390 switch (valueString.charAt(i)) 391 { 392 case '0': 393 year = (year * 10); 394 break; 395 396 case '1': 397 year = (year * 10) + 1; 398 break; 399 400 case '2': 401 year = (year * 10) + 2; 402 break; 403 404 case '3': 405 year = (year * 10) + 3; 406 break; 407 408 case '4': 409 year = (year * 10) + 4; 410 break; 411 412 case '5': 413 year = (year * 10) + 5; 414 break; 415 416 case '6': 417 year = (year * 10) + 6; 418 break; 419 420 case '7': 421 year = (year * 10) + 7; 422 break; 423 424 case '8': 425 year = (year * 10) + 8; 426 break; 427 428 case '9': 429 year = (year * 10) + 9; 430 break; 431 432 default: 433 Message message = WARN_ATTR_SYNTAX_GENERALIZED_TIME_INVALID_YEAR.get( 434 valueString, String.valueOf(valueString.charAt(i))); 435 throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX, 436 message); 437 } 438 } 439 440 441 // The next two characters are the month, and they must form the string 442 // representation of an integer between 01 and 12. 443 char m1 = valueString.charAt(4); 444 char m2 = valueString.charAt(5); 445 switch (m1) 446 { 447 case '0': 448 // m2 must be a digit between 1 and 9. 449 switch (m2) 450 { 451 case '1': 452 month = Calendar.JANUARY; 453 break; 454 455 case '2': 456 month = Calendar.FEBRUARY; 457 break; 458 459 case '3': 460 month = Calendar.MARCH; 461 break; 462 463 case '4': 464 month = Calendar.APRIL; 465 break; 466 467 case '5': 468 month = Calendar.MAY; 469 break; 470 471 case '6': 472 month = Calendar.JUNE; 473 break; 474 475 case '7': 476 month = Calendar.JULY; 477 break; 478 479 case '8': 480 month = Calendar.AUGUST; 481 break; 482 483 case '9': 484 month = Calendar.SEPTEMBER; 485 break; 486 487 default: 488 Message message = 489 WARN_ATTR_SYNTAX_GENERALIZED_TIME_INVALID_MONTH.get(valueString, 490 valueString.substring(4, 6)); 491 throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX, 492 message); 493 } 494 break; 495 case '1': 496 // m2 must be a digit between 0 and 2. 497 switch (m2) 498 { 499 case '0': 500 month = Calendar.OCTOBER; 501 break; 502 503 case '1': 504 month = Calendar.NOVEMBER; 505 break; 506 507 case '2': 508 month = Calendar.DECEMBER; 509 break; 510 511 default: 512 Message message = 513 WARN_ATTR_SYNTAX_GENERALIZED_TIME_INVALID_MONTH.get(valueString, 514 valueString.substring(4, 6)); 515 throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX, 516 message); 517 } 518 break; 519 default: 520 Message message = 521 WARN_ATTR_SYNTAX_GENERALIZED_TIME_INVALID_MONTH.get(valueString, 522 valueString.substring(4, 6)); 523 throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX, 524 message); 525 } 526 527 528 // The next two characters should be the day of the month, and they must 529 // form the string representation of an integer between 01 and 31. 530 // This doesn't do any validation against the year or month, so it will 531 // allow dates like April 31, or February 29 in a non-leap year, but we'll 532 // let those slide. 533 char d1 = valueString.charAt(6); 534 char d2 = valueString.charAt(7); 535 switch (d1) 536 { 537 case '0': 538 // d2 must be a digit between 1 and 9. 539 switch (d2) 540 { 541 case '1': 542 day = 1; 543 break; 544 545 case '2': 546 day = 2; 547 break; 548 549 case '3': 550 day = 3; 551 break; 552 553 case '4': 554 day = 4; 555 break; 556 557 case '5': 558 day = 5; 559 break; 560 561 case '6': 562 day = 6; 563 break; 564 565 case '7': 566 day = 7; 567 break; 568 569 case '8': 570 day = 8; 571 break; 572 573 case '9': 574 day = 9; 575 break; 576 577 default: 578 Message message = 579 WARN_ATTR_SYNTAX_GENERALIZED_TIME_INVALID_DAY.get(valueString, 580 valueString.substring(6, 8)); 581 throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX, 582 message); 583 } 584 break; 585 586 case '1': 587 // d2 must be a digit between 0 and 9. 588 switch (d2) 589 { 590 case '0': 591 day = 10; 592 break; 593 594 case '1': 595 day = 11; 596 break; 597 598 case '2': 599 day = 12; 600 break; 601 602 case '3': 603 day = 13; 604 break; 605 606 case '4': 607 day = 14; 608 break; 609 610 case '5': 611 day = 15; 612 break; 613 614 case '6': 615 day = 16; 616 break; 617 618 case '7': 619 day = 17; 620 break; 621 622 case '8': 623 day = 18; 624 break; 625 626 case '9': 627 day = 19; 628 break; 629 630 default: 631 Message message = 632 WARN_ATTR_SYNTAX_GENERALIZED_TIME_INVALID_DAY.get(valueString, 633 valueString.substring(6, 8)); 634 throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX, 635 message); 636 } 637 break; 638 639 case '2': 640 // d2 must be a digit between 0 and 9. 641 switch (d2) 642 { 643 case '0': 644 day = 20; 645 break; 646 647 case '1': 648 day = 21; 649 break; 650 651 case '2': 652 day = 22; 653 break; 654 655 case '3': 656 day = 23; 657 break; 658 659 case '4': 660 day = 24; 661 break; 662 663 case '5': 664 day = 25; 665 break; 666 667 case '6': 668 day = 26; 669 break; 670 671 case '7': 672 day = 27; 673 break; 674 675 case '8': 676 day = 28; 677 break; 678 679 case '9': 680 day = 29; 681 break; 682 683 default: 684 Message message = 685 WARN_ATTR_SYNTAX_GENERALIZED_TIME_INVALID_DAY.get(valueString, 686 valueString.substring(6, 8)); 687 throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX, 688 message); 689 } 690 break; 691 692 case '3': 693 // d2 must be either 0 or 1. 694 switch (d2) 695 { 696 case '0': 697 day = 30; 698 break; 699 700 case '1': 701 day = 31; 702 break; 703 704 default: 705 Message message = 706 WARN_ATTR_SYNTAX_GENERALIZED_TIME_INVALID_DAY.get(valueString, 707 valueString.substring(6, 8)); 708 throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX, 709 message); 710 } 711 break; 712 713 default: 714 Message message = 715 WARN_ATTR_SYNTAX_GENERALIZED_TIME_INVALID_DAY.get(valueString, 716 valueString.substring(6, 8)); 717 throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX, 718 message); 719 } 720 721 722 // The next two characters must be the hour, and they must form the string 723 // representation of an integer between 00 and 23. 724 char h1 = valueString.charAt(8); 725 char h2 = valueString.charAt(9); 726 switch (h1) 727 { 728 case '0': 729 switch (h2) 730 { 731 case '0': 732 hour = 0; 733 break; 734 735 case '1': 736 hour = 1; 737 break; 738 739 case '2': 740 hour = 2; 741 break; 742 743 case '3': 744 hour = 3; 745 break; 746 747 case '4': 748 hour = 4; 749 break; 750 751 case '5': 752 hour = 5; 753 break; 754 755 case '6': 756 hour = 6; 757 break; 758 759 case '7': 760 hour = 7; 761 break; 762 763 case '8': 764 hour = 8; 765 break; 766 767 case '9': 768 hour = 9; 769 break; 770 771 default: 772 Message message = 773 WARN_ATTR_SYNTAX_GENERALIZED_TIME_INVALID_HOUR.get(valueString, 774 valueString.substring(8, 10)); 775 throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX, 776 message); 777 } 778 break; 779 780 case '1': 781 switch (h2) 782 { 783 case '0': 784 hour = 10; 785 break; 786 787 case '1': 788 hour = 11; 789 break; 790 791 case '2': 792 hour = 12; 793 break; 794 795 case '3': 796 hour = 13; 797 break; 798 799 case '4': 800 hour = 14; 801 break; 802 803 case '5': 804 hour = 15; 805 break; 806 807 case '6': 808 hour = 16; 809 break; 810 811 case '7': 812 hour = 17; 813 break; 814 815 case '8': 816 hour = 18; 817 break; 818 819 case '9': 820 hour = 19; 821 break; 822 823 default: 824 Message message = 825 WARN_ATTR_SYNTAX_GENERALIZED_TIME_INVALID_HOUR.get(valueString, 826 valueString.substring(8, 10)); 827 throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX, 828 message); 829 } 830 break; 831 832 case '2': 833 switch (h2) 834 { 835 case '0': 836 hour = 20; 837 break; 838 839 case '1': 840 hour = 21; 841 break; 842 843 case '2': 844 hour = 22; 845 break; 846 847 case '3': 848 hour = 23; 849 break; 850 851 default: 852 Message message = 853 WARN_ATTR_SYNTAX_GENERALIZED_TIME_INVALID_HOUR.get(valueString, 854 valueString.substring(8, 10)); 855 throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX, 856 message); 857 } 858 break; 859 860 default: 861 Message message = 862 WARN_ATTR_SYNTAX_GENERALIZED_TIME_INVALID_HOUR.get(valueString, 863 valueString.substring(8, 10)); 864 throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX, 865 message); 866 } 867 868 869 // Next, there should be either two digits comprising an integer between 00 870 // and 59 (for the minute), a letter 'Z' (for the UTC specifier), a plus 871 // or minus sign followed by two or four digits (for the UTC offset), or a 872 // period or comma representing the fraction. 873 m1 = valueString.charAt(10); 874 switch (m1) 875 { 876 case '0': 877 case '1': 878 case '2': 879 case '3': 880 case '4': 881 case '5': 882 // There must be at least two more characters, and the next one must 883 // be a digit between 0 and 9. 884 if (length < 13) 885 { 886 Message message = WARN_ATTR_SYNTAX_GENERALIZED_TIME_INVALID_CHAR.get( 887 valueString, String.valueOf(m1), 10); 888 throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX, 889 message); 890 } 891 892 893 minute = 10 * (m1 - '0'); 894 895 switch (valueString.charAt(11)) 896 { 897 case '0': 898 break; 899 900 case '1': 901 minute += 1; 902 break; 903 904 case '2': 905 minute += 2; 906 break; 907 908 case '3': 909 minute += 3; 910 break; 911 912 case '4': 913 minute += 4; 914 break; 915 916 case '5': 917 minute += 5; 918 break; 919 920 case '6': 921 minute += 6; 922 break; 923 924 case '7': 925 minute += 7; 926 break; 927 928 case '8': 929 minute += 8; 930 break; 931 932 case '9': 933 minute += 9; 934 break; 935 936 default: 937 Message message = WARN_ATTR_SYNTAX_GENERALIZED_TIME_INVALID_MINUTE. 938 get(valueString, 939 valueString.substring(10, 12)); 940 throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX, 941 message); 942 } 943 944 break; 945 946 case 'Z': 947 // This is fine only if we are at the end of the value. 948 if (length == 11) 949 { 950 try 951 { 952 GregorianCalendar calendar = new GregorianCalendar(); 953 calendar.setLenient(false); 954 calendar.setTimeZone(TimeZone.getTimeZone(TIME_ZONE_UTC)); 955 calendar.set(year, month, day, hour, minute, second); 956 calendar.set(Calendar.MILLISECOND, 0); 957 return calendar.getTimeInMillis(); 958 } 959 catch (Exception e) 960 { 961 if (debugEnabled()) 962 { 963 TRACER.debugCaught(DebugLogLevel.ERROR, e); 964 } 965 966 // This should only happen if the provided date wasn't legal 967 // (e.g., September 31). 968 Message message = WARN_ATTR_SYNTAX_GENERALIZED_TIME_ILLEGAL_TIME. 969 get(valueString, String.valueOf(e)); 970 throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX, 971 message, e); 972 } 973 } 974 else 975 { 976 Message message = WARN_ATTR_SYNTAX_GENERALIZED_TIME_INVALID_CHAR.get( 977 valueString, String.valueOf(m1), 10); 978 throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX, 979 message); 980 } 981 982 case '+': 983 case '-': 984 // These are fine only if there are exactly two or four more digits that 985 // specify a valid offset. 986 if ((length == 13) || (length == 15)) 987 { 988 try 989 { 990 GregorianCalendar calendar = new GregorianCalendar(); 991 calendar.setLenient(false); 992 calendar.setTimeZone(getTimeZoneForOffset(valueString, 10)); 993 calendar.set(year, month, day, hour, minute, second); 994 calendar.set(Calendar.MILLISECOND, 0); 995 return calendar.getTimeInMillis(); 996 } 997 catch (Exception e) 998 { 999 if (debugEnabled()) 1000 { 1001 TRACER.debugCaught(DebugLogLevel.ERROR, e); 1002 } 1003 1004 // This should only happen if the provided date wasn't legal 1005 // (e.g., September 31). 1006 Message message = WARN_ATTR_SYNTAX_GENERALIZED_TIME_ILLEGAL_TIME. 1007 get(valueString, String.valueOf(e)); 1008 throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX, 1009 message, e); 1010 } 1011 } 1012 else 1013 { 1014 Message message = WARN_ATTR_SYNTAX_GENERALIZED_TIME_INVALID_CHAR.get( 1015 valueString, String.valueOf(m1), 10); 1016 throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX, 1017 message); 1018 } 1019 1020 case '.': 1021 case ',': 1022 return finishDecodingFraction(valueString, 11, year, month, day, hour, 1023 minute, second, 3600000); 1024 1025 default: 1026 Message message = WARN_ATTR_SYNTAX_GENERALIZED_TIME_INVALID_CHAR.get( 1027 valueString, String.valueOf(m1), 10); 1028 throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX, 1029 message); 1030 } 1031 1032 1033 // Next, there should be either two digits comprising an integer between 00 1034 // and 60 (for the second, including a possible leap second), a letter 'Z' 1035 // (for the UTC specifier), a plus or minus sign followed by two or four 1036 // digits (for the UTC offset), or a period or comma to start the fraction. 1037 char s1 = valueString.charAt(12); 1038 switch (s1) 1039 { 1040 case '0': 1041 case '1': 1042 case '2': 1043 case '3': 1044 case '4': 1045 case '5': 1046 // There must be at least two more characters, and the next one must 1047 // be a digit between 0 and 9. 1048 if (length < 15) 1049 { 1050 Message message = WARN_ATTR_SYNTAX_GENERALIZED_TIME_INVALID_CHAR.get( 1051 valueString, String.valueOf(s1), 12); 1052 throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX, 1053 message); 1054 } 1055 1056 1057 second = 10 * (s1 - '0'); 1058 1059 switch (valueString.charAt(13)) 1060 { 1061 case '0': 1062 break; 1063 1064 case '1': 1065 second += 1; 1066 break; 1067 1068 case '2': 1069 second += 2; 1070 break; 1071 1072 case '3': 1073 second += 3; 1074 break; 1075 1076 case '4': 1077 second += 4; 1078 break; 1079 1080 case '5': 1081 second += 5; 1082 break; 1083 1084 case '6': 1085 second += 6; 1086 break; 1087 1088 case '7': 1089 second += 7; 1090 break; 1091 1092 case '8': 1093 second += 8; 1094 break; 1095 1096 case '9': 1097 second += 9; 1098 break; 1099 1100 default: 1101 Message message = WARN_ATTR_SYNTAX_GENERALIZED_TIME_INVALID_MINUTE. 1102 get(valueString, 1103 valueString.substring(12, 14)); 1104 throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX, 1105 message); 1106 } 1107 1108 break; 1109 1110 case '6': 1111 // There must be at least two more characters and the next one must be 1112 // a 0. 1113 if (length < 15) 1114 { 1115 Message message = WARN_ATTR_SYNTAX_GENERALIZED_TIME_INVALID_CHAR.get( 1116 valueString, String.valueOf(s1), 12); 1117 throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX, 1118 message); 1119 } 1120 1121 if (valueString.charAt(13) != '0') 1122 { 1123 Message message = 1124 WARN_ATTR_SYNTAX_GENERALIZED_TIME_INVALID_SECOND.get(valueString, 1125 valueString.substring(12, 14)); 1126 throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX, 1127 message); 1128 } 1129 1130 second = 60; 1131 break; 1132 1133 case 'Z': 1134 // This is fine only if we are at the end of the value. 1135 if (length == 13) 1136 { 1137 try 1138 { 1139 GregorianCalendar calendar = new GregorianCalendar(); 1140 calendar.setLenient(false); 1141 calendar.setTimeZone(TimeZone.getTimeZone(TIME_ZONE_UTC)); 1142 calendar.set(year, month, day, hour, minute, second); 1143 calendar.set(Calendar.MILLISECOND, 0); 1144 return calendar.getTimeInMillis(); 1145 } 1146 catch (Exception e) 1147 { 1148 if (debugEnabled()) 1149 { 1150 TRACER.debugCaught(DebugLogLevel.ERROR, e); 1151 } 1152 1153 // This should only happen if the provided date wasn't legal 1154 // (e.g., September 31). 1155 Message message = WARN_ATTR_SYNTAX_GENERALIZED_TIME_ILLEGAL_TIME. 1156 get(valueString, String.valueOf(e)); 1157 throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX, 1158 message, e); 1159 } 1160 } 1161 else 1162 { 1163 Message message = WARN_ATTR_SYNTAX_GENERALIZED_TIME_INVALID_CHAR.get( 1164 valueString, String.valueOf(s1), 12); 1165 throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX, 1166 message); 1167 } 1168 1169 case '+': 1170 case '-': 1171 // These are fine only if there are exactly two or four more digits that 1172 // specify a valid offset. 1173 if ((length == 15) || (length == 17)) 1174 { 1175 try 1176 { 1177 GregorianCalendar calendar = new GregorianCalendar(); 1178 calendar.setLenient(false); 1179 calendar.setTimeZone(getTimeZoneForOffset(valueString, 12)); 1180 calendar.set(year, month, day, hour, minute, second); 1181 calendar.set(Calendar.MILLISECOND, 0); 1182 return calendar.getTimeInMillis(); 1183 } 1184 catch (Exception e) 1185 { 1186 if (debugEnabled()) 1187 { 1188 TRACER.debugCaught(DebugLogLevel.ERROR, e); 1189 } 1190 1191 // This should only happen if the provided date wasn't legal 1192 // (e.g., September 31). 1193 Message message = WARN_ATTR_SYNTAX_GENERALIZED_TIME_ILLEGAL_TIME. 1194 get(valueString, String.valueOf(e)); 1195 throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX, 1196 message, e); 1197 } 1198 } 1199 else 1200 { 1201 Message message = WARN_ATTR_SYNTAX_GENERALIZED_TIME_INVALID_CHAR.get( 1202 valueString, String.valueOf(s1), 12); 1203 throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX, 1204 message); 1205 } 1206 1207 case '.': 1208 case ',': 1209 return finishDecodingFraction(valueString, 13, year, month, day, hour, 1210 minute, second, 60000); 1211 1212 default: 1213 Message message = WARN_ATTR_SYNTAX_GENERALIZED_TIME_INVALID_CHAR.get( 1214 valueString, String.valueOf(s1), 12); 1215 throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX, 1216 message); 1217 } 1218 1219 1220 // Next, there should be either a period or comma followed by between one 1221 // and three digits (to specify the sub-second), a letter 'Z' (for the UTC 1222 // specifier), or a plus or minus sign followed by two our four digits (for 1223 // the UTC offset). 1224 switch (valueString.charAt(14)) 1225 { 1226 case '.': 1227 case ',': 1228 return finishDecodingFraction(valueString, 15, year, month, day, hour, 1229 minute, second, 1000); 1230 1231 case 'Z': 1232 // This is fine only if we are at the end of the value. 1233 if (length == 15) 1234 { 1235 try 1236 { 1237 GregorianCalendar calendar = new GregorianCalendar(); 1238 calendar.setLenient(false); 1239 calendar.setTimeZone(TimeZone.getTimeZone(TIME_ZONE_UTC)); 1240 calendar.set(year, month, day, hour, minute, second); 1241 calendar.set(Calendar.MILLISECOND, 0); 1242 return calendar.getTimeInMillis(); 1243 } 1244 catch (Exception e) 1245 { 1246 if (debugEnabled()) 1247 { 1248 TRACER.debugCaught(DebugLogLevel.ERROR, e); 1249 } 1250 1251 // This should only happen if the provided date wasn't legal 1252 // (e.g., September 31). 1253 Message message = WARN_ATTR_SYNTAX_GENERALIZED_TIME_ILLEGAL_TIME. 1254 get(valueString, String.valueOf(e)); 1255 throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX, 1256 message, e); 1257 } 1258 } 1259 else 1260 { 1261 Message message = WARN_ATTR_SYNTAX_GENERALIZED_TIME_INVALID_CHAR.get( 1262 valueString, String.valueOf(valueString.charAt(14)), 14); 1263 throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX, 1264 message); 1265 } 1266 1267 case '+': 1268 case '-': 1269 // These are fine only if there are exactly two or four more digits that 1270 // specify a valid offset. 1271 if ((length == 17) || (length == 19)) 1272 { 1273 try 1274 { 1275 GregorianCalendar calendar = new GregorianCalendar(); 1276 calendar.setLenient(false); 1277 calendar.setTimeZone(getTimeZoneForOffset(valueString, 14)); 1278 calendar.set(year, month, day, hour, minute, second); 1279 calendar.set(Calendar.MILLISECOND, 0); 1280 return calendar.getTimeInMillis(); 1281 } 1282 catch (Exception e) 1283 { 1284 if (debugEnabled()) 1285 { 1286 TRACER.debugCaught(DebugLogLevel.ERROR, e); 1287 } 1288 1289 // This should only happen if the provided date wasn't legal 1290 // (e.g., September 31). 1291 Message message = WARN_ATTR_SYNTAX_GENERALIZED_TIME_ILLEGAL_TIME. 1292 get(valueString, String.valueOf(e)); 1293 throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX, 1294 message, e); 1295 } 1296 } 1297 else 1298 { 1299 Message message = WARN_ATTR_SYNTAX_GENERALIZED_TIME_INVALID_CHAR.get( 1300 valueString, String.valueOf(valueString.charAt(14)), 14); 1301 throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX, 1302 message); 1303 } 1304 1305 default: 1306 Message message = WARN_ATTR_SYNTAX_GENERALIZED_TIME_INVALID_CHAR.get( 1307 valueString, String.valueOf(valueString.charAt(14)), 14); 1308 throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX, 1309 message); 1310 } 1311 } 1312 1313 1314 1315 /** 1316 * Completes decoding the generalized time value containing a fractional 1317 * component. It will also decode the trailing 'Z' or offset. 1318 * 1319 * @param value The whole value, including the fractional component and 1320 * time zone information. 1321 * @param startPos The position of the first character after the period 1322 * in the value string. 1323 * @param year The year decoded from the provided value. 1324 * @param month The month decoded from the provided value. 1325 * @param day The day decoded from the provided value. 1326 * @param hour The hour decoded from the provided value. 1327 * @param minute The minute decoded from the provided value. 1328 * @param second The second decoded from the provided value. 1329 * @param multiplier The multiplier value that should be used to scale the 1330 * fraction appropriately. If it's a fraction of an hour, 1331 * then it should be 3600000 (60*60*1000). If it's a 1332 * fraction of a minute, then it should be 60000. If it's 1333 * a fraction of a second, then it should be 1000. 1334 * 1335 * @return The timestamp created from the provided generalized time value 1336 * including the fractional element. 1337 * 1338 * @throws DirectoryException If the provided value cannot be parsed as a 1339 * valid generalized time string. 1340 */ 1341 private static long finishDecodingFraction(String value, int startPos, 1342 int year, int month, int day, 1343 int hour, int minute, int second, 1344 int multiplier) 1345 throws DirectoryException 1346 { 1347 int length = value.length(); 1348 StringBuilder fractionBuffer = new StringBuilder(2 + length - startPos); 1349 fractionBuffer.append("0."); 1350 1351 TimeZone timeZone = null; 1352 1353 outerLoop: 1354 for (int i=startPos; i < length; i++) 1355 { 1356 char c = value.charAt(i); 1357 switch (c) 1358 { 1359 case '0': 1360 case '1': 1361 case '2': 1362 case '3': 1363 case '4': 1364 case '5': 1365 case '6': 1366 case '7': 1367 case '8': 1368 case '9': 1369 fractionBuffer.append(c); 1370 break; 1371 1372 case 'Z': 1373 // This is only acceptable if we're at the end of the value. 1374 if (i != (value.length() - 1)) 1375 { 1376 Message message = 1377 WARN_ATTR_SYNTAX_GENERALIZED_TIME_ILLEGAL_FRACTION_CHAR. 1378 get(value, String.valueOf(c)); 1379 throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX, 1380 message); 1381 } 1382 1383 timeZone = TimeZone.getTimeZone(TIME_ZONE_UTC); 1384 break outerLoop; 1385 1386 case '+': 1387 case '-': 1388 timeZone = getTimeZoneForOffset(value, i); 1389 break outerLoop; 1390 1391 default: 1392 Message message = 1393 WARN_ATTR_SYNTAX_GENERALIZED_TIME_ILLEGAL_FRACTION_CHAR. 1394 get(value, String.valueOf(c)); 1395 throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX, 1396 message); 1397 } 1398 } 1399 1400 if (fractionBuffer.length() == 2) 1401 { 1402 Message message = 1403 WARN_ATTR_SYNTAX_GENERALIZED_TIME_EMPTY_FRACTION.get(value); 1404 throw new DirectoryException( 1405 ResultCode.INVALID_ATTRIBUTE_SYNTAX, message); 1406 } 1407 1408 if (timeZone == null) 1409 { 1410 Message message = 1411 WARN_ATTR_SYNTAX_GENERALIZED_TIME_NO_TIME_ZONE_INFO.get(value); 1412 throw new DirectoryException( 1413 ResultCode.INVALID_ATTRIBUTE_SYNTAX, message); 1414 } 1415 1416 Double fractionValue = Double.parseDouble(fractionBuffer.toString()); 1417 long additionalMilliseconds = Math.round(fractionValue * multiplier); 1418 1419 try 1420 { 1421 GregorianCalendar calendar = new GregorianCalendar(); 1422 calendar.setLenient(false); 1423 calendar.setTimeZone(timeZone); 1424 calendar.set(year, month, day, hour, minute, second); 1425 calendar.set(Calendar.MILLISECOND, 0); 1426 return calendar.getTimeInMillis() + additionalMilliseconds; 1427 } 1428 catch (Exception e) 1429 { 1430 if (debugEnabled()) 1431 { 1432 TRACER.debugCaught(DebugLogLevel.ERROR, e); 1433 } 1434 1435 // This should only happen if the provided date wasn't legal 1436 // (e.g., September 31). 1437 Message message = WARN_ATTR_SYNTAX_GENERALIZED_TIME_ILLEGAL_TIME.get( 1438 value, String.valueOf(e)); 1439 throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX, 1440 message, e); 1441 } 1442 } 1443 1444 1445 1446 /** 1447 * Decodes a time zone offset from the provided value. 1448 * 1449 * @param value The whole value, including the offset. 1450 * @param startPos The position of the first character that is 1451 * contained in the offset. This should be the 1452 * position of the plus or minus character. 1453 * 1454 * @return The {@code TimeZone} object representing the decoded time zone. 1455 * 1456 * @throws DirectoryException If the provided value does not contain a valid 1457 * offset. 1458 */ 1459 private static TimeZone getTimeZoneForOffset(String value, int startPos) 1460 throws DirectoryException 1461 { 1462 String offSetStr = value.substring(startPos); 1463 if ((offSetStr.length() != 3) && (offSetStr.length() != 5)) 1464 { 1465 Message message = WARN_ATTR_SYNTAX_GENERALIZED_TIME_INVALID_OFFSET.get( 1466 value, offSetStr); 1467 throw new DirectoryException( 1468 ResultCode.INVALID_ATTRIBUTE_SYNTAX, message); 1469 } 1470 1471 1472 // The first character must be either a plus or minus. 1473 switch (offSetStr.charAt(0)) 1474 { 1475 case '+': 1476 case '-': 1477 // These are OK. 1478 break; 1479 1480 default: 1481 Message message = WARN_ATTR_SYNTAX_GENERALIZED_TIME_INVALID_OFFSET.get( 1482 value, offSetStr); 1483 throw new DirectoryException( 1484 ResultCode.INVALID_ATTRIBUTE_SYNTAX, 1485 message); 1486 } 1487 1488 1489 // The first two characters must be an integer between 00 and 23. 1490 switch (offSetStr.charAt(1)) 1491 { 1492 case '0': 1493 case '1': 1494 switch (offSetStr.charAt(2)) 1495 { 1496 case '0': 1497 case '1': 1498 case '2': 1499 case '3': 1500 case '4': 1501 case '5': 1502 case '6': 1503 case '7': 1504 case '8': 1505 case '9': 1506 // These are all fine. 1507 break; 1508 1509 default: 1510 Message message = WARN_ATTR_SYNTAX_GENERALIZED_TIME_INVALID_OFFSET. 1511 get(value, offSetStr); 1512 throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX, 1513 message); 1514 } 1515 break; 1516 1517 case '2': 1518 switch (offSetStr.charAt(2)) 1519 { 1520 case '0': 1521 case '1': 1522 case '2': 1523 case '3': 1524 // These are all fine. 1525 break; 1526 1527 default: 1528 Message message = WARN_ATTR_SYNTAX_GENERALIZED_TIME_INVALID_OFFSET. 1529 get(value, offSetStr); 1530 throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX, 1531 message); 1532 } 1533 break; 1534 1535 default: 1536 Message message = WARN_ATTR_SYNTAX_GENERALIZED_TIME_INVALID_OFFSET.get( 1537 value, offSetStr); 1538 throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX, 1539 message); 1540 } 1541 1542 1543 // If there are two more characters, then they must be an integer between 1544 // 00 and 59. 1545 if (offSetStr.length() == 5) 1546 { 1547 switch (offSetStr.charAt(3)) 1548 { 1549 case '0': 1550 case '1': 1551 case '2': 1552 case '3': 1553 case '4': 1554 case '5': 1555 switch (offSetStr.charAt(4)) 1556 { 1557 case '0': 1558 case '1': 1559 case '2': 1560 case '3': 1561 case '4': 1562 case '5': 1563 case '6': 1564 case '7': 1565 case '8': 1566 case '9': 1567 // These are all fine. 1568 break; 1569 1570 default: 1571 Message message = 1572 WARN_ATTR_SYNTAX_GENERALIZED_TIME_INVALID_OFFSET. 1573 get(value, offSetStr); 1574 throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX, 1575 message); 1576 } 1577 break; 1578 1579 default: 1580 Message message = WARN_ATTR_SYNTAX_GENERALIZED_TIME_INVALID_OFFSET. 1581 get(value, offSetStr); 1582 throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX, 1583 message); 1584 } 1585 } 1586 1587 1588 // If we've gotten here, then it looks like a valid offset. We can create a 1589 // time zone by using "GMT" followed by the offset. 1590 return TimeZone.getTimeZone("GMT" + offSetStr); 1591 } 1592 } 1593