joynr.dispatching.RequestReplyManager.js Maven / Gradle / Ivy
/*jslint es5: true */
/*
* #%L
* %%
* Copyright (C) 2011 - 2016 BMW Car IT GmbH
* %%
* Licensed 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.
* #L%
*/
define(
"joynr/dispatching/RequestReplyManager",
[
"global/Promise",
"joynr/dispatching/types/Reply",
"joynr/messaging/MessagingQos",
"joynr/messaging/inprocess/InProcessAddress",
"joynr/util/Typing",
"joynr/util/UtilInternal",
"joynr/util/JSONSerializer",
"joynr/util/LongTimer",
"joynr/exceptions/MethodInvocationException",
"joynr/exceptions/ProviderRuntimeException",
"joynr/types/Version",
"joynr/system/LoggerFactory"
],
function(
Promise,
Reply,
MessagingQos,
InProcessAddress,
Typing,
Util,
JSONSerializer,
LongTimer,
MethodInvocationException,
ProviderRuntimeException,
Version,
LoggerFactory) {
/**
* The RequestReplyManager is responsible maintaining a list of providers that wish to
* receive incoming requests, and also a list of requestReplyIds which is used to match
* an incoming message with an expected reply.
*
* @name RequestReplyManager
* @constructor
*
* @param {Dispatcher}
* dispatcher
* @param {TypeRegistry}
* typeRegistry - the global type registry that records the type names
* together with their constructor.
*/
function RequestReplyManager(dispatcher, typeRegistry) {
var log = LoggerFactory.getLogger("joynr.dispatching.RequestReplyManager");
var providers = {};
var replyCallers = {};
//var deletedReplyCallers = {};
/**
* @name RequestReplyManager#sendRequest
* @function
*
* @param {Object}
* settings
* @param {String}
* settings.from participantId of the sender
* @param {String}
* settings.to participantId of the receiver
* @param {MessagingQos}
* settings.messagingQos quality-of-service parameters such as time-to-live
* @param {Request}
* settings.request the Request to send
* @returns {Promise} the Promise for the Request
*/
this.sendRequest = function sendRequest(settings) {
var addReplyCaller = this.addReplyCaller;
return new Promise(function(resolve, reject) {
addReplyCaller(settings.request.requestReplyId, {
resolve : resolve,
reject : reject
}, settings.messagingQos.ttl);
// resolve will be called upon successful response
dispatcher.sendRequest(settings).catch(function(error) {
delete replyCallers[settings.request.requestReplyId];
reject(error);
});
});
};
/**
* @name RequestReplyManager#sendOneWayRequest
* @function
*
* @param {Object}
* settings
* @param {String}
* settings.from participantId of the sender
* @param {String}
* settings.to participantId of the receiver
* @param {MessagingQos}
* settings.messagingQos quality-of-service parameters such as time-to-live
* @param {OneWayRequest}
* settings.request the Request to send
* @returns {Promise} the Promise for the Request
*/
this.sendOneWayRequest = function sendOneWayRequest(settings) {
return new Promise(function(resolve, reject) {
dispatcher.sendOneWayRequest(settings).then(resolve).catch(reject);
});
};
/**
* The function addRequestCaller is called when a provider wishes to receive
* incoming requests for the given participantId
*
* @name RequestReplyManager#addRequestCaller
* @function
*
* @param {String}
* participantId of the provider receiving the incoming requests
* @param {Provider}
* provider
*/
this.addRequestCaller = function addRequestCaller(participantId, provider) {
providers[participantId] = provider;
};
/**
* The function addReplyCaller is called when a proxy get/set or rpc is called and
* is waiting for a reply The reply caller is automatically
* removed when the ttl expires.
*
* @name RequestReplyManager#addReplyCaller
* @function
*
* @param {String}
* requestReplyId
* @param {ReplyCaller}
* replyCaller
* @param {Number}
* ttl_ms relative number of milliseconds to wait for the reply.
* The replycaller will be removed in ttl_ms and and Error will be passed
* to the replyCaller
*/
this.addReplyCaller = function addReplyCaller(requestReplyId, replyCaller, ttl_ms) {
replyCallers[requestReplyId] = replyCaller;
LongTimer.setTimeout(function replyCallMissed() {
var replyCaller = replyCallers[requestReplyId];
if (replyCaller === undefined) {
return;
}
replyCaller.reject(new Error("Request with id \"" + requestReplyId + "\" failed: ttl expired"));
// remove the replyCaller from replyCallers in
// ttl_ms
// deletedReplyCallers[requestReplyId] = replyCaller;
delete replyCallers[requestReplyId];
}, ttl_ms);
};
/**
* The function removeRequestCaller is called when a provider no longer wishes to
* receive incoming requests
*
* @name RequestReplyManager#removeRequestCaller
* @function
*
* @param {String}
* participantId
*/
this.removeRequestCaller =
function removeRequestCaller(participantId) {
try {
delete providers[participantId];
} catch (error) {
log.error("error removing provider with participantId: "
+ participantId
+ " error: "
+ error);
}
};
/**
* @name RequestReplyManager#handleRequest
* @function
*
* @param {Request}
* request
*/
this.handleRequest =
function handleRequest(providerParticipantId, request, callbackDispatcher) {
var provider = providers[providerParticipantId];
var exception;
if (!provider) {
// TODO error handling request
// TODO what if no provider is found in the mean time?
// Do we need to add a task to handleRequest later?
exception = new MethodInvocationException({
detailMessage: "error handling request: "
+ JSONSerializer.stringify(request)
+ " for providerParticipantId "
+ providerParticipantId
});
callbackDispatcher(new Reply({
error : exception,
requestReplyId : request.requestReplyId
}));
return;
}
// augment the type information
//var typedArgs = Typing.augmentTypes(request.params, typeRegistry);
// if there's an operation available to call
var result;
if (provider[request.methodName]
&& provider[request.methodName].callOperation) {
// may throw an immediate exception when callOperation checks the
// arguments, in this case exception must be caught.
// If final customer provided method implementation gets called,
// that one may return either promise (preferred) or direct result
// and may possibly also throw exception in the latter case.
try {
result =
provider[request.methodName].callOperation(
request.params,
request.paramDatatypes);
} catch(internalException) {
exception = internalException;
}
// otherwise, check whether request is an attribute get, set or an operation
} else {
var match = request.methodName.match(/([gs]et)?(\w+)/);
var getSet = match[1];
if (getSet) {
var attributeName = match[2];
var attributeObject = provider[attributeName] || provider[Util.firstLower(attributeName)];
// if the attribute exists in the provider
if (attributeObject && !attributeObject.callOperation) {
try {
if (getSet === "get") {
result = attributeObject.get();
} else if (getSet === "set") {
result = attributeObject.set(request.params[0]);
}
} catch(internalGetterSetterException) {
if (internalGetterSetterException instanceof ProviderRuntimeException) {
exception = internalGetterSetterException;
} else {
exception = new ProviderRuntimeException({
detailMessage: "getter/setter method of attribute " +
attributeName + " reported an error"
});
}
}
}
// if neither an operation nor an attribute exists in the
// provider => deliver MethodInvocationException
else {
exception = new MethodInvocationException({
detailMessage: "Could not find an operation \""
+ request.methodName
+ "\" or an attribute \""
+ attributeName
+ "\" in the provider",
providerVersion: new Version({
majorVersion: provider.constructor.MAJOR_VERSION,
minorVersion: provider.constructor.MINOR_VERSION
})
});
}
}
// if no operation was found and methodName didn't start with "get"
// or "set" => deliver MethodInvocationException
else {
exception = new MethodInvocationException({
detailMessage: "Could not find an operation \""
+ request.methodName + "\" in the provider",
providerVersion: new Version({
majorVersion: provider.constructor.MAJOR_VERSION,
minorVersion: provider.constructor.MINOR_VERSION
})
});
}
}
/* Asynchronously pass the result back to the dispatcher
*
* Call operations can be a sync or async method. In the sync case,
* the return value of the call operation
* is simply the result of the call. In the async case, the provider has
* the possibility to return a promise
* object. In this case, we wait until the promise object is resolved
* and call then the callbackDispatcher
*/
if (!exception && Util.isPromise(result)) {
result.then(function(response) {
callbackDispatcher(new Reply({
response : response,
requestReplyId : request.requestReplyId
}));
return response;
}).catch(function(internalException) {
callbackDispatcher(new Reply({
error : internalException,
requestReplyId : request.requestReplyId
}));
return internalException;
});
} else {
if (exception) {
// return stored exception
LongTimer.setTimeout(function asyncCallbackDispatcher() {
callbackDispatcher(new Reply({
error : exception,
requestReplyId : request.requestReplyId
}));
}, 0);
} else {
// return result of call
LongTimer.setTimeout(function asyncCallbackDispatcher() {
callbackDispatcher(new Reply({
response : result,
requestReplyId : request.requestReplyId
}));
}, 0);
}
}
};
/**
* @name RequestReplyManager#handleOneWayRequest
* @function
*
* @param {String}
* providerParticipantId
* @param {OneWayRequest}
* request
*/
this.handleOneWayRequest =
function handleOneWayRequest(providerParticipantId, request) {
var provider = providers[providerParticipantId];
if (!provider) {
throw new MethodInvocationException({
detailMessage: "error handling one-way request: "
+ JSONSerializer.stringify(request)
+ " for providerParticipantId "
+ providerParticipantId
});
}
// if there's an operation available to call
if (provider[request.methodName]
&& provider[request.methodName].callOperation) {
// If final customer provided method implementation gets called,
// that one may return either promise (preferred) or direct result
// and may possibly also throw exception in the latter case.
provider[request.methodName].callOperation(
request.params,
request.paramDatatypes);
} else {
throw new MethodInvocationException({
detailMessage: "Could not find an operation \""
+ request.methodName + "\" in the provider",
providerVersion: new Version({
majorVersion: provider.constructor.MAJOR_VERSION,
minorVersion: provider.constructor.MINOR_VERSION
})
});
}
};
/**
* @name RequestReplyManager#handleReply
* @function
*
* @param {Reply}
* reply
*/
this.handleReply =
function handleReply(reply) {
var replyCaller = replyCallers[reply.requestReplyId];
if (replyCaller === undefined) {
log
.error("error handling reply resolve, because replyCaller could not be found: "
+ JSONSerializer.stringify(reply, undefined, 4));
return;
}
try {
if (reply.error) {
if (reply.error instanceof Error) {
replyCaller.reject(reply.error);
} else {
replyCaller.reject(Typing.augmentTypes(
reply.error,
typeRegistry));
}
} else {
replyCaller.resolve(reply.response);
}
// deletedReplyCallers[reply.requestReplyId] = replyCallers[reply.requestReplyId];
delete replyCallers[reply.requestReplyId];
} catch (e) {
log.error("exception thrown during handling reply "
+ JSONSerializer.stringify(reply, undefined, 4)
+ ":\n"
+ e.stack);
}
};
}
return RequestReplyManager;
});
© 2015 - 2025 Weber Informatics LLC | Privacy Policy