| //////////////////////////////////////////////////////////////////////////////// |
| // |
| // Licensed to the Apache Software Foundation (ASF) under one or more |
| // contributor license agreements. See the NOTICE file distributed with |
| // this work for additional information regarding copyright ownership. |
| // The ASF licenses this file to You 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 mx.messaging.channels.amfx |
| { |
| |
| import flash.net.getClassByAlias; |
| import flash.utils.ByteArray; |
| import flash.utils.Dictionary; |
| import flash.utils.IExternalizable; |
| import flash.utils.getDefinitionByName; |
| |
| import mx.logging.Log; |
| import mx.messaging.errors.ChannelError; |
| import mx.resources.IResourceManager; |
| import mx.resources.ResourceManager; |
| import mx.utils.HexDecoder; |
| |
| [ResourceBundle("messaging")] |
| |
| [ExcludeClass] |
| |
| /** |
| * Decodes an AMFX packet into an ActionScript Object graph. |
| * Headers and the result body are accessed from the returned |
| * AMFXResult. |
| * @private |
| */ |
| public class AMFXDecoder |
| { |
| //-------------------------------------------------------------------------- |
| // |
| // Constructor |
| // |
| //-------------------------------------------------------------------------- |
| |
| public function AMFXDecoder() |
| { |
| super(); |
| } |
| |
| //-------------------------------------------------------------------------- |
| // |
| // Class variables |
| // |
| //-------------------------------------------------------------------------- |
| |
| /** |
| * @private |
| * Storage for the resourceManager getter. |
| * This gets initialized on first access, |
| * not at static initialization time, in order to ensure |
| * that the Singleton registry has already been initialized. |
| */ |
| private static var _resourceManager:IResourceManager; |
| |
| /** |
| * @private |
| * A reference to the object which manages |
| * all of the application's localized resources. |
| * This is a singleton instance which implements |
| * the IResourceManager interface. |
| */ |
| private static function get resourceManager():IResourceManager |
| { |
| if (!_resourceManager) |
| _resourceManager = ResourceManager.getInstance(); |
| |
| return _resourceManager; |
| } |
| |
| //-------------------------------------------------------------------------- |
| // |
| // Public Methods |
| // |
| //-------------------------------------------------------------------------- |
| |
| public function decode(xml:XML):AMFXResult |
| { |
| if (xml) |
| { |
| var context:AMFXContext = new AMFXContext(); |
| context.log = Log.getLogger("mx.messaging.channels.amfx.AMFXDecoder"); |
| XML.ignoreWhitespace = false; |
| return decodePacket(xml, context); |
| } |
| else |
| { |
| return null; |
| } |
| } |
| |
| //-------------------------------------------------------------------------- |
| // |
| // Static Methods |
| // |
| //-------------------------------------------------------------------------- |
| |
| private static function decodePacket(xml:XML, context:AMFXContext):AMFXResult |
| { |
| var result:AMFXResult = new AMFXResult(); |
| |
| var message:String; |
| |
| var name:String = xml.localName(); |
| if (name == "amfx") |
| { |
| var v:String = xml.attribute("ver").toString(); |
| var version:uint = uint(v); |
| |
| if (supportedVersion(version)) |
| { |
| var h:XMLList = xml.child("header"); |
| if (h) |
| { |
| result.headers = decodeHeaders(h, context); |
| } |
| |
| var body:XML = xml.body[0]; |
| |
| if (body) |
| { |
| result.result = decodeBody(body, context); |
| } |
| else |
| { |
| message = resourceManager.getString( |
| "messaging", "noAMFXBody"); |
| throw new ChannelError(message); |
| } |
| } |
| else |
| { |
| message = resourceManager.getString( |
| "messaging", "unsupportedAMFXVersion", [ version ]); |
| throw new ChannelError(message); |
| } |
| } |
| else |
| { |
| message = resourceManager.getString( |
| "messaging", "noAMFXNode") |
| throw new ChannelError(message); |
| } |
| |
| return result; |
| } |
| |
| private static function decodeHeaders(xmlList:XMLList, context:AMFXContext):Array |
| { |
| var headers:Array = []; |
| |
| for (var i:uint = 0; i < xmlList.length(); i++) |
| { |
| var h:XML = xmlList[i]; |
| var name:String = h.attribute("name").toString(); |
| |
| var header:AMFXHeader = new AMFXHeader(); |
| header.name = name; |
| |
| var temp:String = h.attribute("mustUnderstand").toString(); |
| header.mustUnderstand = (temp == "true"); |
| |
| var children:XMLList = h.children(); |
| if (children.length() > 0) |
| { |
| header.content = decodeValue(children[0], context); |
| } |
| else |
| { |
| header.content = null; |
| } |
| |
| headers[i] = header; |
| } |
| |
| return headers; |
| } |
| |
| private static function decodeBody(xml:XML, context:AMFXContext):Object |
| { |
| var result:Object; |
| var children:XMLList = xml.children(); |
| |
| if (children.length() == 1) |
| { |
| result = decodeValue(children[0], context); |
| } |
| else if (children.length() > 1) |
| { |
| result = []; |
| for (var i:uint = 0; i < children.length(); i++) |
| { |
| result[i] = decodeValue(children[i], context); |
| } |
| } |
| |
| return result; |
| } |
| |
| public static function decodeValue(xml:XML, context:AMFXContext):Object |
| { |
| var result:Object; |
| var name:String = xml.localName(); |
| |
| if (name == "string") |
| { |
| result = decodeString(xml, context); |
| } |
| else if (name == "true") |
| { |
| result = true; |
| } |
| else if (name == "false") |
| { |
| result = false; |
| } |
| else if (name == "array") |
| { |
| result = decodeArray(xml, context); |
| } |
| else if (name == "object") |
| { |
| result = decodeObject(xml, context); |
| } |
| else if (name == "ref") |
| { |
| result = decodeRef(xml, context); |
| } |
| else if (name == "dictionary") |
| { |
| result = decodeDictionary(xml, context); |
| } |
| else if (name == "double") |
| { |
| var n:String = xml.text().toString(); |
| result = Number(n); |
| } |
| else if (name == "int") |
| { |
| var i:String = xml.text().toString(); |
| result = int(i); |
| } |
| else if (name == "null") |
| { |
| result = null; |
| } |
| else if (name == "date") |
| { |
| result = decodeDate(xml, context); |
| } |
| else if (name == "xml") |
| { |
| var x:String = xml.text().toString(); |
| // If we had CDATA, restore any escaped close CDATA "]]>" tokens |
| if (hasEscapedCloseCDATA(xml)) |
| { |
| x = x.replace(REGEX_CLOSE_CDATA, "]]>"); |
| } |
| |
| result = new XML(x); |
| } |
| else if (name == "bytearray") |
| { |
| result = decodeByteArray(xml); |
| } |
| else if (name == "undefined") |
| { |
| result = undefined; |
| } |
| |
| return result; |
| } |
| |
| private static function decodeArray(xml:XML, context:AMFXContext):Array |
| { |
| var array:Array = []; |
| |
| // Remember Array |
| context.addObject(array); |
| |
| var entries:XMLList = xml.*; |
| if (entries) |
| { |
| for (var i:uint = 0; i < entries.length(); i++) |
| { |
| var x:XML = entries[i]; |
| var prop:Object; |
| if (x.localName() == "item") |
| { |
| var propName:String = x.attribute("name").toString(); |
| prop = decodeValue(x.*[0], context); |
| array[propName] = prop; |
| } |
| else |
| { |
| prop = decodeValue(x, context); |
| array.push(prop); |
| } |
| } |
| } |
| |
| return array; |
| } |
| |
| private static function decodeDictionary(xml:XML, context:AMFXContext):Dictionary |
| { |
| var dictionary:Dictionary = new Dictionary(); |
| |
| context.addObject(dictionary); // Remember the dictionary. |
| |
| var entries:XMLList = xml.*; |
| if (entries == null) |
| return dictionary; |
| |
| for (var i:uint = 0; i < entries.length(); i = i + 2) |
| { |
| var keyXml:XML = entries[i]; |
| var valueXml:XML = entries[i + 1]; |
| var key:Object= decodeValue(keyXml, context); |
| var value:Object = decodeValue(valueXml, context); |
| dictionary[key] = value; |
| } |
| |
| return dictionary; |
| } |
| |
| private static function decodeByteArray(xml:XML):ByteArray |
| { |
| var str:String = xml.text().toString(); |
| |
| var decoder:HexDecoder = new HexDecoder(); |
| decoder.decode(str); |
| return decoder.drain(); |
| } |
| |
| private static function decodeDate(xml:XML, context:AMFXContext):Date |
| { |
| var d:String = xml.text().toString(); |
| var time:Number = new Number(d); |
| var result:Date = new Date(); |
| result.setTime(time); |
| |
| // Remember Date object |
| context.addObject(result); |
| |
| return result; |
| } |
| |
| private static function decodeObject(xml:XML, context:AMFXContext):Object |
| { |
| var result:Object; |
| var className:String = xml.attribute("type").toString(); |
| |
| if (className) |
| { |
| try |
| { |
| var classType:Class = null; |
| try |
| { |
| classType = getClassByAlias(className); |
| } |
| catch(e1:Error) |
| { |
| if (!classType) |
| classType = getDefinitionByName(className) as Class; |
| } |
| |
| result = new classType(); |
| } |
| catch(e:Error) |
| { |
| if (context.log) |
| context.log.warn("Error instantiating class: {0}. Reverting to anonymous Object.", className); |
| |
| result = {}; |
| } |
| } |
| else |
| { |
| className = "Object"; |
| result = {}; |
| } |
| |
| // Remember Object |
| context.addObject(result); |
| |
| var entries:XMLList = xml.*; |
| |
| if (entries && entries.length() > 0) |
| { |
| var traits:Object; |
| var tx:XML = entries[0]; |
| var message:String; |
| |
| if (tx.localName() == "traits") |
| { |
| traits = decodeTraits(tx, className, context); |
| delete entries[0]; |
| } |
| |
| if (!traits) |
| { |
| message = resourceManager.getString( |
| "messaging", "AMFXTraitsNotFirst") |
| throw new ChannelError(message); |
| } |
| |
| if (traits.externalizable) |
| { |
| if (result is IExternalizable) |
| { |
| var ext:IExternalizable = IExternalizable(result); |
| tx = entries[0]; |
| if (tx.localName() == "bytearray") |
| { |
| var ba:ByteArray = decodeByteArray(tx); |
| |
| try |
| { |
| ext.readExternal(ba); |
| } |
| catch(e:Error) |
| { |
| message = resourceManager.getString( |
| "messaging", "errorReadingIExternalizable", |
| [ (e.message+e.getStackTrace()) ]); |
| throw new ChannelError(message); |
| } |
| } |
| } |
| else |
| { |
| message = resourceManager.getString( |
| "messaging", "notImplementingIExternalizable", |
| [ className ]); |
| throw new ChannelError(message); |
| } |
| } |
| else |
| { |
| for (var i:uint = 0; i < entries.length(); i++) |
| { |
| var propName:String = traits.properties[i]; |
| var propValue:* = decodeValue(entries[i], context); |
| |
| try |
| { |
| result[propName] = propValue; |
| } |
| catch(e2:Error) |
| { |
| if (context.log != null) |
| context.log.warn("Cannot set property '{0}' on type '{1}'.", propName, className); |
| } |
| } |
| } |
| } |
| return result; |
| } |
| |
| private static function decodeRef(xml:XML, context:AMFXContext):Object |
| { |
| var result:*; |
| |
| var message:String; |
| |
| var idAttr:String = xml.attribute("id").toString(); |
| if (idAttr) |
| { |
| var ref:int = parseInt(idAttr); |
| |
| if (!isNaN(ref)) |
| { |
| result = context.getObject(ref); |
| } |
| |
| if (result === undefined) |
| { |
| message = resourceManager.getString( |
| "messaging", "unknownReference", [ idAttr ]); |
| throw new ChannelError(message); |
| } |
| } |
| else |
| { |
| message = resourceManager.getString( |
| "messaging", "referenceMissingId"); |
| throw new ChannelError(message); |
| } |
| |
| return result; |
| } |
| |
| private static function decodeString(xml:XML, context:AMFXContext, isTrait:Boolean = false):String |
| { |
| var str:String; |
| |
| var refAttr:String = xml.attribute("id").toString(); |
| if (refAttr) |
| { |
| var ref:uint = uint(refAttr); |
| if (!isNaN(ref)) |
| { |
| str = context.getString(ref); |
| } |
| |
| if (!str) |
| { |
| var message:String = resourceManager.getString( |
| "messaging", "unknownStringReference", [ refAttr ]); |
| throw new ChannelError(message); |
| } |
| } |
| else |
| { |
| str = xml.text().toString(); |
| |
| // If we had CDATA, restore any escaped close CDATA "]]>" tokens |
| // Note that trait names won't have CDATA sections... so no need to check them |
| if (!isTrait && hasEscapedCloseCDATA(xml)) |
| { |
| str = str.replace(REGEX_CLOSE_CDATA, "]]>"); |
| } |
| |
| //Remember string |
| context.addString(str); |
| } |
| |
| return str; |
| } |
| |
| private static function decodeTraits(xml:XML, className:String, context:AMFXContext):Object |
| { |
| var traits:Object; |
| |
| var refAttr:String = xml.attribute("id").toString(); |
| if (refAttr) |
| { |
| var ref:uint = uint(refAttr); |
| if (!isNaN(ref)) |
| { |
| traits = context.getTraitInfo(ref); |
| } |
| |
| if (!traits) |
| { |
| var message:String = resourceManager.getString( |
| "messaging", "unknownTraitReference", [ refAttr ]); |
| throw new ChannelError(message); |
| } |
| } |
| else |
| { |
| traits = {}; |
| traits.name = className; |
| traits.alias = className; |
| traits.properties = []; |
| traits.externalizable = false; |
| |
| var ext:String = xml.attribute("externalizable").toString(); |
| if (ext == "true") |
| { |
| traits.externalizable = true; |
| } |
| |
| var nodes:XMLList = xml.*; |
| if (nodes) |
| { |
| for (var i:uint = 0; i < nodes.length(); i++) |
| { |
| traits.properties[i] = decodeString(nodes[i], context, true); |
| } |
| } |
| |
| //Remember traits |
| context.addTraitInfo(traits); |
| } |
| |
| return traits; |
| } |
| |
| private static function hasEscapedCloseCDATA(xml:XML):Boolean |
| { |
| var s:String = xml.toXMLString(); |
| |
| if (s.indexOf("]]>") != -1) |
| { |
| return s.indexOf("]]>") != -1; |
| } |
| else |
| { |
| return false; |
| } |
| } |
| |
| private static function supportedVersion(ver:uint):Boolean |
| { |
| for (var i:uint = 0; i < SUPPORTED_VERSIONS.length; i++) |
| { |
| if (ver == SUPPORTED_VERSIONS[i]) |
| return true; |
| } |
| |
| return false; |
| } |
| |
| //-------------------------------------------------------------------------- |
| // |
| // Static Constants |
| // |
| //-------------------------------------------------------------------------- |
| |
| private static const SUPPORTED_VERSIONS:Array = [3]; |
| private static const REGEX_CLOSE_CDATA:RegExp = new RegExp("]]>", "g"); |
| } |
| |
| } |