blob: aeda46ab8bca88dcb814d69b53a71e8d7dcbda95 [file] [log] [blame]
////////////////////////////////////////////////////////////////////////////////
//
// 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.rpc
{
import mx.core.mx_internal;
import mx.logging.ILogger;
import mx.logging.Log;
import mx.messaging.errors.MessagingError;
import mx.messaging.events.MessageEvent;
import mx.messaging.events.MessageFaultEvent;
import mx.messaging.messages.AsyncMessage;
import mx.messaging.messages.IMessage;
import mx.netmon.NetworkMonitor;
import mx.resources.IResourceManager;
import mx.resources.ResourceManager;
import mx.rpc.events.AbstractEvent;
import mx.rpc.events.FaultEvent;
import mx.rpc.events.InvokeEvent;
import mx.rpc.events.ResultEvent;
import mx.utils.ObjectProxy;
import mx.utils.StringUtil;
import org.apache.royale.events.Event;
import org.apache.royale.events.EventDispatcher;
import org.apache.royale.reflection.getQualifiedClassName;
use namespace mx_internal;
//[ResourceBundle("rpc")]
/**
* An invoker is an object that actually executes a remote procedure call (RPC).
* For example, RemoteObject, HTTPService, and WebService objects are invokers.
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
public class AbstractInvoker extends EventDispatcher
{
//--------------------------------------------------------------------------
//
// Constructor
//
//--------------------------------------------------------------------------
/**
* @private
*/
public function AbstractInvoker()
{
super();
_log = Log.getLogger("mx.rpc.AbstractInvoker");
activeCalls = new ActiveCalls();
}
//-------------------------------------------------------------------------
//
// Variables
//
//-------------------------------------------------------------------------
/**
* @private
*/
private var resourceManager:IResourceManager =
ResourceManager.getInstance();
//-------------------------------------------------------------------------
//
// Properties
//
//-------------------------------------------------------------------------
private var _keepLastResult:Boolean = true;
private var _keepLastResultSet: Boolean = false;
/** Flag indicating whether the operation should keep its last call result for later access.
* <p> If set to true, the last call result will be accessible through <code>lastResult</code> bindable property. </p>
* <p> If set to false, the last call result will be cleared after the call,
* and must be processed in the operation's result handler.
* This will allow the result object to be garbage collected,
* which is especially useful if the operation is only called a few times and returns a large result. </p>
* <p>If not set, will use the <code>keepLastResult</code> value of its owning Service, if any, or the default value.</p>
* @see #lastResult
* @see mx.rpc.AbstractService#keepLastResult
* @default true
*
* @playerversion Flash 10
* @playerversion AIR 3
* @productversion Flex 4.11
*/
public function get keepLastResult():Boolean
{
return _keepLastResult;
}
public function set keepLastResult(value:Boolean):void
{
_keepLastResult = value;
_keepLastResultSet = true;
}
/** @private
* sets keepLastResult if not set locally, typically by container Service or RemoteObject
* @param value
*/
mx_internal function setKeepLastResultIfNotSet( value: Boolean):void
{
if (!_keepLastResultSet)
_keepLastResult = value;
}
[Bindable("resultForBinding")]
/**
* The result of the last invocation.
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
public function get lastResult():Object
{
return _result;
}
[Inspectable(defaultValue="true", category="General")]
/**
* When this value is true, anonymous objects returned are forced to bindable objects.
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
public function get makeObjectsBindable():Boolean
{
return _makeObjectsBindable;
}
public function set makeObjectsBindable(b:Boolean):void
{
_makeObjectsBindable = b;
}
/**
* This property is set usually by framework code which wants to modify the
* behavior of a service invocation without modifying the way in which the
* service is called externally. This allows you to add a "filter" step on
* the method call to ensure for example that you do not return duplicate
* instances for the same id or to insert parameters for performing on-demand
* paging.
*
* When this is set to a non-null value on the send call, the operationManager function
* is called instead. It returns the token that the caller uses to be notified
* of the result. Typically the called function will at some point clear this
* property temporarily, then invoke the operation again actually sending it to
* the server this time.
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*
* @royalesuppresspublicvarwarning
*/
public var operationManager:Function;
/**
* Specifies an optional return type for the operation. Used in situations where
* you want to coerce the over-the-wire information into a specific ActionScript class
* or to provide metadata for other services as to the return type of this operation.
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*
* @royalesuppresspublicvarwarning
*/
public var resultType:Class;
/**
* Like resultType, used to define the ActionScript class used by a given operation though
* this property only applies to operations which return a multi-valued result (e.g. an Array
* or ArrayCollection (IList)). This property specifies an ActionScript class for the members of the
* array or array collection. When you set resultElementType, you do not have to set
* resultType. In that case, the operation returns an Array if makeObjectsbindable is
* false and an ArrayCollection otherwise.
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*
* @royalesuppresspublicvarwarning
*/
public var resultElementType:Class;
/**
* Event dispatched for binding when the <code>result</code> property
* changes.
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
mx_internal static const BINDING_RESULT:String = "resultForBinding";
//-------------------------------------------------------------------------
//
// Public Methods
//
//-------------------------------------------------------------------------
/**
* Cancels the last service invocation or an invokation with the specified ID.
* Even though the network operation may still continue, no result or fault event
* is dispatched.
*
* @param id The messageId of the invocation to cancel. Optional. If omitted, the
* last service invocation is canceled.
*
* @return The AsyncToken associated with the call that is cancelled or null if no call was cancelled.
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
public function cancel(id:String = null):AsyncToken
{
if (id != null)
return activeCalls.removeCall(id);
else
return activeCalls.cancelLast();
}
/**
* Sets the <code>result</code> property of the invoker to <code>null</code>.
* This is useful when the result is a large object that is no longer being
* used.
*
* @param fireBindingEvent Set to <code>true</code> if you want anything
* bound to the result to update. Otherwise, set to
* <code>false</code>.
* The default value is <code>true</code>
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
public function clearResult(fireBindingEvent:Boolean = true):void
{
if (fireBindingEvent)
setResult(null);
else
_result = null;
}
/**
* This hook is exposed to update the lastResult property. Since lastResult
* is ordinarily updated automatically by the service, you do not typically
* call this. It is used by managed services that want to ensure lastResult
* always points to "the" managed instance for a given identity even if the
* the service returns a new copy of the same object.
*
* @param result The new value for the lastResult property.
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
public function setResult(result:Object):void
{
_result = result;
dispatchEvent(new Event(BINDING_RESULT));
}
//-------------------------------------------------------------------------
//
// Internal Methods
//
//-------------------------------------------------------------------------
/**
* This method is overridden in subclasses to redirect the event to another
* class.
*
* @private
*/
mx_internal function dispatchRpcEvent(event:AbstractEvent):void
{
event.callTokenResponders();
if (!event.isDefaultPrevented())
{
dispatchEvent(event);
}
}
/**
* Monitor an rpc event that is being dispatched
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
mx_internal function monitorRpcEvent(event:AbstractEvent):void
{
if (NetworkMonitor.isMonitoring())
{
if (event is mx.rpc.events.ResultEvent)
{
NetworkMonitor.monitorResult(event.message, mx.rpc.events.ResultEvent(event).result);
}
else if (event is mx.rpc.events.FaultEvent)
{
//trace(" AbstractInvoker: MonitorFault - message:" + event.message);
NetworkMonitor.monitorFault(event.message, mx.rpc.events.FaultEvent(event).fault);
}
}
}
/**
* Take the MessageAckEvent and take the result, store it, and broadcast out
* appropriately.
*
* @private
*/
protected function resultHandler(event:MessageEvent):void
{
var token:AsyncToken = preHandle(event);
//if the handler didn't give us something just bail
if (token == null)
return;
if (processResult(event.message, token))
{
dispatchEvent(new Event(BINDING_RESULT));
var resultEvent:ResultEvent = ResultEvent.createEvent(_result, token, event.message);
resultEvent.headers = _responseHeaders;
dispatchRpcEvent(resultEvent);
// we are done with the result, clear if not kept, for GC
if (!_keepLastResult){
_result = null;
}
}
//no else, we assume process would have dispatched the faults if necessary
}
/**
* Take the fault and convert it into a rpc.events.FaultEvent.
*
* @private
*/
mx_internal function faultHandler(event:MessageFaultEvent):void
{
var msgEvent:MessageEvent = MessageEvent.createEvent(MessageEvent.MESSAGE, event.message);
var token:AsyncToken = preHandle(msgEvent);
// continue only on a matching or empty correlationId
// empty correlationIds could be the result of de/serialization errors
if ((token == null) &&
(AsyncMessage(event.message).correlationId != null) &&
(AsyncMessage(event.message).correlationId != "") &&
(event.faultCode != "Client.Authentication"))
{
return;
}
if (processFault(event.message, token))
{
var fault:Fault = new Fault(event.faultCode, event.faultString, event.faultDetail);
fault.content = event.message.body;
fault.rootCause = event.rootCause;
var faultEvent:FaultEvent = FaultEvent.createEvent(fault, token, event.message);
faultEvent.headers = _responseHeaders;
dispatchRpcEvent(faultEvent);
}
}
/**
* Return the id for the NetworkMonitor.
* @private
*/
mx_internal function getNetmonId():String
{
return null;
}
/**
* @private
*/
mx_internal function invoke(message:IMessage, token:AsyncToken = null) : AsyncToken
{
if (token == null)
token = new AsyncToken(message);
else
token.setMessage(message);
activeCalls.addCall(message.messageId, token);
var fault:Fault;
try
{
//asyncRequest.invoke(message, new AsyncResponder(resultHandler, faultHandler, token));
asyncRequest.invoke(message, new Responder(resultHandler, faultHandler));
dispatchRpcEvent(InvokeEvent.createEvent(token, message));
}
catch(e:Error)
{
if (e is MessagingError)
{
_log.warn(e.toString());
var errorText:String = resourceManager.getString(
"rpc", "cannotConnectToDestination",
[ asyncRequest.destination ]);
fault = new Fault("InvokeFailed", e.toString(), errorText);
new AsyncDispatcher(dispatchRpcEvent, [FaultEvent.createEvent(fault, token, message)], 10);
}
else
{
_log.warn(e.toString());
fault = new Fault("InvokeFailed", e.message);
new AsyncDispatcher(dispatchRpcEvent, [FaultEvent.createEvent(fault, token, message)], 10);
}
}
return token;
}
/**
* Find the matching call object and pass it back.
*
* @private
*/
mx_internal function preHandle(event:MessageEvent):AsyncToken
{
return activeCalls.removeCall(AsyncMessage(event.message).correlationId);
}
/**
* @private
*/
mx_internal function processFault(message:IMessage, token:AsyncToken):Boolean
{
return true;
}
/**
* @private
*/
mx_internal function processResult(message:IMessage, token:AsyncToken):Boolean
{
var body:Object = message.body;
if (makeObjectsBindable && (body != null) && (getQualifiedClassName(body) == "Object"))
{
_result = new ObjectProxy(body);
}
else
{
_result = body;
}
return true;
}
/**
* @private
*/
mx_internal function get asyncRequest():AsyncRequest
{
if (_asyncRequest == null)
{
_asyncRequest = new AsyncRequest();
}
return _asyncRequest;
}
/**
* @private
*/
mx_internal function set asyncRequest(req:AsyncRequest):void
{
_asyncRequest = req;
}
/**
* @private
*/
mx_internal var activeCalls:ActiveCalls;
/**
* @private
*/
mx_internal var _responseHeaders:Array;
/**
* @private
*/
mx_internal var _result:Object;
/**
* @private
*/
mx_internal var _makeObjectsBindable:Boolean;
/**
* @private
*/
private var _asyncRequest:AsyncRequest;
/**
* @private
*/
private var _log:ILogger;
}
}