Package flex.messaging.io.amfx

Source Code of flex.messaging.io.amfx.AmfxInput$TraitsContext

/*************************************************************************
*
* ADOBE CONFIDENTIAL
* __________________
*
*  [2002] - [2007] Adobe Systems Incorporated
*  All Rights Reserved.
*
* NOTICE:  All information contained herein is, and remains
* the property of Adobe Systems Incorporated and its suppliers,
* if any.  The intellectual and technical concepts contained
* herein are proprietary to Adobe Systems Incorporated
* and its suppliers and may be covered by U.S. and Foreign Patents,
* patents in process, and are protected by trade secret or copyright law.
* Dissemination of this information or reproduction of this material
* is strictly forbidden unless prior written permission is obtained
* from Adobe Systems Incorporated.
*/
package flex.messaging.io.amfx;

import flex.messaging.io.AbstractProxy;
import flex.messaging.io.BeanProxy;
import flex.messaging.io.ClassAliasRegistry;
import flex.messaging.io.PropertyProxy;
import flex.messaging.io.PropertyProxyRegistry;
import flex.messaging.io.SerializationContext;
import flex.messaging.io.SerializationException;
import flex.messaging.io.TypeMarshallingContext;
import flex.messaging.io.amf.AmfTrace;
import flex.messaging.io.amf.ActionMessage;
import flex.messaging.io.amf.MessageHeader;
import flex.messaging.io.amf.MessageBody;
import flex.messaging.io.amf.ASObject;
import flex.messaging.io.amf.Amf3Input;
import flex.messaging.io.ArrayCollection;
import flex.messaging.util.ClassUtil;
import flex.messaging.util.Hex;
import flex.messaging.util.XMLUtil;
import flex.messaging.MessageException;

import java.util.List;
import java.util.ArrayList;
import java.util.Date;
import java.util.Stack;
import java.util.Iterator;
import java.util.HashMap;
import java.util.Map;
import java.util.EmptyStackException;
import java.io.IOException;
import java.io.Externalizable;
import java.io.ByteArrayInputStream;
import java.lang.reflect.Array;

import org.xml.sax.Attributes;

/**
* Context for AMFX specific SAX handler.
*
* Contains start and end tag handlers for each of
* the XML elements that occur in an AMFX request. The
* AmfxMessageDeserializer enforces a naming convention
* for these handlers of xyz_start for the start handler
* and xyz_end for the end handler of element xyz.
*
* Note that this context MUST be reset if reused between
* AMFX packet parsings.
*
* @author Peter Farland
*
* @see AmfxMessageDeserializer
* @see AmfxOutput
*/
public class AmfxInput
{
    private SerializationContext context;
    private BeanProxy beanproxy = new BeanProxy();

    private final ArrayList objectTable;
    private final ArrayList stringTable;
    private final ArrayList traitsTable;

    private StringBuffer text;

    private ActionMessage message;
    private MessageHeader currentHeader;
    private MessageBody currentBody;
    private Stack objectStack;
    private Stack proxyStack;
    private Stack arrayPropertyStack;
    private Stack ecmaArrayIndexStack;
    private Stack strictArrayIndexStack;
    private Stack traitsStack;
    private boolean isStringReference;
    private boolean isTraitProperty;

    /*
     *  DEBUG LOGGING
     */
    protected boolean isDebug;
    protected AmfTrace trace;

    public AmfxInput(SerializationContext context)
    {
        this.context = context;

        stringTable = new ArrayList(64);
        objectTable = new ArrayList(64);
        traitsTable = new ArrayList(10);

        objectStack = new Stack();
        proxyStack = new Stack();
        arrayPropertyStack = new Stack();
        strictArrayIndexStack = new Stack();
        ecmaArrayIndexStack = new Stack();
        traitsStack = new Stack();

        text = new StringBuffer(32);
    }

