1 (function (GCN) {
  2 
  3 	'use strict';
  4 
  5 	// One needs to be cautious when using channel-inherited ids to load content
  6 	// objects.  The object data that is loaded varies depending on whether or
  7 	// not you are in a channel, and whether or not a channel local copy of the
  8 	// inherited object exists in a given channel.
  9 	//
 10 	// Consider working in a channel (with a node id 2 for example).  Further
 11 	// consider that this channel inherits a page with the id 123, and that at
 12 	// some point in time a channel-local copy of this page is made with the id
 13 	// 456.  Now consider the following statements:
 14 	//
 15 	// GCN.channel(2);
 16 	// var p1 = GCN.page(123, function () {});
 17 	// var p2 = GCN.page(456, function () {});
 18 	// GCN.channel(false);
 19 	// var p3 = GCN.page(123, function () {});
 20 	// var p4 = GCN.page(456, function () {});
 21 	//
 22 	// The fact that page 456 is a channel-local copy of page 123 in the
 23 	// channel 2, means that p1, and p2 will be two difference objects on
 24 	// the client which reference the exact same object on the backend.
 25 	//
 26 	// If client changes were made to p1 and p2, and both objects are saved(),
 27 	// one set of changes will be overridden, with the last changes to reach
 28 	// the server clobbering those of the first.
 29 	//
 30 	// In this senario out library cache would contain the following entries:
 31 	//
 32 	// "PageAPI:0/123" = @clientobject1 => @serverobject1
 33 	// "PageAPI:0/456" = @clientobject2 => @serverobject2
 34 	// "PageAPI:2/123" = @clientobject3 => @serverobject2
 35 	// "PageAPI:2/456" = @clientobject4 => @serverobject2
 36 	//
 37 	// What we should do in the future is have object p1, and p2 be references
 38 	// to the same instance on the client side.  This would make it symmetrical
 39 	// with the REST-API.  The challenge in the above scenario is that 2
 40 	// requests will be made to the server for 2 different id, and not until
 41 	// the requests complete will be know whether a channel-local copy was
 42 	// returned.
 43 	//
 44 	// What we should do is that in _processResponse() we should
 45 	// check if there exists any object matching any of the values of
 46 	// getCacheKeyVariations(), and if so, we should through away the incoming
 47 	// data and use the data that is previously loaded.  We would need to make
 48 	// sure that _clearCache() does in fact clear references for a given
 49 	// instance.
 50 	//
 51 	// Example:
 52 	// !_processResponse': function (data) {
 53 	//    var keys = GCN.cache.getCacheKeyVariations(this, data[this._type]);
 54 	//    GCN.cache.addCacheReferences(keys, this);
 55 	//    ...
 56 
 57 	// === multichannelling ===================================================
 58 
 59 	/**
 60 	 * Constructs the nodeId query parameter for REST-API calls.
 61 	 *
 62 	 * @param {ContentObjectAPI} obj A content object instance.
 63 	 * @param {string=} delimiter Optional delimiter character.
 64 	 * @return {string} Query parameter string.
 65 	 */
 66 	function getChannelParameter(obj, delimiter) {
 67 		if (false === obj._channel) {
 68 			return '';
 69 		}
 70 		return (delimiter || '?') + 'nodeId=' + obj._channel;
 71 	}
 72 
 73 	/**
 74 	 * Initializes a placeholder content object by giving to it its fetched
 75 	 * data, assigning it a hash, and moving it from the temporary cache into
 76 	 * the main cache.
 77 	 *
 78 	 * @param {Chainback} obj A placeholder content object.
 79 	 * @param {object} data The data of the localized object that was returned
 80 	 *                      by the "localize/" REST-API call.
 81 	 */
 82 	function initialize(obj, data) {
 83 		var objId =  obj.id();
 84 		obj._name = data.name;
 85 		obj._data = data;
 86 		obj._fetched = true;
 87 		obj.multichannelling.derivedFrom._removeFromTempCache(obj);
 88 		obj.multichannelling.derivedFrom.clear();
 89 		obj._setHash(objId);
 90 		obj._addToCache();
 91 	}
 92 
 93 	/**
 94 	 * Fetches the object data with which to populate the given placeholder
 95 	 * content object.
 96 	 *
 97 	 * @param {Chainback} obj A placeholder content object.
 98 	 * @param {function(ContentObjectAPI)} success A callback function to
 99 	 *                                             receive the localized
100 	 *                                             content object when it is
101 	 *                                             successfully initialized.
102 	 * @param {function(GCNError)=} error Optional custom error handler.
103 	 */
104 	function fetch(obj, success, error) {
105 		obj._authAjax({
106 			url: GCN.settings.BACKEND_PATH + '/rest/' + obj._type + '/load/'
107 			     + obj.id() + getChannelParameter(obj),
108 			data: obj.multichannelling.derivedFrom._loadParams(),
109 			error: error,
110 			success: function (response) {
111 				if (GCN.getResponseCode(response) !== 'OK') {
112 					GCN.handleResponseError(response, error);
113 				} else {
114 					success(response);
115 				}
116 			}
117 		});
118 	}
119 
120 	/**
121 	 * Create a localized version of a content object.
122 	 *
123 	 * @param {ContentObjectAPI} obj Content Object
124 	 * @param {function(ContentObjectAPI)} success a callback function when the
125 	 *                                             object is successfully
126 	 *                                             localized.
127 	 * @param {function(gcnerror)=} error optional custom error handler.
128 	 */
129 	function createLocalizedVersion(obj, success, error) {
130 		var derived = obj.multichannelling.derivedFrom;
131 		obj._authAjax({
132 			url: GCN.settings.BACKEND_PATH + '/rest/' + derived._type
133 			     + '/localize/' + derived.id(),
134 			type: 'POST',
135 			json: { channelId: derived._channel },
136 			error: error,
137 			success: function (response) {
138 				if (GCN.getResponseCode(response) !== 'OK') {
139 					GCN.handleResponseError(response, error);
140 				} else {
141 					success();
142 				}
143 			}
144 		});
145 	}
146 
147 	/**
148 	 * Delete the localized version of a content object.
149 	 *
150 	 * @param {ContentObjectAPI} obj Content Object
151 	 * @param {function(ContentObjectAPI)} success a callback function when the
152 	 *                                             object is successfully
153 	 *                                             localized.
154 	 * @param {function(gcnerror)=} error optional custom error handler.
155 	 */
156 	function deleteLocalizedVersion(obj, success, error) {
157 		var derived = obj.multichannelling.derivedFrom;
158 		derived._authAjax({
159 			url: GCN.settings.BACKEND_PATH + '/rest/' + derived._type
160 			     + '/unlocalize/' + derived.id(),
161 			type: 'POST',
162 			json: { channelId: derived._channel },
163 			error: error,
164 			success: function (response) {
165 				if (GCN.getResponseCode(response) !== 'OK') {
166 					GCN.handleResponseError(response, error);
167 				} else {
168 					success();
169 				}
170 			}
171 		});
172 	}
173 
174 	/**
175 	 * Fetches the contents of the object from which our placeholder content
176 	 * object is derived from.
177 	 *
178 	 * @param {ContentObjectAPI} obj A placeholder content object waiting for
179 	 *                               data.
180 	 * @param {function(Chainback)} success A callback function that will
181 	 *                                      receive the content object when it
182 	 *                                      has been successfully read from the
183 	 *                                      backend.
184 	 * @param {function(GCNError)=} error Optional custom error handler.
185 	 */
186 	function fetchDerivedObject(obj, success, error) {
187 		obj.multichannelling.derivedFrom._read(success, error);
188 	}
189 
190 	/**
191 	 * Poplates a placeholder that represents an localized content object with
192 	 * with its data to such that it becomes a fully fetched object.
193 	 *
194 	 * Will first cause the inherited object from which this placeholder is
195 	 * derived to be localized.
196 	 *
197 	 * @param {Chainback} obj A placholder content object what is waiting for
198 	 *                        data.
199 	 * @param {function(Chainback)} success A callback function that will
200 	 *                                      receive the content object when it
201 	 *                                      has been successfully read from the
202 	 *                                      backend.
203 	 * @param {function(GCNError)=} error Optional custom error handler.
204 	 */
205 	function localize(obj, success, error) {
206 		fetchDerivedObject(obj, function (derived) {
207 			if (!derived.prop('inherited')) {
208 				var err = GCN.createError(
209 					'CANNOT_LOCALIZE',
210 					'Cannot localize an object which is not inherited',
211 					obj
212 				);
213 				GCN.handleError(err, error);
214 				return;
215 			}
216 			createLocalizedVersion(obj, function () {
217 				fetch(obj, function (response) {
218 					initialize(obj, response[obj._type]);
219 					success(obj);
220 				}, error);
221 			}, error);
222 		}, error);
223 	}
224 
225 	/**
226 	 * Poplates a placeholder that represents an inherited content object with
227 	 * with its data to such that it becomes a fully fetched object.
228 	 *
229 	 * Will first cause the local object from which this placeholder is derived
230 	 * to be deleted so that the inherited object data is re-exposed.
231 	 *
232 	 * @param {Chainback} obj A placholder content object what is waiting for
233 	 *                        data.
234 	 * @param {function(Chainback)} success A callback function that will
235 	 *                                      receive the content object when it
236 	 *                                      has been successfully read from the
237 	 *                                      backend.
238 	 * @param {function(GCNError)=} error Optional custom error handler.
239 	 */
240 	function unlocalize(obj, success, error) {
241 		fetchDerivedObject(obj, function (derived) {
242 			if (derived.prop('inherited')) {
243 				var err = GCN.createError(
244 					'CANNONT_UNLOCALIZE',
245 					'Cannot unlocalize an object that was not first localized',
246 					obj
247 				);
248 				GCN.handleError(err, error);
249 				return false;
250 			}
251 			deleteLocalizedVersion(obj, success, error);
252 		}, error);
253 	}
254 
255 	GCN.multichannelling =  {
256 		localize: localize,
257 		unlocalize: unlocalize
258 	};
259 
260 	// === caching ============================================================
261 
262 	/**
263 	 * Determines whether the incoming data is that of a multi-channel
264 	 * localized copy.
265 	 *
266 	 * When loading content objects in multi-channel nodes, there exists the
267 	 * possibility that the object data that is returned does not match the id
268 	 * of the one being requested.  This is the case when the request is made
269 	 * with a channel specified, and the requested id is of an inherited page
270 	 * which has previously been localized.  In such situations, the response
271 	 * will contain the data for the local copy and not the original inherited
272 	 * data.
273 	 *
274 	 * @param {ContentObjectAPI} obj The content object whose data was
275 	 *                               requested.
276 	 * @param {object} data The content object data from the server response.
277 	 * @return {boolean} True if the data object contains data for the local
278 	 *                   copy of this content object.
279 	 */
280 	function isLocalizedData(obj, data) {
281 		return obj._channel && (data.id !== obj.id());
282 	}
283 
284 	/**
285 	 * Generates a hash from the given parameters.
286 	 *
287 	 * @param {?string} prefix
288 	 * @param {string} type The Chainback object type.
289 	 * @param {number} channel A node id.
290 	 * @param {number} id An object id.
291 	 * @return {string} A hash key.
292 	 */
293 	function makeCacheHash(prefix, type, channel, id) {
294 		return (prefix ? prefix + '::' : '') + type + ':' + channel + '/' + id;
295 	}
296 
297 	/**
298 	 * Generates a list of hash keys which should map to the given obj. 
299 	 *
300 	 * @param {Chainback} obj A content object instance.
301 	 * @param {object} data The object's data received from the REST-API.
302 	 * @param {Array.<string>} An array of hash keys.
303 	 */
304 	function getCacheKeyVariations(obj, data) {
305 		var ctor = obj._constructor;
306 		var channel = obj._channel;
307 		var idFromObj = obj.id();
308 		var idFromData = data.id;
309 		var type = ctor.__chainbacktype__;
310 		var prefix = (ctor._needsChainedHash && obj._chain)
311 		           ? obj._chain.__gcnhash__
312 		           : '';
313 		var keys = [];
314 		keys.push(makeCacheHash(prefix, type, channel, idFromObj));
315 		if (isLocalizedData(obj, data)) {
316 			keys.push(makeCacheHash(prefix, type, 0, idFromData));
317 			keys.push(makeCacheHash(prefix, type, channel, idFromData));
318 		}
319 		return keys;
320 	}
321 
322 	/**
323 	 * Maps an obj into its constructor's cache against a list of hash keys.
324 	 *
325 	 * @param {Array.<string>} keys A set of hash keys.
326 	 * @param {Chainback} obj A chainback instance which the given keys should
327 	 *                        should be mapped to. 
328 	 */
329 	function addCacheReferences(keys, obj) {
330 		var cache = obj._constructor.__gcncache__;
331 		var i;
332 		for (i = 0; i < keys.length; i++) {
333 			cache[keys[i]] = obj;
334 		}
335 	}
336 
337 	GCN.cache = {
338 		getKeyVariations: getCacheKeyVariations,
339 		addReferences: addCacheReferences
340 	};
341 
342 }(GCN));
343