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.io.UnsupportedEncodingException;
    17  import java.util.ArrayList;
    18  import java.util.Collections;
    19  import java.util.Iterator;
    20  import java.util.List;
    21  import java.util.StringTokenizer;
    22
    23  import org.jboss.netty.buffer.ChannelBuffer;
    24
    25  /**
    26   * This class parses, creates and provides the very important Encapsulated header to the Decoder and
    27   * encapsulates the complexity of that header.
    28   *
    29   * @author Michael Mimo Moratti (mimo@mimo.ch)
    30   *
    31   */
    32  public final class Encapsulated {
    33
    34  	private List<Entry> entries;
    35
    36  	public Encapsulated() {
    37  		entries = new ArrayList<Encapsulated.Entry>();
    38  	}
    39
    40  	/**
    41  	 * Creates an instance based on the value given.
    42  	 *
    43  	 * @param headerValue valid Encapsulated value.
    44  	 */
    45  	public Encapsulated(String headerValue) {
    46  		this();
    47  		parseHeaderValue(headerValue);
    48  	}
    49
    50  	/**
    51  	 * Gets whether a given entry exists in the header value.
    52  	 *
    53  	 * @param entity the entity such as REQHDR, RESHDR and so on.
    54  	 * @return boolean true if the entity in question is present.
    55  	 */
    56  	public boolean containsEntry(IcapMessageElementEnum entity) {
    57  		for(Entry entry : entries) {
    58  			if(entry.getName().equals(entity)) {
    59  				return true;
    60  			}
    61  		}
    62  		return false;
    63   	}
    64
    65  	/**
    66  	 * Gets whether the message contains a body entity. This can be any valid body entry
    67  	 * including the null-body entity which indicates that the message does not contain.
    68  	 * a body.
    69  	 *
    70  	 * @return the correct @see {@link IcapMessageElementEnum} value.
    71  	 */
    72  	public IcapMessageElementEnum containsBodyEntry() {
    73  		IcapMessageElementEnum body = null;
    74  		for(Entry entry : entries) {
    75  			if(entry.getName().equals(IcapMessageElementEnum.OPTBODY)) {
    76  				body = entry.getName();
    77  				break;
    78  			} else if(entry.getName().equals(IcapMessageElementEnum.REQBODY)) {
    79  				body = entry.getName();
    80  				break;
    81  			} else if(entry.getName().equals(IcapMessageElementEnum.RESBODY)) {
    82  				body = entry.getName();
    83  				break;
    84  			} else if(entry.getName().equals(IcapMessageElementEnum.NULLBODY)) {
    85  				body = entry.getName();
    86  				break;
    87  			}
    88  		}
    89  		return body;
    90  	}
    91
    92  	/**
    93  	 * Iterator method. This method will provide the next available
    94  	 * Entry. Eventually it will return null.
    95  	 * Whether to return the next valid entry in the list dependens on the
    96  	 * @see Encapsulated#setEntryAsProcessed(IcapMessageElementEnum) method.
    97  	 *
    98  	 * @return @see {@link IcapMessageElementEnum} or null if no more entries are available.
    99  	 */
    100 	public IcapMessageElementEnum getNextEntry() {
    101 		IcapMessageElementEnum entryName = null;
    102 		for(Entry entry : entries) {
    103 			if(!entry.isProcessed()) {
    104 				entryName = entry.getName();
    105 				break;
    106 			}
    107 		}
    108 		return entryName;
    109 	}
    110
    111 	/**
    112 	 * reports that a given entry was processed and that the @see Encapsulated#getNextEntry()
    113 	 * can now return the next entry in line or null if no more are present.
    114 	 *
    115 	 * @param entryName the entry that was procesed.
    116 	 */
    117 	public void setEntryAsProcessed(IcapMessageElementEnum entryName) {
    118 		Entry entry = getEntryByName(entryName);
    119 		if(entry != null) {
    120 			entry.setIsProcessed();
    121 		}
    122 	}
    123
    124 	/**
    125 	 * Sets an entry with it's corresponding position.
    126 	 *
    127 	 * @param name the name of the Entry.
    128 	 * @param position the position of the entry within the icap message.
    129 	 */
    130 	public void addEntry(IcapMessageElementEnum name, int position) {
    131 		Entry entry = new Entry(name,position);
    132 		entries.add(entry);
    133 	}
    134
    135 	/**
    136 	 * This method encodes an {@link Encapsulated} instance into an Icap Message.
    137 	 *
    138 	 * @param buffer the Channelbuffer to encode into.
    139 	 * @return the amount of bytes that where written to the buffer while encoding.
    140 	 * @throws UnsupportedEncodingException If a character cannot be encoded in ASCII.
    141 	 */
    142 	public int encode(ChannelBuffer buffer) throws UnsupportedEncodingException {
    143 		int index = buffer.readableBytes();
    144 		Collections.sort(entries);
    145 		buffer.writeBytes("Encapsulated: ".getBytes(IcapCodecUtil.ASCII_CHARSET));
    146 		Iterator<Entry> entryIterator = entries.iterator();
    147 		while(entryIterator.hasNext()) {
    148 			Entry entry = entryIterator.next();
    149 			buffer.writeBytes(entry.getName().getValue().getBytes(IcapCodecUtil.ASCII_CHARSET));
    150 			buffer.writeBytes("=".getBytes(IcapCodecUtil.ASCII_CHARSET));
    151 			buffer.writeBytes(Integer.toString(entry.getPosition()).getBytes(IcapCodecUtil.ASCII_CHARSET));
    152 			if(entryIterator.hasNext()) {
    153 				buffer.writeByte(',');
    154 				buffer.writeByte(IcapCodecUtil.SPACE);
    155 			}
    156 		}
    157         buffer.writeBytes(IcapCodecUtil.CRLF);
    158         buffer.writeBytes(IcapCodecUtil.CRLF);
    159 		return buffer.readableBytes() - index;
    160 	}
    161
    162 	/*
    163 	REQMOD request: 	 [req-hdr] req-body
    164 	REQMOD response: 	{[req-hdr] req-body} | {[res-hdr] res-body}
    165 	RESPMOD request:	 [req-hdr] [res-hdr] res-body
    166 	RESPMOD response:	 [res-hdr] res-body
    167 	OPTIONS response:	 opt-body
    168 	 */
    169 	private void parseHeaderValue(String headerValue) {
    170 		if(headerValue == null) {
    171 			throw new IcapDecodingError("No value associated with Encapsualted header");
    172 		}
    173 		StringTokenizer tokenizer = new StringTokenizer(headerValue,",");
    174 		while(tokenizer.hasMoreTokens()) {
    175 			String parameterString = tokenizer.nextToken();
    176 			if(parameterString != null) {
    177 				String[] parameter = splitParameter(parameterString.trim());
    178 				try {
    179 					int value = Integer.parseInt(parameter[1]);
    180 					Entry entry = new Entry(IcapMessageElementEnum.fromString(parameter[0]),value);
    181 					entries.add(entry);
    182 				} catch(NumberFormatException nfe) {
    183 					throw new IcapDecodingError("the Encapsulated header value [" + parameter[1] + "] for the key [" + parameter[0] + "] is not a number");
    184 				}
    185 			}
    186 		}
    187 		Collections.sort(entries);
    188 	}
    189
    190 	private String[] splitParameter(String parameter) {
    191 		int offset = parameter.indexOf('=');
    192 		if(offset <= 0) {
    193 			throw new IcapDecodingError("Encapsulated header value was not understood [" + parameter + "]");
    194 		}
    195 		String key = parameter.substring(0,offset);
    196 		String value = parameter.substring(offset + 1,parameter.length());
    197 		if(value.contains(",")) {
    198 			value = value.substring(0,value.indexOf(','));
    199 		}
    200 		return new String[]{key.trim(),value};
    201 	}
    202
    203 	private Entry getEntryByName(IcapMessageElementEnum entryName) {
    204 		Entry returnValue = null;
    205 		for(Entry entry : entries) {
    206 			if(entry.getName().equals(entryName)) {
    207 				returnValue = entry;
    208 				break;
    209 			}
    210 		}
    211 		return returnValue;
    212 	}
    213
    214 	private final static class Entry implements Comparable<Entry> {
    215
    216 		private final IcapMessageElementEnum name;
    217 		private final Integer position;
    218 		private boolean processed;
    219
    220 		public Entry(IcapMessageElementEnum name, Integer position) {
    221 			this.name = name;
    222 			this.position = position;
    223 		}
    224
    225 		public IcapMessageElementEnum getName() {
    226 			return name;
    227 		}
    228
    229 		public int getPosition() {
    230 			return position;
    231 		}
    232
    233 		public void setIsProcessed() {
    234 			processed = true;
    235 		}
    236
    237 		public boolean isProcessed() {
    238 			return processed;
    239 		}
    240
    241 		@Override
    242 		public int compareTo(Entry entry) {
    243 			if(this.name.equals(IcapMessageElementEnum.NULLBODY)) {
    244 				return 1;
    245 			}
    246 			return this.position.compareTo(entry.position);
    247 		}
    248
    249 		@Override
    250 		public String toString() {
    251 			return name + "=" + position + " : " + processed;
    252 		}
    253 	}
    254
    255 	@Override
    256 	public String toString() {
    257 		StringBuilder builder = new StringBuilder();
    258 		builder.append("Encapsulated: ");
    259 		for(Entry entry : entries) {
    260 			if(entry != null) {
    261 				builder.append(" [").append(entry.toString()).append("] ");
    262 			}
    263 		}
    264  		return builder.toString();
    265 	}
    266 }