1 // ==========================================================================
  2 // Project:   The M-Project - Mobile HTML5 Application Framework
  3 // Copyright: (c) 2010 M-Way Solutions GmbH. All rights reserved.
  4 //            (c) 2011 panacoda GmbH. All rights reserved.
  5 // Creator:   Sebastian
  6 // Date:      25.02.2010
  7 // License:   Dual licensed under the MIT or GPL Version 2 licenses.
  8 //            http://github.com/mwaylabs/The-M-Project/blob/master/MIT-LICENSE
  9 //            http://github.com/mwaylabs/The-M-Project/blob/master/GPL-LICENSE
 10 // ==========================================================================
 11 
 12 M.TARGET_REMOTE = 'remote';
 13 M.TARGET_LOCAL = 'local';
 14 M.TARGET_BOTH = 'both';
 15 
 16 M.PRIO_REMOTE = 'prio_remote';
 17 M.PRIO_LOCAL = 'prio_local';
 18 M.PRIO_BOTH = 'prio_both';
 19 
 20 
 21 m_require('core/utility/logger.js');
 22 
 23 /**
 24  * @class
 25  *
 26  * 
 27  *
 28  * @extends M.Object
 29  */
 30 M.DataProviderHybrid = M.Object.extend(
 31 /** @scope M.DataProviderHybrid.prototype */ {
 32 
 33     /**
 34      * The type of this object.
 35      *
 36      * @type String
 37      */
 38     type: 'M.DataProviderHybrid',
 39 
 40     /**
 41      * Indicates whether data provider operates asynchronously or not.
 42      *
 43      * @type Boolean
 44      */
 45     isAsync: YES,
 46 
 47     /**
 48      *
 49      * @type Object
 50      */
 51     localProvider: null,
 52 
 53     /**
 54      *
 55      * @type Object
 56      */
 57     remoteProvider: null,
 58 
 59     /**
 60      * Defines the operation type: 1 for 'remote' or 'local', 2 for 'both'
 61      * @type Number
 62      */
 63     usedProviders: 0,
 64 
 65     callbackCounter: 0,
 66 
 67     onSuccess: null,
 68 
 69     onError: null,
 70 
 71     callbacks: {
 72         success: {
 73             local: null,
 74             remote: null
 75         },
 76         error: {
 77             local: null,
 78             remote: null
 79         }
 80     },
 81 
 82     /**
 83      * 
 84      * @type Object
 85      */
 86     config: null,
 87 
 88     configure: function(obj) {
 89         var dp = this.extend({
 90             config:obj
 91         });
 92         if(!dp.config.local) {
 93             throw M.Error.extend({
 94                 code: M.ERR_MODEL_PROVIDER_NOT_SET,
 95                 msg: 'No local data provider passed'
 96             });
 97         }
 98         if(!dp.config.remote) {
 99             throw M.Error.extend({
100                 code: M.ERR_MODEL_PROVIDER_NOT_SET,
101                 msg: 'No remote data provider passed'
102             });
103         }
104         dp.localProvider = dp.config.local;
105         dp.remoteProvider = dp.config.remote;
106 
107         // maybe some value checking before
108         return dp;
109     },
110 
111     /**
112      *
113      */
114     find: function(obj) {
115         this.crud(obj, 'find');
116     },
117 
118     /**
119      * 
120      */
121     save: function(obj) {
122         this.crud(obj, 'save');
123     },
124 
125     /**
126      *
127      */
128     del: function(obj) {
129         this.crud(obj, 'del');
130     },
131 
132 
133     /**
134      *
135      * @param {Obj} obj The param obj
136      * @param {String} op The operation to be performed on the actual data provider
137      */
138     crud: function(obj, op) {
139 
140         obj.target = obj.target || M.TARGET_BOTH;
141 
142         if(!obj.prio) {
143             if(obj.target === M.TARGET_BOTH) {
144                 obj.prio = M.PRIO_BOTH;
145             } else {
146                 if(obj.target === M.TARGET_LOCAL) {
147                     obj.prio = M.PRIO_LOCAL;
148                 } else if(obj.target === M.PRIO_REMOTE) {
149                     obj.prio = M.PRIO_REMOTE;
150                 }
151             }
152         }
153 
154         this.callbackCounter = 0;
155         this.setOriginCallbacks(obj);
156         /* set intermediate callbacks for data provider call */
157         this.setIntermediateCallbacks(obj);
158 
159         switch(obj.target) {
160 
161             case M.TARGET_LOCAL:
162                 this.usedProviders = 1;
163                 this.localProvider[op](obj);
164                 break;
165 
166             case M.TARGET_REMOTE:
167                 this.usedProviders = 1;
168                 this.remoteProvider[op](obj);
169                 break;
170 
171             case M.TARGET_BOTH:
172                 this.usedProviders = 2;
173                 this.localProvider[op](obj);
174                 this.remoteProvider[op](obj);
175                 break;
176         }
177     },
178     
179     setOriginCallbacks: function(obj) {
180         if (obj.onSuccess && obj.onSuccess.target && obj.onSuccess.action) {
181             obj.onSuccess = this.bindToCaller(obj.onSuccess.target, obj.onSuccess.target[obj.onSuccess.action]);
182             this.onSuccess = obj.onSuccess;
183         } else if(obj.onSuccess === 'function') {
184             this.onSuccess = obj.onSuccess;
185         }
186 
187         if (obj.onError && obj.onError.target && obj.onError.action) {
188             obj.onError = this.bindToCaller(obj.onError.target, obj.onError.target[obj.onSuccess.action]);
189             this.onError = obj.onError;
190         } else if(obj.onError === 'function') {
191             this.onError = obj.onError;
192         }
193     },
194 
195     setIntermediateCallbacks: function(obj) {
196         obj.onSuccess = {
197             target: this,
198             action: 'handleSuccessCallback'
199         };
200         obj.onError = {
201             target: this,
202             action: 'handleErrorCallback'
203         };
204     },
205 
206     handleSuccessCallback: function(res, obj, dp) {
207 
208         if(dp.type === this.localProvider.type) {
209             this.callbacks.success.local = {result: res, param: obj, dataProvider:dp};
210         } else if(dp.type === this.remoteProvider.type) {
211             this.callbacks.success.remote = {result: res, param: obj, dataProvider:dp};
212         }
213 
214         this.callbackCounter = this.callbackCounter + 1;
215 
216         if(this.callbackCounter === this.usedProviders) {
217             this.calculateOperationState();
218         }
219     },
220 
221     handleErrorCallback: function(err, obj, dp) {
222  
223         if(dp.type === this.localProvider.type) {
224             this.callbacks.error.local = {err: err, param: obj, dataProvider:dp};
225         } else if(dp.type === this.remoteProvider.type) {
226             this.callbacks.error.remote = {err: err, param: obj, dataProvider:dp};
227 
228             // TODO: put into remote data providers
229             obj.model.state_remote = M.STATE_FAIL;
230         }
231 
232         this.callbackCounter = this.callbackCounter + 1;
233 
234         /* if this is the last callback */
235         if(this.callbackCounter === this.usedProviders) {
236             this.calculateOperationState();
237         }
238     },
239     
240     calculateOperationState: function(obj) {
241         switch(obj.prio) {
242             case M.PRIO_LOCAL:
243                 if(!this.callbacks.success.local) {
244                     this.onError(this.error.local.err, obj);
245                 } else {
246                     this.onSuccess(this.success.local.result, obj);
247                 }
248             case M.PRIO_REMOTE:
249                 if(!this.callbacks.error.local) {
250                     this.onError(this.error.remote.err, obj);
251                 } else {
252                     this.onSuccess(this.success.remote.result, obj);
253                 }
254             case M.PRIO_BOTH:
255                 /* if one of the callback failed */
256                 if(!this.callbacks.success.local || !this.callbacks.success.remote) {
257                     /* return remote error */
258                     this.onError(this.error.remote.err, obj);
259                 } else {  /* if both callbacks have been success callbacks */
260                     this.onSuccess(this.success.remote.result, obj);
261                 }
262             break;
263         }
264     }
265 });