      1 /*
      2  * Copyright (C) 2008 WaveMaker Software, Inc.
      3  *
      4  * This file is part of WaveMaker Studio.
      5  *
      6  * WaveMaker Studio is free software: you can redistribute it and/or modify
      7  * it under the terms of the GNU Affero General Public License as published by
      8  * the Free Software Foundation, version 3 of the License, only.
      9  *
     10  * WaveMaker Studio is distributed in the hope that it will be useful,
     11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
     12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
     13  * GNU Affero General Public License for more details.
     14  *
     15  * You should have received a copy of the GNU Affero General Public License
     16  * along with WaveMaker Studio.  If not, see <http://www.gnu.org/licenses/>.
     17  */
     18 dojo.provide("wm.base.components.ServiceVariableBase");
     19 dojo.require("wm.base.components.Variable");
     20 dojo.require("wm.base.components.Service");
     21 dojo.require("wm.base.components.ServiceQueue");
     22
     23 //===========================================================================
     24 // Provides basic service calling infrastructure
     25 //===========================================================================
     26 /**
     27 	Infrastructure for encapsulating a {@link wm.Service} configuration with a trigger to invoke the configured service.
     28 	@name wm.ServiceCallBaseMixin
     29 	@class
     30 	@noindex
     31 */
     32 dojo.declare("wm.ServiceCallBaseMixin", null, {
     33 	/** @lends wm.ServiceCallBaseMixin.prototype */
     34 	/**
     35 		Set true to automatically <a href="#update">update</a> this service when it's created and
     36 		anytime the service configuration or input is modified.
     37 		@type String
     38 	*/
     39 	autoUpdate: false,
     40 	/**
     41 		Name of the service called by this object.
     42 		@type String
     43 	*/
     44 	service: "",
     45 	/**
     46 		Name of the operation to invoke on the service.
     47 		@type String
     48 	*/
     49 	operation: "",
     50 	configure: "(configure)",
     51 	updateNow: "(update now)",
     52 	queue: "(serviceCalls)",
     53 	loaded: function() {
     54 		this.inherited(arguments);
     55 		this.connectAutoUpdate();
     56 	},
     57 	init: function() {
     58 		this.inherited(arguments);
     59 		new wm.ServiceQueue({name: "queue", owner: this});
     60 	},
     61 	connectAutoUpdate: function() {
     62 		if (this.owner && this.owner.start)
     63 			this.connect(this.owner, "start", this, "doAutoUpdate");
     64 	},
     65 	doAutoUpdate: function() {
     66 		if (this.autoUpdate && !this._loading /*&& !this.isDesignLoaded()*/)
     67 			this.update();
     68 	},
     69 	setAutoUpdate: function(inAutoUpdate) {
     70 		this.autoUpdate = inAutoUpdate;
     71 		this.doAutoUpdate();
     72 	},
     73 	/**
     74 		Invoke the service.
     75 		Use the <a href="onResult">onResult</a>,
     76 		<a href="onSuccess">onSuccess</a>,
     77 		and/or <a href="onError">onError</a> events
     78 		to monitor the outcome of the service call.
     79 	*/
     80 	update: function() {
     81 		if (this.isDesignLoaded())
     82 				return this.doDesigntimeUpdate();
     83 			else
     84 				return this._update();
     85 	},
     86 	_update: function() {
     87 		if (this._serviceUpdating)
     88 			return;
     89 		this._serviceUpdating = true;
     90 		return this.request();
     91 	},
     92 	// stub that would typically call a service and hook up result processing
     93 	request: function() {
     94 	},
     95 	result: function(inResult) {
     96 		this.processResult(inResult);
     97 		return inResult;
     98 	},
     99 	processResult: function(inResult) {
    100 		this.onResult(inResult);
    101 		this.onSuccess(inResult);
    102 		this.components.queue.update();
    103 	},
    104 	error: function(inError) {
    105 		this.processError(inError);
    106 		return inError;
    107 	},
    108 	processError: function(inError) {
    109 		this.onResult(inError);
    110 		this.onError(inError);
    111 	},
    112 	/**
    113 		onResult event fires whenever a service returns, whether the
    114 		service returned successfully or reported an error.
    115 		@param {Any} inData Result data. The format of this data on the service.
    116 		@event
    117 	*/
    118 	// fires on success or error
    119 	onResult: function(inData) {
    120 	},
    121 	/**
    122 		onSuccess event fires whenever a service returns successfully.
    123 		@param {Any} inData Result data. The format of this data on the service.
    124 		@event
    125 	*/
    126 	// fires only on success
    127 	onSuccess: function(inData) {
    128 	},
    129 	/**
    130 		onError event fires whenever a service reports an error.
    131 		@param {Any} inData Result data. The format of this data on the service.
    132 		@event
    133 	*/
    134 	// fires only on error
    135 	onError: function(inError) {
    136 	}
    137 });
    138
    139 /**#@+ @design */
    140 wm.ServiceCallBaseMixin.extend({
    141 	/** @lends wm.ServiceCallBaseMixin.prototype */
    142 	makePropEdit: function(inName, inValue, inDefault) {
    143 		switch (inName) {
    144 			case "queue":
    145 			case "configure":
    146 			case "updateNow":
    147 				return makeReadonlyButtonEdit(inName, inValue, inDefault);
    148 		}
    149 		return this.inherited(arguments);
    150 	},
    151 	editProp: function(inName, inValue, inInspector) {
    152 		switch (inName) {
    153 			case "updateNow":
    154 				return this.update();
    155 			case "queue":
    156 				this.showQueueDialog();
    157 				return;
    158 			case "configure":
    159 				this.showConfigureDialog();
    160 				return;
    161 		}
    162 		return this.inherited(arguments);
    163 	},
    164 	showConfigureDialog: function() {
    165 	},
    166 	showQueueDialog: function() {
    167 		var d = wm.ServiceQueue.dialog, q = this.components.queue;
    168 		if (d) {
    169 			d.page.binding = q;
    170 			d.page.update();
    171 		}else{
    172 			wm.ServiceQueue.dialog = d = new wm.PageDialog({
    173 				name: "queueDialog",
    174 				owner: studio,
    175 				contentWidth: 600,
    176 				contentHeight: 400,
    177 				hideControls: true,
    178 				pageName: "QueueDialog",
    179 				pageProperties: {binding: q}
    180 			});
    181 		}
    182 		d.show();
    183 	},
    184 	doDesigntimeUpdate: function() {
    185 		if (!studio.isLiveLayoutReady()) {
    186 			studio.liveDataClick();
    187 			if (studio._deploying && studio._deployer) {
    188 				var designUpdate = new dojo.Deferred();
    189 				studio._deployer.addCallback(dojo.hitch(this, function(inResult) {
    190 					var d = this.update();
    191 					if (d)
    192 						d.addCallback(function() {
    193 							designUpdate.callback();
    194 						});
    195 					return inResult;
    196 				}));
    197 				return designUpdate;
    198 			}
    199 		} else
    200 			return this._update();
    201 	}
    202 });
    203 /**#@- @design */
    204
    205 //===========================================================================
    206 // Provides input data for service calling
    207 //===========================================================================
    208 /**
    209 	Provides input data for service calling
    210 	@name wm.ServiceCallInputMixin
    211 	@class
    212 	@noindex
    213 */
    214 dojo.declare("wm.ServiceCallInputMixin", null, {
    215 	/** @lends wm.ServiceCallInputMixin.prototype */
    216 	//service: "",
    217 	//operation: "",
    218 	clearInput: "(clear input)",
    219 	init: function() {
    220 		this.inherited(arguments);
    221 		this.input = new wm.ServiceInputVariable({name: "input", data: [], owner: this, type: "any" });
    222 		this.setService(this.service);
    223 		this.setOperation(this.operation);
    224 		this.subscribe(this.input.getRuntimeId() + "-changed", this, "inputChanged");
    225 	},
    226 	loaded: function() {
    227 		this.input = this.components.input;
    228 		// before loading, "input" and "binding" are not initialized
    229 		this.setOperation(this.operation);
    230 		// input bindings may need to reinitialize after gleaning
    231 		// operation type information (in light of constants)
    232 		this.input.components.binding.refresh();
    233 		this.inherited(arguments);
    234 	},
    235 	setService: function(inService) {
    236 		this.service = inService == this._noService ? "" : inService;
    237 		wm.fire(this._service, "destroy");
    238 		this._service = null;
    239 		if (this.service) {
    240 			var s = wm.services.byName[this.service];
    241 			// FIXME: avoid need for service types at runtime via default here
    242 			// remove knowledge of default service type here
    243 			if (!s)
    244 				s = wm.services.add({name: this.service, type: "wm.JsonRpcService"});
    245 			var ctor = s && s.type ? dojo.getObject(s.type) : null;
    246 			if (ctor) {
    247 				// FIXME: we don't want this object streamed, so we don't want it in our list of owned components
    248 				this._service = new ctor(/*{name: this.service, owner: this}*/);
    249 				// FIXME: otoh, without owner, we don't know how to resolve paths at design time
    250 				this._service.owner = this;
    251 				this._service.service = this.service;
    252 				this._service.initService();
    253 				//this._service.setName(this.service);
    254 			}
    255 		}
    256 	},
    257 	setOperation: function(inOperation) {
    258 		this.operation = inOperation;
    259 		var op = this._service && this._service.getOperation(this.operation);
    260 		// input has custom type, schema matches parameter list
    261 		this.input.setType(this.operation + "Inputs");
    262 		this.operationChanged(op);
    263 		this.doAutoUpdate();
    264 	},
    265 	operationChanged: function(inOperationInfo) {
    266 		var op = inOperationInfo;
    267 		if (op)
    268 			this.input.setDataSchema(op.parameters);
    269 	},
    270 	getOperationHint: function() {
    271 		var op = this._service && this._service.getOperation(this.operation);
    272 		return (op && op.hint) || "";
    273 	},
    274 	inputChanged: function() {
    275 		this.doAutoUpdate();
    276 	},
    277 	doClearInput: function() {
    278 		this.input.destroy();
    279 		this.input = new wm.ServiceInputVariable({name: "input", data: [], owner: this });
    280 		// hack to repair the input type
    281 		this.setOperation(this.operation);
    282 	},
    283 	_update: function() {
    284 		if (this._serviceUpdating)
    285 			return;
    286 		this._serviceUpdating = true;
    287 		this.onBeforeUpdate(this.input);
    288 		var
    289 			input = this.input.getData(),
    290 			args = [],
    291 			dataSchema = this.input._dataSchema;
    292 		// convert data to array of service operation arguments
    293 		for (var p in dataSchema) {
    294 			var d = input && input[p];
    295 			args.push(d !== undefined ? d : null);
    296 		}
    297 		wm.logging && console.debug("update", this.getId(), "operation", this.operation, "args", args);
    298 		return this.request(args);
    299 	},
    300 	_defArgs: [ {}, {} ],
    301 	request: function(inArgs) {
    302 		var s = this._service;
    303 		if(!s || !this.operation){return;}
    304 		var d = s.invoke(this.operation, inArgs || this._defArgs);
    305 		if (d) {
    306 			d.addBoth(dojo.hitch(this, function(inResult) {
    307 				this._serviceUpdating = false;
    308 				return inResult;
    309 			}));
    310 			d.addCallbacks(dojo.hitch(this, "result"), dojo.hitch(this, "error"));
    311 			return d;
    312 		}
    313 	},
    314 	onBeforeUpdate: function(ioInputData) {
    315 	}
    316 });
    317
    318 //===========================================================================
    319 // Variable used as a service input
    320 //===========================================================================
    321 /**
    322 	Variable used as a service input
    323 	@name wm.ServiceInputVariable
    324 	@class
    325 	@noindex
    326 	@extends wm.Variable
    327 */
    328 dojo.declare("wm.ServiceInputVariable", wm.Variable, {
    329 	/** @lends wm.ServiceInputVariable.prototype */
    330 	_allowLazyLoad: false,
    331 	isDataProp: function(inProp) {
    332 		// Note: it's important we assume all properties are data properties unless _dataSchema is set
    333 		// Since the dataSchema is set externally,
    334 		// bindings may set data properties before data schema is set, creating errors.
    335 		return (inProp in this._dataSchema) || wm.isEmpty(this._dataSchema);
    336 	}
    337 });
    338
    339 wm.Object.extendSchema(wm.ServiceInputVariable, {
    340 	dataSet: { ignore: 1, defaultBindTarget: false, isObject: true, type: "any"}
    341 });
    342
    343 //===========================================================================
    344 // Basic data class that gets data from a service
    345 //===========================================================================
    346 /**
    347 	Basic data class that gets its data from a service
    348 	@name wm.ServiceVariableBase
    349 	@class
    350 	@noindex
    351 	@extends wm.Variable
    352 	@extends wm.ServiceCallBaseMixin
    353 */
    354 dojo.declare("wm.ServiceVariableBase", [wm.Variable, wm.ServiceCallBaseMixin], {
    355 	/** @lends wm.ServiceVariableBase.prototype */
    356 	processResult: function(inResult) {
    357 		// note: we should no longer need to translate primitive data to objects here
    358 		// as was previously done. Variable will now take care of this.
    359 		this.setData(inResult);
    360 		this.inherited(arguments);
    361 	}
    362 });
    363
    364 wm.Object.extendSchema(wm.ServiceVariableBase, {
    365 	configure: { group: "common", order: 20},
    366 	service: {group: "common", order: 23 },
    367 	autoUpdate: {group: "common", order: 25},
    368 	updateNow: { group: "operation", order: 10},
    369 	queue: { group: "operation", order: 20},
    370 	json: {ignore: 1},
    371 	listType: {ignore: 1},
    372 	isList: {ignore: 1},
    373 	// binding inherited from Variable, keep it and write it but don't show it
    374 	// potentially needed for source bindings.
    375 	binding: {ignore: 1, writeonly: 1},
    376 	type: { ignore: 1 },
    377 	dataSet: { ignore: 1, defaultBindTarget: 1, isObject: true, type: "any"}
    378 });
    379
    380