Source: service.js

const BaseEntity = require('./baseentity');
const Logs = require('./logs');

/**
 * A Secondary Web Service.
 * 
 * @augments BaseEntity
 */
class Service extends BaseEntity {

	/**
	 * Creates an object representing a secondary web service stored at the back-end.
	 * 
	 * @param {Connection} connection - A Connection object representing an established connection to an openEO back-end.
	 * @param {string} serviceId - The service ID.
	 */
	constructor(connection, serviceId) {
		super(connection, ["id", "title", "description", "process", "url", "type", "enabled", "configuration", "attributes", "created", "plan", "costs", "budget"]);
		/**
		 * The identifier of the service.
		 * @public
		 * @readonly
		 * @type {string}
		 */
		this.id = serviceId;
		/**
		 * @public
		 * @readonly
		 * @type {?string}
		 */
		this.title = undefined;
		/**
		 * @public
		 * @readonly
		 * @type {?string}
		 */
		this.description = undefined;
		/**
		 * The process chain to be executed.
		 * @public
		 * @readonly
		 * @type {?Process}
		 */
		this.process = undefined;
		/**
		 * URL at which the secondary web service is accessible
		 * @public
		 * @readonly
		 * @type {?string}
		 */
		this.url = undefined;
		/**
		 * Web service type (protocol / standard) that is exposed.
		 * @public
		 * @readonly
		 * @type {?string}
		 */
		this.type = undefined;
		/**
		 * @public
		 * @readonly
		 * @type {?boolean}
		 */
		this.enabled = undefined;
		/**
		 * Map of configuration settings, i.e. the setting names supported by the secondary web service combined with actual values. 
		 * @public
		 * @readonly
		 * @type {?object.<string, *>}
		 */
		this.configuration = undefined;
		/**
		 * Additional attributes of the secondary web service, e.g. available layers for a WMS based on the bands in the underlying GeoTiff.
		 * @public
		 * @readonly
		 * @type {?object.<string, *>}
		 */
		this.attributes = undefined;
		/**
		 * Date and time of creation, formatted as a RFC 3339 date-time.
		 * @public
		 * @readonly
		 * @type {?string}
		 */
		this.created = undefined;
		/**
		 * The billing plan to process and charge the service with.
		 * @public
		 * @readonly
		 * @type {?string}
		 */
		this.plan = undefined;
		/**
		 * An amount of money or credits in the currency specified by the back-end.
		 * @public
		 * @readonly
		 * @type {?number}
		 */
		this.costs = undefined;
		/**
		 * Maximum amount of costs the request is allowed to produce in the currency specified by the back-end.
		 * @public
		 * @readonly
		 * @type {?number}
		 */
		this.budget = undefined;
	}

	/**
	 * Updates the data stored in this object by requesting the secondary web service metadata from the back-end.
	 * 
	 * @async
	 * @returns {Promise<Service>} The updates service object (this).
	 * @throws {Error}
	 */
	async describeService() {
		let response = await this.connection._get('/services/' + this.id);
		return this.setAll(response.data);
	}

	/**
	 * Modifies the secondary web service at the back-end and afterwards updates this object, too.
	 * 
	 * @async
	 * @param {object} parameters - An object with properties to update, each of them is optional, but at least one of them must be specified. Additional properties can be set if the server supports them.
	 * @param {Process} parameters.process - A new process.
	 * @param {string} parameters.title - A new title.
	 * @param {string} parameters.description - A new description.
	 * @param {boolean} parameters.enabled - Enables (`true`) or disables (`false`) the service.
	 * @param {object.<string, *>} parameters.configuration - A new set of configuration parameters to set for the service.
	 * @param {string} parameters.plan - A new plan.
	 * @param {number} parameters.budget - A new budget.
	 * @returns {Promise<Service>} The updated service object (this).
	 * @throws {Error}
	 */
	async updateService(parameters) {
		await this.connection._patch('/services/' + this.id, this._convertToRequest(parameters));
		if (this._supports('describeService')) {
			return await this.describeService();
		}
		else {
			return this.setAll(parameters);
		}
	}

	/**
	 * Deletes the secondary web service from the back-end.
	 * 
	 * @async
	 * @throws {Error}
	 */
	async deleteService() {
		await this.connection._delete('/services/' + this.id);
	}

	/**
	 * Get logs for the secondary web service from the back-end.
	 * 
	 * @returns {Logs}
	 */
	debugService() {
		return new Logs(this.connection, '/services/' + this.id + '/logs');
	}

	/**
	 * Checks for new log entries every x seconds.
	 * 
	 * On every status change (enabled/disabled) observed or on new log entries
	 * (if supported by the back-end and not disabled via `requestLogs`), the
	 * callback is executed. It may also be executed once at the beginning.
	 * The callback receives the updated service (this object) and the logs (array) passed.
	 * 
	 * Returns a function that can be called to stop monitoring the service manually.
	 * The monitoring must be stopped manually, otherwise it runs forever.
	 * 
	 * This is only supported if describeService is supported by the back-end.
	 * 
	 * @param {Function} callback 
	 * @param {number} [interval=60] - Interval between update requests, in seconds as integer.
	 * @param {boolean} [requestLogs=true] - Enables/Disables requesting logs
	 * @returns {Function}
	 * @throws {Error}
	 */
	monitorService(callback, interval = 60, requestLogs = true) {
		if (typeof callback !== 'function' || interval < 1) {
			return;
		}
		let capabilities = this.connection.capabilities();
		if (!capabilities.hasFeature('describeService')) {
			throw new Error('Monitoring Services not supported by the back-end.');
		}

		let wasEnabled = this.enabled;
		let intervalId = null;
		let logIterator = null;
		if (capabilities.hasFeature('debugService') && requestLogs) {
			logIterator = this.debugService();
		}
		let monitorFn = async () => {
			if (this.getDataAge() > 1) {
				await this.describeService();
			}
			let logs = logIterator ? await logIterator.nextLogs() : [];
			if (wasEnabled !== this.enabled || logs.length > 0) {
				callback(this, logs);
			}
			wasEnabled = this.enabled;
		};
		setTimeout(monitorFn, 0);
		intervalId = setInterval(monitorFn, interval * 1000);
		let stopFn = () => {
			if (intervalId) {
				clearInterval(intervalId);
				intervalId = null;
			}
		};
		return stopFn;
	}
}

module.exports = Service;