View Javadoc

1   /*******************************************************************************
2    * Copyright (c) 2011 Michael Mimo Moratti.
3    *  
4    * Michael Mimo Moratti licenses this file to you under the Apache License, version 2.0
5    * (the "License"); you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at:
7    *     http://www.apache.org/licenses/LICENSE-2.0
8    * Unless required by applicable law or agreed to in writing, software
9    * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
10   * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
11   * License for the specific language governing permissions and limitations
12   * under the License.
13   *******************************************************************************/
14  package ch.mimo.netty.handler.codec.icap;
15  
16  import java.text.ParseException;
17  import java.text.SimpleDateFormat;
18  import java.util.Date;
19  import java.util.LinkedHashSet;
20  import java.util.Locale;
21  import java.util.Map;
22  import java.util.Set;
23  
24  /**
25   * Icap Headers
26   * 
27   * This class provides a linked list implementation in order to store Icap headers.
28   * 
29   * @author Michael Mimo Moratti (mimo@mimo.ch)
30   *
31   */
32  public final class IcapHeaders {
33  	
34  	private Entry base;
35  	private Entry head;
36  	
37  	private static final String DATE_FORMAT = "E, dd MMM yyyy HH:mm:ss z";
38  	
39  	/**
40  	 * The most common Icap Header names.
41  	 * 
42  	 * @author Michael Mimo Moratti (mimo@mimo.ch)
43  	 *
44  	 */
45  	public static final class Names {
46          /**
47           * {@code "Cache-Control"}
48           */
49  		public static final String CACHE_CONTROL = "Cache-Control";
50          /**
51           * {@code "Connection"}
52           */
53  		public static final String CONNECTION = "Connection";
54          /**
55           * {@code "Date"}
56           */
57  		public static final String DATE = "Date";	
58          /**
59           * {@code "Expires"}
60           */
61  		public static final String EXPIRES = "Expires";
62          /**
63           * {@code "Pragma"}
64           */
65  		public static final String PRAGMA = "Pragma";
66          /**
67           * {@code "Trailer"}
68           */
69  		public static final String TRAILER = "Trailer";
70          /**
71           * {@code "Upgrade"}
72           */
73  		public static final String UPGRADE = "Upgrade";
74          /**
75           * {@code "Encapsulated"}
76           */
77  		public static final String ENCAPSULATED = "Encapsulated";
78          /**
79           * {@code "Authorization"}
80           */
81  		public static final String AUTHORIZATION = "Authorization";
82          /**
83           * {@code "Allow"}
84           */
85  		public static final String ALLOW = "Allow";
86          /**
87           * {@code "From"}
88           */
89  		public static final String FROM = "From";
90          /**
91           * {@code "Host"}
92           */
93  		public static final String HOST = "Host";
94          /**
95           * {@code "Referer"}
96           */
97  		public static final String REFERER = "Referer";
98          /**
99           * {@code "User-Agent"}
100          */
101 		public static final String USER_AGENT = "User-Agent";
102         /**
103          * {@code "Preview"}
104          */
105 		public static final String PREVIEW = "Preview";
106 		
107 		/**
108 		 * {@code "ISTag"}
109 		 */
110 		public static final String ISTAG = "ISTag";
111 		
112 		/**
113 		 * {@code "Methods"}
114 		 */
115 		public static final String METHODS = "Methods";
116 		
117 		/**
118 		 * {@code "Service"}
119 		 */
120 		public static final String SERVICE = "Service";
121 		
122 		/**
123 		 * {@code "Opt-body-type"}
124 		 */
125 		public static final String OPT_BODY_TYPE = "Opt-body-type";
126 		
127 		/**
128 		 * {@code "Max-connections"}
129 		 */
130 		public static final String MAX_CONNECTIONS = "Max-connections";
131 		
132 		/**
133 		 * {@code "Options-TTL"}
134 		 */
135 		public static final String OPTIONS_TTL = "Options-TTL";
136 		
137 		/**
138 		 * {@code "Service-ID"}
139 		 */
140 		public static final String SERVICE_ID = "Service-ID";
141 		
142 		/**
143 		 * {@code "Transfer-Preview"}
144 		 */
145 		public static final String TRANSFER_PREVIEW = "Transfer-Preview";
146 		
147 		/**
148 		 * {@code "Transfer-Ignore"}
149 		 */
150 		public static final String TRANSFER_IGNORE = "Transfer-Ignore";
151 		
152 		/**
153 		 * {@code "Transfer-Complete"}
154 		 */
155 		public static final String TRANSFER_COMPLETE = "Transfer-Complete";
156 	}
157 	
158 	public IcapHeaders() {
159 	}
160 	
161 	public void clearHeaders() {
162 		base = null;
163 		head = null;
164 	}
165 	
166 	/**
167 	 * Adds a header key,value combination to the list
168 	 * The existence of such a header name will have no impact. The header is simply added
169 	 * to the end of the linked list. 
170 	 * 
171 	 * @param name Icap message header name
172 	 * @param value Icap message header value. Can also be null
173 	 */
174 	public void addHeader(String name, Object value) {
175 		Entry entry = new Entry(name,value);
176 		if(base == null) {
177 			base = entry;
178 			head = entry;
179 		} else {
180 			Entry currentHead = head;
181 			head = entry;
182 			entry.before = currentHead;
183 			currentHead.after = entry;
184 		}
185 	}
186 	
187 	public void addDateHeader(String name, Date value) {
188 		SimpleDateFormat format = new SimpleDateFormat(DATE_FORMAT,Locale.ENGLISH);
189 		addHeader(name,format.format(value));
190 	}
191 	
192 	/**
193 	 * Sets one header at the end of the list.
194 	 * Headers with the same name that are already in the list will be removed first!
195 	 * 
196 	 * @param name Icap message header name
197 	 * @param value Icap message header value. Can also be null
198 	 */
199 	public void setHeader(String name, Object value) {
200 		removeHeader(name);
201 		addHeader(name,value);
202 	}
203 	
204 	/**
205 	 * Sets one header with many values.
206 	 * Headers with the same name that are already in the list will be removed first!
207 	 * 
208 	 * @param name Icap message header name
209 	 * @param values Icap message header value. Can also be null
210 	 */
211 	public void setHeader(String name, Iterable<?> values) {
212 		removeHeader(name);
213 		for(Object value : values) {
214 			addHeader(name,value);
215 		}
216 	}
217 	
218 	/**
219 	 * retrieves a header value from the list.
220 	 * If no header exists with the given name null is returned.
221 	 *
222 	 * If there are multiple headers with the same name only the first occurence in the
223 	 * list is returned.
224 	 * 
225 	 * @param name Icap message header name
226 	 * @return String value or null
227 	 */
228 	public String getHeader(String name) {
229 		Entry entry = base;
230 		while(entry != null) {
231 			if(identicalKeys(entry.getKey(),name)) {
232 				return entry.getValue();
233 			}
234 			entry = entry.after;
235 		}
236 		return null;
237 	}
238 	
239 	/**
240 	 * retrieves a date header value as @see {@link Date}
241 	 * If the header does not exist null is returned. 
242 	 * If the date cannot be parsed a parsing exception is thrown.
243 	 * 
244 	 * @param name Icap message header name
245 	 * @return if possible a valid @see {@link Date} instance or null
246 	 * @throws IllegalArgumentException if the date string value cannot be parsed.
247 	 */
248 	public Date getDateHeader(String name) {
249 		Date date = null;
250 		SimpleDateFormat format = new SimpleDateFormat(DATE_FORMAT,Locale.ENGLISH);
251 		String value = getHeader(name);
252 		if(value != null) {
253 			try {
254 				date = format.parse(value);
255 			} catch (ParseException e) {
256 				throw new IllegalArgumentException("The header value [" + value + "] is not a valid date",e);
257 			}
258 		}
259 		return date;
260 	}
261 	
262 	/**
263 	 * retrieves all values for one header name.
264 	 * If no header exists with that given name an empty set is returned.
265 	 * 
266 	 * @param name Icap message header name
267 	 * @return Set of values from all headers with the same name, or empty set.
268 	 */
269 	public Set<String> getHeaders(String name) {
270 		Set<String> values = new LinkedHashSet<String>();
271 		Entry entry = base;
272 		while(entry != null) {
273 			if(identicalKeys(entry.getKey(),name)) {
274 				values.add(entry.getValue());
275 			}
276 			entry = entry.after;
277 		}
278 		return values;
279 	}
280 	
281 	/**
282 	 * retrieval method for all headers that are currently in this list.
283 	 * 
284 	 * @return Set of Map Entry instances.
285 	 */
286 	public Set<Map.Entry<String, String>> getHeaders() {
287 		Set<Map.Entry<String, String>> headers = new LinkedHashSet<Map.Entry<String,String>>();
288 		Entry entry = base;
289 		while(entry != null) {
290 			headers.add(entry);
291 			entry = entry.after;
292 		}
293 		return headers;
294 	}
295 	
296 	/**
297 	 * check method to validate if a certain header does exists in the list.
298 	 * 
299 	 * @param name Icap message header name
300 	 * @return boolean true if the header exists.
301 	 */
302 	public boolean containsHeader(String name) {
303 		return getHeader(name) != null;
304 	}
305 	
306 	/**
307 	 * removes all headers with the same name from the list.
308 	 * 
309 	 * @param name Icap message header name
310 	 */
311 	public void removeHeader(String name) {
312 		if(base == null) {
313 			return;
314 		}
315 		Entry entry = null;
316 		if(base.after == null) {
317 			if(identicalKeys(base.getKey(),name)) {
318 				base = null;
319 				return;
320 			}
321 		} else {
322 			entry = base.after;
323 		}
324 		while(entry != null) {
325 			if(identicalKeys(entry.getKey(),name)) {
326 				Entry before = entry.before;
327 				Entry after = entry.after;
328 				before.after = after;
329 				after.before = before;
330 				entry = after;
331 			} else {
332 				entry = entry.after;
333 			}
334 		}
335 		if(identicalKeys(base.getKey(),name)) {
336 			base = base.after;
337 			base.before = null;
338 		}
339 	}
340 	
341 	/**
342 	 * retrieval method for all header names.
343 	 * this list is unique. If a header name has two entries the name is returned only once.
344 	 * 
345 	 * @return unique set with all header names in the list.
346 	 */
347 	public Set<String> getHeaderNames() {
348 		Set<String> names = new LinkedHashSet<String>();
349 		Entry entry = base;
350 		while(entry != null) {
351 			names.add(entry.getKey());
352 			entry = entry.after;
353 		}
354 		return names;
355 	}
356 	
357 	/**
358 	 * Convenience method to retrieve the @see {@link Integer} value from a Icap Preview header.
359 	 * If the header does not exist the value 0 is returned.
360 	 * If the header value cannot be parsed into a valid integer a @see {@link IcapDecodingError} is thrown.
361 	 * 
362 	 * @return int value of preview header.
363 	 */
364 	public int getPreviewHeaderValue() {
365 		String value = getHeader(Names.PREVIEW);
366 		int result = 0;
367 		try {
368 			if(value != null) {
369 				result = Integer.parseInt(value);
370 			}
371 		} catch(NumberFormatException nfe) {
372 			throw new IcapDecodingError("Unable to understand the preview amount value [" + value + "]");
373 		}
374 		return result;
375 	}
376 	
377 	private boolean identicalKeys(String key1, String key2) {
378 		if(key1 != null & key2 != null) {
379 			return key1.equalsIgnoreCase(key2);
380 		}
381 		return false;
382 	}
383 
384 	private static final class Entry implements Map.Entry<String, String> {
385 		
386 		private String key;
387 		private String value;
388 		
389 		private Entry before, after;
390 		
391 		Entry(String key, Object value) {
392 			IcapCodecUtil.validateHeaderName(key);
393 			this.key = key;
394 			if(value != null) {
395 				this.value = value.toString();
396 				IcapCodecUtil.validateHeaderValue(this.value);
397 			}
398 		}
399 		
400 		public String getKey() {
401 			return key;
402 		}
403 		
404 		public String getValue() {
405 			return value;
406 		}
407 		
408 		@Override
409 		public String setValue(String value) {
410 			return null;
411 		}
412 	}
413 	
414 	@Override
415 	public String toString() {
416 		StringBuilder builder = new StringBuilder();
417 		for(String name : getHeaderNames()) {
418 			builder.append("[" + name + "] = [" + getHeaders(name) + "]");
419 		}
420 		return builder.toString();
421 	}
422 }