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.TypeUtil;
049 import org.granite.util.XMLUtil;
050 import org.granite.util.XMLUtilFactory;
051 import org.w3c.dom.Document;
052
053 import flex.messaging.io.ArrayCollection;
054
055 /**
056 * @author Franck WOLFF
057 */
058 public 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 }