service-model
Version:
An object oriented web service framework inspired by Windows Communication Foundation.
236 lines (235 loc) • 10.1 kB
JavaScript
var endpointDescription_1 = require("./endpointDescription");
var contractDescription_1 = require("./contractDescription");
var operationDescription_1 = require("./operationDescription");
var annotations_1 = require("../annotations");
/**
* A description of a service.
*
* <uml>
* hide members
* hide circle
* DispatcherFactory *-- ServiceDescription : services
* ServiceDescription *-- EndpointDescription : endpoints
* Type -* ServiceDescription : serviceType
* ServiceDescription *- ServiceBehavior : behaviors
* </uml>
*/
var ServiceDescription = (function () {
/**
* Constructs a [[ServiceDescription]].
* @param serviceType Metadata information for the concrete type that implements the service.
* @param name The name of the service. If not specified, defaults to the name of the service constructor.
*/
function ServiceDescription(serviceType, name) {
var _this = this;
/**
* A list of behaviors that can extend the service.
*/
this.behaviors = [];
/**
* A list of endpoints for the service.
*/
this.endpoints = [];
if (!serviceType) {
throw new Error("Missing required argument 'serviceType'.");
}
this.serviceType = serviceType;
this.name = name || serviceType.name;
// Now go through list of all type attributes looking for any service behaviors
this.serviceType.getAnnotations(true).forEach(function (annotation) {
if (_this._isServiceBehavior(annotation)) {
_this.behaviors.push(annotation);
}
});
}
/**
* Adds an endpoint to the service.
* @param contractName The name of the service contract handled by the endpoint.
* @param address The base address of the endpoint.
* @param behaviors A list of behaviors to add to the endpoint.
*/
ServiceDescription.prototype.addEndpoint = function (contractName, address, behaviors) {
if (!contractName) {
throw new Error("Missing required argument 'contractName'.");
}
if (!address) {
throw new Error("Missing required argument 'address'.");
}
var contract = this._ensureContract(contractName);
if (!contract) {
throw new Error("Contract '" + contractName + "' not found on service '" + this.name + "'.");
}
var endpoint = new endpointDescription_1.EndpointDescription(contract, address);
this.endpoints.push(endpoint);
if (Array.isArray(behaviors)) {
endpoint.behaviors = endpoint.behaviors.concat(behaviors);
}
else {
endpoint.behaviors.push(behaviors);
}
return endpoint;
};
/**
* Gets a contract implemented on the service, creating it if it has not yet been created.
* @param name The name of the contract.
* @hidden
*/
ServiceDescription.prototype._ensureContract = function (name) {
if (!this._contracts) {
this._buildContracts();
}
return this._getContract(name);
};
/**
* Returns true if the service implements the contract; otherwise, returns false.
* @param name The name of the contract.
* @hidden
*/
ServiceDescription.prototype._hasContract = function (name) {
return !!this._getContract(name);
};
/**
* Gets a contract implemented on the service.
* @param name The name of the contract.
* @hidden
*/
ServiceDescription.prototype._getContract = function (name) {
for (var i = 0; i < this._contracts.length; i++) {
if (this._contracts[i].name == name) {
return this._contracts[i];
}
}
};
/**
* Builds out all contracts implemented on the service.
* @hidden
*/
ServiceDescription.prototype._buildContracts = function () {
var _this = this;
this._contracts = [];
// Go through list of contract attributes stubbing out contracts and looking for duplicates
var contactAttributes = this.serviceType.getAnnotations(annotations_1.ContractAnnotation, true);
for (var i = 0; i < contactAttributes.length; i++) {
var contactAttribute = contactAttributes[i];
if (this._hasContract(contactAttribute.name)) {
throw new Error("Duplicate contract with name '" + contactAttribute.name + "' on service '" + this.serviceType.name + "'.");
}
var contact = new contractDescription_1.ContractDescription(contactAttribute.name);
this._contracts.push(contact);
}
if (this._contracts.length == 0) {
return;
}
// Now go through list of all type attributes looking for any contract behaviors
this.serviceType.getAnnotations(true).forEach(function (annotation) {
if (_this._isContractBehavior(annotation)) {
var targetContract = _this._getTargetContract(annotation);
if (!targetContract) {
throw new Error("Target contract must be specified on contract behavior attribute when service has multiple contracts.");
}
targetContract.behaviors.push(annotation);
}
});
// Go through all operations and add to appropriate contract
for (var i = 0; i < this.serviceType.methods.length; i++) {
var method = this.serviceType.methods[i];
var operationAttribute = method.getAnnotations(annotations_1.OperationAnnotation)[0];
if (operationAttribute) {
var targetContract = this._getTargetContract(operationAttribute);
if (!targetContract) {
throw new Error("Target contract must be specified on operation attribute when service has multiple contracts.");
}
this._createOperation(targetContract, operationAttribute, method);
}
}
};
/**
* Returns true if the object is a [[ContractBehavior]].
* @hidden
*/
ServiceDescription.prototype._isContractBehavior = function (obj) {
return typeof obj["applyContractBehavior"] === "function";
};
/**
* Returns true if the object is an [[OperationBehavior]].
* @hidden
*/
ServiceDescription.prototype._isOperationBehavior = function (obj) {
return typeof obj["applyOperationBehavior"] === "function";
};
/**
* Returns true if the object is a [[ServiceBehavior]].
* @hidden
*/
ServiceDescription.prototype._isServiceBehavior = function (obj) {
return typeof obj["applyServiceBehavior"] === "function";
};
/**
* Gets the target contract for a behavior annotation.
* @hidden
*/
ServiceDescription.prototype._getTargetContract = function (obj) {
// If target contract is specified, look it up.
var contractName = obj["contract"];
if (contractName) {
var contract = this._getContract(contractName);
if (!contract) {
throw new Error("Could not find target contract '" + contractName + "'.");
}
return contract;
}
// If target contract is not specified, return the first contract if one only have one.
if (this._contracts.length === 1) {
return this._contracts[0];
}
return null;
};
/**
* Creates the operation description for a method on the service.
* @hidden
*/
ServiceDescription.prototype._createOperation = function (contact, operationAttribute, method) {
// create the operation contract and add to the service contract
var operationDescription = new operationDescription_1.OperationDescription(contact, method, operationAttribute ? operationAttribute.name : undefined);
if (operationAttribute) {
if (operationAttribute.isOneWay !== undefined) {
operationDescription.isOneWay = operationAttribute.isOneWay;
}
if (operationAttribute.timeout !== undefined) {
operationDescription.timeout = operationAttribute.timeout;
}
}
var finalParameter = method.parameters.length > 0 && method.parameters[method.parameters.length - 1];
if (!finalParameter || (finalParameter.type && !finalParameter.type.isFunction)) {
throw new Error("Invalid operation '" + operationDescription.name + "' on service '" + this.name + "'. Final parameter on operation must be a callback function.");
}
if (!this.disableMissingMetadataError && finalParameter && !finalParameter.type) {
throw new Error("Missing parameter type metadata. Please make sure the --emitDecoratorMetadata option is " +
"enabled on the TypeScript compiler. If using plain JavaScript set disableMissingMetadataError to " +
"true on the ServiceDescription.");
}
this._addOperationBehaviors(operationDescription, method);
contact.operations.push(operationDescription);
};
/**
* Adds any [[OperationBehaviors]] that exist as annotations on the method.
* @hidden
*/
ServiceDescription.prototype._addOperationBehaviors = function (description, method) {
var attributes = method.getAnnotations();
for (var i = 0; i < attributes.length; i++) {
var attribute = attributes[i];
if (this._isOperationBehavior(attribute)) {
var targetContract = this._getTargetContract(attribute);
if (!targetContract) {
throw new Error("Target contract must be specified on operation behavior when service has multiple contracts.");
}
if (targetContract === description.contract) {
description.behaviors.push(attribute);
}
}
}
};
return ServiceDescription;
})();
exports.ServiceDescription = ServiceDescription;