View Javadoc

1   /*
2    * Copyright  2003-2005 The Apache Software Foundation.
3    *
4    *  Licensed under the Apache License, Version 2.0 (the "License");
5    *  you may not use this file except in compliance with the License.
6    *  You may obtain a copy of the License at
7    *
8    *      http://www.apache.org/licenses/LICENSE-2.0
9    *
10   *  Unless required by applicable law or agreed to in writing, software
11   *  distributed under the License is distributed on an "AS IS" BASIS,
12   *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   *  See the License for the specific language governing permissions and
14   *  limitations under the License.
15   *
16   */
17  
18  package org.apache.ws.security.util;
19  
20  import java.text.DateFormat;
21  import java.text.FieldPosition;
22  import java.text.ParsePosition;
23  import java.text.SimpleDateFormat;
24  import java.text.ParseException;
25  import java.util.Date;
26  import java.util.TimeZone;
27  
28  import org.apache.commons.logging.Log;
29  import org.apache.commons.logging.LogFactory;
30  
31  /***
32   * A {@link DateFormat} for the format of the dateTime simpleType as specified in the
33   * XML Schema specification. See <a href="http://www.w3.org/TR/xmlschema-2/#dateTime">
34   * XML Schema Part 2: Datatypes, W3C Recommendation 02 May 2001, Section 3.2.7.1</a>.
35   *
36   * @author Ian P. Springer
37   * @author Werner Dittmann
38   */
39  public class XmlSchemaDateFormat extends DateFormat {
40  	/***
41  	 * Logger.
42  	 */
43  	private static Log log = LogFactory.getLog(XmlSchemaDateFormat.class);
44  
45  	private static boolean doDebug = false;
46  
47  	static {
48  		doDebug = log.isDebugEnabled();
49  	}
50  
51  	/***
52  	 * Message retriever.
53  	 */
54  	//   private static final MessageRetriever MSG = ResourceKeys.MSG;
55  	/***
56  	 * DateFormat for Zulu (UTC) form of an XML Schema dateTime string.
57  	 */
58  	private static final DateFormat DATEFORMAT_XSD_ZULU = new SimpleDateFormat(
59  			"yyyy-MM-dd'T'HH:mm:ss.SSS'Z'");
60  
61  	static {
62  		DATEFORMAT_XSD_ZULU.setTimeZone(TimeZone.getTimeZone("UTC"));
63  	}
64  
65  	/***
66  	 * This method was snarfed from <tt>org.apache.axis.encoding.ser.CalendarDeserializer</tt>,
67  	 * which was written by Sam Ruby (rubys@us.ibm.com) and Rich Scheuerle (scheu@us.ibm.com).
68  	 * Better error reporting was added.
69  	 *
70  	 * @see DateFormat#parse(java.lang.String)
71  	 */
72  	public Date parse(String src, ParsePosition parse_pos) {
73  		Date date;
74  
75  		// validate fixed portion of format
76  		int index = 0;
77  		try {
78  			if (src != null) {
79  				if ((src.charAt(0) == '+') || (src.charAt(0) == '-')) {
80  					src = src.substring(1);
81  				}
82  
83  				if (src.length() < 19) {
84  					parse_pos.setIndex(src.length() - 1);
85  					handleParseError(parse_pos, "TOO_FEW_CHARS");
86  				}
87  				validateChar(src, parse_pos, index = 4, '-', "EXPECTED_DASH");
88  				validateChar(src, parse_pos, index = 7, '-', "EXPECTED_DASH");
89  				validateChar(src, parse_pos, index = 10, 'T', "EXPECTED_CAPITAL_T");
90  				validateChar(src, parse_pos, index = 13, ':', "EXPECTED_COLON_IN_TIME");
91  				validateChar(src, parse_pos, index = 16, ':', "EXPECTED_COLON_IN_TIME");
92  			}
93  
94  			// convert what we have validated so far
95  			try {
96  				synchronized (DATEFORMAT_XSD_ZULU) {
97  					date = DATEFORMAT_XSD_ZULU.parse((src == null) ? null
98  							: (src.substring(0, 19) + ".000Z"));
99  				}
100 			} catch (Exception e) {
101 				throw new NumberFormatException(e.toString());
102 			}
103 
104 			index = 19;
105 
106 			// parse optional milliseconds
107 			if (src != null) {
108 				if ((index < src.length()) && (src.charAt(index) == '.')) {
109 					int milliseconds = 0;
110 					int start = ++index;
111 
112 					while ((index < src.length())
113 							&& Character.isDigit(src.charAt(index))) {
114 						index++;
115 					}
116 
117 					String decimal = src.substring(start, index);
118 
119 					if (decimal.length() == 3) {
120 						milliseconds = Integer.parseInt(decimal);
121 					} else if (decimal.length() < 3) {
122 						milliseconds = Integer.parseInt((decimal + "000")
123 								.substring(0, 3));
124 					} else {
125 						milliseconds = Integer
126 								.parseInt(decimal.substring(0, 3));
127 
128 						if (decimal.charAt(3) >= '5') {
129 							++milliseconds;
130 						}
131 					}
132 
133 					// add milliseconds to the current date
134 					date.setTime(date.getTime() + milliseconds);
135 				}
136 
137 				// parse optional timezone
138 				if (((index + 5) < src.length())
139 						&& ((src.charAt(index) == '+') || (src.charAt(index) == '-'))) {
140 					validateCharIsDigit(src, parse_pos, index + 1, "EXPECTED_NUMERAL");
141 					validateCharIsDigit(src, parse_pos, index + 2, "EXPECTED_NUMERAL");
142 					validateChar(src, parse_pos, index + 3, ':', "EXPECTED_COLON_IN_TIMEZONE");
143 					validateCharIsDigit(src, parse_pos, index + 4, "EXPECTED_NUMERAL");
144 					validateCharIsDigit(src, parse_pos, index + 5, "EXPECTED_NUMERAL");
145 
146 					final int hours = (((src.charAt(index + 1) - '0') * 10) + src
147 							.charAt(index + 2)) - '0';
148 					final int mins = (((src.charAt(index + 4) - '0') * 10) + src
149 							.charAt(index + 5)) - '0';
150 					int millisecs = ((hours * 60) + mins) * 60 * 1000;
151 
152 					// subtract millisecs from current date to obtain GMT
153 					if (src.charAt(index) == '+') {
154 						millisecs = -millisecs;
155 					}
156 
157 					date.setTime(date.getTime() + millisecs);
158 					index += 6;
159 				}
160 
161 				if ((index < src.length()) && (src.charAt(index) == 'Z')) {
162 					index++;
163 				}
164 
165 				if (index < src.length()) {
166 					handleParseError(parse_pos, "TOO_MANY_CHARS");
167 				}
168 			}
169 		} catch (ParseException pe) {
170 			log.error(pe.toString());
171 			index = 0; // IMPORTANT: this tells DateFormat.parse() to throw a ParseException
172 			parse_pos.setErrorIndex(index);
173 			date = null;
174 		}
175 		parse_pos.setIndex(index);
176 		return (date);
177 	}
178 
179 	/***
180 	 * @see DateFormat#format(java.util.Date)
181 	 */
182 	public StringBuffer format(Date date, StringBuffer append_buf,
183 			FieldPosition field_pos) {
184 		String str;
185 
186 		synchronized (DATEFORMAT_XSD_ZULU) {
187 			str = DATEFORMAT_XSD_ZULU.format(date);
188 		}
189 
190 		if (append_buf == null) {
191 			append_buf = new StringBuffer();
192 		}
193 
194 		append_buf.append(str);
195 
196 		return (append_buf);
197 	}
198 
199 	private void validateChar(String str, ParsePosition parse_pos, int index,
200 			char expected, String error_reason) throws ParseException {
201 		if (str.charAt(index) != expected) {
202 			handleParseError(parse_pos, error_reason);
203 		}
204 	}
205 
206 	private void validateCharIsDigit(String str, ParsePosition parse_pos,
207 			int index, String error_reason) throws ParseException {
208 		if (!Character.isDigit(str.charAt(index))) {
209 			handleParseError(parse_pos, error_reason);
210 		}
211 	}
212 
213 	private void handleParseError(ParsePosition parse_pos, String error_reason)
214 			throws ParseException {
215 		throw new ParseException("INVALID_XSD_DATETIME", parse_pos.getErrorIndex());
216 	}
217 
218 }