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    }