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 }