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