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