1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29 package uk.ac.rdg.resc.jstyx.messages;
30
31 import org.apache.mina.common.ByteBuffer;
32 import org.apache.mina.protocol.ProtocolViolationException;
33 import org.apache.mina.protocol.ProtocolEncoderOutput;
34
35 import org.apache.log4j.Logger;
36
37 import uk.ac.rdg.resc.jstyx.StyxUtils;
38
39 /***
40 * Abstract superclass for all Styx messages.
41 *
42 * @author Jon Blower
43 * $Revision: 507 $
44 * $Date: 2005-12-01 08:21:56 +0000 (Thu, 01 Dec 2005) $
45 * $Log$
46 * Revision 1.14 2005/12/01 08:21:56 jonblower
47 * Fixed javadoc comments
48 *
49 * Revision 1.13 2005/11/03 17:09:27 jonblower
50 * Created more efficient RreadMessage that involves less copying of buffers (still reliable)
51 *
52 * Revision 1.12 2005/11/03 07:46:55 jonblower
53 * Trying to fix bug with sending RreadMessages
54 *
55 * Revision 1.11 2005/05/10 19:17:54 jonblower
56 * Added dispose() method
57 *
58 * Revision 1.10 2005/03/22 17:48:27 jonblower
59 * Removed debug code that tracked ByteBuffer allocation
60 *
61 * Revision 1.9 2005/03/21 17:57:11 jonblower
62 * Trying to fix ByteBuffer leak in SGS server
63 *
64 * Revision 1.8 2005/03/18 13:56:00 jonblower
65 * Improved freeing of ByteBuffers, and bug fixes
66 *
67 * Revision 1.7 2005/03/16 17:56:22 jonblower
68 * Replaced use of java.nio.ByteBuffer with MINA's ByteBuffer to minimise copying of buffers
69 *
70 * Revision 1.6 2005/03/15 16:56:19 jonblower
71 * Changed to allow re-use of ByteBuffers once message is finished with
72 *
73 * Revision 1.5 2005/03/15 09:01:48 jonblower
74 * Message type now stored as short, not int
75 *
76 * Revision 1.4 2005/03/11 14:02:15 jonblower
77 * Merged MINA-Test_20059309 into main line of development
78 *
79 * Revision 1.3.2.3 2005/03/11 12:30:46 jonblower
80 * Changed so that message payloads are always ints, not longs
81 *
82 * Revision 1.3.2.2 2005/03/10 14:05:26 jonblower
83 * Reinstated getFid() and getName() methods
84 *
85 * Revision 1.3.2.1 2005/03/10 11:50:59 jonblower
86 * Changed to fit with MINA framework
87 *
88 */
89 public abstract class StyxMessage
90 {
91
92 private static final Logger log = Logger.getLogger(StyxMessage.class);
93
94 protected static final String lock = new String();
95
96
97 protected int length;
98
99
100
101 protected short type;
102 protected int tag;
103 protected String name;
104 protected ByteBuffer buf;
105
106 private int bytesRead;
107
108 /***
109 * Creates a new instance of StyxMessage.
110 */
111 protected StyxMessage(int length, short type, int tag)
112 {
113 this.length = length;
114 this.type = type;
115 this.tag = tag;
116 this.name = "StyxMessage";
117 this.buf = null;
118
119 }
120
121 /***
122 * @return The name of this message (e.g. "Tread", "Rwalk", etc)
123 */
124 public final String getName()
125 {
126 return this.name;
127 }
128
129 /***
130 * @return The total length of the StyxMessage in bytes
131 */
132 public final int getLength()
133 {
134 return this.length;
135 }
136
137 /***
138 * @return The type of the message
139 */
140 public final short getType()
141 {
142 return this.type;
143 }
144
145 /***
146 * @return The tag of the message
147 */
148 public final int getTag()
149 {
150 return this.tag;
151 }
152
153 /***
154 * Sets the tag of the message
155 */
156 public final void setTag(int newTag)
157 {
158 this.tag = newTag;
159 }
160
161 /***
162 * @return the buffer containing the body of this message
163 */
164 public final ByteBuffer getBuffer()
165 {
166 return this.buf;
167 }
168
169 /***
170 * @return the fid associated with this message. This default implementation
171 * returns StyxUtils.NOFID; subclasses should override this. This method
172 * only exists in this superclass as a convenience for the StyxMon application.
173 */
174 public long getFid()
175 {
176 return StyxUtils.NOFID;
177 }
178
179 /***
180 * Read bytes from the given ByteBuffer into this Message. There may still
181 * be bytes remaining in the input buffer after this method has been called.
182 * @param in The org.apache.mina.common.ByteBuffer that contains the data.
183 * @return true if we now have a complete StyxMessage, false otherwise
184 * @throws ProtocolViolationException if the bytes do not represent a valid
185 * StyxMessage
186 */
187 public final boolean readBytesFrom(ByteBuffer in) throws ProtocolViolationException
188 {
189 int bodyLength = this.length - StyxUtils.HEADER_LENGTH;
190 if (bodyLength == 0)
191 {
192
193 return true;
194 }
195 if (this.buf == null)
196 {
197 this.bytesRead = 0;
198
199
200 if (in.remaining() == bodyLength)
201 {
202
203
204
205
206 log.debug("input buffer contains a whole message; won't create new buffer");
207
208
209 in.acquire();
210 this.buf = in;
211 this.bytesRead = bodyLength;
212
213 this.decode();
214 return true;
215 }
216 else
217 {
218
219
220 this.buf = ByteBuffer.allocate(bodyLength);
221 }
222 }
223 if (this.bytesRead >= bodyLength)
224 {
225
226 return true;
227 }
228
229
230
231
232 int bytesLeft = bodyLength - this.bytesRead;
233 int bytesToRead = bytesLeft < in.remaining() ? bytesLeft : in.remaining();
234
235
236 byte[] b = new byte[bytesToRead];
237 in.get(b);
238 this.buf.put(b);
239 this.bytesRead += b.length;
240
241
242
243 if (this.bytesRead >= bodyLength)
244 {
245
246
247 this.buf.flip();
248 this.decode();
249 return true;
250 }
251 else
252 {
253 return false;
254 }
255 }
256
257 /***
258 * Called when we have a complete message. Simply wraps the buffer as a
259 * StyxBuffer to make it easy to read Styx primitives and calls
260 * this.decodeBody()
261 */
262 private void decode() throws ProtocolViolationException
263 {
264 StyxBuffer styxBuf = new StyxBuffer(this.buf);
265 this.decodeBody(styxBuf);
266 }
267
268 /***
269 * Called when a complete message has arrived; signals that we are ready
270 * to interpret the raw bytes in the buffer and turn them into meaningful
271 * information. Subclasses should make sure that the buffer is no longer
272 * needed once this method has finished, as the underlying buffer will
273 * be reused.
274 * @throws ProtocolViolationException if the buffer doesn't contain a valid
275 * StyxMessage body
276 */
277 protected abstract void decodeBody(StyxBuffer styxBuf)
278 throws ProtocolViolationException;
279
280 /***
281 * Called by StyxMessageEncoder to send a message to the output
282 */
283 public void write(ProtocolEncoderOutput out) throws ProtocolViolationException
284 {
285
286 this.encode();
287 synchronized(lock)
288 {
289
290 out.write(this.buf);
291 }
292 }
293
294 /***
295 * Called by StyxMessageEncoder when we are about to send a message. Creates
296 * the underlying ByteBuffer, wraps it as a StyxBuffer, writes the header
297 * information, calls encodeBody() to write the body information, then
298 * flips the buffer so that it is ready for writing to the output stream.
299 * @throws ProtocolViolationException if a problem occurred encoding the
300 * message (shouldn't happen)
301 */
302 public void encode() throws ProtocolViolationException
303 {
304
305 log.debug("Allocating new ByteBuffer of length " + this.length);
306 this.buf = ByteBuffer.allocate(this.length);
307
308
309 StyxBuffer styxBuf = new StyxBuffer(this.buf);
310 styxBuf.putUInt(this.length).putUByte(this.type).putUShort(this.tag);
311 this.encodeBody(styxBuf);
312 this.buf.flip();
313 }
314
315 /***
316 * Encode the body of the message into bytes in the underlying buffer
317 */
318 protected abstract void encodeBody(StyxBuffer styxBuf)
319 throws ProtocolViolationException;
320
321 /***
322 * @return String representation of this StyxMessage
323 */
324 public String toString()
325 {
326 StringBuffer s = new StringBuffer(this.name);
327 s.append(": ");
328 s.append(this.length);
329 s.append(", ");
330 s.append(this.type);
331 s.append(", ");
332 s.append(this.tag);
333 s.append(this.getElements());
334 return s.toString();
335 }
336
337 /***
338 * @return the body elements of this message as a string
339 */
340 protected abstract String getElements();
341
342 /***
343 * @return a human-readable string that displays the contents of the message,
344 * without the header info. This default implementation simply calls
345 * this.getElements(): subclasses should override this behaviour
346 */
347 public String toFriendlyString()
348 {
349 return this.getElements();
350 }
351
352 /***
353 * Sends request to release the underlying ByteBuffer back to the pool.
354 * Actually, this just decrements the reference count for the buffer; the
355 * buffer is only released when this count reaches zero. This is called
356 * once the StyxMessageDecoder.decode() method has finished.
357 */
358 void release()
359 {
360 if (this.buf != null)
361 {
362 this.buf.release();
363 }
364 }
365
366 /***
367 * This is called <b>after</b> the message has been sent (in
368 * StyxServerProtocolHandler.messageSent()) and is a signal to free any
369 * resources associated with the message (e.g. an RreadMessage can release
370 * the ByteBuffer holding the payload). This default implementation does
371 * nothing: subclasses should override if necessary.
372 */
373 public void dispose()
374 {
375 return;
376 }
377
378 /***
379 * Static factory method for creating a StyxMessage. Called by StyxMessageDecoder
380 * when the header of a message has been decoded. Returns the appropriate
381 * subclass of StyxMessage, depending on the provided type.
382 * @param length The total length of the message (header and body)
383 * @param type The numeric code representing the message type
384 * @param tag The message tag
385 * @return A StyxMessage of the appropriate type, depending on the tag
386 * @throws ProtocolViolationException if the message is of an unknown type
387 */
388 public static StyxMessage createStyxMessage(int length, short type, int tag)
389 throws ProtocolViolationException
390 {
391 if (log.isDebugEnabled())
392 {
393 log.debug("Creating StyxMessage(length = " + length + ", type = "
394 + type + ", tag = " + tag + ")");
395 }
396 if (type == 100)
397 {
398 return new TversionMessage(length, type, tag);
399 }
400 else if (type == 101)
401 {
402 return new RversionMessage(length, type, tag);
403 }
404 else if (type == 102)
405 {
406 return new TauthMessage(length, type, tag);
407 }
408 else if (type == 103)
409 {
410 return new RauthMessage(length, type, tag);
411 }
412 else if (type == 104)
413 {
414 return new TattachMessage(length, type, tag);
415 }
416 else if (type == 105)
417 {
418 return new RattachMessage(length, type, tag);
419 }
420
421 else if (type == 107)
422 {
423 return new RerrorMessage(length, type, tag);
424 }
425 else if (type == 108)
426 {
427 return new TflushMessage(length, type, tag);
428 }
429 else if (type == 109)
430 {
431 return new RflushMessage(length, type, tag);
432 }
433 else if (type == 110)
434 {
435 return new TwalkMessage(length, type, tag);
436 }
437 else if (type == 111)
438 {
439 return new RwalkMessage(length, type, tag);
440 }
441 else if (type == 112)
442 {
443 return new TopenMessage(length, type, tag);
444 }
445 else if (type == 113)
446 {
447 return new RopenMessage(length, type, tag);
448 }
449 else if (type == 114)
450 {
451 return new TcreateMessage(length, type, tag);
452 }
453 else if (type == 115)
454 {
455 return new RcreateMessage(length, type, tag);
456 }
457 else if (type == 116)
458 {
459 return new TreadMessage(length, type, tag);
460 }
461 else if (type == 117)
462 {
463 return new RreadMessage(length, type, tag);
464 }
465 else if (type == 118)
466 {
467 return new TwriteMessage(length, type, tag);
468 }
469 else if (type == 119)
470 {
471 return new RwriteMessage(length, type, tag);
472 }
473 else if (type == 120)
474 {
475 return new TclunkMessage(length, type, tag);
476 }
477 else if (type == 121)
478 {
479 return new RclunkMessage(length, type, tag);
480 }
481 else if (type == 122)
482 {
483 return new TremoveMessage(length, type, tag);
484 }
485 else if (type == 123)
486 {
487 return new RremoveMessage(length, type, tag);
488 }
489 else if (type == 124)
490 {
491 return new TstatMessage(length, type, tag);
492 }
493 else if (type == 125)
494 {
495 return new RstatMessage(length, type, tag);
496 }
497 else if (type == 126)
498 {
499 return new TwstatMessage(length, type, tag);
500 }
501 else if (type == 127)
502 {
503 return new RwstatMessage(length, type, tag);
504 }
505 else
506 {
507 throw new ProtocolViolationException("Unknown message type " + type);
508 }
509 }
510
511 }