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    }