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    }