    public void reset()
    {
        stringTable.clear();
        objectTable.clear();
        traitsTable.clear();
        objectStack.clear();
        proxyStack.clear();
        arrayPropertyStack.clear();
        traitsStack.clear();
        currentBody = null;
        currentHeader = null;

        TypeMarshallingContext marshallingContext = TypeMarshallingContext.getTypeMarshallingContext();
        marshallingContext.reset();
    }

    public void setDebugTrace(AmfTrace trace)
    {
        this.trace = trace;
        isDebug = this.trace != null;
    }

    public void setActionMessage(ActionMessage msg)
    {
        message = msg;
    }

    public Object readObject() throws IOException
    {
        return null;
    }


    // XML Considerations

    public void text(String s)
    {
        text.append(s);
    }


    //
    // AMFX Message Structure
    //

    public void start_amfx(Attributes attributes)
    {
        String ver = attributes.getValue("ver");
        int version = ActionMessage.CURRENT_VERSION;
        if (ver != null)
        {
            try
            {
                version = Integer.parseInt(ver);
            }
            catch (NumberFormatException ex)
            {
                throw new MessageException("Unknown version: " + ver);
            }
        }

        if (isDebug)
            trace.version(version);

        message.setVersion(version);
    }

    public void end_amfx()
    {
    }

    public void start_header(Attributes attributes)
    {
        if (currentHeader != null || currentBody != null)
            throw new MessageException("Unexpected header tag.");

        currentHeader = new MessageHeader();

        String name = attributes.getValue("name");
        currentHeader.setName(name);

        String mu = attributes.getValue("mustUnderstand");
        boolean mustUnderstand = false;
        if (mu != null)
        {
            mustUnderstand = Boolean.valueOf(mu).booleanValue();
            currentHeader.setMustUnderstand(mustUnderstand);
        }

        if (isDebug)
            trace.startHeader(name, mustUnderstand, message.getHeaderCount());
    }

    public void end_header()
    {
        message.addHeader(currentHeader);
        currentHeader = null;

        if (isDebug)
            trace.endHeader();
    }

    public void start_body(Attributes attributes)
    {
        if (currentBody != null || currentHeader != null)
            throw new MessageException("Unexpected body tag.");

        currentBody = new MessageBody();

        String targetURI = attributes.getValue("targetURI");
        currentBody.setTargetURI(targetURI);

        String responseURI = attributes.getValue("responseURI");
        currentBody.setResponseURI(responseURI);

        if (isDebug)
            trace.startMessage(targetURI, responseURI, message.getBodyCount());
    }

    public void end_body()
    {
        message.addBody(currentBody);
        currentBody = null;

        if (isDebug)
            trace.endMessage();
    }


    //
    // ActionScript Types
    //

    public void start_array(Attributes attributes)
    {
        int length = 10;
        String len = attributes.getValue("length");
        if (len != null)
        {
            try
            {
                len = len.trim();
                length = Integer.parseInt(len);
                if (length < 0)
                    throw new NumberFormatException();
            }
            catch (NumberFormatException ex)
            {
                throw new MessageException("Invalid array length: " + len);
            }
        }


        String ecma = attributes.getValue("ecma");
        boolean isECMA = "true".equalsIgnoreCase(ecma);

        Object array;

        if (isECMA)
        {
            array = new HashMap();
        }
        else if (context.legacyCollection)
        {
            array = new ArrayList(length);
        }
        else
        {
            array = new Object[length];
        }

        setValue(array);

        ecmaArrayIndexStack.push(new int[]{0});
        strictArrayIndexStack.push(new int[]{0});

        objectTable.add(array);
        objectStack.push(array);
        proxyStack.push(null);

        if (isECMA)
        {
            if (isDebug)
                trace.startECMAArray(objectTable.size() - 1);
        }
        else
        {
            if (isDebug)
                trace.startAMFArray(objectTable.size() - 1);
        }
    }

