UNPKG

service-model

Version:

An object oriented web service framework inspired by Windows Communication Foundation.

236 lines (235 loc) 10.1 kB
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;