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 029package org.granite.messaging.amf.io; 030 031import java.io.ByteArrayInputStream; 032import java.io.DataInputStream; 033import java.io.IOException; 034import java.io.InputStream; 035import java.io.ObjectInput; 036import java.io.UTFDataFormatException; 037import java.util.ArrayList; 038import java.util.Calendar; 039import java.util.Date; 040import java.util.GregorianCalendar; 041import java.util.List; 042import java.util.TimeZone; 043 044import javax.xml.parsers.DocumentBuilder; 045import javax.xml.parsers.DocumentBuilderFactory; 046 047import org.granite.context.GraniteContext; 048import org.granite.logging.Logger; 049import org.granite.messaging.amf.AMF0Body; 050import org.granite.messaging.amf.AMF0Message; 051import org.w3c.dom.Document; 052 053import 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 */ 063public 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}