/* * 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.object; import java.lang.reflect.Field; import java.lang.reflect.Method; import java.util.Collection; import java.util.Date; import java.util.Iterator; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Vector; import org.apache.commons.beanutils.BeanMap; import org.red5.annotations.DontSerialize; import org.red5.annotations.RemoteClass; import org.red5.io.amf3.ByteArray; import org.red5.io.amf3.IExternalizable; import org.red5.io.utils.ObjectMap; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.w3c.dom.Document; /** * The Serializer class writes data output and handles the data according to the * core data types * * @author The Red5 Project (red5@osflash.org) * @author Luke Hubbard, Codegent Ltd (luke@codegent.com) * @author Harald Radi (harald.radi@nme.at) */ public class Serializer { protected static Logger log = LoggerFactory.getLogger(Serializer.class); /** * Serializes output to a core data type object * * @param out Output writer * @param any Object to serialize */ public void serialize(Output out, Object any) { serialize(out, null, null, null, any); } /** * Serializes output to a core data type object * * @param out Output writer * @param field The field to serialize * @param getter The getter method if not a field * @param object Parent object * @param value Object to serialize */ @SuppressWarnings("unchecked") public void serialize(Output out, Field field, Method getter, Object object, Object value) { log.trace("serialize"); if (value instanceof IExternalizable) { // make sure all IExternalizable objects are serialized as objects out.writeObject(value, this); } else if (value instanceof ByteArray) { // write ByteArray objects directly out.writeByteArray((ByteArray) value); } else if (value instanceof Vector) { log.trace("Serialize Vector"); // scan the vector to determine the generic type Vector<?> vector = (Vector<?>) value; int ints = 0; int longs = 0; int dubs = 0; int nans = 0; for (Object o : vector) { if (o instanceof Integer) { ints++; } else if (o instanceof Long) { longs++; } else if (o instanceof Number || o instanceof Double) { dubs++; } else { nans++; } } // look at the type counts if (nans > 0) { // if we have non-number types, use object ((org.red5.io.amf3.Output) out).enforceAMF3(); out.writeVectorObject((Vector<Object>) value); } else if (dubs == 0 && longs == 0) { // no doubles or longs out.writeVectorInt((Vector<Integer>) value); } else if (dubs == 0 && ints == 0) { // no doubles or ints out.writeVectorUInt((Vector<Long>) value); } else { // handle any other types of numbers ((org.red5.io.amf3.Output) out).enforceAMF3(); out.writeVectorNumber((Vector<Double>) value); } } else { if (writeBasic(out, value)) { log.trace("Wrote as basic"); } else if (!writeComplex(out, value)) { log.trace("Unable to serialize: {}", value); } } } /** * Writes a primitive out as an object * * @param out * Output writer * @param basic * Primitive * @return boolean true if object was successfully serialized, false * otherwise */ @SuppressWarnings("rawtypes") protected boolean writeBasic(Output out, Object basic) { if (basic == null) { out.writeNull(); } else if (basic instanceof Boolean) { out.writeBoolean((Boolean) basic); } else if (basic instanceof Number) { out.writeNumber((Number) basic); } else if (basic instanceof String) { out.writeString((String) basic); } else if (basic instanceof Enum) { out.writeString(((Enum) basic).name()); } else if (basic instanceof Date) { out.writeDate((Date) basic); } else { return false; } return true; } /** * Writes a complex type object out * * @param out Output writer * @param complex Complex datatype object * @return boolean true if object was successfully serialized, false * otherwise */ public boolean writeComplex(Output out, Object complex) { log.trace("writeComplex"); if (writeListType(out, complex)) { return true; } else if (writeArrayType(out, complex)) { return true; } else if (writeXMLType(out, complex)) { return true; } else if (writeCustomType(out, complex)) { return true; } else if (writeObjectType(out, complex)) { return true; } else { return false; } } /** * Writes Lists out as a data type * * @param out * Output write * @param listType * List type * @return boolean true if object was successfully serialized, false * otherwise */ protected boolean writeListType(Output out, Object listType) { log.trace("writeListType"); if (listType instanceof List<?>) { writeList(out, (List<?>) listType); } else { return false; } return true; } /** * Writes a List out as an Object * * @param out * Output writer * @param list * List to write as Object */ protected void writeList(Output out, List<?> list) { if (!list.isEmpty()) { int size = list.size(); // if its a small list, write it as an array if (size < 100) { out.writeArray(list, this); return; } // else we should check for lots of null values, // if there are over 80% then its probably best to do it as a map int nullCount = 0; for (int i = 0; i < size; i++) { if (list.get(i) == null) { nullCount++; } } if (nullCount > (size * 0.8)) { out.writeMap(list, this); } else { out.writeArray(list, this); } } else { out.writeArray(new Object[] {}, this); } } /** * Writes array (or collection) out as output Arrays, Collections, etc * * @param out * Output object * @param arrType * Array or collection type * @return <code>true</code> if the object has been written, otherwise * <code>false</code> */ @SuppressWarnings("all") protected boolean writeArrayType(Output out, Object arrType) { log.trace("writeArrayType"); if (arrType instanceof Collection) { out.writeArray((Collection<Object>) arrType, this); } else if (arrType instanceof Iterator) { writeIterator(out, (Iterator<Object>) arrType); } else if (arrType.getClass().isArray() && arrType.getClass().getComponentType().isPrimitive()) { out.writeArray(arrType, this); } else if (arrType instanceof Object[]) { out.writeArray((Object[]) arrType, this); } else { return false; } return true; } /** * Writes an iterator out to the output * * @param out * Output writer * @param it * Iterator to write */ protected void writeIterator(Output out, Iterator<Object> it) { log.trace("writeIterator"); // Create LinkedList of collection we iterate thru and write it out later LinkedList<Object> list = new LinkedList<Object>(); while (it.hasNext()) { list.addLast(it.next()); } // Write out collection out.writeArray(list, this); } /** * Writes an xml type out to the output * * @param out * Output writer * @param xml * XML * @return boolean <code>true</code> if object was successfully written, * <code>false</code> otherwise */ protected boolean writeXMLType(Output out, Object xml) { log.trace("writeXMLType"); // If it's a Document write it as Document if (xml instanceof Document) { writeDocument(out, (Document) xml); } else { return false; } return true; } /** * Writes a document to the output * * @param out * Output writer * @param doc * Document to write */ protected void writeDocument(Output out, Document doc) { out.writeXML(doc); } /** * Write typed object to the output * * @param out * Output writer * @param obj * Object type to write * @return <code>true</code> if the object has been written, otherwise * <code>false</code> */ @SuppressWarnings("all") protected boolean writeObjectType(Output out, Object obj) { if (obj instanceof ObjectMap || obj instanceof BeanMap) { out.writeObject((Map) obj, this); } else if (obj instanceof Map) { out.writeMap((Map) obj, this); } else if (obj instanceof RecordSet) { out.writeRecordSet((RecordSet) obj, this); } else { out.writeObject(obj, this); } return true; } // Extension points /** * Pre processes an object TODO // must be implemented * * @return Prerocessed object * @param any Object to preprocess */ public Object preProcessExtension(Object any) { // Does nothing right now but will later return any; } /** * Writes a custom data to the output * * @param out Output writer * @param obj Custom data * @return <code>true</code> if the object has been written, otherwise * <code>false</code> */ protected boolean writeCustomType(Output out, Object obj) { if (out.isCustom(obj)) { // Write custom data out.writeCustom(obj); return true; } else { return false; } } /** * Checks whether the field should be serialized or not * * @param keyName key name * @param field The field to be serialized * @param getter Getter method for field * @return <code>true</code> if the field should be serialized, otherwise * <code>false</code> */ public boolean serializeField(String keyName, Field field, Method getter) { log.trace("serializeField - keyName: {} field: {} method: {}", new Object[] { keyName, field, getter }); if ("class".equals(keyName)) { return false; } if ((field != null && field.isAnnotationPresent(DontSerialize.class)) || (getter != null && getter.isAnnotationPresent(DontSerialize.class))) { log.trace("Skipping {} because its marked with @DontSerialize", keyName); return false; } log.trace("Serialize field: {}", field); return true; } /** * Handles classes by name, also provides "shortened" class aliases where appropriate. * * @param objectClass * @return class name for given object */ public String getClassName(Class<?> objectClass) { RemoteClass annotation = objectClass.getAnnotation(RemoteClass.class); if (annotation != null) { return annotation.alias(); } String className = objectClass.getName(); if (className.startsWith("org.red5.compatibility.")) { // Strip compatibility prefix from classname className = className.substring(23); if ("flex.messaging.messages.AsyncMessageExt".equals(className)) { className = "DSA"; } else if ("flex.messaging.messages.CommandMessageExt".equals(className)) { className = "DSC"; } else if ("flex.messaging.messages.AcknowledgeMessageExt".equals(className)) { className = "DSK"; } } return className; } }