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