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.Date;
033    import java.util.TimeZone;
034    
035    import org.opends.messages.Message;
036    import org.opends.messages.MessageBuilder;
037    import org.opends.server.admin.std.server.AttributeSyntaxCfg;
038    import org.opends.server.api.ApproximateMatchingRule;
039    import org.opends.server.api.AttributeSyntax;
040    import org.opends.server.api.EqualityMatchingRule;
041    import org.opends.server.api.OrderingMatchingRule;
042    import org.opends.server.api.SubstringMatchingRule;
043    import org.opends.server.config.ConfigException;
044    import org.opends.server.core.DirectoryServer;
045    import org.opends.server.loggers.debug.DebugTracer;
046    import org.opends.server.protocols.asn1.ASN1OctetString;
047    import org.opends.server.types.AttributeValue;
048    import org.opends.server.types.ByteString;
049    import org.opends.server.types.DebugLogLevel;
050    import org.opends.server.types.DirectoryException;
051    import org.opends.server.types.ResultCode;
052    
053    import static org.opends.messages.SchemaMessages.*;
054    import static org.opends.server.loggers.debug.DebugLogger.*;
055    import static org.opends.server.loggers.ErrorLogger.*;
056    import static org.opends.server.schema.SchemaConstants.*;
057    import static org.opends.server.util.ServerConstants.*;
058    
059    
060    
061    /**
062     * This class implements the UTC time attribute syntax.  This is very similar to
063     * the generalized time syntax (and actually has been deprecated in favor of
064     * that), but requires that the minute be provided and does not allow for
065     * sub-second times.  All matching will be performed using the generalized time
066     * matching rules, and equality, ordering, and substring matching will be
067     * allowed.
068     */
069    public class UTCTimeSyntax
070           extends AttributeSyntax<AttributeSyntaxCfg>
071    {
072      /**
073       * The tracer object for the debug logger.
074       */
075      private static final DebugTracer TRACER = getTracer();
076    
077    
078    
079      /**
080       * The lock that will be used to provide threadsafe access to the date
081       * formatter.
082       */
083      private static Object dateFormatLock;
084    
085    
086    
087      /**
088       * The date formatter that will be used to convert dates into UTC time values.
089       * Note that all interaction with it must be synchronized.
090       */
091      private static SimpleDateFormat dateFormat;
092    
093    
094    
095      // The default equality matching rule for this syntax.
096      private EqualityMatchingRule defaultEqualityMatchingRule;
097    
098      // The default ordering matching rule for this syntax.
099      private OrderingMatchingRule defaultOrderingMatchingRule;
100    
101      // The default substring matching rule for this syntax.
102      private SubstringMatchingRule defaultSubstringMatchingRule;
103    
104    
105    
106      /*
107       * Create the date formatter that will be used to construct and parse
108       * normalized UTC time values.
109       */
110      static
111      {
112        dateFormat = new SimpleDateFormat(DATE_FORMAT_UTC_TIME);
113        dateFormat.setLenient(false);
114        dateFormat.setTimeZone(TimeZone.getTimeZone("UTC"));
115    
116        dateFormatLock = new Object();
117      }
118    
119    
120    
121      /**
122       * Creates a new instance of this syntax.  Note that the only thing that
123       * should be done here is to invoke the default constructor for the
124       * superclass.  All initialization should be performed in the
125       * <CODE>initializeSyntax</CODE> method.
126       */
127      public UTCTimeSyntax()
128      {
129        super();
130      }
131    
132    
133    
134      /**
135       * {@inheritDoc}
136       */
137      public void initializeSyntax(AttributeSyntaxCfg configuration)
138             throws ConfigException
139      {
140        defaultEqualityMatchingRule =
141             DirectoryServer.getEqualityMatchingRule(EMR_GENERALIZED_TIME_OID);
142        if (defaultEqualityMatchingRule == null)
143        {
144          logError(ERR_ATTR_SYNTAX_UNKNOWN_EQUALITY_MATCHING_RULE.get(
145              EMR_GENERALIZED_TIME_OID, SYNTAX_UTC_TIME_NAME));
146        }
147    
148        defaultOrderingMatchingRule =
149             DirectoryServer.getOrderingMatchingRule(OMR_GENERALIZED_TIME_OID);
150        if (defaultOrderingMatchingRule == null)
151        {
152          logError(ERR_ATTR_SYNTAX_UNKNOWN_ORDERING_MATCHING_RULE.get(
153              OMR_GENERALIZED_TIME_OID, SYNTAX_UTC_TIME_NAME));
154        }
155    
156        defaultSubstringMatchingRule =
157             DirectoryServer.getSubstringMatchingRule(SMR_CASE_IGNORE_OID);
158        if (defaultSubstringMatchingRule == null)
159        {
160          logError(ERR_ATTR_SYNTAX_UNKNOWN_SUBSTRING_MATCHING_RULE.get(
161              SMR_CASE_IGNORE_OID, SYNTAX_UTC_TIME_NAME));
162        }
163      }
164    
165    
166    
167      /**
168       * Retrieves the common name for this attribute syntax.
169       *
170       * @return  The common name for this attribute syntax.
171       */
172      public String getSyntaxName()
173      {
174        return SYNTAX_UTC_TIME_NAME;
175      }
176    
177    
178    
179      /**
180       * Retrieves the OID for this attribute syntax.
181       *
182       * @return  The OID for this attribute syntax.
183       */
184      public String getOID()
185      {
186        return SYNTAX_UTC_TIME_OID;
187      }
188    
189    
190    
191      /**
192       * Retrieves a description for this attribute syntax.
193       *
194       * @return  A description for this attribute syntax.
195       */
196      public String getDescription()
197      {
198        return SYNTAX_UTC_TIME_DESCRIPTION;
199      }
200    
201    
202    
203      /**
204       * Retrieves the default equality matching rule that will be used for
205       * attributes with this syntax.
206       *
207       * @return  The default equality matching rule that will be used for
208       *          attributes with this syntax, or <CODE>null</CODE> if equality
209       *          matches will not be allowed for this type by default.
210       */
211      public EqualityMatchingRule getEqualityMatchingRule()
212      {
213        return defaultEqualityMatchingRule;
214      }
215    
216    
217    
218      /**
219       * Retrieves the default ordering matching rule that will be used for
220       * attributes with this syntax.
221       *
222       * @return  The default ordering matching rule that will be used for
223       *          attributes with this syntax, or <CODE>null</CODE> if ordering
224       *          matches will not be allowed for this type by default.
225       */
226      public OrderingMatchingRule getOrderingMatchingRule()
227      {
228        return defaultOrderingMatchingRule;
229      }
230    
231    
232    
233      /**
234       * Retrieves the default substring matching rule that will be used for
235       * attributes with this syntax.
236       *
237       * @return  The default substring matching rule that will be used for
238       *          attributes with this syntax, or <CODE>null</CODE> if substring
239       *          matches will not be allowed for this type by default.
240       */
241      public SubstringMatchingRule getSubstringMatchingRule()
242      {
243        return defaultSubstringMatchingRule;
244      }
245    
246    
247    
248      /**
249       * Retrieves the default approximate matching rule that will be used for
250       * attributes with this syntax.
251       *
252       * @return  The default approximate matching rule that will be used for
253       *          attributes with this syntax, or <CODE>null</CODE> if approximate
254       *          matches will not be allowed for this type by default.
255       */
256      public ApproximateMatchingRule getApproximateMatchingRule()
257      {
258        // Approximate matching will not be allowed by default.
259        return null;
260      }
261    
262    
263    
264      /**
265       * Indicates whether the provided value is acceptable for use in an attribute
266       * with this syntax.  If it is not, then the reason may be appended to the
267       * provided buffer.
268       *
269       * @param  value          The value for which to make the determination.
270       * @param  invalidReason  The buffer to which the invalid reason should be
271       *                        appended.
272       *
273       * @return  <CODE>true</CODE> if the provided value is acceptable for use with
274       *          this syntax, or <CODE>false</CODE> if not.
275       */
276      public boolean valueIsAcceptable(ByteString value,
277                                       MessageBuilder invalidReason)
278      {
279        // Get the value as a string and verify that it is at least long enough for
280        // "YYYYMMDDhhmmZ", which is the shortest allowed value.
281        String valueString = value.stringValue().toUpperCase();
282        int    length      = valueString.length();
283        if (length < 11)
284        {
285          Message message = ERR_ATTR_SYNTAX_UTC_TIME_TOO_SHORT.get(valueString);
286          invalidReason.append(message);
287          return false;
288        }
289    
290    
291        // The first two characters are the year, and they must be numeric digits
292        // between 0 and 9.
293        for (int i=0; i < 2; i++)
294        {
295          switch (valueString.charAt(i))
296          {
297            case '0':
298            case '1':
299            case '2':
300            case '3':
301            case '4':
302            case '5':
303            case '6':
304            case '7':
305            case '8':
306            case '9':
307              // These are all fine.
308              break;
309            default:
310              Message message = ERR_ATTR_SYNTAX_UTC_TIME_INVALID_YEAR.get(
311                      valueString, String.valueOf(valueString.charAt(i)));
312              invalidReason.append(message);
313              return false;
314          }
315        }
316    
317    
318        // The next two characters are the month, and they must form the string
319        // representation of an integer between 01 and 12.
320        char m1 = valueString.charAt(2);
321        char m2 = valueString.charAt(3);
322        switch (m1)
323        {
324          case '0':
325            // m2 must be a digit between 1 and 9.
326            switch (m2)
327            {
328              case '1':
329              case '2':
330              case '3':
331              case '4':
332              case '5':
333              case '6':
334              case '7':
335              case '8':
336              case '9':
337                // These are all fine.
338                break;
339              default:
340                Message message = ERR_ATTR_SYNTAX_UTC_TIME_INVALID_MONTH.get(
341                        valueString, valueString.substring(2, 4));
342                invalidReason.append(message);
343                return false;
344            }
345            break;
346          case '1':
347            // m2 must be a digit between 0 and 2.
348            switch (m2)
349            {
350              case '0':
351              case '1':
352              case '2':
353                // These are all fine.
354                break;
355              default:
356                Message message = ERR_ATTR_SYNTAX_UTC_TIME_INVALID_MONTH.get(
357                        valueString, valueString.substring(2, 4));
358                invalidReason.append(message);
359                return false;
360            }
361            break;
362          default:
363            Message message = ERR_ATTR_SYNTAX_UTC_TIME_INVALID_MONTH.get(
364                    valueString, valueString.substring(2, 4));
365            invalidReason.append(message);
366            return false;
367        }
368    
369    
370        // The next two characters should be the day of the month, and they must
371        // form the string representation of an integer between 01 and 31.
372        // This doesn't do any validation against the year or month, so it will
373        // allow dates like April 31, or February 29 in a non-leap year, but we'll
374        // let those slide.
375        char d1 = valueString.charAt(4);
376        char d2 = valueString.charAt(5);
377        switch (d1)
378        {
379          case '0':
380            // d2 must be a digit between 1 and 9.
381            switch (d2)
382            {
383              case '1':
384              case '2':
385              case '3':
386              case '4':
387              case '5':
388              case '6':
389              case '7':
390              case '8':
391              case '9':
392                // These are all fine.
393                break;
394              default:
395                Message message = ERR_ATTR_SYNTAX_UTC_TIME_INVALID_DAY.get(
396                        valueString, valueString.substring(4, 6));
397                invalidReason.append(message);
398                return false;
399            }
400            break;
401          case '1':
402            // Treated the same as '2'.
403          case '2':
404            // d2 must be a digit between 0 and 9.
405            switch (d2)
406            {
407              case '0':
408              case '1':
409              case '2':
410              case '3':
411              case '4':
412              case '5':
413              case '6':
414              case '7':
415              case '8':
416              case '9':
417                // These are all fine.
418                break;
419              default:
420                Message message = ERR_ATTR_SYNTAX_UTC_TIME_INVALID_DAY.get(
421                        valueString, valueString.substring(4, 6));
422                invalidReason.append(message);
423                return false;
424            }
425            break;
426          case '3':
427            // d2 must be either 0 or 1.
428            switch (d2)
429            {
430              case '0':
431              case '1':
432                // These are all fine.
433                break;
434              default:
435                Message message = ERR_ATTR_SYNTAX_UTC_TIME_INVALID_DAY.get(
436                        valueString, valueString.substring(4, 6));
437                invalidReason.append(message);
438                return false;
439            }
440            break;
441          default:
442            Message message = ERR_ATTR_SYNTAX_UTC_TIME_INVALID_DAY.get(valueString,
443                                        valueString.substring(4, 6));
444            invalidReason.append(message);
445            return false;
446        }
447    
448    
449        // The next two characters must be the hour, and they must form the string
450        // representation of an integer between 00 and 23.
451        char h1 = valueString.charAt(6);
452        char h2 = valueString.charAt(7);
453        switch (h1)
454        {
455          case '0':
456            // This is treated the same as '1'.
457          case '1':
458            switch (h2)
459            {
460              case '0':
461              case '1':
462              case '2':
463              case '3':
464              case '4':
465              case '5':
466              case '6':
467              case '7':
468              case '8':
469              case '9':
470                // These are all fine.
471                break;
472              default:
473                Message message = ERR_ATTR_SYNTAX_UTC_TIME_INVALID_HOUR.get(
474                        valueString, valueString.substring(6, 8));
475                invalidReason.append(message);
476                return false;
477            }
478            break;
479          case '2':
480            // This must be a digit between 0 and 3.
481            switch (h2)
482            {
483              case '0':
484              case '1':
485              case '2':
486              case '3':
487                // These are all fine.
488                break;
489              default:
490                Message message = ERR_ATTR_SYNTAX_UTC_TIME_INVALID_HOUR.get(
491                        valueString, valueString.substring(6, 8));
492                invalidReason.append(message);
493                return false;
494            }
495            break;
496          default:
497            Message message = ERR_ATTR_SYNTAX_UTC_TIME_INVALID_HOUR.get(valueString,
498                                        valueString.substring(6, 8));
499            invalidReason.append(message);
500            return false;
501        }
502    
503    
504        // Next, there should be two digits comprising an integer between 00 and 59
505        // for the minute.
506        m1 = valueString.charAt(8);
507        switch (m1)
508        {
509          case '0':
510          case '1':
511          case '2':
512          case '3':
513          case '4':
514          case '5':
515            // There must be at least two more characters, and the next one must
516            // be a digit between 0 and 9.
517            if (length < 11)
518            {
519              Message message = ERR_ATTR_SYNTAX_UTC_TIME_INVALID_CHAR.get(
520                      valueString, String.valueOf(m1), 8);
521              invalidReason.append(message);
522              return false;
523            }
524    
525            switch (valueString.charAt(9))
526            {
527              case '0':
528              case '1':
529              case '2':
530              case '3':
531              case '4':
532              case '5':
533              case '6':
534              case '7':
535              case '8':
536              case '9':
537                // These are all fine.
538                break;
539              default:
540                Message message = ERR_ATTR_SYNTAX_UTC_TIME_INVALID_MINUTE.get(
541                        valueString, valueString.substring(8, 10));
542                invalidReason.append(message);
543                return false;
544            }
545    
546            break;
547    
548          default:
549            Message message = ERR_ATTR_SYNTAX_UTC_TIME_INVALID_CHAR.get(
550                    valueString, String.valueOf(m1), 8);
551            invalidReason.append(message);
552            return false;
553        }
554    
555    
556        // Next, there should be either two digits comprising an integer between 00
557        // and 60 (for the second, including a possible leap second), a letter 'Z'
558        // (for the UTC specifier), or a plus or minus sign followed by four digits
559        // (for the UTC offset).
560        char s1 = valueString.charAt(10);
561        switch (s1)
562        {
563          case '0':
564          case '1':
565          case '2':
566          case '3':
567          case '4':
568          case '5':
569            // There must be at least two more characters, and the next one must
570            // be a digit between 0 and 9.
571            if (length < 13)
572            {
573              Message message = ERR_ATTR_SYNTAX_UTC_TIME_INVALID_CHAR.get(
574                      valueString, String.valueOf(s1), 10);
575              invalidReason.append(message);
576              return false;
577            }
578    
579            switch (valueString.charAt(11))
580            {
581              case '0':
582              case '1':
583              case '2':
584              case '3':
585              case '4':
586              case '5':
587              case '6':
588              case '7':
589              case '8':
590              case '9':
591                // These are all fine.
592                break;
593              default:
594                Message message = ERR_ATTR_SYNTAX_UTC_TIME_INVALID_SECOND.get(
595                        valueString, valueString.substring(10, 12));
596                invalidReason.append(message);
597                return false;
598            }
599    
600            break;
601          case '6':
602            // There must be at least two more characters and the next one must be
603            // a 0.
604            if (length < 13)
605            {
606    
607              Message message = ERR_ATTR_SYNTAX_UTC_TIME_INVALID_CHAR.get(
608                      valueString, String.valueOf(s1), 10);
609              invalidReason.append(message);
610              return false;
611            }
612    
613            if (valueString.charAt(11) != '0')
614            {
615              Message message = ERR_ATTR_SYNTAX_UTC_TIME_INVALID_SECOND.get(
616                      valueString, valueString.substring(10, 12));
617              invalidReason.append(message);
618              return false;
619            }
620    
621            break;
622          case 'Z':
623            // This is fine only if we are at the end of the value.
624            if (length == 11)
625            {
626              return true;
627            }
628            else
629            {
630              Message message = ERR_ATTR_SYNTAX_UTC_TIME_INVALID_CHAR.get(
631                      valueString, String.valueOf(s1), 10);
632              invalidReason.append(message);
633              return false;
634            }
635    
636          case '+':
637          case '-':
638            // These are fine only if there are exactly four more digits that
639            // specify a valid offset.
640            if (length == 15)
641            {
642              return hasValidOffset(valueString, 11, invalidReason);
643            }
644            else
645            {
646              Message message = ERR_ATTR_SYNTAX_UTC_TIME_INVALID_CHAR.get(
647                      valueString, String.valueOf(s1), 10);
648              invalidReason.append(message);
649              return false;
650            }
651    
652          default:
653            Message message = ERR_ATTR_SYNTAX_UTC_TIME_INVALID_CHAR.get(
654                    valueString, String.valueOf(s1), 10);
655            invalidReason.append(message);
656            return false;
657        }
658    
659    
660        // The last element should be either a letter 'Z' (for the UTC specifier),
661        // or a plus or minus sign followed by four digits (for the UTC offset).
662        switch (valueString.charAt(12))
663        {
664          case 'Z':
665            // This is fine only if we are at the end of the value.
666            if (length == 13)
667            {
668              return true;
669            }
670            else
671            {
672              Message message = ERR_ATTR_SYNTAX_UTC_TIME_INVALID_CHAR.get(
673                      valueString, String.valueOf(valueString.charAt(12)), 12);
674              invalidReason.append(message);
675              return false;
676            }
677    
678          case '+':
679          case '-':
680            // These are fine only if there are four or two more digits that
681            // specify a valid offset.
682            if ((length == 17) || (length == 15))
683            {
684              return hasValidOffset(valueString, 13, invalidReason);
685            }
686            else
687            {
688              Message message = ERR_ATTR_SYNTAX_UTC_TIME_INVALID_CHAR.get(
689                      valueString, String.valueOf(valueString.charAt(12)), 12);
690              invalidReason.append(message);
691              return false;
692            }
693    
694          default:
695            Message message = ERR_ATTR_SYNTAX_UTC_TIME_INVALID_CHAR.get(
696                    valueString, String.valueOf(valueString.charAt(12)), 12);
697            invalidReason.append(message);
698            return false;
699        }
700      }
701    
702    
703    
704      /**
705       * Indicates whether the provided string contains a valid set of two or four
706       * UTC offset digits.  The provided string must have either two or four
707       * characters from the provided start position to the end of the value.
708       *
709       * @param  value          The whole value, including the offset.
710       * @param  startPos       The position of the first character that is
711       *                        contained in the offset.
712       * @param  invalidReason  The buffer to which the invalid reason may be
713       *                        appended if the string does not contain a valid set
714       *                        of UTC offset digits.
715       *
716       * @return  <CODE>true</CODE> if the provided offset string is valid, or
717       *          <CODE>false</CODE> if it is not.
718       */
719      private boolean hasValidOffset(String value, int startPos,
720                                     MessageBuilder invalidReason)
721      {
722        int offsetLength = value.length() - startPos;
723        if (offsetLength < 2)
724        {
725          Message message = ERR_ATTR_SYNTAX_UTC_TIME_TOO_SHORT.get(value);
726          invalidReason.append(message);
727          return false;
728        }
729    
730        // The first two characters must be an integer between 00 and 23.
731        switch (value.charAt(startPos))
732        {
733          case '0':
734          case '1':
735            switch (value.charAt(startPos+1))
736            {
737              case '0':
738              case '1':
739              case '2':
740              case '3':
741              case '4':
742              case '5':
743              case '6':
744              case '7':
745              case '8':
746              case '9':
747                // These are all fine.
748                break;
749              default:
750                Message message = ERR_ATTR_SYNTAX_UTC_TIME_INVALID_OFFSET.get(value,
751                                            value.substring(startPos,
752                                                            startPos+offsetLength));
753                invalidReason.append(message);
754                return false;
755            }
756            break;
757          case '2':
758            switch (value.charAt(startPos+1))
759            {
760              case '0':
761              case '1':
762              case '2':
763              case '3':
764                // These are all fine.
765                break;
766              default:
767                Message message = ERR_ATTR_SYNTAX_UTC_TIME_INVALID_OFFSET.get(value,
768                                            value.substring(startPos,
769                                                            startPos+offsetLength));
770                invalidReason.append(message);
771                return false;
772            }
773            break;
774          default:
775            Message message = ERR_ATTR_SYNTAX_UTC_TIME_INVALID_OFFSET.get(value,
776                                        value.substring(startPos,
777                                                        startPos+offsetLength));
778            invalidReason.append(message);
779            return false;
780        }
781    
782    
783        // If there are two more characters, then they must be an integer between
784        // 00 and 59.
785        if (offsetLength == 4)
786        {
787          switch (value.charAt(startPos+2))
788          {
789            case '0':
790            case '1':
791            case '2':
792            case '3':
793            case '4':
794            case '5':
795              switch (value.charAt(startPos+3))
796              {
797                case '0':
798                case '1':
799                case '2':
800                case '3':
801                case '4':
802                case '5':
803                case '6':
804                case '7':
805                case '8':
806                case '9':
807                  // These are all fine.
808                  break;
809                default:
810                  Message message =
811                       ERR_ATTR_SYNTAX_UTC_TIME_INVALID_OFFSET.get(
812                               value,value.substring(startPos,
813                               startPos+offsetLength));
814                  invalidReason.append(message);
815                  return false;
816              }
817              break;
818            default:
819              Message message = ERR_ATTR_SYNTAX_UTC_TIME_INVALID_OFFSET.get(value,
820                                          value.substring(startPos,
821                                                          startPos+offsetLength));
822              invalidReason.append(message);
823              return false;
824          }
825        }
826    
827        return true;
828      }
829    
830    
831    
832      /**
833       * Retrieves an attribute value containing a UTC time representation of the
834       * provided date.
835       *
836       * @param  d  The date for which to retrieve the UTC time value.
837       *
838       * @return  The attribute value created from the date.
839       */
840      public static AttributeValue createUTCTimeValue(Date d)
841      {
842        String valueString;
843    
844        synchronized (dateFormatLock)
845        {
846          valueString = dateFormat.format(d);
847        }
848    
849        return new AttributeValue(new ASN1OctetString(valueString),
850                                  new ASN1OctetString(valueString));
851      }
852    
853    
854    
855      /**
856       * Decodes the provided normalized value as a UTC time value and
857       * retrieves a Java <CODE>Date</CODE> object containing its representation.
858       *
859       * @param  normalizedValue  The normalized UTC time value to decode to a
860       *                          Java <CODE>Date</CODE>.
861       *
862       * @return  The Java <CODE>Date</CODE> created from the provided UTC time
863       *          value.
864       *
865       * @throws  DirectoryException  If the provided value cannot be parsed as a
866       *                              valid UTC time string.
867       */
868      public static Date decodeUTCTimeValue(ByteString normalizedValue)
869             throws DirectoryException
870      {
871        String valueString = normalizedValue.stringValue();
872        try
873        {
874          synchronized (dateFormatLock)
875          {
876            return dateFormat.parse(valueString);
877          }
878        }
879        catch (Exception e)
880        {
881          if (debugEnabled())
882          {
883            TRACER.debugCaught(DebugLogLevel.ERROR, e);
884          }
885    
886          Message message = ERR_ATTR_SYNTAX_UTC_TIME_CANNOT_PARSE.get(
887              valueString, String.valueOf(e));
888          throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX,
889                                       message, e);
890        }
891      }
892    }
893