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