1 /* repositorymanager.js is part of Aloha Editor project http://aloha-editor.org
  2  *
  3  * Aloha Editor is a WYSIWYG HTML5 inline editing library and editor.
  4  * Copyright (c) 2010-2012 Gentics Software GmbH, Vienna, Austria.
  5  * Contributors http://aloha-editor.org/contribution.php
  6  *
  7  * Aloha Editor is free software; you can redistribute it and/or
  8  * modify it under the terms of the GNU General Public License
  9  * as published by the Free Software Foundation; either version 2
 10  * of the License, or any later version.
 11  *
 12  * Aloha Editor is distributed in the hope that it will be useful,
 13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
 14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 15  * GNU General Public License for more details.
 16  *
 17  * You should have received a copy of the GNU General Public License
 18  * along with this program; if not, write to the Free Software
 19  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
 20  *
 21  * As an additional permission to the GNU GPL version 2, you may distribute
 22  * non-source (e.g., minimized or compacted) forms of the Aloha-Editor
 23  * source code without the copy of the GNU GPL normally required,
 24  * provided you include this license notice and a URL through which
 25  * recipients can access the Corresponding Source.
 26  */
 27 define(['aloha/core', 'util/class', 'jquery', 'aloha/console'], function (Aloha, Class, jQuery, console) {
 28 	'use strict';
 29 
 30 	/**
 31 	 * Repository Manager
 32 	 * @namespace Aloha
 33 	 * @class RepositoryManager
 34 	 * @singleton
 35 	 */
 36 	Aloha.RepositoryManager = Class.extend({
 37 
 38 		repositories: [],
 39 		settings: {},
 40 		initialized: false,
 41 
 42 		/**
 43 		 * Initialize all registered repositories
 44 		 * Before we invoke each repositories init method, we merge the global
 45 		 * repository settings into each repository's custom settings
 46 		 * Warning: testing has shown that repositories are maybe not loaded yet
 47 		 * (found that case in IE7), so don't rely on that in this init function.
 48 		 *
 49 		 * @todo: Write unit tests to check that global and custom settings are
 50 		 * applied correctly
 51 		 *
 52 		 * @return void
 53 		 * @hide
 54 		 */
 55 		init: function () {
 56 			var repositories = this.repositories;
 57 
 58 			if (Aloha.settings && Aloha.settings.repositories) {
 59 				this.settings = Aloha.settings.repositories;
 60 			}
 61 
 62 			// use the configured repository manger query timeout or 5 sec
 63 			this.settings.timeout = this.settings.timeout || 5000;
 64 
 65 			var count_repositories = repositories.length;
 66 			var i;
 67 			for (i = 0; i < count_repositories; ++i) {
 68 				var repository = repositories[i];
 69 				this.initRepository(repository);
 70 			}
 71 
 72 			this.initialized = true;
 73 		},
 74 
 75 		/**
 76 		 * Register a Repository.
 77 		 *
 78 		 * @param {Aloha.Repository} repository Repository to register
 79 		 */
 80 		register: function (repository) {
 81 			if (!this.getRepository(repository.repositoryId)) {
 82 				this.repositories.push(repository);
 83 
 84 				// If we have initialized already we have to call
 85 				// this on our own (late-loading)
 86 				if (this.initialized) {
 87 					this.initRepository(repository);
 88 				}
 89 			} else {
 90 				console.warn(this, 'A repository with name { ' + repository.repositoryId + ' } already registerd. Ignoring this.');
 91 			}
 92 		},
 93 
 94 		/**
 95 		 * Initializes a repository.
 96 		 *
 97 		 * @param {Aloha.Repository} repository Repository to initialize
 98 		 */
 99 		initRepository: function (repository) {
100 
101 			if (!repository.settings) {
102 				repository.settings = {};
103 			}
104 
105 			if (this.settings[repository.repositoryId]) {
106 				jQuery.extend(repository.settings, this.settings[repository.repositoryId]);
107 			}
108 
109 			repository.init();
110 		},
111 
112 		/**
113 		 * Returns the repository object identified by repositoryId.
114 		 *
115 		 * @param {String} repositoryId - the name of the repository
116 		 * @return {?Aloha.Repository} a repository or null if name not found
117 		 */
118 		getRepository: function (repositoryId) {
119 			var repositories = this.repositories,
120 				i,
121 				j = repositories.length;
122 
123 			for (i = 0; i < j; ++i) {
124 				if (repositories[i].repositoryId === repositoryId) {
125 					return repositories[i];
126 				}
127 			}
128 
129 			return null;
130 		},
131 
132 		/**
133 		 * Searches a all repositories for repositoryObjects matching query and
134 		 * repositoryObjectType.
135 		 *
136 		<pre><code>
137 			var params = {
138 					queryString: 'hello',
139 					objectTypeFilter: ['website'],
140 					filter: null,
141 					inFolderId: null,
142 					orderBy: null,
143 					maxItems: null,
144 					skipCount: null,
145 					renditionFilter: null,
146 					repositoryId: null
147 			};
148 			Aloha.RepositoryManager.query( params, function( items ) {
149 				// do something with the result items
150 				console.log(items);
151 			});
152 		</code></pre>
153 		 *
154 		 * @param {Object <String,Mixed>} params object with properties
155 		 * <div class="mdetail-params"><ul>
156 		 * <li><code> queryString</code> :  String <div class="sub-desc">The query string for full text search</div></li>
157 		 * <li><code> objectTypeFilter</code> : array  (optional) <div class="sub-desc">Object types that will be returned.</div></li>
158 		 * <li><code> filter</code> : array (optional) <div class="sub-desc">Attributes that will be returned.</div></li>
159 		 * <li><code> inFolderId</code> : boolean  (optional) <div class="sub-desc">This is indicates whether or not a candidate object is a child-object of the folder object identified by the given inFolderId (objectId).</div></li>
160 		 * <li><code> inTreeId</code> : boolean  (optional) <div class="sub-desc">This indicates whether or not a candidate object is a descendant-object of the folder object identified by the given inTreeId (objectId).</div></li>
161 		 * <li><code> orderBy</code> : array  (optional) <div class="sub-desc">ex. [{lastModificationDate:’DESC’, name:’ASC’}]</div></li>
162 		 * <li><code> maxItems</code> : Integer  (optional) <div class="sub-desc">number items to return as result</div></li>
163 		 * <li><code> skipCount</code> : Integer  (optional) <div class="sub-desc">This is tricky in a merged multi repository scenario</div></li>
164 		 * <li><code> renditionFilter</code> : array  (optional) <div class="sub-desc">Instead of termlist an array of kind or mimetype is expected. If null or array.length == 0 all renditions are returned. See http://docs.oasis-open.org/cmis/CMIS/v1.0/cd04/cmis-spec-v1.0.html#_Ref237323310 for renditionFilter</div></li>
165 		 * </ul></div>
166 		 * @param {Function} callback - defines a callback function( items ) which will be called when all repositories returned their results or after a time out of 5sec.
167 		 * "items" is an Array of objects construced with Document/Folder.
168 		 * @void
169 		 */
170 		query: function (params, callback) {
171 			var that = this,
172 				repo,
173 				// The merged results, collected from repository responses
174 				allitems = [],
175 				// the merge metainfo, collected from repository responses
176 				allmetainfo = {
177 					numItems: 0,
178 					hasMoreItems: false
179 				},
180 				// The set of repositories towhich we want to delegate work
181 				repositories = [],
182 				// A counting semaphore (working in reverse, ie: 0 means free)
183 				numOpenCallbacks = 0,
184 				// When this timer times-out, whatever has been collected in
185 				// allitems will be returned to the calling client, and
186 				// numOpenCallbacks will be reset to 0
187 				timer,
188 				i,
189 			    j,
190 				/**
191 				 * Invoked by each repository when it wants to present its
192 				 * results to the manager.
193 				 *
194 				 * Collects the results from each repository, and decrements
195 				 * the numOpenCallbacks semaphore to indicate that there is one
196 				 * less repository for which we are waiting a reponse.
197 				 *
198 				 * If a repository invokes this callback after all
199 				 * openCallbacks have been closed (ie: numOpenCallbacks == 0),
200 				 * then the repository was too late ("missed the ship"), and
201 				 * will be ignored.
202 				 *
203 				 * If numOpenCallbacks decrements to 0 during this call, it
204 				 * means that the the manager is ready to report the results
205 				 * back to the client through the queryCallback method.
206 				 *
207 				 * nb: "this" is reference to the calling repository.
208 				 *
209 				 * @param {Array} items - Results returned by the repository
210 				 * @param {Object<String,Number>} metainfo - optional Metainfo returned by the repository
211 				 */
212 				processResults = function (items, metainfo) {
213 					if (numOpenCallbacks === 0) {
214 						return;
215 					}
216 
217 					var j = items ? items.length : 0;
218 
219 					if (j) {
220 						// Add the repositoryId for each item if a negligent
221 						// repository did not do so.
222 						if (!items[0].repositoryId) {
223 							var repoId = this.repositoryId,
224 								i;
225 							for (i = 0; i < j; ++i) {
226 								items[i].repositoryId = repoId;
227 							}
228 						}
229 
230 						jQuery.merge(allitems, items);
231 					}
232 
233 					if (metainfo && allmetainfo) {
234 						if (jQuery.isNumeric(metainfo.numItems) && jQuery.isNumeric(allmetainfo.numItems)) {
235 							allmetainfo.numItems += metainfo.numItems;
236 						} else {
237 							allmetainfo.numItems = undefined;
238 						}
239 
240 						if (typeof metainfo.hasMoreItems === 'boolean' && typeof allmetainfo.hasMoreItems === 'boolean') {
241 							allmetainfo.hasMoreItems = allmetainfo.hasMoreItems || metainfo.hasMoreItems;
242 						} else {
243 							allmetainfo.hasMoreItems = undefined;
244 						}
245 
246 						if (metainfo.timeout) {
247 							allmetainfo.timeout = true;
248 						}
249 					} else {
250 						// at least one repository did not return metainfo, so
251 						// we have no aggregated metainfo at all
252 						allmetainfo = undefined;
253 					}
254 					console.debug(this, "The repository " + this.repositoryId + " returned with " + j + " results.");
255 					// TODO how to return the metainfo here?
256 					if (--numOpenCallbacks === 0) {
257 						that.queryCallback(callback, allitems, allmetainfo, timer);
258 					}
259 				};
260 
261 			// Unless the calling client specifies otherwise, we will wait a
262 			// maximum of 5 seconds for all repositories to be queried and
263 			// respond. 5 seconds is deemed to be the reasonable time to wait
264 			// when querying the repository manager in the context of something
265 			// like autocomplete
266 			var timeout = parseInt(params.timeout, 10) || this.settings.timeout;
267 			timer = window.setTimeout(function () {
268 				if (numOpenCallbacks > 0) {
269 					console.warn(this, numOpenCallbacks + " repositories did not return before the configured timeout of " + timeout + "ms.");
270 				}
271 				numOpenCallbacks = 0;
272 				// store in the metainfo, that a timeout occurred
273 				allmetainfo = allmetainfo || {};
274 				allmetainfo.timeout = true;
275 				that.queryCallback(callback, allitems, allmetainfo, timer);
276 			}, timeout);
277 
278 			// If repositoryId or a list of repository ids, is not specified in
279 			// the params object, then we will query all registered
280 			// repositories
281 			if (params.repositoryId) {
282 				repositories.push(this.getRepository(params.repositoryId));
283 			} else {
284 				repositories = this.repositories;
285 			}
286 
287 			j = repositories.length;
288 
289 			var repoQueue = [];
290 
291 			// We need to know how many callbacks we will open before invoking
292 			// the query method on each, so that as soon as the first one does
293 			// callback, the correct number of open callbacks will be available
294 			// to check.
295 
296 			for (i = 0; i < j; ++i) {
297 				repo = repositories[i];
298 
299 				// if no repositoryId is given query all repositories
300 				// if a repositoryID is given only query if it is the right repository
301 				if ((!params.repositoryId || repo.repositoryId === params.repositoryId) && typeof repo.query === 'function') {
302 					++numOpenCallbacks;
303 					repoQueue.push(repo);
304 				}
305 			}
306 
307 			j = repoQueue.length;
308 
309 			function makeApplyRepoToProcessResults(repo) {
310 				return function () {
311 					processResults.apply(repo, arguments);
312 				};
313 			}
314 
315 			for (i = 0; i < j; ++i) {
316 				repo = repoQueue[i];
317 				repo.query(params, makeApplyRepoToProcessResults(repo));
318 			}
319 
320 			// If none of the repositories implemented the query method, then
321 			// don't wait for the timeout, simply report to the client
322 			if (numOpenCallbacks === 0) {
323 				this.queryCallback(callback, allitems, allmetainfo, timer);
324 			}
325 		},
326 
327 		/**
328 		 * Passes all the results we have collected to the client through the
329 		 * callback it specified
330 		 *
331 		 * @param {Function} callback - Callback specified by client when
332 		 *								invoking the query method
333 		 * @param {Array} items - Results, collected from all repositories
334 		 * @param {Object<String,Number>} metainfo - optional object containing metainfo
335 		 * @param {Timer} timer - We need to clear this timer
336 		 * @return void
337 		 * @hide
338 		 */
339 		queryCallback: function (callback, items, metainfo, timer) {
340 			if (timer) {
341 				clearTimeout(timer);
342 				timer = undefined;
343 			}
344 
345 			// TODO: Implement sorting based on repository specification
346 			// sort items by weight
347 			//items.sort( function( a, b ) {
348 			//	return ( b.weight || 0 ) - ( a.weight || 0 );
349 			//} );
350 
351 			// prepare result data for the JSON Reader
352 			var result = {
353 				items: items,
354 				results: items.length
355 			};
356 
357 			if (metainfo) {
358 				result.numItems = metainfo.numItems;
359 				result.hasMoreItems = metainfo.hasMoreItems;
360 				result.timeout = metainfo.timeout;
361 			}
362 
363 			callback.call(this, result);
364 		},
365 
366 		/**
367 		 * @todo: This method needs to be covered with some unit tests
368 		 *
369 		 * Returns children items. (see query for an example)
370 		 * @param {Object<String,Mixed>} params - object with properties
371 		 * <div class="mdetail-params"><ul>
372 		 * <li><code> objectTypeFilter</code> : array  (optional) <div class="sub-desc">Object types that will be returned.</div></li>
373 		 * <li><code> filter</code> : array  (optional) <div class="sub-desc">Attributes that will be returned.</div></li>
374 		 * <li><code> inFolderId</code> : boolean  (optional) <div class="sub-desc">This indicates whether or not a candidate object is a child-object of the folder object identified by the given inFolderId (objectId).</div></li>
375 		 * <li><code> orderBy</code> : array  (optional) <div class="sub-desc">ex. [{lastModificationDate:’DESC’, name:’ASC’}]</div></li>
376 		 * <li><code> maxItems</code> : Integer  (optional) <div class="sub-desc">number items to return as result</div></li>
377 		 * <li><code> skipCount</code> : Integer  (optional) <div class="sub-desc">This is tricky in a merged multi repository scenario</div></li>
378 		 * <li><code> renditionFilter</code> : array  (optional) <div class="sub-desc">Instead of termlist an array of kind or mimetype is expected. If null or array.length == 0 all renditions are returned. See http://docs.oasis-open.org/cmis/CMIS/v1.0/cd04/cmis-spec-v1.0.html#_Ref237323310 for renditionFilter</div></li>
379 		 * </ul></div>
380 		 * @param {Function} callback - defines a callback function( items ) which will be called when all repositories returned their results or after a time out of 5sec.
381 		 * "items" is an Array of objects construced with Document/Folder.
382 		 * @void
383 		 */
384 		getChildren: function (params, callback) {
385 			var that = this,
386 				repo,
387 				// The marged results, collected from repository responses
388 				allitems = [],
389 				// The set of repositories towhich we want to delegate work
390 				repositories = [],
391 				// A counting semaphore (working in reverse, ie: 0 means free)
392 				numOpenCallbacks = 0,
393 				// When this timer times-out, whatever has been collected in
394 				// allitems will be returned to the calling client, and
395 				// numOpenCallbacks will be reset to 0
396 				timer,
397 				i,
398 			    j,
399 				processResults = function (items) {
400 					if (numOpenCallbacks === 0) {
401 						return;
402 					}
403 
404 					if (allitems && items) {
405 						jQuery.merge(allitems, items);
406 					}
407 
408 					if (--numOpenCallbacks === 0) {
409 						that.getChildrenCallback(callback, allitems, timer);
410 					}
411 				};
412 
413 			// If the inFolderId is the default id of 'aloha', then return all
414 			// registered repositories
415 			if (params.inFolderId === 'aloha') {
416 				var repoFilter = params.repositoryFilter,
417 					hasRepoFilter = (repoFilter && repoFilter.length);
418 
419 				j = this.repositories.length;
420 
421 				for (i = 0; i < j; ++i) {
422 					repo = this.repositories[i];
423 					if (!hasRepoFilter || jQuery.inArray(repo.repositoryId, repoFilter) > -1) {
424 						repositories.push(new Aloha.RepositoryFolder({
425 							id: repo.repositoryId,
426 							name: repo.repositoryName,
427 							repositoryId: repo.repositoryId,
428 							type: 'repository',
429 							hasMoreItems: true
430 						}));
431 					}
432 				}
433 
434 				that.getChildrenCallback(callback, repositories, null);
435 
436 				return;
437 			}
438 
439 			repositories = this.repositories;
440 
441 			var timeout = parseInt(params.timeout, 10) || this.settings.timeout;
442 			timer = window.setTimeout(function () {
443 				numOpenCallbacks = 0;
444 				that.getChildrenCallback(callback, allitems, timer);
445 			}, timeout);
446 
447 			j = repositories.length;
448 
449 			function makeApplyRepoToProcessResults(repo) {
450 				return function () {
451 					processResults.apply(repo, arguments);
452 				};
453 			}
454 
455 			for (i = 0; i < j; ++i) {
456 				repo = repositories[i];
457 
458 				// if no repositoryId is given query all repositories
459 				// if a repositoryID is given only query if it is the right repository
460 				if ((!params.repositoryId || repo.repositoryId === params.repositoryId) && typeof repo.getChildren === 'function') {
461 					++numOpenCallbacks;
462 					repo.getChildren(params, makeApplyRepoToProcessResults(repo));
463 				}
464 			}
465 
466 			if (numOpenCallbacks === 0) {
467 				this.getChildrenCallback(callback, allitems, timer);
468 			}
469 		},
470 
471 		/**
472 		 * Returns results for getChildren to calling client
473 		 *
474 		 * @return void
475 		 * @hide
476 		 */
477 		getChildrenCallback: function (callback, items, timer) {
478 			if (timer) {
479 				clearTimeout(timer);
480 				timer = undefined;
481 			}
482 
483 			callback.call(this, items);
484 		},
485 
486 		/**
487 		 * @fixme: Not tested, but the code for this function does not seem to
488 		 *        compute repository.makeClean will be undefined
489 		 *
490 		 * @todo: Rewrite this function header comment so that is clearer
491 		 *
492 		 * Pass an object, which represents an marked repository to corresponding
493 		 * repository, so that it can make the content clean (prepare for saving)
494 		 *
495 		 * @param {jQuery} obj - representing an editable
496 		 * @return void
497 		 */
498 		makeClean: function (obj) {
499 			// iterate through all registered repositories
500 			var that = this,
501 				repository = {},
502 				i = 0,
503 				j = that.repositories.length;
504 
505 			// find all repository tags
506 			obj.find('[data-gentics-aloha-repository=' + this.prefix + ']').each(function () {
507 				while (i < j) {
508 					repository.makeClean(obj);
509 					i += 1;
510 				}
511 				console.debug(that, 'Passing contents of HTML Element with id { ' + this.attr('id') + ' } for cleaning to repository { ' + repository.repositoryId + ' }');
512 				repository.makeClean(this);
513 			});
514 		},
515 
516 		/**
517 		 * Marks an object as repository of this type and with this item.id.
518 		 * Objects can be any DOM objects as A, SPAN, ABBR, etc. or
519 		 * special objects such as aloha-aloha_block elements.
520 		 * This method marks the target obj with two private attributes:
521 		 * (see http://dev.w3.org/html5/spec/elements.html#embedding-custom-non-visible-data)
522 		 * * data-gentics-aloha-repository: stores the repositoryId
523 		 * * data-gentics-aloha-object-id: stores the object.id
524 		 *
525 		 * @param {DOMObject} obj - DOM object to mark
526 		 * @param {Aloha.Repository.Object} item - the item which is applied to obj,
527 		 *		if set to null, the data-GENTICS-... attributes are removed
528 		 * @return void
529 		 */
530 		markObject: function (obj, item) {
531 			if (!obj) {
532 				return;
533 			}
534 
535 			if (item) {
536 				var repository = this.getRepository(item.repositoryId);
537 
538 				if (repository) {
539 					jQuery(obj).attr({
540 						'data-gentics-aloha-repository': item.repositoryId,
541 						'data-gentics-aloha-object-id': item.id
542 					});
543 
544 					repository.markObject(obj, item);
545 				} else {
546 					console.error(this, 'Trying to apply a repository { ' + item.name + ' } to an object, but item has no repositoryId.');
547 				}
548 			} else {
549 				jQuery(obj).removeAttr('data-gentics-aloha-repository').removeAttr('data-gentics-aloha-object-id');
550 			}
551 		},
552 
553 		/**
554 		 * Get the object for which the given DOM object is marked from the
555 		 * repository.
556 		 *
557 		 * @param {DOMObject} obj - DOM object which probably is marked
558 		 * @param {Function} callback - callback function
559 		 */
560 		getObject: function (obj, callback) {
561 			var that = this,
562 				$obj = jQuery(obj),
563 				repository = this.getRepository($obj.attr('data-gentics-aloha-repository')),
564 				itemId = $obj.attr('data-gentics-aloha-object-id');
565 
566 			if (repository && itemId) {
567 				// initialize the item cache (per repository) if not already done
568 				this.itemCache = this.itemCache || [];
569 				this.itemCache[repository.repositoryId] = this.itemCache[repository.repositoryId] || [];
570 
571 				// when the item is cached, we just call the callback method
572 				if (this.itemCache[repository.repositoryId][itemId]) {
573 					callback.call(this, [this.itemCache[repository.repositoryId][itemId]]);
574 				} else {
575 					// otherwise we get the object from the repository
576 					repository.getObjectById(itemId, function (items) {
577 						// make sure the item is in the cache (for subsequent calls)
578 						that.itemCache[repository.repositoryId][itemId] = items[0];
579 						callback.call(this, items);
580 					});
581 				}
582 			}
583 		},
584 
585 		/**
586 		 * This function gets called by the repository browser when a folder is opened.
587 		 * At the moment this forwards the call to the repository only.
588 		 * 
589 		 * @param {object}	folder	object that gets forwarded
590 		 */
591 		folderOpened: function (folder) {
592 			var repository = this.getRepository(folder.repositoryId);
593 
594 			if (typeof repository.folderOpened === 'function') {
595 				repository.folderOpened(folder);
596 			}
597 		},
598 
599 		/**
600 		 * This function gets called by the repository browser when a folder is closed.
601 		 * At the moment this forwards the call to the repository only.
602 		 * 
603 		 * @param {object}	folder	object that gets forwarded
604 		 */
605 		folderClosed: function (folder) {
606 			var repository = this.getRepository(folder.repositoryId);
607 
608 			if (typeof repository.folderClosed === 'function') {
609 				repository.folderClosed(folder);
610 			}
611 		},
612 
613 		/**
614 		 * This function gets called by the repository browser when a folder is selected.
615 		 * At the moment this forwards the call to the repository only.
616 		 * 
617 		 * @param {object}	folder	object that gets forwarded
618 		 */
619 		folderSelected: function (folder) {
620 			var repository = this.getRepository(folder.repositoryId);
621 
622 			if (typeof repository.folderSelected === 'function') {
623 				repository.folderSelected(folder);
624 			}
625 		},
626 
627 		/**
628 		 * Get the selected folder
629 		 * @returns selected folder or undefined
630 		 */
631 		getSelectedFolder: function () {
632 			var i, len = this.repositories.length,
633 				selected;
634 
635 			for (i = 0; i < len; ++i) {
636 				if (typeof this.repositories[i].getSelectedFolder === 'function') {
637 					selected = this.repositories[i].getSelectedFolder();
638 					if (selected) {
639 						return selected;
640 					}
641 				}
642 			}
643 		},
644 
645 		/**
646 		 * @return {String} name of repository manager object
647 		 */
648 		toString: function () {
649 			return 'repositorymanager';
650 		}
651 
652 	});
653 
654 	Aloha.RepositoryManager = new Aloha.RepositoryManager();
655 
656 	// We return the constructor, not the instance of Aloha.RepositoryManager
657 	return Aloha.RepositoryManager;
658 });
659