001/* 002 GRANITE DATA SERVICES 003 Copyright (C) 2011 GRANITE DATA SERVICES S.A.S. 004 005 This file is part of Granite Data Services. 006 007 Granite Data Services is free software; you can redistribute it and/or modify 008 it under the terms of the GNU Library General Public License as published by 009 the Free Software Foundation; either version 2 of the License, or (at your 010 option) any later version. 011 012 Granite Data Services is distributed in the hope that it will be useful, but 013 WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 014 FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License 015 for more details. 016 017 You should have received a copy of the GNU Library General Public License 018 along with this library; if not, see <http://www.gnu.org/licenses/>. 019*/ 020 021package org.granite.messaging.amf.io; 022 023import java.io.DataOutputStream; 024import java.io.Externalizable; 025import java.io.IOException; 026import java.io.ObjectOutput; 027import java.io.OutputStream; 028import java.lang.reflect.Array; 029import java.math.BigDecimal; 030import java.math.BigInteger; 031import java.util.Calendar; 032import java.util.Collection; 033import java.util.Date; 034import java.util.HashMap; 035import java.util.IdentityHashMap; 036import java.util.Map; 037 038import org.granite.config.flex.Channel; 039import org.granite.context.GraniteContext; 040import org.granite.logging.Logger; 041import org.granite.messaging.amf.AMF3Constants; 042import org.granite.messaging.amf.io.convert.Converters; 043import org.granite.messaging.amf.io.util.ClassGetter; 044import org.granite.messaging.amf.io.util.DefaultJavaClassDescriptor; 045import org.granite.messaging.amf.io.util.IndexedJavaClassDescriptor; 046import org.granite.messaging.amf.io.util.JavaClassDescriptor; 047import org.granite.messaging.amf.io.util.externalizer.Externalizer; 048import org.granite.util.TypeUtil; 049import org.granite.util.XMLUtil; 050import org.granite.util.XMLUtilFactory; 051import org.w3c.dom.Document; 052 053import flex.messaging.io.ArrayCollection; 054 055/** 056 * @author Franck WOLFF 057 */ 058public class AMF3Serializer extends DataOutputStream implements ObjectOutput, AMF3Constants { 059 060 /////////////////////////////////////////////////////////////////////////// 061 // Fields. 062 063 protected static final Logger log = Logger.getLogger(AMF3Serializer.class); 064 protected static final Logger logMore = Logger.getLogger(AMF3Serializer.class.getName() + "_MORE"); 065 066 protected final Map<String, Integer> storedStrings = new HashMap<String, Integer>(); 067 protected final Map<Object, Integer> storedObjects = new IdentityHashMap<Object, Integer>(); 068 protected final Map<String, IndexedJavaClassDescriptor> storedClassDescriptors 069 = new HashMap<String, IndexedJavaClassDescriptor>(); 070 071 protected final GraniteContext context = GraniteContext.getCurrentInstance(); 072 protected final Converters converters = context.getGraniteConfig().getConverters(); 073 074 protected final boolean externalizeLong 075 = (context.getGraniteConfig().getExternalizer(Long.class.getName()) != null); 076 protected final boolean externalizeBigInteger 077 = (context.getGraniteConfig().getExternalizer(BigInteger.class.getName()) != null); 078 protected final boolean externalizeBigDecimal 079 = (context.getGraniteConfig().getExternalizer(BigDecimal.class.getName()) != null); 080 081 protected final XMLUtil xmlUtil = XMLUtilFactory.getXMLUtil(); 082 083 protected final boolean debug = log.isDebugEnabled(); 084 protected final boolean debugMore = logMore.isDebugEnabled(); 085 086 protected Channel channel = null; 087 088 /////////////////////////////////////////////////////////////////////////// 089 // Constructor. 090 091 public AMF3Serializer(OutputStream out) { 092 super(out); 093 094 if (debugMore) logMore.debug("new AMF3Serializer(out=%s)", out); 095 } 096 097 /////////////////////////////////////////////////////////////////////////// 098 // ObjectOutput implementation. 099 100 public void writeObject(Object o) throws IOException { 101 if (debugMore) logMore.debug("writeObject(o=%s)", o); 102 103 try { 104 if (o == null) 105 write(AMF3_NULL); 106 else if (!(o instanceof Externalizable)) { 107 108 if (converters.hasReverters()) 109 o = converters.revert(o); 110 111 if (o == null) 112 write(AMF3_NULL); 113 else if (o instanceof String || o instanceof Character) 114 writeAMF3String(o.toString()); 115 else if (o instanceof Boolean) 116 write(((Boolean)o).booleanValue() ? AMF3_BOOLEAN_TRUE : AMF3_BOOLEAN_FALSE); 117 else if (o instanceof Number) { 118 if (o instanceof Integer || o instanceof Short || o instanceof Byte) 119 writeAMF3Integer(((Number)o).intValue()); 120 else if (externalizeLong && o instanceof Long) 121 writeAMF3Object(o); 122 else if (externalizeBigInteger && o instanceof BigInteger) 123 writeAMF3Object(o); 124 else if (externalizeBigDecimal && o instanceof BigDecimal) 125 writeAMF3Object(o); 126 else 127 writeAMF3Number(((Number)o).doubleValue()); 128 } 129 else if (o instanceof Date) 130 writeAMF3Date((Date)o); 131 else if (o instanceof Calendar) 132 writeAMF3Date(((Calendar)o).getTime()); 133 else if (o instanceof Document) 134 writeAMF3Xml((Document)o); 135 else if (o instanceof Collection<?>) 136 writeAMF3Collection((Collection<?>)o); 137 else if (o.getClass().isArray()) { 138 if (o.getClass().getComponentType() == Byte.TYPE) 139 writeAMF3ByteArray((byte[])o); 140 else 141 writeAMF3Array(o); 142 } 143 else 144 writeAMF3Object(o); 145 } else 146 writeAMF3Object(o); 147 } 148 catch (IOException e) { 149 throw e; 150 } 151 catch (Exception e) { 152 throw new AMF3SerializationException(e); 153 } 154 } 155 156 /////////////////////////////////////////////////////////////////////////// 157 // AMF3 serialization. 158 159 protected void writeAMF3Integer(int i) throws IOException { 160 if (debugMore) logMore.debug("writeAMF3Integer(i=%d)", i); 161 162 if (i < AMF3_INTEGER_MIN || i > AMF3_INTEGER_MAX) { 163 if (debugMore) logMore.debug("writeAMF3Integer() - %d is out of AMF3 int range, writing it as a Number", i); 164 writeAMF3Number(i); 165 } else { 166 write(AMF3_INTEGER); 167 writeAMF3IntegerData(i); 168 } 169 } 170 171 protected void writeAMF3IntegerData(int i) throws IOException { 172 if (debugMore) logMore.debug("writeAMF3IntegerData(i=%d)", i); 173 174 if (i < AMF3_INTEGER_MIN || i > AMF3_INTEGER_MAX) 175 throw new IllegalArgumentException("Integer out of range: " + i); 176 177 if (i < 0 || i >= 0x200000) { 178 write(((i >> 22) & 0x7F) | 0x80); 179 write(((i >> 15) & 0x7F) | 0x80); 180 write(((i >> 8) & 0x7F) | 0x80); 181 write(i & 0xFF); 182 } else { 183 if (i >= 0x4000) 184 write(((i >> 14) & 0x7F) | 0x80); 185 if (i >= 0x80) 186 write(((i >> 7) & 0x7F) | 0x80); 187 write(i & 0x7F); 188 } 189 } 190 191 protected void writeAMF3Number(double d) throws IOException { 192 if (debugMore) logMore.debug("writeAMF3Number(d=%f)", d); 193 194 write(AMF3_NUMBER); 195 writeDouble(d); 196 } 197 198 protected void writeAMF3String(String s) throws IOException { 199 if (debugMore) logMore.debug("writeAMF3String(s=%s)", s); 200 201 write(AMF3_STRING); 202 writeAMF3StringData(s); 203 } 204 205 protected void writeAMF3StringData(String s) throws IOException { 206 if (debugMore) logMore.debug("writeAMF3StringData(s=%s)", s); 207 208 if (s.length() == 0) { 209 write(0x01); 210 return; 211 } 212 213 int index = indexOfStoredStrings(s); 214 215 if (index >= 0) 216 writeAMF3IntegerData(index << 1); 217 else { 218 addToStoredStrings(s); 219 220 final int sLength = s.length(); 221 222 // Compute and write modified UTF-8 string length. 223 int uLength = 0; 224 for (int i = 0; i < sLength; i++) { 225 int c = s.charAt(i); 226 if ((c >= 0x0001) && (c <= 0x007F)) 227 uLength++; 228 else if (c > 0x07FF) 229 uLength += 3; 230 else 231 uLength += 2; 232 } 233 writeAMF3IntegerData((uLength << 1) | 0x01); 234 235 // Write modified UTF-8 bytes. 236 for (int i = 0; i < sLength; i++) { 237 int c = s.charAt(i); 238 if ((c >= 0x0001) && (c <= 0x007F)) { 239 write(c); 240 } else if (c > 0x07FF) { 241 write(0xE0 | ((c >> 12) & 0x0F)); 242 write(0x80 | ((c >> 6) & 0x3F)); 243 write(0x80 | ((c >> 0) & 0x3F)); 244 } else { 245 write(0xC0 | ((c >> 6) & 0x1F)); 246 write(0x80 | ((c >> 0) & 0x3F)); 247 } 248 } 249 } 250 } 251 252 protected void writeAMF3Xml(Document doc) throws IOException { 253 if (debugMore) logMore.debug("writeAMF3Xml(doc=%s)", doc); 254 255 byte xmlType = AMF3_XMLSTRING; 256 Channel channel = getChannel(); 257 if (channel != null && channel.isLegacyXmlSerialization()) 258 xmlType = AMF3_XML; 259 write(xmlType); 260 261 int index = indexOfStoredObjects(doc); 262 if (index >= 0) 263 writeAMF3IntegerData(index << 1); 264 else { 265 addToStoredObjects(doc); 266 267 byte[] bytes = xmlUtil.toString(doc).getBytes("UTF-8"); 268 writeAMF3IntegerData((bytes.length << 1) | 0x01); 269 write(bytes); 270 } 271 } 272 273 protected void writeAMF3Date(Date date) throws IOException { 274 if (debugMore) logMore.debug("writeAMF3Date(date=%s)", date); 275 276 write(AMF3_DATE); 277 278 int index = indexOfStoredObjects(date); 279 if (index >= 0) 280 writeAMF3IntegerData(index << 1); 281 else { 282 addToStoredObjects(date); 283 writeAMF3IntegerData(0x01); 284 writeDouble(date.getTime()); 285 } 286 } 287 288 protected void writeAMF3Array(Object array) throws IOException { 289 if (debugMore) logMore.debug("writeAMF3Array(array=%s)", array); 290 291 write(AMF3_ARRAY); 292 293 int index = indexOfStoredObjects(array); 294 if (index >= 0) 295 writeAMF3IntegerData(index << 1); 296 else { 297 addToStoredObjects(array); 298 299 int length = Array.getLength(array); 300 writeAMF3IntegerData(length << 1 | 0x01); 301 write(0x01); 302 for (int i = 0; i < length; i++) 303 writeObject(Array.get(array, i)); 304 } 305 } 306 307 protected void writeAMF3ByteArray(byte[] bytes) throws IOException { 308 if (debugMore) logMore.debug("writeAMF3ByteArray(bytes=%s)", bytes); 309 310 write(AMF3_BYTEARRAY); 311 312 int index = indexOfStoredObjects(bytes); 313 if (index >= 0) 314 writeAMF3IntegerData(index << 1); 315 else { 316 addToStoredObjects(bytes); 317 318 writeAMF3IntegerData(bytes.length << 1 | 0x01); 319 //write(bytes); 320 321 for (int i = 0; i < bytes.length; i++) 322 out.write(bytes[i]); 323 } 324 } 325 326 protected void writeAMF3Collection(Collection<?> c) throws IOException { 327 if (debugMore) logMore.debug("writeAMF3Collection(c=%s)", c); 328 329 Channel channel = getChannel(); 330 if (channel != null && channel.isLegacyCollectionSerialization()) 331 writeAMF3Array(c.toArray()); 332 else { 333 ArrayCollection ac = (c instanceof ArrayCollection ? (ArrayCollection)c : new ArrayCollection(c)); 334 writeAMF3Object(ac); 335 } 336 } 337 338 protected void writeAMF3Object(Object o) throws IOException { 339 if (debug) log.debug("writeAMF3Object(o=%s)...", o); 340 341 write(AMF3_OBJECT); 342 343 int index = indexOfStoredObjects(o); 344 if (index >= 0) 345 writeAMF3IntegerData(index << 1); 346 else { 347 addToStoredObjects(o); 348 349 ClassGetter classGetter = context.getGraniteConfig().getClassGetter(); 350 if (debug) log.debug("writeAMF3Object() - classGetter=%s", classGetter); 351 352 Class<?> oClass = classGetter.getClass(o); 353 if (debug) log.debug("writeAMF3Object() - oClass=%s", oClass); 354 355 JavaClassDescriptor desc = null; 356 357 // write class description. 358 IndexedJavaClassDescriptor iDesc = getFromStoredClassDescriptors(oClass); 359 if (iDesc != null) { 360 desc = iDesc.getDescriptor(); 361 writeAMF3IntegerData(iDesc.getIndex() << 2 | 0x01); 362 } 363 else { 364 iDesc = addToStoredClassDescriptors(oClass); 365 desc = iDesc.getDescriptor(); 366 367 writeAMF3IntegerData((desc.getPropertiesCount() << 4) | (desc.getEncoding() << 2) | 0x03); 368 writeAMF3StringData(desc.getName()); 369 370 for (int i = 0; i < desc.getPropertiesCount(); i++) 371 writeAMF3StringData(desc.getPropertyName(i)); 372 } 373 if (debug) log.debug("writeAMF3Object() - desc=%s", desc); 374 375 // write object content. 376 if (desc.isExternalizable()) { 377 Externalizer externalizer = desc.getExternalizer(); 378 379 if (externalizer != null) { 380 if (debug) log.debug("writeAMF3Object() - using externalizer=%s", externalizer); 381 try { 382 externalizer.writeExternal(o, this); 383 } catch (IOException e) { 384 throw e; 385 } catch (Exception e) { 386 throw new RuntimeException("Could not externalize object: " + o, e); 387 } 388 } 389 else { 390 if (debug) log.debug("writeAMF3Object() - legacy Externalizable=%s", o); 391 ((Externalizable)o).writeExternal(this); 392 } 393 } 394 else { 395 if (debug) log.debug("writeAMF3Object() - writing defined properties..."); 396 for (int i = 0; i < desc.getPropertiesCount(); i++) { 397 Object obj = desc.getPropertyValue(i, o); 398 if (debug) log.debug("writeAMF3Object() - writing defined property: %s=%s", desc.getPropertyName(i), obj); 399 writeObject(obj); 400 } 401 402 if (desc.isDynamic()) { 403 if (debug) log.debug("writeAMF3Object() - writing dynamic properties..."); 404 Map<?, ?> oMap = (Map<?, ?>)o; 405 for (Map.Entry<?, ?> entry : oMap.entrySet()) { 406 Object key = entry.getKey(); 407 if (key != null) { 408 String propertyName = key.toString(); 409 if (propertyName.length() > 0) { 410 if (debug) log.debug( 411 "writeAMF3Object() - writing dynamic property: %s=%s", 412 propertyName, entry.getValue() 413 ); 414 writeAMF3StringData(propertyName); 415 writeObject(entry.getValue()); 416 } 417 } 418 } 419 writeAMF3StringData(""); 420 } 421 } 422 } 423 424 if (debug) log.debug("writeAMF3Object(o=%s) - Done", o); 425 } 426 427 /////////////////////////////////////////////////////////////////////////// 428 // Cached objects methods. 429 430 protected void addToStoredStrings(String s) { 431 if (!storedStrings.containsKey(s)) { 432 Integer index = Integer.valueOf(storedStrings.size()); 433 if (debug) log.debug("addToStoredStrings(s=%s) at index=%d", s, index); 434 storedStrings.put(s, index); 435 } 436 } 437 438 protected int indexOfStoredStrings(String s) { 439 Integer index = storedStrings.get(s); 440 if (debug) log.debug("indexOfStoredStrings(s=%s) -> %d", s, (index != null ? index : -1)); 441 return (index != null ? index : -1); 442 } 443 444 protected void addToStoredObjects(Object o) { 445 if (o != null && !storedObjects.containsKey(o)) { 446 Integer index = Integer.valueOf(storedObjects.size()); 447 if (debug) log.debug("addToStoredObjects(o=%s) at index=%d", o, index); 448 storedObjects.put(o, index); 449 } 450 } 451 452 protected int indexOfStoredObjects(Object o) { 453 Integer index = storedObjects.get(o); 454 if (debug) log.debug("indexOfStoredObjects(o=%s) -> %d", o, (index != null ? index : -1)); 455 return (index != null ? index : -1); 456 } 457 458 protected IndexedJavaClassDescriptor addToStoredClassDescriptors(Class<?> clazz) { 459 final String name = JavaClassDescriptor.getClassName(clazz); 460 461 if (debug) log.debug("addToStoredClassDescriptors(clazz=%s)", clazz); 462 463 if (storedClassDescriptors.containsKey(name)) 464 throw new RuntimeException( 465 "Descriptor of \"" + name + "\" is already stored at index: " + 466 getFromStoredClassDescriptors(clazz).getIndex() 467 ); 468 469 // find custom class descriptor and instantiate if any 470 JavaClassDescriptor desc = null; 471 472 Class<? extends JavaClassDescriptor> descriptorType 473 = context.getGraniteConfig().getJavaDescriptor(clazz.getName()); 474 if (descriptorType != null) { 475 Class<?>[] argsDef = new Class[]{Class.class}; 476 Object[] argsVal = new Object[]{clazz}; 477 try { 478 desc = TypeUtil.newInstance(descriptorType, argsDef, argsVal); 479 } catch (Exception e) { 480 throw new RuntimeException("Could not instantiate Java descriptor: " + descriptorType); 481 } 482 } 483 484 if (desc == null) 485 desc = new DefaultJavaClassDescriptor(clazz); 486 487 IndexedJavaClassDescriptor iDesc = new IndexedJavaClassDescriptor(storedClassDescriptors.size(), desc); 488 489 if (debug) log.debug("addToStoredClassDescriptors() - putting: name=%s, iDesc=%s", name, iDesc); 490 491 storedClassDescriptors.put(name, iDesc); 492 493 return iDesc; 494 } 495 496 protected IndexedJavaClassDescriptor getFromStoredClassDescriptors(Class<?> clazz) { 497 if (debug) log.debug("getFromStoredClassDescriptors(clazz=%s)", clazz); 498 499 String name = JavaClassDescriptor.getClassName(clazz); 500 IndexedJavaClassDescriptor iDesc = storedClassDescriptors.get(name); 501 502 if (debug) log.debug("getFromStoredClassDescriptors() -> %s", iDesc); 503 504 return iDesc; 505 } 506 507 /////////////////////////////////////////////////////////////////////////// 508 // Utilities. 509 510 protected Channel getChannel() { 511 if (channel == null) { 512 String channelId = context.getAMFContext().getChannelId(); 513 if (channelId != null) 514 channel = context.getServicesConfig().findChannelById(channelId); 515 if (channel == null) 516 log.debug("Could not get channel for channel id: %s", channelId); 517 } 518 return channel; 519 } 520}