    public void end_array()
    {
        try
        {
            objectStack.pop();
            proxyStack.pop();
            ecmaArrayIndexStack.pop();
            strictArrayIndexStack.pop();
        }
        catch (EmptyStackException ex)
        {
            throw new MessageException("Unexpected end of array");
        }

        if (isDebug)
            trace.endAMFArray();
    }

    // <bytearray>010F0A</bytearray>

    public void start_bytearray(Attributes attributes)
    {
        text.delete(0, text.length());
    }

    public void end_bytearray()
    {
        String bs = text.toString().trim();
       
        Hex.Decoder decoder = new Hex.Decoder();
        decoder.decode((String)bs);
        byte[] value = decoder.drain();

        setValue(value);

        if (isDebug)
            trace.startByteArray(objectTable.size() - 1, bs.length());
    }

    public void start_date(Attributes attributes)
    {
        text.delete(0, text.length());
    }

    public void end_date()
    {
        String d = text.toString().trim();
        try
        {
            long l = Long.parseLong(d);
            Date date = new Date(l);
            setValue(date);

            objectTable.add(date); //Dates can be sent by reference

            if (isDebug)
                trace.write(date);
        }
        catch (NumberFormatException ex)
        {
            throw new MessageException("Invalid date: " + d);
        }
    }

    public void start_double(Attributes attributes)
    {
        text.delete(0, text.length());
    }

    public void end_double()
    {
        String ds = text.toString().trim();
        try
        {
            Double d = Double.valueOf(ds);
            setValue(d);

            if (isDebug)
                trace.write(d.doubleValue());
        }
        catch (NumberFormatException ex)
        {
            throw new MessageException("Invalid double: " + ds);
        }
    }

    public void start_false(Attributes attributes)
    {
        setValue(Boolean.FALSE);

        if (isDebug)
            trace.write(false);
    }

    public void end_false()
    {
    }

    public void start_item(Attributes attributes)
    {
        String name = attributes.getValue("name");
        if (name != null)
        {
            name = name.trim();
            if (name.length() <= 0)
                throw new MessageException("Array item names cannot be the empty string.");

            char c = name.charAt(0);
            if (!(Character.isLetterOrDigit(c) || c == '_'))
                throw new MessageException("Invalid item name: " + name +
                        ". Array item names must start with a letter, a digit or the underscore '_' character.");
        }
        else
        {
            throw new MessageException("Array item must have a name attribute.");
        }

        //Check that we're expecting an ECMA array
        Object o = objectStack.peek();
        if (!(o instanceof Map))
        {
            throw new MessageException("Unexpected array item name: " + name +
                    ". Please set the ecma attribute to 'true'.");
        }

        arrayPropertyStack.push(name);
    }

    public void end_item()
    {
        arrayPropertyStack.pop();
    }

    public void start_int(Attributes attributes)
    {
        text.delete(0, text.length());
    }

    public void end_int()
    {
        String is = text.toString().trim();
        try
        {
            Integer i = Integer.valueOf(is);
            setValue(i);

            if (isDebug)
                trace.write(i.intValue());
        }
        catch (NumberFormatException ex)
        {
            throw new MessageException("Invalid int: " + is);
        }
    }

    public void start_null(Attributes attributes)
    {
        setValue(null);

        if (isDebug)
            trace.writeNull();
    }

    public void end_null()
    {
    }

    // <object type="com.my.Class">

