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