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