////////////////////////////////////////////////////////////////////////////////
//
// 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.config
{
import flash.utils.getDefinitionByName;
import flash.utils.getQualifiedClassName;
import mx.collections.ArrayCollection;
import mx.core.mx_internal;
import mx.messaging.Channel;
import mx.messaging.ChannelSet;
import mx.messaging.MessageAgent;
import mx.messaging.errors.InvalidChannelError;
import mx.messaging.errors.InvalidDestinationError;
import mx.resources.IResourceManager;
import mx.resources.ResourceManager;
import mx.utils.ObjectUtil;
use namespace mx_internal;
[ResourceBundle("messaging")]
/**
* This class provides access to the server messaging configuration information.
* This class encapsulates information from the services-config.xml file on the client
* and is used by the messaging system to provide configured ChannelSets and Channels
* to the messaging framework.
*
*
The XML source is provided during the compilation process.
* However, there is currently no internal restriction preventing the
* acquisition of this XML data by other means, such as network, local file
* system, or shared object at runtime.
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion BlazeDS 4
* @productversion LCDS 3
*/
public class ServerConfig
{
//--------------------------------------------------------------------------
//
// Static Constants
//
//--------------------------------------------------------------------------
/**
* @private
* Channel config parsing constant.
*/
public static const CLASS_ATTR:String = "type";
/**
* @private
* Channel config parsing constant.
*/
public static const URI_ATTR:String = "uri";
//--------------------------------------------------------------------------
//
// 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;
}
//--------------------------------------------------------------------------
//
// Static Variables
//
//--------------------------------------------------------------------------
/**
* @private
* The server configuration data.
*/
public static var serverConfigData:XML;
/**
* @private
* Caches shared ChannelSets, keyed by strings having the format:
* :[true|false] - where the final
* flag indicates whether the ChannelSet should be used for clustered
* destinations or not.
*/
private static var _channelSets:Object = {};
/**
* @private
* Caches shared clustered Channel instances keyed by Channel id.
*/
private static var _clusteredChannels:Object = {};
/**
* @private
* Caches shared unclustered Channel instances keyed by Channel id.
*/
private static var _unclusteredChannels:Object = {};
/**
* @private
* Keeps track of Channel endpoint uris whose configuration has been fetched
* from the server.
*/
private static var _configFetchedChannels:Object;
//--------------------------------------------------------------------------
//
// Static Properties
//
//--------------------------------------------------------------------------
//----------------------------------
// xml
//----------------------------------
/**
* The XML configuration; this value must contain the relevant portions of
* the <services> tag from the services-config.xml file.
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion BlazeDS 4
* @productversion LCDS 3
*/
public static function get xml():XML
{
if (serverConfigData == null)
serverConfigData = ;
return serverConfigData;
}
/**
* @private
*/
public static function set xml(value:XML):void
{
serverConfigData = value;
// Reset cached Channels and ChannelSets.
_channelSets = {};
_clusteredChannels = {};
_unclusteredChannels = {};
}
//----------------------------------
// channelSetFactory
//----------------------------------
/**
* @private
* A Class factory to use to generate auto-instantiated ChannelSet instances
* as is done in getChannelSet(String).
* Default factory is the base ChannelSet class.
*/
public static var channelSetFactory:Class = ChannelSet;
//--------------------------------------------------------------------------
//
// Static Methods
//
//--------------------------------------------------------------------------
/**
* This method ensures that the destinations specified contain identical
* channel definitions.
* If the channel definitions between the two destinations specified are
* not identical this method will throw an ArgumentError.
*
* @param destinationA:String first destination to compare against
* @param destinationB:String second destination to compare channels with
* @throw ArgumentError if the channel definitions of the specified
* destinations aren't identical.
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion BlazeDS 4
* @productversion LCDS 3
*/
public static function checkChannelConsistency(destinationA:String,
destinationB:String):void
{
var channelIdsA:Array = getChannelIdList(destinationA);
var channelIdsB:Array = getChannelIdList(destinationB);
if (ObjectUtil.compare(channelIdsA, channelIdsB) != 0)
throw new ArgumentError("Specified destinations are not channel consistent");
}
/**
* Returns a shared instance of the configured Channel.
*
* @param id The id of the desired Channel.
*
* @param clustered True if the Channel will be used in a clustered
* fashion; otherwise false.
*
* @return The Channel instance.
*
* @throws mx.messaging.errors.InvalidChannelError If no Channel has the specified id.
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion BlazeDS 4
* @productversion LCDS 3
*/
public static function getChannel(id:String,
clustered:Boolean = false):Channel
{
var channel:Channel;
if (!clustered)
{
if (id in _unclusteredChannels)
{
return _unclusteredChannels[id];
}
else
{
channel = createChannel(id);
_unclusteredChannels[id] = channel;
return channel;
}
}
else
{
if (id in _clusteredChannels)
{
return _clusteredChannels[id];
}
else
{
channel = createChannel(id);
_clusteredChannels[id] = channel;
return channel;
}
}
}
/**
* Returns a shared ChannelSet for use with the specified destination
* belonging to the service that handles the specified message type.
*
* @param destinationId The target destination id.
*
* @return The ChannelSet.
*
* @throws mx.messaging.errors.InvalidDestinationError If the specified destination
* does not have channels and the application
* did not define default channels.
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion BlazeDS 4
* @productversion LCDS 3
*/
public static function getChannelSet(destinationId:String):ChannelSet
{
var destinationConfig:XML = getDestinationConfig(destinationId);
return internalGetChannelSet(destinationConfig, destinationId);
}
/**
* Returns the property information for the specified destination
*
* @param destinationId The id of the desired destination.
*
* @return XMLList containing the <property> tag information.
*
* @throws mx.messaging.errors.InvalidDestinationError If the specified destination is not found.
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion BlazeDS 4
* @productversion LCDS 3
*/
public static function getProperties(destinationId:String):XMLList
{
var destination:XMLList = xml..destination.(@id == destinationId);
if (destination.length() > 0)
{
return destination.properties;
}
else
{
var message:String = resourceManager.getString(
"messaging", "unknownDestination", [ destinationId ]);
throw new InvalidDestinationError(message);
}
return destination;
}
//--------------------------------------------------------------------------
//
// Static Internal Methods
//
//--------------------------------------------------------------------------
/**
* This method returns true iff the channelset specified has channels with
* ids or uris that match those found in the destination specified.
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion BlazeDS 4
* @productversion LCDS 3
*/
mx_internal static function channelSetMatchesDestinationConfig(channelSet:ChannelSet,
destination:String):Boolean
{
if (channelSet != null)
{
if (ObjectUtil.compare(channelSet.channelIds, getChannelIdList(destination)) == 0)
return true;
// if any of the specified channelset channelIds do not match then
// we have to move to comparing the uris, as the ids could be null
// in the specified channelset.
var csUris:Array = [];
var csChannels:Array = channelSet.channels;
for (var i:uint = 0; i 0? endpoint[0].attribute(URI_ATTR).toString() : null;
if (dsUri != null)
dsUris.push(dsUri);
}
return ObjectUtil.compare(csUris, dsUris) == 0;
}
return false;
}
/**
* @private
* returns if the specified endpoint has been fetched already
*/
mx_internal static function fetchedConfig(endpoint:String):Boolean
{
return _configFetchedChannels != null && _configFetchedChannels[endpoint] != null;
}
/**
* @private
* This method returns a list of the channel ids for the given destination
* configuration. If no channels exist for the destination, it returns a
* list of default channel ids for the applcation
*/
mx_internal static function getChannelIdList(destination:String):Array
{
var destinationConfig:XML = getDestinationConfig(destination);
return destinationConfig? getChannelIds(destinationConfig)
: getDefaultChannelIds();
}
/**
* @private
* Used by the Channels to determine whether the Channel should request
* dynamic configuration from the server for its MessageAgents.
*/
mx_internal static function needsConfig(channel:Channel):Boolean
{
// Configuration for the endpoint has not been fetched by some other channel.
if (_configFetchedChannels == null || _configFetchedChannels[channel.endpoint] == null)
{
var channelSets:Array = channel.channelSets;
var m:int = channelSets.length;
for (var i:int = 0; i < m; i++)
{
// If the channel belongs to an advanced ChannelSet, always fetch runtime config.
if (getQualifiedClassName(channelSets[i]).indexOf("Advanced") != -1)
return true;
// Otherwise, only fetch if a connected MessageAgent requires it.
var messageAgents:Array = ChannelSet(channelSets[i]).messageAgents;
var n:int = messageAgents.length;
for (var j:int = 0; j < n; j++)
{
if (MessageAgent(messageAgents[j]).needsConfig)
return true;
}
}
}
return false;
}
/**
* @private
* This method updates the xml with serverConfig object returned from the
* server during initial client connect
*/
mx_internal static function updateServerConfigData(serverConfig:ConfigMap, endpoint:String = null):void
{
if (serverConfig != null)
{
if (endpoint != null)
{
// Add the endpoint uri to the list of uris whose configuration
// has been fetched.
if (_configFetchedChannels == null)
_configFetchedChannels = {};
_configFetchedChannels[endpoint] = true;
}
var newServices:XML = ;
convertToXML(serverConfig, newServices);
// Update default-channels of the application.
xml["default-channels"] = newServices["default-channels"];
// Update the service destinations.
for each (var newService:XML in newServices..service)
{
var oldServices:XMLList = xml.service.(@id == newService.@id);
var oldDestinations:XMLList;
var newDestination:XML;
// The service already exists, update its destinations.
if (oldServices.length() != 0)
{
var oldService:XML = oldServices[0]; // Service ids are unique.
for each (newDestination in newService..destination)
{
oldDestinations = oldService.destination.(@id == newDestination.@id);
if (oldDestinations.length() != 0)
delete oldDestinations[0]; // Destination ids are unique.
oldService.appendChild(newDestination.copy());
}
}
// The service does not exist which means that this is either a new
// service with its destinations, or a proxy service (eg. GatewayService)
// with configuration for existing destinations for other services.
else
{
for each (newDestination in newService..destination)
{
oldDestinations = xml..destination.(@id == newDestination.@id);
if (oldDestinations.length() != 0) // Replace the existing destination.
{
oldDestinations[0] = newDestination[0].copy(); // Destination ids are unique.
delete newService..destination.(@id == newDestination.@id)[0];
}
}
if (newService.children().length() > 0) // Add the new service.
xml.appendChild(newService);
}
}
// Update the channels
var newChannels:XMLList = newServices.channels;
if (newChannels.length() > 0)
{
var oldChannels:XML = xml.channels[0];
if (oldChannels == null || oldChannels.length() == 0)
{
xml.appendChild(newChannels);
}
// Commenting this section out as there is no real use case
// for updating channel definitions.
/*
else
{
for each (var newChannel:XML in newChannels.channel)
{
var oldChannel:XMLList = oldChannels.channel.(@id == newChannel.@id);
if (oldChannel.length() > 0)
{
// Assuming only one channel exists with the same id.
delete oldChannel[0];
}
oldChannels.appendChild(newChannel);
}
}
*/
}
}
}
//--------------------------------------------------------------------------
//
// Static Private Methods
//
//--------------------------------------------------------------------------
/**
* Helper method that builds a new Channel instance based on the
* configuration for the specified id.
*
* @param id The id for the configured Channel to build.
*
* @return The Channel instance.
*
* @throws mx.messaging.errors.InvalidChannelError If no configuration data for the specified
* id exists.
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion BlazeDS 4
* @productversion LCDS 3
*/
private static function createChannel(channelId:String):Channel
{
var message:String;
var channels:XMLList = xml.channels.channel.(@id == channelId);
if (channels.length() == 0)
{
message = resourceManager.getString(
"messaging", "unknownChannelWithId", [ channelId ]);
throw new InvalidChannelError(message);
}
var channelConfig:XML = channels[0];
var className:String = channelConfig.attribute(CLASS_ATTR).toString();
var endpoint:XMLList = channelConfig.endpoint;
/// uri might be undefined when client-load-balancing urls are specified.
var uri:String = endpoint.length() > 0? endpoint[0].attribute(URI_ATTR).toString() : null;
var channel:Channel = null;
try
{
var channelClass:Class = getDefinitionByName(className) as Class;
channel = new channelClass(channelId, uri);
channel.applySettings(channelConfig);
// If we have an WSRP_ENCODED_CHANNEL in FlashVars,
// use that instead of uri configured in the config file
if (LoaderConfig.parameters != null && LoaderConfig.parameters.WSRP_ENCODED_CHANNEL != null)
channel.url = LoaderConfig.parameters.WSRP_ENCODED_CHANNEL;
}
catch(e:ReferenceError)
{
message = resourceManager.getString(
"messaging", "unknownChannelClass", [ className ]);
throw new InvalidChannelError(message);
}
return channel;
}
/**
* Converts the ConfigMap of properties into XML
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion BlazeDS 4
* @productversion LCDS 3
*/
private static function convertToXML(config:ConfigMap, configXML:XML):void
{
for (var propertyKey:Object in config)
{
var propertyValue:Object = config[propertyKey];
if (propertyValue is String)
{
if (propertyKey == "")
{
// Add as a value
configXML.appendChild(propertyValue);
}
else
{
// Add as an attribute
configXML.@[propertyKey] = propertyValue;
}
}
else if (propertyValue is ArrayCollection || propertyValue is Array)
{
var propertyValueList:Array;
if (propertyValue is ArrayCollection)
propertyValueList = ArrayCollection(propertyValue).toArray();
else
propertyValueList = propertyValue as Array;
for (var i:int = 0; i < propertyValueList.length; i++)
{
var propertyXML1:XML = <{propertyKey}>{propertyKey}>
configXML.appendChild(propertyXML1);
convertToXML(propertyValueList[i] as ConfigMap, propertyXML1);
}
}
else // assuming that it is ConfigMap
{
var propertyXML2:XML = <{propertyKey}>{propertyKey}>
configXML.appendChild(propertyXML2);
convertToXML(propertyValue as ConfigMap, propertyXML2);
}
}
}
private static function getChannelIds(destinationConfig:XML):Array
{
var result:Array = [];
var channels:XMLList = destinationConfig.channels.channel;
var n:int = channels.length();
for (var i:int = 0; i < n; i++)
{
result.push(channels[i].@ref.toString());
}
return result;
}
/**
* @private
* This method returns a list of default channel ids for the application
*/
private static function getDefaultChannelIds():Array
{
var result:Array = [];
var channels:XMLList = xml["default-channels"].channel;
var n:int = channels.length();
for (var i:int = 0; i < n; i++)
{
result.push(channels[i].@ref.toString());
}
return result;
}
/**
* Returns the destination XML data specific to the destination and message
* type specified. Returns null if the destination is not found.
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion BlazeDS 4
* @productversion LCDS 3
*/
private static function getDestinationConfig(destinationId:String):XML
{
var destinations:XMLList = xml..destination.(@id == destinationId);
var destinationCount:int = destinations.length();
if (destinationCount == 0)
{
return null;
}
else
{
// Destination ids are unique among services
return destinations[0];
}
}
/**
* Helper method to look up and return a cached ChannelSet (and build and
* cache an instance if necessary).
*
* @param destinationConfig The configuration for the target destination.
* @param destinatonId The id of the target destination.
*
* @return The ChannelSet.
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion BlazeDS 4
* @productversion LCDS 3
*/
private static function internalGetChannelSet(
destinationConfig:XML,
destinationId:String):ChannelSet
{
var channelIds:Array;
var clustered:Boolean;
if (destinationConfig == null)
{
channelIds = getDefaultChannelIds();
if (channelIds.length == 0)
{
var message:String = resourceManager.getString(
"messaging", "noChannelForDestination", [ destinationId ]);
throw new InvalidDestinationError(message);
}
clustered = false;
}
else
{
channelIds = getChannelIds(destinationConfig);
clustered = (destinationConfig.properties.network.cluster.length() > 0) ? true : false;
}
var channelSetId:String = channelIds.join(",") + ":" + clustered;
if (channelSetId in _channelSets)
{
return _channelSets[channelSetId];
}
else
{
var channelSet:ChannelSet = new channelSetFactory(channelIds, clustered);
var heartbeatMillis:int = serverConfigData["flex-client"]["heartbeat-interval-millis"];
if (heartbeatMillis > 0) channelSet.heartbeatInterval = heartbeatMillis;
if (clustered) channelSet.initialDestinationId = destinationId;
_channelSets[channelSetId] = channelSet;
return channelSet;
}
}
}
}