////////////////////////////////////////////////////////////////////////////////
//
// 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* * @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
* 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." * /> *
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 Returnstrue
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;
}
}
}