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 }