//////////////////////////////////////////////////////////////////////////////// // // 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 { import flash.events.EventDispatcher; import mx.core.IMXMLObject; import mx.core.mx_internal; import mx.events.PropertyChangeEvent; import mx.logging.ILogger; import mx.logging.Log; import mx.messaging.FlexClient; import mx.messaging.channels.PollingChannel; import mx.messaging.config.ConfigMap; import mx.messaging.config.ServerConfig; import mx.messaging.errors.InvalidDestinationError; import mx.messaging.events.ChannelEvent; import mx.messaging.events.ChannelFaultEvent; import mx.messaging.events.MessageAckEvent; import mx.messaging.events.MessageEvent; import mx.messaging.events.MessageFaultEvent; import mx.messaging.messages.AbstractMessage; import mx.messaging.messages.AcknowledgeMessage; import mx.messaging.messages.CommandMessage; import mx.messaging.messages.ErrorMessage; import mx.messaging.messages.IMessage; import mx.messaging.messages.MessagePerformanceUtils; import mx.netmon.NetworkMonitor; import mx.resources.IResourceManager; import mx.resources.ResourceManager; import mx.utils.Base64Encoder; import mx.utils.UIDUtil; use namespace mx_internal; /** * Dispatched when an acknowledge message is received for a sent message. * * @eventType mx.messaging.events.MessageAckEvent.ACKNOWLEDGE * * @langversion 3.0 * @playerversion Flash 9 * @playerversion AIR 1.1 * @productversion BlazeDS 4 * @productversion LCDS 3 * * @langversion 3.0 * @playerversion Flash 9 * @playerversion AIR 1.1 * @productversion BlazeDS 4 * @productversion LCDS 3 */ [Event(name="acknowledge", type="mx.messaging.events.MessageAckEvent")] /** * Dispatched when a message fault occurs. * * @eventType mx.messaging.events.MessageFaultEvent.FAULT * * @langversion 3.0 * @playerversion Flash 9 * @playerversion AIR 1.1 * @productversion BlazeDS 4 * @productversion LCDS 3 */ [Event(name="fault", type="mx.messaging.events.MessageFaultEvent")] /** * Dispatched when the underlying Channel the MessageAgent is using connects. * * @eventType mx.messaging.events.ChannelEvent.CONNECT * * @langversion 3.0 * @playerversion Flash 9 * @playerversion AIR 1.1 * @productversion BlazeDS 4 * @productversion LCDS 3 */ [Event(name="channelConnect", type="mx.messaging.events.ChannelEvent")] /** * Dispatched when the underlying Channel the MessageAgent is using disconnects. * * @eventType mx.messaging.events.ChannelEvent.DISCONNECT * * @langversion 3.0 * @playerversion Flash 9 * @playerversion AIR 1.1 * @productversion BlazeDS 4 * @productversion LCDS 3 */ [Event(name="channelDisconnect", type="mx.messaging.events.ChannelEvent")] /** * Dispatched when the underlying Channel the MessageAgent is using faults. * * @eventType mx.messaging.events.ChannelFaultEvent.FAULT * * @langversion 3.0 * @playerversion Flash 9 * @playerversion AIR 1.1 * @productversion BlazeDS 4 * @productversion LCDS 3 */ [Event(name="channelFault", type="mx.messaging.events.ChannelFaultEvent")] /** * Dispatched when the connected property of the MessageAgent changes. * Also dispatched when the subscribed of a Consumer changes. * @see mx.messaging.Consumer * @eventType mx.events.PropertyChangeEvent.PROPERTY_CHANGE * * @langversion 3.0 * @playerversion Flash 9 * @playerversion AIR 1.1 * @productversion BlazeDS 4 * @productversion LCDS 3 */ [Event(name="propertyChange", type="mx.events.PropertyChangeEvent")] [ResourceBundle("messaging")] /** * The MessageAgent class provides the basic low-level functionality common to * message handling for a destination. * *

Note: For advanced use only. * Use this class for creating custom message agents like the existing Producer * and Consumer classes.

* * @mxml *

* All message agent classes, including the Producer and Consumer classes, extend * MessageAgent and inherit the following tag attributes: *