    public void start_object(Attributes attributes)
    {
        PropertyProxy proxy = null;

        String type = attributes.getValue("type");
        if (type != null)
        {
            type = type.trim();
        }

        Object object;
       
        if (type != null && type.length() > 0)
        {
            // Check for any registered class aliases
            String aliasedClass = ClassAliasRegistry.getRegistry().getClassName(type);
            if (aliasedClass != null)
                type = aliasedClass;

            if (type == null || type.length() == 0)
            {
                object = new ASObject();
            }
            else if (type.startsWith(">")) // Handle [RemoteClass] (no server alias)
            {
                object = new ASObject();
                ((ASObject)object).setType(type);
            }
            else if (context.instantiateTypes || type.startsWith("flex."))
            {
                Class desiredClass = AbstractProxy.getClassFromClassName(type);

                proxy = PropertyProxyRegistry.getRegistry().getProxyAndRegister(desiredClass);

                if (proxy == null)
                    object = ClassUtil.createDefaultInstance(desiredClass, null);
                else
                    object = proxy.createInstance(type);
            }
            else
            {
                // Just return type info with an ASObject...
                object = new ASObject();
                ((ASObject)object).setType(type);
            }
        }
        else
        {
            // TODO: QUESTION: Pete, Investigate why setValue for ASObject is delayed to endObject
            object = new ASObject(type);
        }

        if (proxy == null)
            proxy = PropertyProxyRegistry.getProxyAndRegister(object);

        objectStack.push(object);
        proxyStack.push(proxy);
        objectTable.add(object);

        if (isDebug)
            trace.startAMFObject(type, objectTable.size() - 1);
    }


    // </object>

    public void end_object()
    {
        if (!traitsStack.empty())
            traitsStack.pop();
       
        if (!objectStack.empty())
        {
            Object obj = objectStack.pop();
            PropertyProxy proxy = (PropertyProxy) proxyStack.pop();

            Object newObj = proxy == null ? obj : proxy.instanceComplete(obj);
            if (newObj != obj)
            {
                int i;
                // Find the index in the list of the old objct and replace it with
                // the new one.
                for (i = 0; i < objectTable.size(); i++)
                    if (objectTable.get(i) == obj)
                        break;

                if (i != objectTable.size())
                    objectTable.set(i, newObj);

                obj = newObj;
            }
            setValue(obj);
        }
        else
        {
            throw new MessageException("Unexpected end of object.");
        }

        if (isDebug)
            trace.endAMFObject();
    }

    public void start_ref(Attributes attributes)
    {
        String id = attributes.getValue("id");
        if (id != null)
        {
            try
            {
                int i = Integer.parseInt(id);
                Object o = objectTable.get(i);
                setValue(o);

                if (isDebug)
                    trace.writeRef(i);
            }
            catch (NumberFormatException ex)
            {
                throw new MessageException("Invalid object reference: " + id);
            }
            catch (IndexOutOfBoundsException ex)
            {
                throw new MessageException("Unknown object reference: " + id);
            }
        }
        else
        {
            throw new MessageException("Unknown object reference: " + id);
        }

    }

    public void end_ref()
    {
    }

    public void start_string(Attributes attributes)
    {
        String id = attributes.getValue("id");
        if (id != null)
        {
            isStringReference = true;

            try
            {
                int i = Integer.parseInt(id);
                String s = (String)stringTable.get(i);
                if (isTraitProperty)
                {
                    TraitsContext traitsContext = (TraitsContext)traitsStack.peek();
                    traitsContext.add(s);
                }
                else
                {
                    setValue(s);
                }
            }
            catch (NumberFormatException ex)
            {
                throw new MessageException("Invalid string reference: " + id);
            }
            catch (IndexOutOfBoundsException ex)
            {
                throw new MessageException("Unknown string reference: " + id);
            }
        }
        else
        {
            text.delete(0, text.length());
            isStringReference = false;
        }
    }

    public void end_string()
    {
        if (!isStringReference)
        {
            String s = text.toString();

            // Special case the empty string as it isn't counted as in
            // the string reference table
            if (s.length() > 0)
            {
                // Traits won't contain CDATA
                if (!isTraitProperty)
                    s = unescapeCloseCDATA(s);

                stringTable.add(s);
            }

            if (isTraitProperty)
            {
                TraitsContext traitsContext = (TraitsContext)traitsStack.peek();
                traitsContext.add(s);
            }
            else
            {
                setValue(s);

                if (isDebug)
                    trace.writeString((String)s);
            }
        }
    }


