/* * RED5 Open Source Flash Server - http://code.google.com/p/red5/ * * Copyright 2006-2012 by respective authors (see below). All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.red5.io.amf3; import java.lang.reflect.Array; import java.lang.reflect.Field; import java.lang.reflect.Method; import java.nio.ByteBuffer; import java.util.Collection; import java.util.Date; import java.util.HashMap; import java.util.Map; import java.util.Set; import java.util.Vector; import net.sf.ehcache.Element; import org.apache.commons.beanutils.BeanMap; import org.apache.mina.core.buffer.IoBuffer; import org.red5.annotations.Anonymous; import org.red5.compatibility.flex.messaging.io.ObjectProxy; import org.red5.io.amf.AMF; import org.red5.io.object.RecordSet; import org.red5.io.object.Serializer; import org.red5.io.object.UnsignedInt; import org.red5.io.utils.HexDump; import org.red5.io.utils.XMLUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.w3c.dom.Document; /** * AMF3 output writer * * @see org.red5.io.amf3.AMF3 * @see org.red5.io.amf3.Input * @author The Red5 Project (red5@osflash.org) * @author Joachim Bauch (jojo@struktur.de) * @author Harald Radi (harald.radi@nme.at) */ public class Output extends org.red5.io.amf.Output implements org.red5.io.object.Output { protected static Logger log = LoggerFactory.getLogger(Output.class); /** * Set to a value above <tt>0</tt> to disable writing of the AMF3 object tag. */ private int amf3_mode; /** * List of strings already written. * */ private Map<String, Integer> stringReferences; /** * Constructor of AMF3 output. * * @param buf instance of IoBuffer * @see IoBuffer */ public Output(IoBuffer buf) { super(buf); amf3_mode = 0; stringReferences = new HashMap<String, Integer>(); } /** * Force using AMF3 everywhere */ public void enforceAMF3() { amf3_mode++; } /** * Provide access to raw data. * * @return IoBuffer */ protected IoBuffer getBuffer() { return buf; } /** {@inheritDoc} */ @Override public boolean supportsDataType(byte type) { return true; } protected void writeAMF3() { if (amf3_mode == 0) { buf.put(AMF.TYPE_AMF3_OBJECT); } } /** {@inheritDoc} */ @Override public void writeBoolean(Boolean bol) { writeAMF3(); buf.put(bol ? AMF3.TYPE_BOOLEAN_TRUE : AMF3.TYPE_BOOLEAN_FALSE); } /** {@inheritDoc} */ @Override public void writeNull() { writeAMF3(); buf.put(AMF3.TYPE_NULL); } protected void putInteger(long value) { if ((value >= -268435456) && (value <= 268435455)) { value &= 536870911; } if (value < 128) { buf.put((byte) value); } else if (value < 16384) { buf.put((byte) (((value >> 7) & 0x7F) | 0x80)); buf.put((byte) (value & 0x7F)); } else if (value < 2097152) { buf.put((byte) (((value >> 14) & 0x7F) | 0x80)); buf.put((byte) (((value >> 7) & 0x7F) | 0x80)); buf.put((byte) (value & 0x7F)); } else if (value < 1073741824) { buf.put((byte) (((value >> 22) & 0x7F) | 0x80)); buf.put((byte) (((value >> 15) & 0x7F) | 0x80)); buf.put((byte) (((value >> 8) & 0x7F) | 0x80)); buf.put((byte) (value & 0xFF)); } else { log.error("Integer out of range: {}", value); } } protected static byte[] encodeString(String string) { Element element = getStringCache().get(string); byte[] encoded = (element == null ? null : (byte[]) element.getObjectValue()); if (encoded == null) { ByteBuffer buf = AMF.CHARSET.encode(string); encoded = new byte[buf.limit()]; buf.get(encoded); getStringCache().put(new Element(string, encoded)); } return encoded; } protected void putString(String str, byte[] encoded) { final int len = encoded.length; Integer pos = stringReferences.get(str); if (pos != null) { // Reference to existing string putInteger(pos << 1); return; } putInteger(len << 1 | 1); buf.put(encoded); stringReferences.put(str, stringReferences.size()); } /** {@inheritDoc} */ @Override public void putString(String string) { if ("".equals(string)) { // Empty string; putInteger(1); return; } final byte[] encoded = encodeString(string); putString(string, encoded); } /** {@inheritDoc} */ @Override public void writeNumber(Number num) { writeAMF3(); if (num.longValue() < AMF3.MIN_INTEGER_VALUE || num.longValue() > AMF3.MAX_INTEGER_VALUE) { // Out of range for integer encoding buf.put(AMF3.TYPE_NUMBER); buf.putDouble(num.doubleValue()); } else if (num instanceof Long || num instanceof Integer || num instanceof Short || num instanceof Byte) { buf.put(AMF3.TYPE_INTEGER); putInteger(num.longValue()); } else { buf.put(AMF3.TYPE_NUMBER); buf.putDouble(num.doubleValue()); } } /** {@inheritDoc} */ @Override public void writeString(String string) { writeAMF3(); buf.put(AMF3.TYPE_STRING); if ("".equals(string)) { putInteger(1); } else { final byte[] encoded = encodeString(string); putString(string, encoded); } } /** {@inheritDoc} */ @Override public void writeDate(Date date) { writeAMF3(); buf.put(AMF3.TYPE_DATE); if (hasReference(date)) { putInteger(getReferenceId(date) << 1); return; } storeReference(date); putInteger(1); buf.putDouble(date.getTime()); } /** {@inheritDoc} */ public void writeArray(Collection<?> array, Serializer serializer) { writeAMF3(); buf.put(AMF3.TYPE_ARRAY); if (hasReference(array)) { putInteger(getReferenceId(array) << 1); return; } storeReference(array); amf3_mode += 1; int count = array.size(); putInteger(count << 1 | 1); putString(""); for (Object item : array) { serializer.serialize(this, item); } amf3_mode -= 1; } /** {@inheritDoc} */ public void writeArray(Object[] array, Serializer serializer) { writeAMF3(); buf.put(AMF3.TYPE_ARRAY); if (hasReference(array)) { putInteger(getReferenceId(array) << 1); return; } storeReference(array); amf3_mode += 1; int count = array.length; putInteger(count << 1 | 1); putString(""); for (Object item : array) { serializer.serialize(this, item); } amf3_mode -= 1; } /** {@inheritDoc} */ public void writeArray(Object array, Serializer serializer) { writeAMF3(); buf.put(AMF3.TYPE_ARRAY); if (hasReference(array)) { putInteger(getReferenceId(array) << 1); return; } storeReference(array); amf3_mode += 1; int count = Array.getLength(array); putInteger(count << 1 | 1); putString(""); for (int i = 0; i < count; i++) { serializer.serialize(this, Array.get(array, i)); } amf3_mode -= 1; } /** {@inheritDoc} */ public void writeMap(Map<Object, Object> map, Serializer serializer) { writeAMF3(); buf.put(AMF3.TYPE_ARRAY); if (hasReference(map)) { putInteger(getReferenceId(map) << 1); return; } storeReference(map); // Search number of starting integer keys int count = 0; for (int i = 0; i < map.size(); i++) { try { if (!map.containsKey(i)) break; } catch (ClassCastException err) { // Map has non-number keys. break; } count++; } amf3_mode += 1; if (count == map.size()) { // All integer keys starting from zero: serialize as regular array putInteger(count << 1 | 1); putString(""); for (int i = 0; i < count; i++) { serializer.serialize(this, map.get(i)); } amf3_mode -= 1; return; } putInteger(count << 1 | 1); // Serialize key-value pairs first for (Map.Entry<Object, Object> entry : map.entrySet()) { Object key = entry.getKey(); if ((key instanceof Number) && !(key instanceof Float) && !(key instanceof Double) && ((Number) key).longValue() >= 0 && ((Number) key).longValue() < count) { // Entry will be serialized later continue; } putString(key.toString()); serializer.serialize(this, entry.getValue()); } putString(""); // Now serialize integer keys starting from zero for (int i = 0; i < count; i++) { serializer.serialize(this, map.get(i)); } amf3_mode -= 1; } /** {@inheritDoc} */ public void writeMap(Collection<?> array, Serializer serializer) { writeAMF3(); buf.put(AMF3.TYPE_ARRAY); if (hasReference(array)) { putInteger(getReferenceId(array) << 1); return; } storeReference(array); // TODO: we could optimize this by storing the first integer // keys after the key-value pairs amf3_mode += 1; putInteger(1); int idx = 0; for (Object item : array) { if (item != null) { putString(String.valueOf(idx)); serializer.serialize(this, item); } idx++; } amf3_mode -= 1; putString(""); } /** {@inheritDoc} */ @Override protected void writeArbitraryObject(Object object, Serializer serializer) { Class<?> objectClass = object.getClass(); // If we need to serialize class information... if (!objectClass.isAnnotationPresent(Anonymous.class)) { putString(serializer.getClassName(objectClass)); } else { putString(""); } // Store key/value pairs amf3_mode += 1; // Iterate thru fields of an object to build "name-value" map from it for (Field field : objectClass.getFields()) { String fieldName = field.getName(); log.debug("Field: {} class: {}", field, objectClass); // Check if the Field corresponding to the getter/setter pair is transient if (!serializeField(serializer, objectClass, fieldName, field, null)) { continue; } Object value; try { // Get field value value = field.get(object); } catch (IllegalAccessException err) { // Swallow on private and protected properties access exception continue; } // Write out prop name putString(fieldName); // Write out serializer.serialize(this, field, null, object, value); } amf3_mode -= 1; // Write out end of object marker putString(""); } /** {@inheritDoc} */ @SuppressWarnings({ "rawtypes" }) public void writeObject(Object object, Serializer serializer) { writeAMF3(); buf.put(AMF3.TYPE_OBJECT); if (hasReference(object)) { putInteger(getReferenceId(object) << 1); return; } storeReference(object); if (object instanceof IExternalizable) { // The object knows how to serialize itself. int type = 1 << 1 | 1; if (object instanceof ObjectProxy) { type |= AMF3.TYPE_OBJECT_PROXY << 2; } else { type |= AMF3.TYPE_OBJECT_EXTERNALIZABLE << 2; } putInteger(type); putString(serializer.getClassName(object.getClass())); amf3_mode += 1; ((IExternalizable) object).writeExternal(new DataOutput(this, serializer)); amf3_mode -= 1; return; } // We have an inline class that is not a reference. // We store the properties using key/value pairs int type = AMF3.TYPE_OBJECT_VALUE << 2 | 1 << 1 | 1; putInteger(type); // Create new map out of bean properties BeanMap beanMap = new BeanMap(object); // Set of bean attributes Set set = beanMap.keySet(); if ((set.size() == 0) || (set.size() == 1 && beanMap.containsKey("class"))) { // BeanMap is empty or can only access "class" attribute, skip it writeArbitraryObject(object, serializer); return; } // Write out either start of object marker for class name or "empty" start of object marker Class<?> objectClass = object.getClass(); if (!objectClass.isAnnotationPresent(Anonymous.class)) { // classname putString(serializer.getClassName(object.getClass())); } else { putString(""); } // Store key/value pairs amf3_mode += 1; for (Object key : set) { String fieldName = key.toString(); log.debug("Field name: {} class: {}", fieldName, objectClass); Field field = getField(objectClass, fieldName); Method getter = getGetter(objectClass, beanMap, fieldName); // Check if the Field corresponding to the getter/setter pair is transient if (!serializeField(serializer, objectClass, fieldName, field, getter)) { continue; } putString(fieldName); serializer.serialize(this, field, getter, object, beanMap.get(key)); } amf3_mode -= 1; // End of object marker putString(""); } /** {@inheritDoc} */ @Override public void writeObject(Map<Object, Object> map, Serializer serializer) { writeAMF3(); buf.put(AMF3.TYPE_OBJECT); if (hasReference(map)) { putInteger(getReferenceId(map) << 1); return; } storeReference(map); // We have an inline class that is not a reference. // We store the properties using key/value pairs int type = AMF3.TYPE_OBJECT_VALUE << 2 | 1 << 1 | 1; putInteger(type); // No classname putString(""); // Store key/value pairs amf3_mode += 1; for (Map.Entry<Object, Object> entry : map.entrySet()) { putString(entry.getKey().toString()); serializer.serialize(this, entry.getValue()); } amf3_mode -= 1; // End of object marker putString(""); } /** {@inheritDoc} */ @Override public void writeRecordSet(RecordSet recordset, Serializer serializer) { writeString("Not implemented."); } /** {@inheritDoc} */ @Override public void writeXML(Document xml) { writeAMF3(); buf.put(AMF3.TYPE_XML); if (hasReference(xml)) { putInteger(getReferenceId(xml) << 1); return; } final byte[] encoded = encodeString(XMLUtils.docToString(xml)); putInteger(encoded.length << 1 | 1); buf.put(encoded); storeReference(xml); } /** {@inheritDoc} */ public void writeByteArray(ByteArray array) { writeAMF3(); buf.put(AMF3.TYPE_BYTEARRAY); if (hasReference(array)) { putInteger(getReferenceId(array) << 1); return; } storeReference(array); IoBuffer data = array.getData(); putInteger(data.limit() << 1 | 1); byte[] tmp = new byte[data.limit()]; int old = data.position(); try { data.position(0); data.get(tmp); buf.put(tmp); } finally { data.position(old); } } /** * Write a Vector<int>. * * @param vector */ @Override public void writeVectorInt(Vector<Integer> vector) { log.debug("writeVectorInt: {}", vector); writeAMF3(); buf.put(AMF3.TYPE_VECTOR_INT); if (hasReference(vector)) { putInteger(getReferenceId(vector) << 1); return; } storeReference(vector); putInteger(vector.size() << 1 | 1); buf.put((byte) 0x00); for (Integer v : vector) { buf.putInt(v); } // debug if (log.isDebugEnabled()) { int pos = buf.position(); buf.position(0); StringBuilder sb = new StringBuilder(); HexDump.dumpHex(sb, buf.array()); log.debug("\n{}", sb); buf.position(pos); } } /** * Write a Vector<uint>. * * @param vector */ @Override public void writeVectorUInt(Vector<Long> vector) { log.debug("writeVectorUInt: {}", vector); writeAMF3(); buf.put(AMF3.TYPE_VECTOR_UINT); if (hasReference(vector)) { putInteger(getReferenceId(vector) << 1); return; } storeReference(vector); putInteger(vector.size() << 1 | 1); buf.put((byte) 0x00); for (Long v : vector) { // update this class to implement valueOf like Long.valueOf UnsignedInt uint = new UnsignedInt(v); byte[] arr = uint.getBytes(); buf.put(arr); } } /** * Write a Vector<Number>. * * @param vector */ @Override public void writeVectorNumber(Vector<Double> vector) { log.debug("writeVectorNumber: {}", vector); buf.put(AMF3.TYPE_VECTOR_NUMBER); if (hasReference(vector)) { putInteger(getReferenceId(vector) << 1); return; } storeReference(vector); putInteger(vector.size() << 1 | 1); putInteger(0); buf.put((byte) 0x00); for (Double v : vector) { buf.putDouble(v); } } /** * Write a Vector<Object>. * * @param vector */ @Override public void writeVectorObject(Vector<Object> vector) { log.debug("writeVectorObject: {}", vector); buf.put(AMF3.TYPE_VECTOR_OBJECT); if (hasReference(vector)) { putInteger(getReferenceId(vector) << 1); return; } storeReference(vector); putInteger(vector.size() << 1 | 1); putInteger(0); buf.put((byte) 0x01); Serializer serializer = new Serializer(); for (Object v : vector) { serializer.serialize(this, v); } } }