*
 *   <mx:tagname
* Properties * channelSet="No default." * clientId="No default." * connected="false" * destination="No default." * requestTimeout="-1" * subtopic="No default." * * * Events * acknowledge="No default." * channelConnect="No default." * channelDisconnect="No default." * channelFault="No default." * fault="No default." * propertyChange="No default." * /> *
* * @langversion 3.0 * @playerversion Flash 9 * @playerversion AIR 1.1 * @productversion BlazeDS 4 * @productversion LCDS 3 */ public class MessageAgent extends EventDispatcher implements IMXMLObject { //-------------------------------------------------------------------------- // // Internal Static Constants // //-------------------------------------------------------------------------- /** * @private * Indicates that the MessageAgent is used an automatically configured ChannelSet * obtained from ServerConfig. */ mx_internal static const AUTO_CONFIGURED_CHANNELSET:int = 0; /** * @private * Indicates that the MessageAgent is using a manually assigned ChannelSet. */ mx_internal static const MANUALLY_ASSIGNED_CHANNELSET:int = 1; //-------------------------------------------------------------------------- // // Constructor // //-------------------------------------------------------------------------- /** * Constructor. * * @langversion 3.0 * @playerversion Flash 9 * @playerversion AIR 1.1 * @productversion BlazeDS 4 * @productversion LCDS 3 */ public function MessageAgent() { super(); } //-------------------------------------------------------------------------- // // Variables // //-------------------------------------------------------------------------- /** * @private * The type of MessageAgent. * This variable is used for logging and MUST be assigned by subclasses. */ protected var _agentType:String = "mx.messaging.MessageAgent"; /** * @private * The Base64 encoded credentials that will be passed through to * the ChannelSet. */ protected var _credentials:String; /** * @private * The character set encoding used to create the credentials String. */ protected var _credentialsCharset:String; /** * @private * Indicates whether the agent is explicitly disconnected. * This allows agents to supress processing of acks/faults that return * after the client has issued an explicit disconnect(). */ protected var _disconnectBarrier:Boolean; /** * @private * This helps in the runtime configuration setup by delaying the connect * event until the configuration has been setup. See acknowledge(). */ private var _pendingConnectEvent:ChannelEvent; /** * @private * The Base64 encoded credentials that are passed through to a * 3rd party. */ private var _remoteCredentials:String = ""; /** * @private * The character set encoding used to create the remoteCredentials String. */ private var _remoteCredentialsCharset:String; /** * @private * Indicates that the remoteCredentials value has changed and should * be sent to the server. */ private var _sendRemoteCredentials:Boolean; /** * @private * The logger MUST be assigned by subclasses, for example * Consumer and Producer. */ protected var _log:ILogger; /** * @private * A queue to store pending outbound messages while waiting for a server response * that contains a server-generated clientId. * Serializing messages from a MessageAgent to the server is essential until we * receive a response containing a server-generated clientId; otherwise the server * will treat each message as if it was sent by a different, "new" MessageAgent instance. */ private var _clientIdWaitQueue:Array; /** * @private * Flag being set to true denotes that we should skip remaining fault * processing logic because the fault has already been handled. * Currently used during an automatic resend of a faulted message if the fault * was due to a server session timeout and is authentication/authorization related. */ protected var _ignoreFault:Boolean = false; /** * @private */ private var resourceManager:IResourceManager = ResourceManager.getInstance(); //-------------------------------------------------------------------------- // // Properties // //-------------------------------------------------------------------------- //---------------------------------- // authenticated //---------------------------------- /** * @private */ private var _authenticated:Boolean; [Bindable(event="propertyChange")] /** * Indicates if this MessageAgent is using an authenticated connection to * its destination. */ public function get authenticated():Boolean { return _authenticated; } /** * @private */ mx_internal function setAuthenticated(value:Boolean, creds:String):void { if (_authenticated != value) { var event:PropertyChangeEvent = PropertyChangeEvent.createUpdateEvent(this, "authenticated", _authenticated, value); _authenticated = value; dispatchEvent(event); if (value) assertCredentials(creds); } } //---------------------------------- // channelSet //---------------------------------- /** * @private */ private var _channelSet:ChannelSet; [Bindable(event="propertyChange")] /** * Provides access to the ChannelSet used by the MessageAgent. The * ChannelSet can be manually constructed and assigned, or it will be * dynamically initialized to use the configured Channels for the * destination for this MessageAgent. * * @langversion 3.0 * @playerversion Flash 9 * @playerversion AIR 1.1 * @productversion BlazeDS 4 * @productversion LCDS 3 */ public function get channelSet():ChannelSet { return _channelSet; } /** * @private */ public function set channelSet(value:ChannelSet):void { internalSetChannelSet(value); _channelSetMode = MANUALLY_ASSIGNED_CHANNELSET; } /** * @private * This method is called by ChannelSet.connect(agent) to set up the bidirectional * relationship between the MessageAgent and the ChannelSet. * It also handles the case of customer code calling channelSet.connect(agent) * directly rather than assigning the ChannelSet to the MessageAgent's channelSet * property. */ mx_internal function internalSetChannelSet(value:ChannelSet):void { if (_channelSet != value) { if (_channelSet != null) _channelSet.disconnect(this); var event:PropertyChangeEvent = PropertyChangeEvent.createUpdateEvent(this, "channelSet", _channelSet, value); _channelSet = value; if (_channelSet != null) { if (_credentials) _channelSet.setCredentials(_credentials, this, _credentialsCharset); _channelSet.connect(this); } dispatchEvent(event); } } //---------------------------------- // clientId //---------------------------------- /** * @private */ private var _clientId:String; [Bindable(event="propertyChange")] /** * Provides access to the client id for the MessageAgent. * MessageAgents are assigned their client id by the remote destination * and this value is used to route messages from the remote destination to * the proper MessageAgent. * * @langversion 3.0 * @playerversion Flash 9 * @playerversion AIR 1.1 * @productversion BlazeDS 4 * @productversion LCDS 3 */ public function get clientId():String { return _clientId; } /** * @private * This method is used to assign a server-generated client id to the MessageAgent * in the common scenario. * It may also be used by the framework to sync up cooperating MessageAgents under * a single client id value so that they appear as a single MessageAgent to the server. * Assigning a client id value will flush any messages that have been queued while we * were waiting for a server-generated client id value to be returned. * Queued messages are sent to the server in order. */ mx_internal function setClientId(value:String):void { if (_clientId != value) { var event:PropertyChangeEvent = PropertyChangeEvent.createUpdateEvent(this, "clientId", _clientId, value); _clientId = value; flushClientIdWaitQueue(); dispatchEvent(event); } } //---------------------------------- // connected //---------------------------------- /** * @private */ private var _connected:Boolean = false; [Bindable(event="propertyChange")] /** * Indicates whether this MessageAgent is currently connected to its * destination via its ChannelSet. The propertyChange event is dispatched when * this property changes. * * @langversion 3.0 * @playerversion Flash 9 * @playerversion AIR 1.1 * @productversion BlazeDS 4 * @productversion LCDS 3 */ public function get connected():Boolean { return _connected; } /** * @private */ protected function setConnected(value:Boolean):void { if (_connected != value) { var event:PropertyChangeEvent = PropertyChangeEvent.createUpdateEvent(this, "connected", _connected, value); _connected = value; dispatchEvent(event); setAuthenticated(value && channelSet && channelSet.authenticated, _credentials); } } //---------------------------------- // destination //---------------------------------- /** * @private */ private var _destination:String = ""; [Bindable(event="propertyChange")] /** * Provides access to the destination for the MessageAgent. * Changing the destination will disconnect the MessageAgent if it is * currently connected. * * @throws mx.messaging.errors.InvalidDestinationError If the destination is null or * zero-length. * * @langversion 3.0 * @playerversion Flash 9 * @playerversion AIR 1.1 * @productversion BlazeDS 4 * @productversion LCDS 3 */ public function get destination():String { return _destination; } /** * @private */ public function set destination(value:String):void { if ((value == null) || value.length == 0) return; // empty/null destination is checked in internalSend. if (_destination != value) { // If we're using an automatically configured ChannelSet, // disconnect from it and null out our ref so we look up the // proper configured ChannelSet for the new destination on our next send(). if ((_channelSetMode == AUTO_CONFIGURED_CHANNELSET) && (channelSet != null)) { channelSet.disconnect(this); channelSet = null; } var event:PropertyChangeEvent = PropertyChangeEvent.createUpdateEvent(this, "destination", _destination, value); _destination = value; dispatchEvent(event); if (Log.isInfo()) _log.info("'{0}' {2} set destination to '{1}'.", id, _destination, _agentType); } } //---------------------------------- // id //---------------------------------- /** * @private */ private var _id:String = UIDUtil.createUID(); [Bindable(event="propertyChange")] /** * @private * The id of this agent. */ public function get id():String { return _id; } /** * @private */ public function set id(value:String):void { if (_id != value) { var event:PropertyChangeEvent = PropertyChangeEvent.createUpdateEvent(this, "id", _id, value); _id = value; dispatchEvent(event); } } //---------------------------------- // requestTimeout //---------------------------------- /** * @private */ private var _requestTimeout:int = -1; [Bindable(event="propertyChange")] /** * Provides access to the request timeout in seconds for sent messages. * If an acknowledgement, response or fault is not received from the * remote destination before the timeout is reached the message is faulted on the client. * A value less than or equal to zero prevents request timeout. * * @langversion 3.0 * @playerversion Flash 9 * @playerversion AIR 1.1 * @productversion BlazeDS 4 * @productversion LCDS 3 */ public function get requestTimeout():int { return _requestTimeout; } /** * @private */ public function set requestTimeout(value:int):void { if (_requestTimeout != value) { var event:PropertyChangeEvent = PropertyChangeEvent.createUpdateEvent(this, "requestTimeout", _requestTimeout, value); _requestTimeout = value; dispatchEvent(event); } } //-------------------------------------------------------------------------- // // Internal Properties // //-------------------------------------------------------------------------- //---------------------------------- // channelSetMode //---------------------------------- /** * @private */ private var _channelSetMode:int = AUTO_CONFIGURED_CHANNELSET; mx_internal function get channelSetMode():int { return _channelSetMode; } //---------------------------------- // configRequested //---------------------------------- /** * @private * Indicates whether the agent has requested configuration from the server. */ mx_internal var configRequested:Boolean = false; //---------------------------------- // needsConfig //---------------------------------- /** * @private */ private var _needsConfig:Boolean; /** * Indicates if this MessageAgent needs to request configuration from the * server. */ mx_internal function get needsConfig():Boolean { return _needsConfig; } /** * @private */ mx_internal function set needsConfig(value:Boolean):void { if (_needsConfig == value) return; _needsConfig = value; if (_needsConfig) { var cs:ChannelSet = channelSet; try { disconnect(); } finally { internalSetChannelSet(cs); } } } //-------------------------------------------------------------------------- // // Methods // //-------------------------------------------------------------------------- /** * Invoked by a MessageResponder upon receiving a result for a sent * message. Subclasses may override this method if they need to perform * custom acknowledgement processing, but must invoke * super.acknowledge() as well. This method dispatches a * MessageAckEvent. * * @param ackMsg The AcknowledgMessage returned. * * @param msg The original sent message. * * @langversion 3.0 * @playerversion Flash 9 * @playerversion AIR 1.1 * @productversion BlazeDS 4 * @productversion LCDS 3 */ public function acknowledge(ackMsg:AcknowledgeMessage, msg:IMessage):void { if (Log.isInfo()) _log.info("'{0}' {2} acknowledge of '{1}'.", id, msg.messageId, _agentType); if (Log.isDebug() && isCurrentChannelNotNull() && getCurrentChannel().mpiEnabled) { try { var mpiutil:MessagePerformanceUtils = new MessagePerformanceUtils(ackMsg); _log.debug(mpiutil.prettyPrint()); } catch (e:Error) { _log.debug("Could not get message performance information for: " + msg.toString()); } } if (configRequested) { configRequested = false; ServerConfig.updateServerConfigData(ackMsg.body as ConfigMap); needsConfig = false; if (_pendingConnectEvent) channelConnectHandler(_pendingConnectEvent); _pendingConnectEvent = null; } if (clientId == null) { if (ackMsg.clientId != null) setClientId(ackMsg.clientId); // Triggers a call to flush the clientId wait queue. else flushClientIdWaitQueue(); } dispatchEvent(MessageAckEvent.createEvent(ackMsg, msg)); monitorRpcMessage(ackMsg,msg); } /** * Disconnects the MessageAgent's network connection. * This method does not wait for outstanding network operations to complete. * * @langversion 3.0 * @playerversion Flash 9 * @playerversion AIR 1.1 * @productversion BlazeDS 4 * @productversion LCDS 3 */ public function disconnect():void { if (!_disconnectBarrier) { // Ensure wait queue for client id value is destroyed. _clientIdWaitQueue = null; // Only set the barrier used to discard post-disconnect results/faults // if the agent is currently connected (otherwise, if this is invoked before // connecting and the client fails to connect to the server, no faults will be // dispatched). if (connected) _disconnectBarrier = true; if (_channelSetMode == AUTO_CONFIGURED_CHANNELSET) internalSetChannelSet(null); else if (_channelSet != null) _channelSet.disconnect(this); } } /** * Invoked by a MessageResponder upon receiving a fault for a sent message. * Subclasses may override this method if they need to perform custom fault * processing, but must invoke super.fault() as well. This * method dispatchs a MessageFaultEvent. * * @param errMsg The ErrorMessage. * * @param msg The original sent message that caused this fault. * * @langversion 3.0 * @playerversion Flash 9 * @playerversion AIR 1.1 * @productversion BlazeDS 4 * @productversion LCDS 3 */ public function fault(errMsg:ErrorMessage, msg:IMessage):void { if (Log.isError()) _log.error("'{0}' {2} fault for '{1}'.", id, msg.messageId, _agentType); _ignoreFault = false; configRequested = false; // Remove retryable hint. if (errMsg.headers[ErrorMessage.RETRYABLE_HINT_HEADER]) delete errMsg.headers[ErrorMessage.RETRYABLE_HINT_HEADER]; if (clientId == null) { if (errMsg.clientId != null) setClientId(errMsg.clientId); // Triggers a call to flush the clientId wait queue. else flushClientIdWaitQueue(); } dispatchEvent(MessageFaultEvent.createEvent(errMsg)); monitorRpcMessage(errMsg,msg); handleAuthenticationFault(errMsg, msg); } /** * Handles a CONNECT ChannelEvent. Subclasses that need to perform custom * processing should override this method, and invoke * super.channelConnectHandler(). * * @param event The ChannelEvent. * * @langversion 3.0 * @playerversion Flash 9 * @playerversion AIR 1.1 * @productversion BlazeDS 4 * @productversion LCDS 3 */ public function channelConnectHandler(event:ChannelEvent):void { _disconnectBarrier = false; // If we are waiting on config to come in we can't be connected until // we get it. See acknowledge(). if (needsConfig) { if (Log.isInfo()) _log.info("'{0}' {1} waiting for configuration information.", id, _agentType); _pendingConnectEvent = event; } else { if (Log.isInfo()) _log.info("'{0}' {1} connected.", id, _agentType); setConnected(true); dispatchEvent(event); } } /** * Handles a DISCONNECT ChannelEvent. Subclasses that need to perform * custom processing should override this method, and invoke * super.channelDisconnectHandler(). * * @param event The ChannelEvent. * * @langversion 3.0 * @playerversion Flash 9 * @playerversion AIR 1.1 * @productversion BlazeDS 4 * @productversion LCDS 3 */ public function channelDisconnectHandler(event:ChannelEvent):void { if (Log.isWarn()) _log.warn("'{0}' {1} channel disconnected.", id, _agentType); setConnected(false); // If we have remoteCredentials we need to send them on reconnect. if (_remoteCredentials != null) { _sendRemoteCredentials = true; } dispatchEvent(event); } /** * Handles a ChannelFaultEvent. Subclasses that need to perform custom * processing should override this method, and invoke * super.channelFaultHandler(). * * @param The ChannelFaultEvent * * @langversion 3.0 * @playerversion Flash 9 * @playerversion AIR 1.1 * @productversion BlazeDS 4 * @productversion LCDS 3 */ public function channelFaultHandler(event:ChannelFaultEvent):void { if (Log.isWarn()) _log.warn("'{0}' {1} channel faulted with {2} {3}", id, _agentType, event.faultCode, event.faultDetail); if (!event.channel.connected) { setConnected(false); // If we have remoteCredentials we need to send them on reconnect. if (_remoteCredentials != null) { _sendRemoteCredentials = true; } } dispatchEvent(event); } /** * Called after the implementing object has been created * and all properties specified on the tag have been assigned. * * @param document MXML document that created this object. * * @param id id used by the document to refer to this object. * If the object is a deep property on the document, id is null. * * @langversion 3.0 * @playerversion Flash 9 * @playerversion AIR 1.1 * @productversion BlazeDS 4 * @productversion LCDS 3 */ public function initialized(document:Object, id:String):void { this.id = id; } /** * Logs the MessageAgent out from its remote destination. * Logging out of a destination applies to everything connected using the same ChannelSet * as specified in the server configuration. For example, if several DataService components * are connected over an RTMP channel and logout() is invoked on one of them, * all other client components that are connected using the same ChannelSet are also logged out. * *

Note: Adobe recommends that you use the mx.messaging.ChannelSet.logout() method * rather than this method.

* * @see mx.messaging.ChannelSet#logout() * * @langversion 3.0 * @playerversion Flash 9 * @playerversion AIR 1.1 * @productversion BlazeDS 4 * @productversion LCDS 3 */ public function logout():void { _credentials = null; if (channelSet) channelSet.logout(this); } /** * Sets the credentials that the MessageAgent uses to authenticate to * destinations. * The credentials are applied to all services connected over the same ChannelSet. * * @param username The username. * @param password The password. * @param charset The character set encoding to use while encoding the * credentials. The default is null, which implies the legacy charset of * ISO-Latin-1. The only other supported charset is "UTF-8". * * @throws flash.errors.IllegalOperationError in two situations; if credentials * have already been set and an authentication is in progress with the remote * detination, or if authenticated and the credentials specified don't match * the currently authenticated credentials. * * @langversion 3.0 * @playerversion Flash 9 * @playerversion AIR 1.1 * @productversion BlazeDS 4 * @productversion LCDS 3 */ public function setCredentials(username:String, password:String, charset:String=null):void { if (username == null && password == null) { _credentials = null; _credentialsCharset = null; } else { var cred:String = username + ":" + password; var encoder:Base64Encoder = new Base64Encoder(); if (charset == Base64Encoder.CHARSET_UTF_8) encoder.encodeUTFBytes(cred); else encoder.encode(cred); _credentials = encoder.drain(); _credentialsCharset = charset; } if (channelSet != null) channelSet.setCredentials(_credentials, this, _credentialsCharset); } /** * Sets the remote credentials that will be passed through to the remote destination * for authenticating to secondary systems. * * @param username The username. * @param password The password. * @param charset The character set encoding to use while encoding the * remote credentials. The default is null, which implies the legacy * charset of ISO-Latin-1. The only other currently supported option is * "UTF-8". * * @langversion 3.0 * @playerversion Flash 9 * @playerversion AIR 1.1 * @productversion BlazeDS 4 * @productversion LCDS 3 */ public function setRemoteCredentials(username:String, password:String, charset:String=null):void { if (username == null && password == null) { _remoteCredentials = ""; _remoteCredentialsCharset = null; } else { var cred:String = username + ":" + password; var encoder:Base64Encoder = new Base64Encoder(); if (charset == Base64Encoder.CHARSET_UTF_8) encoder.encodeUTFBytes(cred); else encoder.encode(cred); _remoteCredentials = encoder.drain(); _remoteCredentialsCharset = charset; } _sendRemoteCredentials = true; } /** * Returns true if there are any pending requests for the passed in message. * This method should be overriden by subclasses * * @param msg The message for which the existence of pending requests is checked. * * @return Returns true if there are any pending requests for the * passed in message. * * @langversion 3.0 * @playerversion Flash 9 * @playerversion AIR 1.1 * @productversion BlazeDS 4 * @productversion LCDS 3 */ public function hasPendingRequestForMessage(msg:IMessage):Boolean { return false; } //-------------------------------------------------------------------------- // // Internal Methods // //-------------------------------------------------------------------------- /** * @private * Internal hook for ChannelSet to assign credentials when it has authenticated * successfully via a direct login(...) call to the server or logged * out directly. */ mx_internal function internalSetCredentials(credentials:String):void { _credentials = credentials; } //-------------------------------------------------------------------------- // // Protected Methods // //-------------------------------------------------------------------------- /** * @private */ final protected function assertCredentials(value:String):void { if (_credentials != null && (_credentials != value)) { var errMsg:ErrorMessage = new ErrorMessage(); errMsg.faultCode = "Client.Authentication.Error"; errMsg.faultString = "Credentials specified do not match those used on underlying connection."; errMsg.faultDetail = "Channel was authenticated with a different set of credentials than those used for this agent."; dispatchEvent(MessageFaultEvent.createEvent(errMsg)); } } /** * @private * Utility method to flush any pending queued messages to send once we have * received a clientId from the remote destination. */ final protected function flushClientIdWaitQueue():void { if (_clientIdWaitQueue != null) { // If we have a valid clientId, flush all pending messages. if (clientId != null) { while (_clientIdWaitQueue.length > 0) { internalSend(_clientIdWaitQueue.shift() as IMessage); } } if (clientId == null) { // If we still don't have a clientId, remove the first queued message and send it. // Leave the queue intact to buffer subsequent sends until we get a response/fault // back for this one. if (_clientIdWaitQueue.length > 0) { var saveQueue:Array = _clientIdWaitQueue; // Make sure we don't just put it back into the queue - we let the first // one through if this is null. _clientIdWaitQueue = null; internalSend(saveQueue.shift() as IMessage); _clientIdWaitQueue = saveQueue; } else { // Regardless of whether the clientId is defined or not, if the wait queue // is empty set it to null to allow the next message to be processed by the // send code path rather than being routed to the queue. _clientIdWaitQueue = null; } } } } /** * Handles the authentication fault on the server. If the authenticated flag is true, * the authentication fault must have been caused by a session expiration on the server. * Set the authenticated state to false and if loginAfterDisconnect flag is enabled, * resend credentials to the server by disconnecting and resending the message again. * * @param errMsg The Error Message. * @param msg The message that caused the fault and should be resent once we have * disconnected/connected causing re-authentication. * * @langversion 3.0 * @playerversion Flash 9 * @playerversion AIR 1.1 * @productversion BlazeDS 4 * @productversion LCDS 3 */ protected function handleAuthenticationFault(errMsg:ErrorMessage, msg:IMessage):void { if (errMsg.faultCode == "Client.Authentication" && authenticated && isCurrentChannelNotNull()) { var currentChannel:Channel = getCurrentChannel(); currentChannel.setAuthenticated(false); if (currentChannel is PollingChannel && (currentChannel as PollingChannel).loginAfterDisconnect) { reAuthorize(msg); _ignoreFault = true; } } } /** * Used to automatically initialize the channelSet property for the * MessageAgent before it connects for the first time. * Subtypes may override to perform custom initialization. * * @param message The message that needs to be sent. * * @langversion 3.0 * @playerversion Flash 9 * @playerversion AIR 1.1 * @productversion BlazeDS 4 * @productversion LCDS 3 */ protected function initChannelSet(message:IMessage):void { if (_channelSet == null) { _channelSetMode = AUTO_CONFIGURED_CHANNELSET; internalSetChannelSet(ServerConfig.getChannelSet(destination)); } if (_channelSet.connected && needsConfig && !configRequested) { message.headers[CommandMessage.NEEDS_CONFIG_HEADER] = true; configRequested = true; } _channelSet.connect(this); if (_credentials != null) channelSet.setCredentials(_credentials, this, _credentialsCharset); } /** * Sends a Message from the MessageAgent to its destination using the * agent's ChannelSet. MessageAgent subclasses must use this method to * send their messages. * * @param message The message to send. * * @param waitForClientId If true the message may be queued until a clientId has been * assigned to the agent. In general this is the desired behavior. * For special behavior (automatic reconnect and resubscribe) the * agent may pass false to override the default queuing behavior. * * @throws mx.messaging.errors.InvalidDestinationError If no destination is set. * * @langversion 3.0 * @playerversion Flash 9 * @playerversion AIR 1.1 * @productversion BlazeDS 4 * @productversion LCDS 3 */ protected function internalSend(message:IMessage, waitForClientId:Boolean = true):void { // If we don't have a client or server assigned clientId, we // need to send a single message and then store any subsequent messages // in a buffer to be sent once we've gotten back a server-generated // clientId. Otherwise, N outbound messages sent before receiving an ack for // the first will result in the generation of N different clientIds in the // response/fault messages from the server. if ((message.clientId == null) && waitForClientId && (clientId == null)) { if (_clientIdWaitQueue == null) { _clientIdWaitQueue = []; // Current message will be sent but subsequent messages sent before // its ack/fault will be queued. } else { _clientIdWaitQueue.push(message); return; // We've queued the message and will send it once we get a clientId or the outstanding send fails. } } if (message.clientId == null) message.clientId = clientId; if (requestTimeout > 0) message.headers[AbstractMessage.REQUEST_TIMEOUT_HEADER] = requestTimeout; if (_sendRemoteCredentials) { if (! ((message is CommandMessage) && (CommandMessage(message).operation == CommandMessage.TRIGGER_CONNECT_OPERATION))) { message.headers[AbstractMessage.REMOTE_CREDENTIALS_HEADER] = _remoteCredentials; message.headers[AbstractMessage.REMOTE_CREDENTIALS_CHARSET_HEADER] = _remoteCredentialsCharset; _sendRemoteCredentials = false; } } if (channelSet != null) { if (!connected && (_channelSetMode == MANUALLY_ASSIGNED_CHANNELSET)) _channelSet.connect(this); if (channelSet.connected && needsConfig && !configRequested) { message.headers[CommandMessage.NEEDS_CONFIG_HEADER] = true; configRequested = true; } channelSet.send(this, message); monitorRpcMessage(message,message); } else if (destination != null && destination.length > 0) { initChannelSet(message); if (channelSet != null) { channelSet.send(this, message); monitorRpcMessage(message,message); } } else { var msg:String = resourceManager.getString( "messaging", "destinationNotSet"); throw new InvalidDestinationError(msg); } } /** * This function should be overriden by sublasses to implement reauthentication due to * server session time-out behavior specific to them. In general, it should follow disconnect, * connect, resend message pattern. * * @param msg The message that caused the fault and should be resent once we have * disconnected/connected causing reauthentication. * * @langversion 3.0 * @playerversion Flash 9 * @playerversion AIR 1.1 * @productversion BlazeDS 4 * @productversion LCDS 3 */ protected function reAuthorize(msg:IMessage):void { // Disconnect all message agents from the Channel to make sure the Channel // is fully disconnected and Channel#internalConnect gets called which // sends the login command to reauthenticate the Channel. if (channelSet != null) channelSet.disconnectAll(); internalSend(msg); } /** * @private */ private function getCurrentChannel():Channel { return channelSet != null? channelSet.currentChannel : null; } /** * @private */ private function isCurrentChannelNotNull():Boolean { return getCurrentChannel() != null; } /** * Monitor a rpc message that is being send */ private function monitorRpcMessage(message:IMessage,actualMessage:IMessage):void { if (NetworkMonitor.isMonitoring()) { if (message is ErrorMessage) { NetworkMonitor.monitorFault(actualMessage, MessageFaultEvent.createEvent(ErrorMessage(message))); } else if (message is AcknowledgeMessage) { NetworkMonitor.monitorResult(message, MessageEvent.createEvent(MessageEvent.RESULT, actualMessage)); } else { NetworkMonitor.monitorInvocation(getNetmonId(), message, this); } } } /** * Return the id for the NetworkMonitor. * @private */ mx_internal function getNetmonId():String { return null; } } }