    public void start_traits(Attributes attributes)
    {
        if (!objectStack.empty())
        {
            List traitsList = new ArrayList();
            TraitsContext traitsContext = new TraitsContext(traitsList);
            traitsStack.push(traitsContext);

            String id = attributes.getValue("id");
            if (id != null)
            {
                try
                {
                    int i = Integer.parseInt(id);
                    List l = (List)traitsTable.get(i);

                    Iterator it = l.iterator();
                    while (it.hasNext())
                    {
                        String prop = (String)it.next();
                        traitsList.add(prop);
                    }
                }
                catch (NumberFormatException ex)
                {
                    throw new MessageException("Invalid traits reference: " + id);
                }
                catch (IndexOutOfBoundsException ex)
                {
                    throw new MessageException("Unknown traits reference: " + id);
                }
            }
            else
            {
                boolean externalizable = false;

                String ext = attributes.getValue("externalizable");
                if (ext != null)
                {
                    externalizable = "true".equals(ext.trim());
                }

                Object obj = objectStack.peek();
                if (externalizable && !(obj instanceof Externalizable))
                {
                    //Class '{className}' must implement java.io.Externalizable to receive client IExternalizable instances.
                    SerializationException ex = new SerializationException();
                    ex.setMessage(10305, new Object[] {obj.getClass().getName()});
                    throw ex;
                }

                traitsTable.add(traitsList);
            }

            isTraitProperty = true;
        }
        else
        {
            throw new MessageException("Unexpected traits");
        }
    }

    public void end_traits()
    {
        isTraitProperty = false;
    }


    public void start_true(Attributes attributes)
    {
        setValue(Boolean.TRUE);

        if (isDebug)
            trace.write(true);
    }

    public void end_true()
    {
    }

    public void start_undefined(Attributes attributes)
    {
        setValue(null);

        if (isDebug)
            trace.writeUndefined();
    }

    public void end_undefined()
    {
    }

    public void start_xml(Attributes attributes)
    {
        text.delete(0, text.length());
    }

    public void end_xml()
    {
        String xml = text.toString();
        xml = unescapeCloseCDATA(xml);

        Object value = XMLUtil.stringToDocument(xml, !(context.legacyXMLNamespaces));
        setValue(value);
    }

    private String unescapeCloseCDATA(String s)
    {
        //Only check if string could possibly have an encoded closing for a CDATA "]]>"
        if (s.length() > 5 && s.indexOf("]]&gt;") != -1)
        {
            s = s.replaceAll("]]&gt;", "]]>");
        }

        return s;
    }

