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([
 28 	'jquery',
 29 	'util/class',
 30 	'aloha/core',
 31 	'aloha/console',
 32 	'aloha/repositoryobjects' // Provides Aloha.RepositoryFolder
 33 ], function (
 34 	$,
 35 	Class,
 36 	Aloha,
 37 	Console,
 38 	__unused__
 39 ) {
 40 	'use strict';
 41 
 42 	/**
 43 	 * Given an input set, returns a new set which is a range of the input set
 44 	 * that maps to the given predicate.
 45 	 *
 46 	 * Prefers native Array.prototype.filter() where available (after JavaScript
 47 	 * 1.6).
 48 	 *
 49 	 * @param {Array} domain
 50 	 * @param {function:boolean} predicate
 51 	 * @return {Array} Sub set of domain
 52 	 */
 53 	var filter = (function (native) {
 54 		return (native
 55 			? function (domain, predicate) {
 56 				return domain.filter(predicate);
 57 			}
 58 			: function (domain, predicate) {
 59 				var codomain = [];
 60 				var i;
 61 				for (i = 0; i < domain.length; i++) {
 62 					if (predicate(domain[i])) {
 63 						codomain.push(domain[i]);
 64 					}
 65 				}
 66 				return codomain;
 67 			}
 68 		);
 69 	}(Array.prototype.hasOwnProperty('filter')));
 70 
 71 	/**
 72 	 * Bundles results, and meta information in preparation for the JSON Reader.
 73 	 *
 74 	 * Used with query().
 75 	 *
 76 	 * @param {Array.<Document|Folder>} items Results, collected from all
 77 	 *                                        repositories.
 78 	 * @param {object<string, number>} meta Optional object containing metainfo.
 79 	 * @return {object} Result object.
 80 	 */
 81 	function bundle(items, meta) {
 82 		var result = {
 83 			items: items,
 84 			results: items.length
 85 		};
 86 		if (meta) {
 87 			result.numItems = meta.numItems;
 88 			result.hasMoreItems = meta.hasMoreItems;
 89 			result.timeout = meta.timeout;
 90 		}
 91 		return result;
 92 	}
 93 
 94 	/**
 95 	 * Passes all the results we have collected to the client through the
 96 	 * callback it specified.
 97 	 *
 98 	 * TODO: Implement sorting based on repository specification sort
 99 	 *       items by weight.
100 	 * items.sort(function (a, b) {
101 	 *	return (b.weight || 0) - (a.weight || 0);
102 	 * });
103 	 *
104 	 * @param {function} callback Callback specified by client when invoking
105 	 *                            the query method.
106 	 * @param {Array.<Document|Folder>|object<string, number>} results
107 	 */
108 	function report(callback, results) {
109 		callback(results);
110 	}
111 
112 	/**
113 	 * Predicates; used to filter lists of repositories based on whether they
114 	 * implement a method or not.
115 	 *
116 	 * @type {object<string, function(Repository):boolean}
117 	 */
118 	var repositoryFilters = {
119 		query: function (repository) {
120 			return typeof repository.query === 'function';
121 		},
122 		getChildren: function (repository) {
123 			return typeof repository.getChildren === 'function';
124 		},
125 		getSelectedFolder: function (repository) {
126 			return typeof repository.getSelectedFolder === 'function';
127 		}
128 	};
129 
130 	/**
131 	 * Repository Manager.
132 	 *
133 	 * @namespace Aloha
134 	 * @class RepositoryManager
135 	 * @singleton
136 	 */
137 	var RepositoryManager = Class.extend({
138 
139 		repositories: [],
140 
141 		settings: (Aloha.settings && Aloha.settings.repositories) || {},
142 
143 		initialized: false,
144 
145 		/**
146 		 * Initializes all registered repositories.
147 		 *
148 		 *                            ???
149 		 *                             |
150 		 *                             v
151 		 *
152 		 * Warning: testing has shown that repositories are maybe not loaded yet
153 		 * (found that case in IE7), so don't rely on that in this init
154 		 * function.
155 		 *
156 		 *                             ^
157 		 *                             |
158 		 *                            !!!
159 		 */
160 		init: function () {
161 			var manager = this;
162 			if (typeof manager.settings.timeout === 'undefined') {
163 				manager.settings.timeout = 5000;
164 			}
165 			var i;
166 			for (i = 0; i < manager.repositories.length; i++) {
167 				manager.initRepository(manager.repositories[i]);
168 			}
169 			manager.initialized = true;
170 		},
171 
172 		/**
173 		 * Registers a Repository.
174 		 *
175 		 * If the repositorie is registered after the Repository Manager is
176 		 * initialized it will be automatically initialized.
177 		 *
178 		 * @param {Repository} repository Repository to register.
179 		 */
180 		register: function (repository) {
181 			var manager = this;
182 			if (!manager.getRepository(repository.repositoryId)) {
183 				manager.repositories.push(repository);
184 				if (manager.initialized) {
185 					manager.initRepository(repository);
186 				}
187 			} else {
188 				Console.warn(manager, 'A repository with name "'
189 						+ repository.repositoryId
190 						+ '" already registerd. Ignoring this.');
191 			}
192 		},
193 
194 		/**
195 		 * Initializes a repository.
196 		 *
197 		 * @param {Repository} repository Repository to initialize.
198 		 */
199 		initRepository: function (repository) {
200 			var manager = this;
201 			if (!repository.settings) {
202 				repository.settings = {};
203 			}
204 			if (manager.settings[repository.repositoryId]) {
205 				$.extend(repository.settings,
206 				         manager.settings[repository.repositoryId]);
207 			}
208 			repository.init();
209 		},
210 
211 		/**
212 		 * Returns the repository identified by repositoryId.
213 		 *
214 		 * @param {String} id Id of repository to retrieve.
215 		 * @return {Repository|null} Repository or null if none with the given
216 		 *                           id is found.
217 		 */
218 		getRepository: function (id) {
219 			var manager = this;
220 			var i;
221 			for (i = 0; i < manager.repositories.length; i++) {
222 				if (manager.repositories[i].repositoryId === id) {
223 					return manager.repositories[i];
224 				}
225 			}
226 			return null;
227 		},
228 
229 		/**
230 		 * Searches all repositories for repositoryObjects matching query and
231 		 * repositoryObjectType.
232 		 *
233 		 * <pre><code>
234 		 *  // Example:
235 		 *  var params = {
236 		 *      queryString: 'hello',
237 		 *      objectTypeFilter: ['website'],
238 		 *      filter: null,
239 		 *      inFolderId: null,
240 		 *      orderBy: null,
241 		 *      maxItems: null,
242 		 *      skipCount: null,
243 		 *      renditionFilter: null,
244 		 *      repositoryId: null
245 		 *  };
246 		 *  Aloha.RepositoryManager.query(params, function (items) {
247 		 *      Console.log(items);
248 		 *  });
249 		 * </code></pre>
250 		 *
251 		 * @param {object<string, mixed>} params
252 		 *
253 		 *       queryString: String             The query string for full text
254 		 *                                       search.
255 		 *  objectTypeFilter: Array   (optional) Object types to be retrieved.
256 		 *            filter: Array   (optional) Attributes that will be
257 		 *                                       included.
258 		 *        inFolderId: boolean (optional) Whether or not a candidate
259 		 *                                       object is a child-object of the
260 		 *                                       folder object identified by the
261 		 *                                       given inFolderId (objectId).
262 		 *          inTreeId: boolean (optional) This indicates whether or
263 		 *                                       not a candidate object is a
264 		 *                                       descendant-object of the folder
265 		 *                                       object identified by the given
266 		 *                                       inTreeId (objectId).
267 		 *           orderBy: Array   (optional) example: [{
268 		 *                                           lastModificationDate: 'DESC',
269 		 *                                           name: 'ASC'
270 		 *                                       }]
271 		 *          maxItems: number  (optional) Number of items to include in
272 		 *                                       result set.
273 		 *         skipCount: number  (optional) This is tricky in a merged
274 		 *                                       multi repository scenario.
275 		 *   renditionFilter: Array   (optional) Instead of termlist, an
276 		 *                                       array of kind or mimetype is
277 		 *                                       expected.  If null or an empty
278 		 *                                       set, then all renditions are
279 		 *                                       returned. See
280 		 *                                       http://docs.oasis-open.org/cmis/CMIS/v1.0/cd04/cmis-spec-v1.0.html#_Ref237323310
281 		 *                                       for renditionFilter.
282 		 *
283 		 * @param {function(Document|Folder)} callback Function to be invoked
284 		 *                                             after the repository
285 		 *                                             manager has finished
286 		 *                                             querying all
287 288 		 *                                             repositories.
289 		 */
290 		query: function (params, callback) {
291 			var manager = this;
292 
293 			var i;
294 
295 			// The merged results, collected from repository responses.
296 			var results = [];
297 
298 			// The merged metainfo, collected from repository responses.
299 			var allmetainfo = {
300 				numItems: 0,
301 				hasMoreItems: false
302 			};
303 
304 			// A counting semaphore (working in reverse, ie: 0 means free).
305 			var numOpenQueries;
306 
307 			// Unless the calling client specifies otherwise, the manager will
308 			// wait a maximum of 5 seconds for all repositories to be queried
309 			// and respond. 5 seconds is deemed to be the reasonable time to
310 			// wait when querying the repository manager in the context of
311 			// something like autocomplete.
312 			var timeout = (params.timeout && parseInt(params.timeout, 10))
313 			           || manager.settings.timeout;
314 
315 			// When this timer times-out, whatever has been collected in
316 			// `results' will be returned to the calling client and all further
317 			// processing aborted.
318 			var timer = window.setTimeout(function () {
319 				// Store in metainfo that a timeout occurred.
320 				allmetainfo = allmetainfo || {};
321 				allmetainfo.timeout = true;
322 
323 				if (numOpenQueries > 0) {
324 					Console.warn(manager, numOpenQueries
325 							+ ' repositories did not return before the '
326 							+ 'configured timeout of ' + timeout + 'ms.');
327 					numOpenQueries = 0;
328 				}
329 				clearTimeout(timer);
330 				report(callback, bundle(results, allmetainfo));
331 			}, timeout);
332 
333 			/**
334 			 * Invoked by each repository when it wants to present its results
335 			 * to the manager.
336 			 *
337 			 * Collects the results from each repository, and decrements the
338 			 * numOpenQueries semaphore to indicate that there is one less
339 			 * repository for which the manager is waiting for a reponse.
340 			 *
341 			 * If a repository invokes this callback after all openCallbacks
342 			 * have been closed (ie: numOpenQueries == 0), then the repository
343 			 * was too late ("missed the ship"), and will be ignored.
344 			 *
345 			 * If numOpenQueries decrements to 0 during this call, it means that
346 			 * the the manager is ready to report the results back to the client
347 			 * through the report() method.
348 			 *
349 			 * @param {Array.<Document|Folder>} items Results returned by the
350 			 *                                        repository.
351 			 * @param {object<string, number>} metainfo Optional Metainfo
352 			 *                                          returned by some
353 			 *                                          repositories.
354 			 */
355 			var process = function (items, metainfo) {
356 				var repository = this;
357 
358 				if (0 === numOpenQueries) {
359 					return;
360 				}
361 
362 				if (items && items.length) {
363 
364 					// Because some negligent repository implementations do not
365 					// set repositoryId properly.
366 					if (!items[0].repositoryId) {
367 						var id = repository.repositoryId;
368 						var i;
369 						for (i = 0; i < items.length; i++) {
370 							items[i].repositoryId = id;
371 						}
372 					}
373 
374 					$.merge(results, items);
375 				}
376 
377 				if (metainfo && allmetainfo) {
378 					allmetainfo.numItems =
379 						($.isNumeric(metainfo.numItems) &&
380 						 $.isNumeric(allmetainfo.numItems))
381 							? allmetainfo.numItems + metainfo.numItems
382 							: undefined;
383 
384 					allmetainfo.hasMoreItems =
385 						(typeof metainfo.hasMoreItems === 'boolean' &&
386 						 typeof allmetainfo.hasMoreItems === 'boolean')
387 							? allmetainfo.hasMoreItems || metainfo.hasMoreItems
388 							: undefined;
389 
390 					if (metainfo.timeout) {
391 						allmetainfo.timeout = true;
392 					}
393 				} else {
394 
395 					// Because if even one repository does not return metainfo,
396 					// so we have no aggregated metainfo at all.
397 					allmetainfo = undefined;
398 				}
399 
400 				Console.debug(manager, 'The repository '
401 						+ repository.repositoryId + 'returned with '
402 						+ items.length + ' results.');
403 
404 				// TODO: how to return the metainfo here?
405 				if (0 === --numOpenQueries) {
406 					clearTimeout(timer);
407 					report(callback, bundle(results, allmetainfo));
408 				}
409 			};
410 
411 			var repositories = params.repositoryId
412 			                 ? [manager.getRepository(params.repositoryId)]
413 			                 : manager.repositories;
414 
415 			var queue = filter(repositories, repositoryFilters.query);
416 
417 			// If none of the repositories implemented the query method, then
418 			// don't wait for the timeout, simply report to the client.
419 			if (0 === queue.length) {
420 				clearTimeout(timer);
421 				report(callback, bundle(results, allmetainfo));
422 				return;
423 			}
424 
425 			var makeProcess = function (repository) {
426 				return function () {
427 					process.apply(repository, arguments);
428 				};
429 			};
430 
431 			numOpenQueries = queue.length;
432 
433 			for (i = 0; i < queue.length; i++) {
434 				queue[i].query(params, makeProcess(queue[i]));
435 			}
436 		},
437 
438 		/**
439 		 * Retrieves children items.
440 		 *
441 		 * @param {object<string,mixed>} params Object with properties.
442 		 *
443 		 *  objectTypeFilter: Array   (optional) Object types to be retrieved.
444 		 *            filter: Array   (optional) Attributes to be retrieved.
445 		 *        inFolderId: boolean (optional) This indicates whether or not
446 		 *                                       a candidate object is a
447 		 *                                       child-object of the folder
448 		 *                                       object identified by the given
449 		 *                                       inFolderId (objectId).
450 		 *           orderBy: Array   (optional) example: [{
451 		 *                                           lastModificationDate: 'DESC',
452 		 *                                           name: 'ASC'
453 		 *                                       }]
454 		 *          maxItems: number  (optional) number Items to return as a result.
455 		 *         skipCount: number  (optional) This is tricky in a merged
456 		 *                                       multi repository scenario.
457 		 *   renditionFilter: Array   (optional) Instead of termlist an Array
458 		 *                                       of kind or mimetype is
459 		 *                                       expected. If null or
460 		 *                                       Array.length == 0 all
461 		 *                                       renditions are returned. See
462 		 *                                       http://docs.oasis-open.org/cmis/CMIS/v1.0/cd04/cmis-spec-v1.0.html#_Ref237323310
463 		 *                                       for renditionFilter.
464 		 *
465 		 * @param {function(Document|Folder)} callback Function to be invoked
466 		 *                                             after the repository
467 		 *                                             manager has finished
468 		 *                                             querying all
469 		 *                                             repositories.
470 		 */
471 		getChildren: function (params, callback) {
472 			var manager = this;
473 
474 			var i;
475 
476 			// The marged results, collected from repository responses.
477 			var results = [];
478 
479 			// A counting semaphore (working in reverse, ie: 0 means free).
480 			var numOpenQueries = 0;
481 
482 			var timeout = (params.timeout && parseInt(params.timeout, 10))
483 			           || manager.settings.timeout;
484 
485 			var timer = window.setTimeout(function () {
486 				if (numOpenQueries > 0) {
487 					Console.warn(manager, numOpenQueries
488 							+ ' repositories did not respond before the '
489 							+ 'configured timeout of ' + timeout + 'ms.');
490 					numOpenQueries = 0;
491 				}
492 				clearTimeout(timer);
493 				report(callback, results);
494 			}, timeout);
495 
496 			var process = function (items) {
497 				if (0 === numOpenQueries) {
498 					return;
499 				}
500 				if (items) {
501 					$.merge(results, items);
502 				}
503 				if (0 === --numOpenQueries) {
504 					clearTimeout(timer);
505 					report(callback, results);
506 				}
507 			};
508 
509 			var repositories = params.repositoryId
510 			                 ? [manager.getRepository(params.repositoryId)]
511 			                 : manager.repositories;
512 
513 			if (params.repositoryFilter && params.repositoryFilter.length) {
514 				repositories = filter(repositories, function (repository) {
515 					return -1 < $.inArray(repository.repositoryId,
516 						params.repositoryFilter);
517 				});
518 			}
519 
520 			// If the inFolderId is the default id of 'aloha', then return all
521 			// registered repositories as the result set.
522 			if ('aloha' === params.inFolderId) {
523 				var hasRepoFilter = params.repositoryFilter
524 				                 && 0 < params.repositoryFilter.length;
525 
526 				for (i = 0; i < repositories.length; i++) {
527 					results.push(new Aloha.RepositoryFolder({
528 						id: repositories[i].repositoryId,
529 						name: repositories[i].repositoryName,
530 						repositoryId: repositories[i].repositoryId,
531 						type: 'repository',
532 						hasMoreItems: true
533 					}));
534 				}
535 
536 				clearTimeout(timer);
537 				report(callback, results);
538 				return;
539 			}
540 
541 			var queue = filter(repositories, repositoryFilters.getChildren);
542 
543 			if (0 === queue.length) {
544 				clearTimeout(timer);
545 				report(callback, results);
546 				return;
547 			}
548 
549 			numOpenQueries = queue.length;
550 
551 			for (i = 0; i < queue.length; i++) {
552 				queue[i].getChildren(params, process);
553 			}
554 		},
555 
556 		/**
557 		 * @fixme: Not tested, but the code for this function does not seem to
558 		 *        compute repository.makeClean will be undefined
559 		 *
560 		 * @todo: Rewrite this function header comment so that is clearer
561 		 *
562 		 * Pass an object, which represents an marked repository to corresponding
563 		 * repository, so that it can make the content clean (prepare for saving)
564 		 *
565 		 * @param {jQuery} obj - representing an editable
566 		 * @return void
567 		 */
568 		makeClean: function (obj) {
569 			// iterate through all registered repositories
570 			var that = this,
571 				repository = {},
572 				i = 0,
573 				j = that.repositories.length;
574 
575 			// find all repository tags
576 			obj.find('[data-gentics-aloha-repository=' + this.prefix + ']').each(function () {
577 				while (i < j) {
578 					repository.makeClean(obj);
579 					i += 1;
580 				}
581 				Console.debug(that, 'Passing contents of HTML Element with id { ' + this.attr('id') + ' } for cleaning to repository { ' + repository.repositoryId + ' }');
582 				repository.makeClean(this);
583 			});
584 		},
585 
586 		/**
587 		 * Marks an object as repository of this type and with this item.id.
588 		 * Objects can be any DOM objects as A, SPAN, ABBR, etc. or
589 		 * special objects such as aloha-aloha_block elements.
590 		 *
591 		 * Marks the target obj with two private attributes:
592 		 * (see http://dev.w3.org/html5/spec/elements.html#embedding-custom-non-visible-data)
593 		 *	- data-gentics-aloha-repository: stores the repositoryId
594 		 *	- data-gentics-aloha-object-id: stores the object.id
595 		 *
596 		 * @param {HTMLElement} obj DOM object to mark.
597 		 * @param {Aloha.Repository.Object} item Item which is applied to obj,
598 		 *                                       if set to null, the
599 		 *                                       "data-GENTICS-..." attributes
600 		 *                                       are removed.
601 		 */
602 		markObject: function (obj, item) {
603 			if (!obj) {
604 				return;
605 			}
606 
607 			var manager = this;
608 
609 			if (item) {
610 				var repository = manager.getRepository(item.repositoryId);
611 				if (repository) {
612 					$(obj).attr({
613 						'data-gentics-aloha-repository': item.repositoryId,
614 						'data-gentics-aloha-object-id': item.id
615 					});
616 					repository.markObject(obj, item);
617 				} else {
618 					Console.error(manager, 'Trying to apply a repository "'
619 							+ item.name
620 							+ '" to an object, but item has no repositoryId.');
621 				}
622 			} else {
623 				$(obj).removeAttr('data-gentics-aloha-repository')
624 				      .removeAttr('data-gentics-aloha-object-id');
625 			}
626 		},
627 
628 		/**
629 		 * Get the object for which the given DOM object is marked from the
630 		 * repository.
631 		 *
632 		 * Will initialize the item cache (per repository) if not already done.
633 		 *
634 		 * @param {HTMLElement} element DOM object which probably is marked.
635 		 * @param {function} callback
636 		 */
637 		getObject: function (element, callback) {
638 			var manager = this;
639 			var $element = $(element);
640 			var itemId = $element.attr('data-gentics-aloha-object-id');
641 			var repositoryId = $element.attr('data-gentics-aloha-repository');
642 			var repository = manager.getRepository(repositoryId);
643 
644 			if (repository && itemId) {
645 				if (!manager.itemCache) {
646 					manager.itemCache = [];
647 				}
648 
649 				var cache = manager.itemCache[repositoryId];
650 				if (!cache) {
651 					cache = manager.itemCache[repositoryId] = [];
652 				}
653 
654 				if (cache[itemId]) {
655 					callback([cache[itemId]]);
656 				} else {
657 					repository.getObjectById(itemId, function (items) {
658 						cache[itemId] = items[0];
659 						callback(items);
660 					});
661 				}
662 			}
663 		},
664 
665 		/**
666 		 * Mark a folder as opened.
667 		 *
668 		 * Called by a repository client (eg: repository browser) when a folder
669 		 * is opened.
670 		 *
671 		 * @param {object|Folder} folder Object with property repositoryId.
672 		 */
673 		folderOpened: function (folder) {
674 			var repository = this.getRepository(folder.repositoryId);
675 			if (typeof repository.folderOpened === 'function') {
676 				repository.folderOpened(folder);
677 			}
678 		},
679 
680 		/**
681 		 * Mark a folder as closed.
682 		 *
683 		 * Called by a repository client (eg: repository browser) when a folder
684 		 * is closed.
685 		 *
686 		 * @param {object|Folder} folder Object with property repositoryId.
687 		 */
688 		folderClosed: function (folder) {
689 			var repository = this.getRepository(folder.repositoryId);
690 			if (typeof repository.folderClosed === 'function') {
691 				repository.folderClosed(folder);
692 			}
693 		},
694 
695 		/**
696 		 * Mark a folder as selected.
697 		 *
698 		 * Called by a repository client (eg: repository browser) when a folder
699 		 * is selected.
700 		 *
701 		 * @param {object|Folder} folder Object with property repositoryId.
702 		 */
703 		folderSelected: function (folder) {
704 			var repository = this.getRepository(folder.repositoryId);
705 			if (typeof repository.folderSelected === 'function') {
706 				repository.folderSelected(folder);
707 			}
708 		},
709 
710 		/**
711 		 * Retrieve the selected folder.
712 		 *
713 		 * @return {Folder} Selected folder or null if it cannot be found.
714 		 */
715 		getSelectedFolder: function () {
716 			var repositories = filter(this.repositories,
717 					repositoryFilters.getSelectedFolder);
718 			var i;
719 			var selected;
720 			for (i = 0; i < repositories.length; i++) {
721 				selected = repositories[i].getSelectedFolder();
722 				if (selected) {
723 					return selected;
724 				}
725 			}
726 			return null;
727 		},
728 
729 		/**
730 		 * Human readable representation of repository manager.
731 		 *
732 		 * @return {string}
733 		 */
734 		toString: function () {
735 			return 'repositorymanager';
736 		}
737 
738 	});
739 
740 	Aloha.RepositoryManager = new RepositoryManager();
741 
742 	return Aloha.RepositoryManager;
743 });
744