001 /**
002 * GRANITE DATA SERVICES
003 * Copyright (C) 2006-2013 GRANITE DATA SERVICES S.A.S.
004 *
005 * This file is part of the Granite Data Services Platform.
006 *
007 * Granite Data Services is free software; you can redistribute it and/or
008 * modify it under the terms of the GNU Lesser General Public
009 * License as published by the Free Software Foundation; either
010 * version 2.1 of the License, or (at your option) any later version.
011 *
012 * Granite Data Services is distributed in the hope that it will be useful,
013 * but WITHOUT ANY WARRANTY; without even the implied warranty of
014 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser
015 * General Public License for more details.
016 *
017 * You should have received a copy of the GNU Lesser General Public
018 * License along with this library; if not, write to the Free Software
019 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
020 * USA, or see <http://www.gnu.org/licenses/>.
021 */
022 /*
023 * www.openamf.org
024 *
025 * Distributable under LGPL license.
026 * See terms of license at gnu.org.
027 */
028
029 package org.granite.messaging.amf.io;
030
031 import java.io.ByteArrayInputStream;
032 import java.io.DataInputStream;
033 import java.io.IOException;
034 import java.io.InputStream;
035 import java.io.ObjectInput;
036 import java.io.UTFDataFormatException;
037 import java.util.ArrayList;
038 import java.util.Calendar;
039 import java.util.Date;
040 import java.util.GregorianCalendar;
041 import java.util.List;
042 import java.util.TimeZone;
043
044 import javax.xml.parsers.DocumentBuilder;
045 import javax.xml.parsers.DocumentBuilderFactory;
046
047 import org.granite.context.GraniteContext;
048 import org.granite.logging.Logger;
049 import org.granite.messaging.amf.AMF0Body;
050 import org.granite.messaging.amf.AMF0Message;
051 import org.w3c.dom.Document;
052
053 import flex.messaging.io.ASObject;
054
055 /**
056 * AMF Deserializer
057 *
058 * @author Jason Calabrese <jasonc@missionvi.com>
059 * @author Pat Maddox <pergesu@users.sourceforge.net>
060 * @author Sylwester Lachiewicz <lachiewicz@plusnet.pl>
061 * @version $Revision: 1.38 $, $Date: 2004/12/09 04:50:07 $
062 */
063 public class AMF0Deserializer {
064
065 private static final Logger log = Logger.getLogger(AMF0Deserializer.class);
066
067 private List<Object> storedObjects = null;
068
069 /**
070 * The AMF input stream
071 */
072 private final InputStream rawInputStream;
073 private final DataInputStream dataInputStream;
074
075 /**
076 * Object to store the deserialized data
077 */
078 private final AMF0Message message = new AMF0Message();
079
080
081 /**
082 * Deserialize message
083 *
084 * @param inputStream message input stream
085 * @throws IOException
086 */
087 public AMF0Deserializer(InputStream inputStream) throws IOException {
088 //log.info("Deserializing Message, for more info turn on debug level");
089
090 // Save the input stream for this object
091 this.rawInputStream = inputStream;
092 this.dataInputStream = inputStream instanceof DataInputStream
093 ? ((DataInputStream)inputStream)
094 : new DataInputStream(inputStream);
095
096 // Read the binary header
097 readHeaders();
098 log.debug("readHeader");
099 // Read the binary body
100 readBodies();
101 log.debug("readBody");
102 }
103
104 public AMF0Message getAMFMessage() {
105 return message;
106 }
107
108 /**
109 * Read message header
110 *
111 * @throws IOException
112 */
113 protected void readHeaders() throws IOException {
114 // version
115 message.setVersion(dataInputStream.readUnsignedShort());
116 // Find total number of header elements
117 int headerCount = dataInputStream.readUnsignedShort();
118 log.debug("headerCount = %d", headerCount);
119
120 // Loop over all the header elements
121 for (int i = 0; i < headerCount; i++) {
122 // clear storedObjects - references are new for every header
123 storedObjects = new ArrayList<Object>();
124 String key = dataInputStream.readUTF();
125 // Find the must understand flag
126 boolean required = dataInputStream.readBoolean();
127 // Grab the length of the header element
128 /*long length =*/ dataInputStream.readInt();
129 // Grab the type of the element
130 byte type = dataInputStream.readByte();
131 // Turn the element into real data
132 Object value = readData(type);
133 // Save the name/value into the headers array
134 message.addHeader(key, required, value);
135 }
136 }
137
138 /**
139 * Read message body
140 *
141 * @throws IOException
142 */
143 protected void readBodies() throws IOException {
144 // Find the total number of body elements
145 int bodyCount = dataInputStream.readUnsignedShort();
146 log.debug("bodyCount = %d", bodyCount);
147
148 // Loop over all the body elements
149 for (int i = 0; i < bodyCount; i++) {
150 //clear storedObjects
151 storedObjects = new ArrayList<Object>();
152 // The target method
153 String method = dataInputStream.readUTF();
154 // The target that the client understands
155 String target = dataInputStream.readUTF();
156 // Get the length of the body element
157 /*long length =*/ dataInputStream.readInt();
158 // Grab the type of the element
159 byte type = dataInputStream.readByte();
160 log.debug("type = 0x%02X", type);
161 // Turn the argument elements into real data
162 Object data = readData(type);
163 // Add the body element to the body object
164 message.addBody(method, target, data, type);
165 }
166 }
167
168 /**
169 * Reads custom class
170 *
171 * @return the read Object
172 * @throws IOException
173 */
174 protected Object readCustomClass() throws IOException {
175 // Grab the explicit type - somehow it works
176 String type = dataInputStream.readUTF();
177 log.debug("Reading Custom Class: %s", type);
178 /*
179 String mappedJavaClass = OpenAMFConfig.getInstance().getJavaClassName(type);
180 if (mappedJavaClass != null) {
181 type = mappedJavaClass;
182 }
183 */
184 ASObject aso = new ASObject(type);
185 // The rest of the bytes are an object without the 0x03 header
186 return readObject(aso);
187 }
188
189 protected ASObject readObject() throws IOException {
190 ASObject aso = new ASObject();
191 return readObject(aso);
192 }
193
194 /**
195 * Reads an object and converts the binary data into an List
196 *
197 * @param aso
198 * @return the read object
199 * @throws IOException
200 */
201 protected ASObject readObject(ASObject aso) throws IOException {
202 storeObject(aso);
203 // Init the array
204 log.debug("reading object");
205 // Grab the key
206 String key = dataInputStream.readUTF();
207 for (byte type = dataInputStream.readByte();
208 type != 9;
209 type = dataInputStream.readByte()) {
210 // Grab the value
211 Object value = readData(type);
212 // Save the name/value pair in the map
213 if (value == null) {
214 log.info("Skipping NULL value for : %s", key);
215 } else {
216 aso.put(key, value);
217 log.debug(" adding {key=%s, value=%s, type=%d}", key, value, type);
218 }
219 // Get the next name
220 key = dataInputStream.readUTF();
221 }
222 log.debug("finished reading object");
223 // Return the map
224 return aso;
225 }
226
227 /**
228 * Reads array
229 *
230 * @return the read array (as a list).
231 * @throws IOException
232 */
233 protected List<?> readArray() throws IOException {
234 // Init the array
235 List<Object> array = new ArrayList<Object>();
236 storeObject(array);
237 log.debug("Reading array");
238 // Grab the length of the array
239 long length = dataInputStream.readInt();
240 log.debug("array length = %d", length);
241 // Loop over all the elements in the data
242 for (long i = 0; i < length; i++) {
243 // Grab the type for each element
244 byte type = dataInputStream.readByte();
245 // Grab the element
246 Object data = readData(type);
247 array.add(data);
248 }
249 // Return the data
250 return array;
251 }
252
253 /**
254 * Store object in internal array
255 *
256 * @param o the object to store
257 */
258 private void storeObject(Object o) {
259 storedObjects.add(o);
260 log.debug("storedObjects.size: %d", storedObjects.size());
261 }
262
263 /**
264 * Reads date
265 *
266 * @return the read date
267 * @throws IOException
268 */
269 protected Date readDate() throws IOException {
270 long ms = (long) dataInputStream.readDouble(); // Date in millis from 01/01/1970
271
272 // here we have to read in the raw
273 // timezone offset (which comes in minutes, but incorrectly signed),
274 // make it millis, and fix the sign.
275 int timeoffset = dataInputStream.readShort() * 60000 * -1; // now we have millis
276
277 TimeZone serverTimeZone = TimeZone.getDefault();
278
279 // now we subtract the current timezone offset and add the one that was passed
280 // in (which is of the Flash client), which gives us the appropriate ms (i think)
281 // -alon
282 Calendar sent = new GregorianCalendar();
283 sent.setTime( (new Date(ms - serverTimeZone.getRawOffset() + timeoffset)));
284
285 TimeZone sentTimeZone = sent.getTimeZone();
286
287 // we have to handle daylight savings ms as well
288 if (sentTimeZone.inDaylightTime(sent.getTime()))
289 {
290 //
291 // Implementation note: we are trying to maintain compatibility
292 // with J2SE 1.3.1
293 //
294 // As such, we can't use java.util.Calendar.getDSTSavings() here
295 //
296 sent.setTime(new java.util.Date(sent.getTime().getTime() - 3600000));
297 }
298
299 return sent.getTime();
300 }
301
302 /**
303 * Reads flushed stored object
304 *
305 * @return the stored object
306 * @throws IOException
307 */
308 protected Object readFlushedSO() throws IOException {
309 int index = dataInputStream.readUnsignedShort();
310 log.debug("Object Index: %d", index);
311 return storedObjects.get(index);
312 }
313
314 /**
315 * Reads object
316 *
317 * @return always null...
318 */
319 protected Object readASObject() {
320 return null;
321 }
322
323 /**
324 * Reads object
325 *
326 * @return the AMF3 decoded object
327 */
328 protected Object readAMF3Data() throws IOException {
329 ObjectInput amf3 = GraniteContext.getCurrentInstance().getGraniteConfig().newAMF3Deserializer(rawInputStream);
330 try {
331 return amf3.readObject();
332 } catch (ClassNotFoundException e) {
333 throw new RuntimeException(e);
334 }
335 }
336
337 /**
338 * Reads object from inputstream with selected type
339 *
340 * @param type
341 * @return the read object
342 * @throws IOException
343 */
344 protected Object readData(byte type) throws IOException {
345 log.debug("Reading data of type: %s", AMF0Body.getObjectTypeDescription(type));
346 switch (type) {
347 case AMF0Body.DATA_TYPE_NUMBER: // 0
348 return new Double(dataInputStream.readDouble());
349 case AMF0Body.DATA_TYPE_BOOLEAN: // 1
350 return new Boolean(dataInputStream.readBoolean());
351 case AMF0Body.DATA_TYPE_STRING: // 2
352 return dataInputStream.readUTF();
353 case AMF0Body.DATA_TYPE_OBJECT: // 3
354 return readObject();
355 case AMF0Body.DATA_TYPE_MOVIE_CLIP: // 4
356 throw new IOException("Unknown/unsupported object type " + AMF0Body.getObjectTypeDescription(type));
357 case AMF0Body.DATA_TYPE_NULL: // 5
358 case AMF0Body.DATA_TYPE_UNDEFINED: //6
359 return null;
360 case AMF0Body.DATA_TYPE_REFERENCE_OBJECT: // 7
361 return readFlushedSO();
362 case AMF0Body.DATA_TYPE_MIXED_ARRAY: // 8
363 /*long length =*/ dataInputStream.readInt();
364 //don't do anything with the length
365 return readObject();
366 case AMF0Body.DATA_TYPE_OBJECT_END: // 9
367 return null;
368 case AMF0Body.DATA_TYPE_ARRAY: // 10
369 return readArray();
370 case AMF0Body.DATA_TYPE_DATE: // 11
371 return readDate();
372 case AMF0Body.DATA_TYPE_LONG_STRING: // 12
373 return readLongUTF(dataInputStream);
374 case AMF0Body.DATA_TYPE_AS_OBJECT: // 13
375 return readASObject();
376 case AMF0Body.DATA_TYPE_RECORDSET: // 14
377 return null;
378 case AMF0Body.DATA_TYPE_XML: // 15
379 return convertToDOM(dataInputStream);
380 case AMF0Body.DATA_TYPE_CUSTOM_CLASS: // 16
381 return readCustomClass();
382 case AMF0Body.DATA_TYPE_AMF3_OBJECT: // 17
383 return readAMF3Data();
384 default :
385 throw new IOException("Unknown/unsupported object type " + AMF0Body.getObjectTypeDescription(type));
386 }
387 }
388
389 /**
390 * This is a hacked verison of Java's DataInputStream.readUTF(), which only
391 * supports Strings <= 65535 UTF-8-encoded characters
392 */
393 private Object readLongUTF(DataInputStream in) throws IOException {
394 int utflen = in.readInt();
395 StringBuffer str = new StringBuffer(utflen);
396 byte bytearr [] = new byte[utflen];
397 int c, char2, char3;
398 int count = 0;
399
400 in.readFully(bytearr, 0, utflen);
401
402 while (count < utflen) {
403 c = bytearr[count] & 0xff;
404 switch (c >> 4) {
405 case 0: case 1: case 2: case 3: case 4: case 5: case 6: case 7:
406 /* 0xxxxxxx*/
407 count++;
408 str.append((char)c);
409 break;
410 case 12: case 13:
411 /* 110x xxxx 10xx xxxx*/
412 count += 2;
413 if (count > utflen)
414 throw new UTFDataFormatException();
415 char2 = bytearr[count-1];
416 if ((char2 & 0xC0) != 0x80)
417 throw new UTFDataFormatException();
418 str.append((char)(((c & 0x1F) << 6) | (char2 & 0x3F)));
419 break;
420 case 14:
421 /* 1110 xxxx 10xx xxxx 10xx xxxx */
422 count += 3;
423 if (count > utflen)
424 throw new UTFDataFormatException();
425 char2 = bytearr[count-2];
426 char3 = bytearr[count-1];
427 if (((char2 & 0xC0) != 0x80) || ((char3 & 0xC0) != 0x80))
428 throw new UTFDataFormatException();
429 str.append((char)(((c & 0x0F) << 12) |
430 ((char2 & 0x3F) << 6) |
431 ((char3 & 0x3F) << 0)));
432 break;
433 default:
434 /* 10xx xxxx, 1111 xxxx */
435 throw new UTFDataFormatException();
436 }
437 }
438
439 // The number of chars produced may be less than utflen
440 return new String(str);
441 }
442
443 public static Document convertToDOM(DataInputStream is) throws IOException {
444 Document document = null;
445 DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
446 int length = is.readInt();
447 try {
448 byte[] buf = new byte[length];
449 is.readFully(buf, 0, length);
450 DocumentBuilder builder = factory.newDocumentBuilder();
451 document = builder.parse(new ByteArrayInputStream(buf));
452 } catch (Exception e) {
453 throw new IOException("Error while parsing xml: " + e.getMessage());
454 }
455 return document;
456 }
457 }