    private void setValue(Object value)
    {
        if (objectStack.empty())
        {
            // Headers
            if (currentHeader != null)
            {
                currentHeader.setData(value);
            }

            // Body
            else if (currentBody  != null)
            {
                currentBody.setData(value);
            }

            else
            {
                throw new MessageException("Unexpected value: " + value);
            }

            return;
        }


        // ActionScript Data
        Object obj = objectStack.peek();

        // <object type="..."> <traits externalizable="true">
        if (obj instanceof Externalizable)
        {
            if (value != null && value.getClass().isArray() && Byte.TYPE.equals(value.getClass().getComponentType()))
            {
                Externalizable extern = (Externalizable)obj;
                Amf3Input objIn = new Amf3Input(context);
                byte[] ba = (byte[])value;
                ByteArrayInputStream baIn = new ByteArrayInputStream(ba);
                try
                {
                    //objIn.setDebugTrace(trace);
                    objIn.setInputStream(baIn);
                    extern.readExternal(objIn);
                }
                catch (ClassNotFoundException ex)
                {
                    throw new MessageException("Error while reading Externalizable class " + extern.getClass().getName(), ex);
                }
                catch (IOException ex)
                {
                    throw new MessageException("Error while reading Externalizable class " + extern.getClass().getName(), ex);
                }
                finally
                {
                    try
                    {
                        objIn.close();
                    }
                    catch (IOException ex)
                    {
                    }
                }
            }
            else
            {
                throw new MessageException("Error while reading Externalizable class. Value must be a byte array.");
            }
        }

        // <object>
        else if (obj instanceof ASObject)
        {
            String prop;

            TraitsContext traitsContext = (TraitsContext)traitsStack.peek();
            try
            {
                prop = traitsContext.next();
            }
            catch (IndexOutOfBoundsException ex)
            {
                throw new MessageException("Object has no trait info for value: " + value);
            }

            ASObject aso = (ASObject)obj;
            aso.put(prop, value);

            if (isDebug)
                trace.namedElement(prop);
        }

        // <array ecma="false"> in ArrayList form
        else if (obj instanceof ArrayList && !(obj instanceof ArrayCollection))
        {
            ArrayList list = (ArrayList)obj;
            list.add(value);

            if (isDebug)
                trace.arrayElement(list.size() - 1);

        }

        // <array ecma="false"> in Object[] form
        else if (obj.getClass().isArray())
        {
            if (!strictArrayIndexStack.empty())
            {
                int[] indexObj = (int[])strictArrayIndexStack.peek();
                int index = indexObj[0];

                if (Array.getLength(obj) > index)
                {
                    Array.set(obj, index, value);
                }
                else
                {
                    throw new MessageException("Index out of bounds at: " + index + " cannot set array value: " + value + "");
                }
                indexObj[0]++;
            }
        }

        // <array ecma="true">
        else if (obj instanceof Map)
        {
            Map map = (Map)obj;

            // <item name="prop">
            if (!arrayPropertyStack.empty())
            {
                String prop = (String)arrayPropertyStack.peek();
                map.put(prop, value);

                if (isDebug)
                    trace.namedElement(prop);

                return;
            }

            // Mixed content, auto-generate string for ECMA Array index
            if (!ecmaArrayIndexStack.empty())
            {
                int[] index = (int[])ecmaArrayIndexStack.peek();

                String prop = String.valueOf(index[0]);
                index[0]++;

                map.put(prop, value);

                if (isDebug)
                    trace.namedElement(prop);
            }
        }

        // <object type="...">
        else
        {
            String prop;

            TraitsContext traitsContext = (TraitsContext)traitsStack.peek();
            try
            {
                prop = traitsContext.next();
            }
            catch (IndexOutOfBoundsException ex)
            {
                throw new MessageException("Object has no trait info for value: " + value, ex);
            }

            try
            {
                // Then check if there's a more suitable proxy now that we have an instance
                PropertyProxy proxy = (PropertyProxy) proxyStack.peek();
                if (proxy == null)
                    proxy = beanproxy;
                proxy.setValue(obj, prop, value);
            }
            catch (Exception ex)
            {
                throw new MessageException("Failed to set property '" + prop + "' with value: " + value, ex);
            }


            if (isDebug)
                trace.namedElement(prop);
        }
    }

    private class TraitsContext
    {
        private List traits;
        private int counter;

        private TraitsContext(List traits)
        {
            this.traits = traits;
        }

        private void add(String trait)
        {
            trait = trait.trim();

            if (trait.length() <= 0)
                throw new MessageException("Traits cannot be the empty string.");

            char c = trait.charAt(0);
            if (!(Character.isLetterOrDigit(c) || c == '_'))
                throw new MessageException("Invalid trait name: " + trait +
                        ". Object property names must start with a letter, a digit or the underscore '_' character.");


            traits.add(trait);
        }

        private String next()
        {
            String trait = (String)traits.get(counter);
            counter++;
            return trait;
        }
    }
}
TOP

Related Classes of flex.messaging.io.amfx.AmfxInput$TraitsContext

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.