      1 /*
      2  *  Copyright (C) 2008-2009 WaveMaker Software, Inc.
      3  *
      4  *  This file is part of the WaveMaker Client Runtime.
      5  *
      6  *  Licensed under the Apache License, Version 2.0 (the "License");
      7  *  you may not use this file except in compliance with the License.
      8  *  You may obtain a copy of the License at
      9  *
     10  *      http://www.apache.org/licenses/LICENSE-2.0
     11  *
     12  *  Unless required by applicable law or agreed to in writing, software
     13  *  distributed under the License is distributed on an "AS IS" BASIS,
     14  *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     15  *  See the License for the specific language governing permissions and
     16  *  limitations under the License.
     17  */
     18 dojo.provide("wm.base.components.ServiceCall");
     19 dojo.require("wm.base.components.Service");
     20 dojo.require("wm.base.components.ServiceQueue");
     21
     22 //===========================================================================
     23 // Provides basic service calling infrastructure
     24 //===========================================================================
     25 // Note: wm.ServiceCall is not a component. This primarily so that it can be combined
     26 // with components that have other capabilities.
     27 /**
     28 	Infrastructure for encapsulating a {@link wm.Service} configuration with a trigger
     29 	to invoke the configured service.
     30 	@name wm.ServiceCall
     31 	@class
     32 	@noindex
     33 */
     34 dojo.declare("wm.ServiceCall", null, {
     35 	/** @lends wm.ServiceCall.prototype */
     36 	/**
     37 		Set true to automatically <a href="#update">update</a> this service when
     38 		the service configuration or input is modified.
     39 		@type String
     40 	*/
     41 	autoUpdate: false,
     42 	/**
     43 		Set true to automatically <a href="#update">update</a> this service when it's created.
     44 		@type String
     45 	*/
     46 	startUpdate: false,
     47 	/**
     48 		Name of the service called by this object.
     49 		@type String
     50 	*/
     51 	service: "",
     52 	/**
     53 		Name of the operation to invoke on the service.
     54 		@type String
     55 	*/
     56 	operation: "",
     57 	_operationInfo: {},
     58 	destroy: function() {
     59 		this.inherited(arguments);
     60 		wm.fire(this._requester, "cancel");
     61 	},
     62 	postInit: function() {
     63 		this.inherited(arguments);
     64 		this.connectStartUpdate();
     65 		if (!this.$.queue)
     66 			new wm.ServiceQueue({name: "queue", owner: this});
     67 		this.initInput();
     68 		this.setService(this.service);
     69 		this._setOperation(this.operation);
     70 	},
     71 	initInput: function() {
     72 		this.input = this.$.input;
     73 		if (!this.input)
     74 			this.input = this.createInput();
     75 		this.subscribe(this.input.getRuntimeId() + "-changed", this, "inputChanged");
     76 	},
     77 	//=======================================================
     78 	// Service
     79 	//=======================================================
     80 	setService: function(inService) {
     81 		this.service = inService;
     82 		this._service = wm.services.getService(this.service) || new wm.Service({});
     83 	},
     84 	//=======================================================
     85 	// Operation
     86 	//=======================================================
     87 	_setOperation: function(inOperation) {
     88 		this.operation = inOperation;
     89 		this._operationInfo = this.getOperationInfo(this.operation);
     90 		this.operationChanged();
     91 	},
     92 	setOperation: function(inOperation) {
     93 		this._setOperation(inOperation);
     94 		this.doAutoUpdate();
     95 	},
     96 	getOperationInfo: function(inOperation) {
     97 		return (this._service && this._service.getOperation(inOperation)) || {};
     98 	},
     99 	operationChanged: function() {
    100 		this.input.operationChanged(this.operation, this._operationInfo.parameters);
    101 	},
    102 	//=======================================================
    103 	// Input
    104 	//=======================================================
    105 	createInput: function() {
    106 		var i = new wm.ServiceInput({name: "input", owner: this });
    107 		i.operationChanged(this.operation, this._operationInfo.parameters);
    108 		return i;
    109 	},
    110 	inputChanged: function() {
    111 		this.doAutoUpdate();
    112 	},
    113 	//=======================================================
    114 	// Updating
    115 	//=======================================================
    116 	connectStartUpdate: function() {
    117 		if (this.owner && this.owner.start)
    118 			this.connect(this.owner, "start", this, "doStartUpdate");
    119 	},
    120 	setAutoUpdate: function(inAutoUpdate) {
    121 		this.autoUpdate = inAutoUpdate;
    122 		this.doAutoUpdate();
    123 	},
    124 	doStartUpdate: function() {
    125 		if (this.startUpdate && !this._loading)
    126 			this.update();
    127 	},
    128 	doAutoUpdate: function() {
    129 		if (this.autoUpdate && !this._loading)
    130 			this.update();
    131 	},
    132 	/**
    133 		Invoke the service.
    134 		Use the <a href="onResult">onResult</a>,
    135 		<a href="onSuccess">onSuccess</a>,
    136 		and/or <a href="onError">onError</a> events
    137 		to monitor the outcome of the service call.
    138 	*/
    139 	update: function() {
    140 		return this.isDesignLoaded() ? this.doDesigntimeUpdate() : this._update();
    141 	},
    142 	_update: function() {
    143 		if (this.canUpdate()) {
    144 			this.onBeforeUpdate(this.input);
    145 			return this.request();
    146 		}
    147 	},
    148 	/**
    149 		Return a boolean value used to determine if the service can be updated.
    150 		Use the <a href="onCanUpdate">onCanUpdate</a>,
    151 		event to control the output of canUpdate.
    152 	*/
    153 	canUpdate: function() {
    154 		var info = {canUpdate: this._getCanUpdate() };
    155 		this.onCanUpdate(info);
    156 		return info.canUpdate;
    157 	},
    158 	_getCanUpdate: function() {
    159 		return this._service && this.operation && !Boolean(this._requester);
    160 	},
    161 	getArgs: function() {
    162 		return this.input.getArgs();
    163 	},
    164 	request: function(inArgs) {
    165 		inArgs = inArgs || this.getArgs();
    166 		wm.logging && console.debug("request", this.getId(), "operation", this.operation, "args", inArgs);
    167 		var d = this._requester = this._service.invoke(this.operation, inArgs);
    168 		return this.processRequest(d);
    169 	},
    170 	processRequest: function(inDeferred) {
    171 		var d = inDeferred;
    172 		if (d) {
    173 			d.canceller = function(inDeferred) {
    174 				inDeferred.fired = 1;
    175 			}
    176 			d.addBoth(dojo.hitch(this, function(r) {
    177 				this._requester = false;
    178 				return r;
    179 			}));
    180 			d.addCallbacks(dojo.hitch(this, "result"), dojo.hitch(this, "error"));
    181 			return d;
    182 		}
    183 	},
    184 	//=======================================================
    185 	// Result Processing
    186 	//=======================================================
    187 	result: function(inResult) {
    188 		this.processResult(inResult);
    189 		return inResult;
    190 	},
    191 	processResult: function(inResult) {
    192 		this.onResult(inResult);
    193 		this.onSuccess(inResult);
    194 		this.$.queue.update();
    195 	},
    196 	error: function(inError) {
    197 		this.processError(inError);
    198 		return inError;
    199 	},
    200 	processError: function(inError) {
    201 		this.onResult(inError);
    202 		this.onError(inError);
    203 	},
    204 	//=======================================================
    205 	// Events
    206 	//=======================================================
    207 	/**
    208 		onCanUpdate event fires before a service is invoked.
    209 		@param {Any} ioUpdate An object containing a canUpdate flag.
    210 		Setting this flag to false will prevent the service from updating.
    211 		@event
    212 	*/
    213 	onCanUpdate: function(ioUpdate) {
    214 	},
    215 	/**
    216 		onBeforeUpdate event fires before a service is invoked.
    217 		@param {wm.ServiceInput} ioInput The input object used to determine what data
    218 		will be passed to the service.
    219 		@event
    220 	*/
    221 	onBeforeUpdate: function(ioInput) {
    222 	},
    223 	/**
    224 		onResult event fires whenever a service returns, whether the
    225 		service returned successfully or reported an error.
    226 		@param {Any} inData Result data. The format of this data on the service.
    227 		@event
    228 	*/
    229 	// fires on success or error
    230 	onResult: function(inData) {
    231 	},
    232 	/**
    233 		onSuccess event fires whenever a service returns successfully.
    234 		@param {Any} inData Result data. The format of this data on the service.
    235 		@event
    236 	*/
    237 	// fires only on success
    238 	onSuccess: function(inData) {
    239 	},
    240 	/**
    241 		onError event fires whenever a service reports an error.
    242 		@param {Any} inData Result data. The format of this data on the service.
    243 		@event
    244 	*/
    245 	// fires only on error
    246 	onError: function(inError) {
    247 	}
    248 });
    249
    250 /**#@+ @design */
    251 wm.ServiceCall.extend({
    252 	clearInput: "(clear input)",
    253 	updateNow: "(update now)",
    254 	queue: "(serviceCalls)",
    255 	/** @lends wm.ServiceCall.prototype */
    256 	doDesigntimeUpdate: function() {
    257 		return studio.makeLiveDataCall(dojo.hitch(this, "_update"));
    258 	},
    259 	doClearInput: function() {
    260 		this.input.destroy();
    261 		this.input = this.createInput();
    262 	},
    263 	set_operation: function(inOperation) {
    264 		this.setOperation(inOperation);
    265 		if (this.isDesignLoaded() && studio.selected == this)
    266 			studio.inspector.inspect(studio.selected);
    267 	},
    268 	getServicesList: function() {
    269 		return [""].concat(wm.services.getNamesList()||[]);
    270 	},
    271 	showQueueDialog: function() {
    272 		var d = wm.ServiceQueue.dialog, q = this.$.queue;
    273 		if (d) {
    274 			d.page.binding = q;
    275 			d.page.update();
    276 		}else{
    277 			wm.ServiceQueue.dialog = d = new wm.PageDialog({
    278 				name: "queueDialog",
    279 				owner: studio,
    280 				contentWidth: 600,
    281 				contentHeight: 400,
    282 				hideControls: true,
    283 				pageName: "QueueDialog",
    284 				pageProperties: {binding: q}
    285 			});
    286 		}
    287 		d.show();
    288 	},
    289 	makePropEdit: function(inName, inValue, inDefault) {
    290 		switch (inName) {
    291 			case "service":
    292 				return makeSelectPropEdit(inName, inValue, this.getServicesList(), inDefault);
    293 			case "operation":
    294 				var
    295 					s = this._service,
    296 					valueOk = s && s.getOperation(inValue),
    297 					methods = s && s.getOperationsList();
    298 				if (!valueOk){
    299 					inValue = methods ? methods[0] : "";
    300 					if (inValue)
    301 						this.set_operation(inValue);
    302 				}
    303 				if (methods)
    304 					return makeSelectPropEdit(inName, inValue, methods, inDefault);
    305 				break;
    306 			case "queue":
    307 			case "updateNow":
    308 			case "clearInput":
    309 				return makeReadonlyButtonEdit(inName, inValue, inDefault);
    310 		}
    311 		return this.inherited(arguments);
    312 	},
    313 	editProp: function(inName, inValue, inInspector) {
    314 		switch (inName) {
    315 			case "updateNow":
    316 				return this.update();
    317 			case "queue":
    318 				this.showQueueDialog();
    319 				return;
    320 			case "clearInput":
    321 				return this.doClearInput();
    322 		}
    323 		return this.inherited(arguments);
    324 	}
    325 });
    326
    327 //===========================================================================
    328 // Variable used as a service input
    329 //===========================================================================
    330 /**
    331 	Variable used as a service input
    332 	@name wm.ServiceInput
    333 	@class
    334 	@noindex
    335 	@extends wm.Variable
    336 */
    337 dojo.declare("wm.ServiceInput", wm.Variable, {
    338 	/** @lends wm.ServiceInput.prototype */
    339 	_allowLazyLoad: false,
    340 	isDataProp: function(inProp) {
    341 		// Note: it's important we assume all properties are data properties unless _dataSchema is set
    342 		// Since the dataSchema is set externally,
    343 		// bindings may set data properties before data schema is set, creating errors.
    344 		return (inProp in this._dataSchema) || wm.isEmpty(this._dataSchema);
    345 	},
    346 	operationChanged: function(inType, inSchema) {
    347 		this.setType(inType + "Inputs");
    348 		this.setDataSchema(inSchema);
    349 		// input bindings may need to reinitialize after gleaning
    350 		// operation type information (in light of constants)
    351 		this.$.binding.refresh();
    352
    353 	},
    354 	getArgs: function() {
    355 		var data= this.getData(), args=[], d;
    356 		// convert to array
    357 		for (var p in this._dataSchema) {
    358 			if (data !== undefined)
    359 				d = data[p];
    360 			args.push(d !== undefined ? d : null);
    361 		}
    362 		return args;
    363 	}
    364 });
    365
    366 wm.Object.extendSchema(wm.ServiceInput, {
    367 	dataSet: { ignore: 1, defaultBindTarget: false, isObject: true, type: "any"}
    368 });
    369
    370 wm.ServiceInputVariable = wm.ServiceInput;
    371