/*! * This file is part of Aloha Editor Project http://aloha-editor.org * Copyright (c) 2010-2011 Gentics Software GmbH, aloha@gentics.com * Contributors http://aloha-editor.org/contribution.php * Licensed unter the terms of http://www.aloha-editor.org/license.html *//* * Aloha Editor is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version.* * * Aloha Editor is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . */ define( [ 'aloha/core', 'util/class', 'aloha/jquery', 'aloha/console' ], function( Aloha, Class, jQuery, console ) { /** * Repository Manager * @namespace Aloha * @class RepositoryManager * @singleton */ Aloha.RepositoryManager = Class.extend( { repositories : [], settings: {}, /** * Initialize all registered repositories * Before we invoke each repositories init method, we merge the global * repository settings into each repository's custom settings * * @todo: Write unit tests to check that global and custom settings are * applied correctly * * @return void * @hide */ init: function() { var repositories = this.repositories, i = 0, j = repositories.length, repository; if ( Aloha.settings && Aloha.settings.repositories ) { this.settings = Aloha.settings.repositories; } // use the configured repository manger query timeout or 5 sec this.settings.timeout = this.settings.timeout || 5000; for ( ; i < j; ++i ) { repository = repositories[ i ]; if ( !repository.settings ) { repository.settings = {}; } if ( this.settings[ repository.repositoryId ] ) { jQuery.extend( repository.settings, this.settings[ repository.repositoryId ] ); } repository.init(); } }, /** * Register a Repository. * * @param {Aloha.Repository} repository Repository to register */ register: function( repository ) { if ( !this.getRepository( repository.repositoryId ) ) { this.repositories.push( repository ); } else { console.warn( this, 'A repository with name { ' + repository.repositoryId + ' } already registerd. Ignoring this.' ); } }, /** * Returns the repository object identified by repositoryId. * * @param {String} repositoryId - the name of the repository * @return {?Aloha.Repository} a repository or null if name not found */ getRepository: function( repositoryId ) { var repositories = this.repositories, i = 0, j = repositories.length; for ( ; i < j; ++i ) { if ( repositories[ i ].repositoryId === repositoryId ) { return repositories[ i ]; } } return null; }, /** * Searches a all repositories for repositoryObjects matching query and * repositoryObjectType. *

			var params = {
					queryString: 'hello',
					objectTypeFilter: ['website'],
					filter: null,
					inFolderId: null,
					orderBy: null,
					maxItems: null,
					skipCount: null,
					renditionFilter: null,
					repositoryId: null
			};
			Aloha.RepositoryManager.query( params, function( items ) {
				// do something with the result items
				console.log(items);
			});
		
* * @param {Object } params object with properties *
    *
  • queryString : String
    The query string for full text search
  • *
  • objectTypeFilter : array (optional)
    Object types that will be returned.
  • *
  • filter : array (optional)
    Attributes that will be returned.
  • *
  • inFolderId : boolean (optional)
    This is indicates whether or not a candidate object is a child-object of the folder object identified by the given inFolderId (objectId).
  • *
  • inTreeId : boolean (optional)
    This indicates whether or not a candidate object is a descendant-object of the folder object identified by the given inTreeId (objectId).
  • *
  • orderBy : array (optional)
    ex. [{lastModificationDate:’DESC’, name:’ASC’}]
  • *
  • maxItems : Integer (optional)
    number items to return as result
  • *
  • skipCount : Integer (optional)
    This is tricky in a merged multi repository scenario
  • *
  • renditionFilter : array (optional)
    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
  • *
* @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. * "items" is an Array of objects construced with Document/Folder. * @void */ query: function( params, callback ) { var that = this, repo, // The merged results, collected from repository responses allitems = [], // the merge metainfo, collected from repository responses allmetainfo = { numItems: 0, hasMoreItems: false }, // The set of repositories towhich we want to delegate work repositories = [], // A counting semaphore (working in reverse, ie: 0 means free) numOpenCallbacks = 0, // When this timer times-out, whatever has been collected in // allitems will be returned to the calling client, and // numOpenCallbacks will be reset to 0 timer, i, j, /** * Invoked by each repository when it wants to present its * results to the manager. * * Collects the results from each repository, and decrements * the numOpenCallbacks semaphore to indicate that there is one * less repository for which we are waiting a reponse. * * If a repository invokes this callback after all * openCallbacks have been closed (ie: numOpenCallbacks == 0), * then the repository was too late ("missed the ship"), and * will be ignored. * * If numOpenCallbacks decrements to 0 during this call, it * means that the the manager is ready to report the results * back to the client through the queryCallback method. * * nb: "this" is reference to the calling repository. * * @param {Array} items - Results returned by the repository * @param {Object} metainfo - optional Metainfo returned by the repository */ processResults = function( items, metainfo ) { if ( numOpenCallbacks === 0 ) { return; } var j = items ? items.length : 0; if ( j ) { // Add the repositoryId for each item if a negligent // repository did not do so. if ( !items[0].repositoryId ) { var repoId = this.repositoryId, i; for ( i = 0; i < j; ++i ) { items[ i ].repositoryId = repoId; } } jQuery.merge( allitems, items ); } if ( metainfo && allmetainfo ) { if ( jQuery.isNumeric( metainfo.numItems ) && jQuery.isNumeric( allmetainfo.numItems ) ) { allmetainfo.numItems += metainfo.numItems; } else { allmetainfo.numItems = undefined; } if ( jQuery.isBoolean( metainfo.hasMoreItems ) && jQuery.isBoolean( allmetainfo.hasMoreItems ) ) { allmetainfo.hasMoreItems = allmetainfo.hasMoreItems || metainfo.hasMoreItems; } else { allmetainfo.hasMoreItems = undefined; } } else { // at least one repository did not return metainfo, so // we have no aggregated metainfo at all allmetainfo = undefined; } // TODO how to return the metainfo here? if ( --numOpenCallbacks === 0 ) { that.queryCallback( callback, allitems, allmetainfo, timer ); } }; // Unless the calling client specifies otherwise, we will wait a // maximum of 5 seconds for all repositories to be queried and // respond. 5 seconds is deemed to be the reasonable time to wait // when querying the repository manager in the context of something // like autocomplete var timeout = parseInt( params.timeout, 10 ) || this.settings.timeout; timer = setTimeout( function() { numOpenCallbacks = 0; that.queryCallback( callback, allitems, allmetainfo, timer ); }, timeout ); // If repositoryId or a list of repository ids, is not specified in // the params object, then we will query all registered // repositories if ( params.repositoryId ) { repositories.push( this.getRepository( params.repositoryId ) ); } else { repositories = this.repositories; } j = repositories.length; var repoQueue = []; // We need to know how many callbacks we will open before invoking // the query method on each, so that as soon as the first one does // callback, the correct number of open callbacks will be available // to check. for ( i = 0; i < j; ++i ) { repo = repositories[ i ]; if ( typeof repo.query === 'function' ) { ++numOpenCallbacks; repoQueue.push( repo ); } } j = repoQueue.length; for ( i = 0; i < j; ++i ) { repo = repoQueue[ i ]; repo.query( params, function() { processResults.apply( repo, arguments ); } ); } // If none of the repositories implemented the query method, then // don't wait for the timeout, simply report to the client if ( numOpenCallbacks === 0 ) { this.queryCallback( callback, allitems, allmetainfo, timer ); } }, /** * Passes all the results we have collected to the client through the * callback it specified * * @param {Function} callback - Callback specified by client when * invoking the query method * @param {Array} items - Results, collected from all repositories * @param {Object} metainfo - optional object containing metainfo * @param {Timer} timer - We need to clear this timer * @return void * @hide */ queryCallback: function( callback, items, metainfo, timer ) { if ( timer ) { clearTimeout( timer ); timer = undefined; } // TODO: Implement sorting based on repository specification // sort items by weight //items.sort( function( a, b ) { // return ( b.weight || 0 ) - ( a.weight || 0 ); //} ); // prepare result data for the JSON Reader var result = { items : items, results : items.length }; if ( metainfo ) { result.numItems = metainfo.numItems; result.hasMoreItems = metainfo.hasMoreItems; } callback.call( this, result ); }, /** * @todo: This method needs to be covered with some unit tests * * Returns children items. (see query for an example) * @param {Object} params - object with properties *
    *
  • objectTypeFilter : array (optional)
    Object types that will be returned.
  • *
  • filter : array (optional)
    Attributes that will be returned.
  • *
  • inFolderId : boolean (optional)
    This indicates whether or not a candidate object is a child-object of the folder object identified by the given inFolderId (objectId).
  • *
  • orderBy : array (optional)
    ex. [{lastModificationDate:’DESC’, name:’ASC’}]
  • *
  • maxItems : Integer (optional)
    number items to return as result
  • *
  • skipCount : Integer (optional)
    This is tricky in a merged multi repository scenario
  • *
  • renditionFilter : array (optional)
    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
  • *
* @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. * "items" is an Array of objects construced with Document/Folder. * @void */ getChildren: function( params, callback ) { var that = this, repo, // The marged results, collected from repository responses allitems = [], // The set of repositories towhich we want to delegate work repositories = [], // A counting semaphore (working in reverse, ie: 0 means free) numOpenCallbacks = 0, // When this timer times-out, whatever has been collected in // allitems will be returned to the calling client, and // numOpenCallbacks will be reset to 0 timer, i, j, processResults = function( items ) { if ( numOpenCallbacks === 0 ) { return; } jQuery.merge( allitems, items ); if ( --numOpenCallbacks === 0 ) { that.getChildrenCallback( callback, allitems, timer ); } }; // If the inFolderId is the default id of 'aloha', then return all // registered repositories if ( params.inFolderId === 'aloha' ) { var repoFilter = params.repositoryFilter, hasRepoFilter = ( repoFilter && repoFilter.length ); j = this.repositories.length; for ( i = 0; i < j; ++i ) { repo = this.repositories[ i ]; if ( !hasRepoFilter || jQuery.inArray( repo.repositoryId, repoFilter ) > -1 ) { repositories.push( new Aloha.RepositoryFolder( { id : repo.repositoryId, name : repo.repositoryName, repositoryId : repo.repositoryId, type : 'repository', hasMoreItems : true } ) ); } } that.getChildrenCallback( callback, repositories, null ); return; } else { repositories = this.repositories; } var timeout = parseInt( params.timeout, 10 ) || this.settings.timeout; timer = setTimeout( function() { numOpenCallbacks = 0; that.getChildrenCallback( callback, allitems, timer ); }, timeout ); j = repositories.length; for ( i = 0; i < j; ++i ) { repo = repositories[ i ]; if ( typeof repo.getChildren === 'function' ) { ++numOpenCallbacks; repo.getChildren( params, function() { processResults.apply( repo, arguments ); } ); } } if ( numOpenCallbacks === 0 ) { this.getChildrenCallback( callback, allitems, timer ); } }, /** * Returns results for getChildren to calling client * * @return void * @hide */ getChildrenCallback: function( callback, items, timer ) { if ( timer ) { clearTimeout( timer ); timer = undefined; } callback.call( this, items ); }, /** * @fixme: Not tested, but the code for this function does not seem to * compute repository.makeClean will be undefined * * @todo: Rewrite this function header comment so that is clearer * * Pass an object, which represents an marked repository to corresponding * repository, so that it can make the content clean (prepare for saving) * * @param {jQuery} obj - representing an editable * @return void */ makeClean: function( obj ) { // iterate through all registered repositories var that = this, repository = {}, i = 0, j = that.repositories.length; // find all repository tags obj.find( '[data-gentics-aloha-repository=' + this.prefix + ']' ) .each( function() { for ( ; i < j; ++i ) { repository.makeClean( obj ); } console.debug( that, 'Passing contents of HTML Element with id { ' + this.attr( 'id' ) + ' } for cleaning to repository { ' + repository.repositoryId + ' }' ); repository.makeClean( this ); } ); }, /** * Marks an object as repository of this type and with this item.id. * Objects can be any DOM objects as A, SPAN, ABBR, etc. or * special objects such as aloha-aloha_block elements. * This method marks the target obj with two private attributes: * (see http://dev.w3.org/html5/spec/elements.html#embedding-custom-non-visible-data) * * data-gentics-aloha-repository: stores the repositoryId * * data-gentics-aloha-object-id: stores the object.id * * @param {DOMObject} obj - DOM object to mark * @param {Aloha.Repository.Object} item - the item which is applied to obj, * if set to null, the data-GENTICS-... attributes are removed * @return void */ markObject: function( obj, item ) { if ( !obj ) { return; } if ( item ) { var repository = this.getRepository( item.repositoryId ); if ( repository ) { jQuery( obj ).attr( { 'data-gentics-aloha-repository' : item.repositoryId, 'data-gentics-aloha-object-id' : item.id } ); repository.markObject( obj, item ); } else { console.error( this, 'Trying to apply a repository { ' + item.name + ' } to an object, but item has no repositoryId.' ); } } else { jQuery( obj ) .removeAttr( 'data-gentics-aloha-repository' ) .removeAttr( 'data-gentics-aloha-object-id' ); } }, /** * Get the object for which the given DOM object is marked from the * repository. * * @param {DOMObject} obj - DOM object which probably is marked * @param {Function} callback - callback function */ getObject: function( obj, callback ) { var that = this, $obj = jQuery( obj ), repository = this.getRepository( $obj.attr( 'data-gentics-aloha-repository' ) ), itemId = $obj.attr( 'data-gentics-aloha-object-id' ); if ( repository && itemId ) { // initialize the item cache (per repository) if not already done this.itemCache = this.itemCache || []; this.itemCache[ repository.repositoryId ] = this.itemCache[ repository.repositoryId ] || []; // when the item is cached, we just call the callback method if ( this.itemCache[ repository.repositoryId ][ itemId ] ) { callback.call( this, [ this.itemCache[ repository.repositoryId ][ itemId ] ] ); } else { // otherwise we get the object from the repository repository.getObjectById( itemId, function( items ) { // make sure the item is in the cache (for subsequent calls) that.itemCache[ repository.repositoryId ][ itemId ] = items[0]; callback.call( this, items ); } ); } } }, /** * @return {String} name of repository manager object */ toString: function() { return 'repositorymanager'; } } ); Aloha.RepositoryManager = new Aloha.RepositoryManager(); // We return the constructor, not the instance of Aloha.RepositoryManager return Aloha.RepositoryManager; } );