[CORD-873] CRUD for Core and Service model from Chameleon
Change-Id: I45c533feba6720b82de3681d862773047e7fd6f8
diff --git a/src/app/datasources/helpers/model-discoverer.service.ts b/src/app/datasources/helpers/model-discoverer.service.ts
new file mode 100644
index 0000000..deaed33
--- /dev/null
+++ b/src/app/datasources/helpers/model-discoverer.service.ts
@@ -0,0 +1,258 @@
+// TODO test me hard!!!
+
+import * as _ from 'lodash';
+import {IXosModeldefsService, IXosModeldef, IXosModelDefsField, IXosModelDefsRelation} from '../rest/modeldefs.rest';
+import {IXosTableCfg} from '../../core/table/table';
+import {IXosFormCfg} from '../../core/form/form';
+import {IXosNavigationService} from '../../core/services/navigation';
+import {IXosConfigHelpersService} from '../../core/services/helpers/config.helpers';
+import {IXosRuntimeStatesService, IXosState} from '../../core/services/runtime-states';
+import {IXosModelStoreService} from '../stores/model.store';
+
+export interface IXosModel {
+  name: string; // the model name
+  app: string; // the service to wich it belong
+  fields: IXosModelDefsField[];
+  relations?: IXosModelDefsRelation[];
+  backendUrl?: string; // the api endpoint
+  clientUrl?: string; // the view url
+  tableCfg?: IXosTableCfg;
+  formCfg?: IXosFormCfg;
+}
+
+// Service
+export interface IXosModelDiscovererService {
+  discover(): ng.IPromise<boolean>;
+  get(modelName: string): IXosModel;
+}
+
+export class XosModelDiscovererService implements IXosModelDiscovererService {
+  static $inject = [
+    '$log',
+    '$q',
+    'XosModelDefs',
+    'ConfigHelpers',
+    'XosRuntimeStates',
+    'XosNavigationService',
+    'XosModelStore'
+  ];
+  private xosModels: IXosModel[] = []; // list of augmented model definitions;
+  private xosServices: string[] = []; // list of loaded services
+
+  constructor (
+    private $log: ng.ILogService,
+    private $q: ng.IQService,
+    private XosModelDefs: IXosModeldefsService,
+    private ConfigHelpers: IXosConfigHelpersService,
+    private XosRuntimeStates: IXosRuntimeStatesService,
+    private XosNavigationService: IXosNavigationService,
+    private XosModelStore: IXosModelStoreService
+  ) {
+  }
+
+  public get(modelName: string): IXosModel|null {
+    return _.find(this.xosModels, m => m.name === modelName);
+  }
+
+  public discover() {
+    const d = this.$q.defer();
+
+    this.XosModelDefs.get()
+      .then((modelsDef: IXosModeldef[]) => {
+
+        const pArray = [];
+        _.forEach(modelsDef, (model: IXosModeldef) => {
+          let p = this.cacheModelEntries(model)
+            .then(model => {
+              return this.addState(model);
+            })
+            .then(model => {
+              return this.addNavItem(model);
+            })
+            .then(model => {
+              return this.getTableCfg(model);
+            })
+            .then(model => {
+              return this.getFormCfg(model);
+            })
+            .then(model => {
+              return this.storeModel(model);
+            })
+            .then(model => {
+              this.$log.debug(`[XosModelDiscovererService] Model ${model.name} stored`);
+              return this.$q.resolve();
+            })
+            .catch(err => {
+              this.$log.error(`[XosModelDiscovererService] Model ${model.name} NOT stored`);
+              // NOTE why this does not resolve?????
+              // return this.$q.resolve();
+              return this.$q.reject();
+            });
+            pArray.push(p);
+        });
+        this.$q.all(pArray)
+          .then(() => {
+            d.resolve(true);
+            this.$log.info('[XosModelDiscovererService] All models loaded!');
+          })
+          .catch(() => {
+            d.resolve(false);
+          });
+      });
+    return d.promise;
+  }
+
+  private serviceNameFromAppName(appName: string): string {
+    return appName.replace('services.', '');
+  }
+
+  private stateNameFromModel(model: IXosModel): string {
+    return `xos.${this.serviceNameFromAppName(model.app)}.${model.name.toLowerCase()}`;
+  }
+
+  private getParentStateFromModel(model: IXosModel): string {
+    let parentState: string;
+    if (model.app === 'core') {
+      parentState = 'xos.core';
+    }
+    else {
+      const serviceName = this.addService(model);
+      parentState = `xos.${serviceName}`;
+    }
+    return parentState;
+  }
+
+  private getApiUrlFromModel(model: IXosModel): string {
+    if (model.app === 'core') {
+      return `/core/${this.ConfigHelpers.pluralize(model.name.toLowerCase())}`;
+    }
+    else {
+      const serviceName = this.serviceNameFromAppName(model.app);
+      return `/${serviceName}/${this.ConfigHelpers.pluralize(model.name.toLowerCase())}`;
+    }
+  }
+
+  // add a service state and navigation item if it is not already there
+  private addService(model: IXosModel): string {
+    const serviceName: string = this.serviceNameFromAppName(model.app);
+    if (!_.find(this.xosServices, n => n === serviceName)) {
+      const serviceState = {
+        url: serviceName,
+        parent: 'xos',
+        abstract: true,
+        template: '<div ui-view></div>'
+      };
+      this.XosRuntimeStates.addState(`xos.${serviceName}`, serviceState);
+
+      this.XosNavigationService.add({
+        label: this.ConfigHelpers.toLabel(serviceName),
+        state: `xos.${serviceName}`,
+      });
+      this.xosServices.push(serviceName);
+    }
+    return serviceName;
+  }
+
+  private addState(model: IXosModel): ng.IPromise<IXosModel> {
+    const d = this.$q.defer();
+    const clientUrl = `/${this.ConfigHelpers.pluralize(model.name.toLowerCase())}/:id?`;
+    const state: IXosState = {
+      parent: this.getParentStateFromModel(model),
+      url: clientUrl,
+      params: {
+        id: null
+      },
+      data: {
+        model: model.name
+      },
+      component: 'xosCrud',
+    };
+    this.XosRuntimeStates.addState(
+      this.stateNameFromModel(model),
+      state
+    );
+
+    // extend model
+    model.clientUrl = `${this.serviceNameFromAppName(model.app)}${clientUrl}`;
+
+    d.resolve(model);
+    return d.promise;
+  }
+
+  private addNavItem(model: IXosModel): ng.IPromise<IXosModel> {
+    const d = this.$q.defer();
+
+    const stateName = this.stateNameFromModel(model);
+
+    const parentState: string = this.getParentStateFromModel(model);
+
+    this.XosNavigationService.add({
+      label: this.ConfigHelpers.pluralize(model.name),
+      state: stateName,
+      parent: parentState
+    });
+
+    d.resolve(model);
+
+    return d.promise;
+  }
+
+  private cacheModelEntries(model: IXosModel): ng.IPromise<IXosModel> {
+    const d = this.$q.defer();
+
+    let called = false;
+    const apiUrl = this.getApiUrlFromModel(model);
+    this.XosModelStore.query(model.name, apiUrl)
+      .subscribe(
+        () => {
+          // skipping the first response as the observable gets created as an empty array
+          if (called) {
+            return d.resolve(model);
+          }
+          called = true;
+        },
+        err => {
+          d.reject(err);
+        }
+      );
+
+    return d.promise;
+  }
+
+  private getTableCfg(model: IXosModel): ng.IPromise<IXosModel> {
+
+    const d = this.$q.defer();
+
+    const stateUrl = this.stateNameFromModel(model);
+
+    model.tableCfg = this.ConfigHelpers.modelToTableCfg(model, stateUrl);
+
+    d.resolve(model);
+
+    return d.promise;
+  }
+
+  private getFormCfg(model: IXosModel): ng.IPromise<IXosModel> {
+
+    const d = this.$q.defer();
+
+    model.formCfg = this.ConfigHelpers.modelToFormCfg(model);
+
+    d.resolve(model);
+
+    return d.promise;
+  }
+
+  private storeModel(model: IXosModel): ng.IPromise<IXosModel> {
+
+    const d = this.$q.defer();
+
+    if (!_.find(this.xosModels, m => m.name === model.name)) {
+      this.xosModels.push(model);
+    }
+
+    d.resolve(model);
+
+    return d.promise;
+  }
+}
diff --git a/src/app/datasources/helpers/search.service.ts b/src/app/datasources/helpers/search.service.ts
index 5565b2c..8cee4d1 100644
--- a/src/app/datasources/helpers/search.service.ts
+++ b/src/app/datasources/helpers/search.service.ts
@@ -1,8 +1,8 @@
 import * as _ from 'lodash';
 import {IXosNavigationService} from '../../core/services/navigation';
-import {IXosState} from '../../../index';
 import {IXosModelStoreService} from '../stores/model.store';
 import {IXosConfigHelpersService} from '../../core/services/helpers/config.helpers';
+import {IXosState} from '../../core/services/runtime-states';
 
 export interface IXosSearchResult {
   label: string;
@@ -15,16 +15,18 @@
 }
 
 export class SearchService {
-  static $inject = ['$rootScope', 'NavigationService', 'ModelStore', 'ConfigHelpers'];
+  static $inject = ['$rootScope', '$log', 'XosNavigationService', 'XosModelStore', 'ConfigHelpers'];
   private states: IXosState[];
 
   constructor (
     private $rootScope: ng.IScope,
+    private $log: ng.ILogService,
     private NavigationService: IXosNavigationService,
-    private ModelStore: IXosModelStoreService,
+    private XosModelStore: IXosModelStoreService,
     private ConfigHelpers: IXosConfigHelpersService
   ) {
     this.$rootScope.$on('xos.core.modelSetup', () => {
+      this.$log.info(`[XosSearchService] Loading views`);
       this.states = this.NavigationService.query().reduce((list, state) => {
         // if it does not have child (otherwise it is abstract)
         if (!state.children || state.children.length === 0) {
@@ -39,18 +41,21 @@
         return list;
       }, []);
       this.states = _.uniqBy(this.states, 'state');
+      this.$log.debug(`[XosSearchService] Views Loaded: `, this.states);
     });
   }
 
   public search(query: string): IXosSearchResult[] {
+    this.$log.info(`[XosSearchService] Searching for: ${query}`);
     const routes: IXosSearchResult[] = _.filter(this.states, s => {
       return s.label.toLowerCase().indexOf(query) > -1;
     }).map(r => {
       r.type = 'View';
       return r;
     });
-
-    const models = _.map(this.ModelStore.search(query), m => {
+    // TODO XosModelStore.search throws an error,
+    // probably there is something wrong saved in the cache!!
+    const models = _.map(this.XosModelStore.search(query), m => {
       return {
         label: m.humanReadableName ? m.humanReadableName : m.name,
         state: this.ConfigHelpers.stateWithParamsForJs(m.modelName, m),
diff --git a/src/app/datasources/helpers/store.helpers.spec.ts b/src/app/datasources/helpers/store.helpers.spec.ts
index 2cb1eaf..9f4370c 100644
--- a/src/app/datasources/helpers/store.helpers.spec.ts
+++ b/src/app/datasources/helpers/store.helpers.spec.ts
@@ -44,11 +44,9 @@
   it('should convert a core model name in an URL', () => {
     expect(service.urlFromCoreModel('Slice')).toBe('/core/slices');
     expect(service.urlFromCoreModel('Xos')).toBe('/core/xoses');
-
-    // handling exceptions
-    expect(service.urlFromCoreModel('SiteRole')).toBe('/core/site_roles');
-    expect(service.urlFromCoreModel('SliceRole')).toBe('/core/slice_roles');
-    expect(service.urlFromCoreModel('SlicePrivilege')).toBe('/core/slice_privileges');
+    expect(service.urlFromCoreModel('SiteRole')).toBe('/core/siteroles');
+    expect(service.urlFromCoreModel('SliceRole')).toBe('/core/sliceroles');
+    expect(service.urlFromCoreModel('SlicePrivilege')).toBe('/core/sliceprivileges');
   });
 
   describe('when updating a collection', () => {
diff --git a/src/app/datasources/helpers/store.helpers.ts b/src/app/datasources/helpers/store.helpers.ts
index 7792590..c26e235 100644
--- a/src/app/datasources/helpers/store.helpers.ts
+++ b/src/app/datasources/helpers/store.helpers.ts
@@ -9,7 +9,7 @@
   updateCollection(event: IWSEvent, subject: BehaviorSubject<any>): BehaviorSubject<any>;
 }
 
-export class StoreHelpers {
+export class StoreHelpers implements IStoreHelpersService {
   static $inject = ['ModelRest'];
 
   constructor (
@@ -18,15 +18,7 @@
   }
 
   public urlFromCoreModel(name: string): string {
-    switch (name) {
-      // FIXME handling exceptions, understand why these 3 endpoints are autogenerated with an _f
-      case 'SiteRole':
-      case 'SliceRole':
-      case 'SlicePrivilege':
-        return `/core/${pluralize(name.split(/(?=[A-Z])/).map(w => w.toLowerCase()).join('_'))}`;
-      default:
-        return `/core/${pluralize(name.toLowerCase())}`;
-    }
+    return `/core/${pluralize(name.toLowerCase())}`;
   }
 
   public updateCollection(event: IWSEvent, subject: BehaviorSubject<any>): BehaviorSubject<any> {
diff --git a/src/app/datasources/index.ts b/src/app/datasources/index.ts
index 04c58db..80d348a 100644
--- a/src/app/datasources/index.ts
+++ b/src/app/datasources/index.ts
@@ -1,22 +1,27 @@
 import {ModelRest} from './rest/model.rest';
 import {AuthService} from './rest/auth.rest';
 import {WebSocketEvent} from './websocket/global';
-import {ModelStore} from './stores/model.store';
+import {XosModelStore} from './stores/model.store';
 import {StoreHelpers} from './helpers/store.helpers';
 import {SynchronizerStore} from './stores/synchronizer.store';
-import {ModeldefsService} from './rest/modeldefs.rest';
+import {XosModeldefsService} from './rest/modeldefs.rest';
 import {xosCore} from '../core/index';
 import {SearchService} from './helpers/search.service';
+import {XosModelDiscovererService} from './helpers/model-discoverer.service';
 
 export const xosDataSources = 'xosDataSources';
 
 angular
-  .module('xosDataSources', ['ngCookies', 'ngResource', xosCore])
+  .module(xosDataSources, ['ngCookies', 'ngResource', xosCore])
   .service('ModelRest', ModelRest)
   .service('AuthService', AuthService)
   .service('WebSocket', WebSocketEvent)
   .service('StoreHelpers', StoreHelpers)
   .service('SynchronizerStore', SynchronizerStore)
-  .service('ModelStore', ModelStore)
-  .service('ModelDefs', ModeldefsService)
+  .service('XosModelStore', XosModelStore)
+  .service('XosModelDefs', XosModeldefsService)
   .service('SearchService', SearchService);
+
+angular
+  .module(xosDataSources)
+  .service('XosModelDiscoverer', XosModelDiscovererService);
diff --git a/src/app/datasources/rest/auth.rest.ts b/src/app/datasources/rest/auth.rest.ts
index 2979bf1..edc2a65 100644
--- a/src/app/datasources/rest/auth.rest.ts
+++ b/src/app/datasources/rest/auth.rest.ts
@@ -7,9 +7,9 @@
 
 export interface IAuthResponseData extends IHttpPromiseCallbackArg<any> {
   data: {
-    user: string;
-    xoscsrftoken: string;
-    xossessionid: string;
+    // user: string;
+    // xoscsrftoken: string;
+    sessionid: string;
   };
 }
 
@@ -37,12 +37,12 @@
 
   public login(data: IAuthRequestData): Promise<any> {
     const d = this.$q.defer();
-    this.$http.post(`${this.AppConfig.apiEndpoint}/utility/login/`, data)
+    this.$http.post(`${this.AppConfig.apiEndpoint}/utility/login`, data)
       .then((res: IAuthResponseData) => {
-        this.$cookies.put('xoscsrftoken', res.data.xoscsrftoken, {path: '/'});
-        this.$cookies.put('xossessionid', res.data.xossessionid, {path: '/'});
-        this.$cookies.put('xosuser', res.data.user, {path: '/'});
-        res.data.user = JSON.parse(res.data.user);
+        // this.$cookies.put('xoscsrftoken', res.data.xoscsrftoken, {path: '/'});
+        this.$cookies.put('sessionid', res.data.sessionid, {path: '/'});
+        // this.$cookies.put('xosuser', res.data.user, {path: '/'});
+        // res.data.user = JSON.parse(res.data.user);
         d.resolve(res.data);
       })
       .catch(e => {
@@ -53,9 +53,9 @@
 
   public logout(): Promise<any> {
     const d = this.$q.defer();
-    this.$http.post(`${this.AppConfig.apiEndpoint}/utility/logout/`, {
-      xoscsrftoken: this.$cookies.get('xoscsrftoken'),
-      xossessionid: this.$cookies.get('xossessionid')
+    this.$http.post(`${this.AppConfig.apiEndpoint}/utility/logout`, {
+      // xoscsrftoken: this.$cookies.get('xoscsrftoken'),
+      // sessionid: this.$cookies.get('sessionid')
     })
       .then(() => {
         this.clearUser();
@@ -68,9 +68,9 @@
   }
 
   public clearUser(): void {
-    this.$cookies.remove('xoscsrftoken', {path: '/'});
-    this.$cookies.remove('xossessionid', {path: '/'});
-    this.$cookies.remove('xosuser', {path: '/'});
+    // this.$cookies.remove('xoscsrftoken', {path: '/'});
+    this.$cookies.remove('sessionid', {path: '/'});
+    // this.$cookies.remove('xosuser', {path: '/'});
   }
 
   public getUser(): IXosUser {
@@ -82,8 +82,8 @@
   }
 
   public isAuthenticated(): boolean {
-    const token = this.$cookies.get('xoscsrftoken');
-    const session = this.$cookies.get('xossessionid');
-    return angular.isDefined(token) && angular.isDefined(session);
+    // const token = this.$cookies.get('xoscsrftoken');
+    const session = this.$cookies.get('sessionid');
+    return angular.isDefined(session);
   }
 }
diff --git a/src/app/datasources/rest/model.rest.ts b/src/app/datasources/rest/model.rest.ts
index 12590af..4aa862c 100644
--- a/src/app/datasources/rest/model.rest.ts
+++ b/src/app/datasources/rest/model.rest.ts
@@ -16,7 +16,15 @@
 
   public getResource(url: string): ng.resource.IResourceClass<ng.resource.IResource<any>> {
     const resource: angular.resource.IResourceClass<any> = this.$resource(`${this.AppConfig.apiEndpoint}${url}/:id/`, {id: '@id'}, {
-      update: { method: 'PUT' }
+      update: { method: 'PUT' },
+      query: {
+        method: 'GET',
+        isArray: true,
+        transformResponse: (res) => {
+          // FIXME chameleon return everything inside "items"
+          return res.items ? res.items : res;
+        }
+      }
     });
 
     resource.prototype.$save = function() {
diff --git a/src/app/datasources/rest/modeldefs.rest.spec.ts b/src/app/datasources/rest/modeldefs.rest.spec.ts
index 9dc4025..677463b 100644
--- a/src/app/datasources/rest/modeldefs.rest.spec.ts
+++ b/src/app/datasources/rest/modeldefs.rest.spec.ts
@@ -3,9 +3,9 @@
 import 'angular-resource';
 import 'angular-cookies';
 import {xosDataSources} from '../index';
-import {IModeldefsService} from './modeldefs.rest';
+import {IXosModeldefsService} from './modeldefs.rest';
 
-let service: IModeldefsService;
+let service: IXosModeldefsService;
 let httpBackend: ng.IHttpBackendService;
 let $scope;
 
@@ -14,7 +14,7 @@
   websocketClient: 'http://xos-test:3000'
 };
 
-describe('The ModelDefs service', () => {
+describe('The XosModelDefs service', () => {
 
   beforeEach(angular.mock.module(xosDataSources));
 
@@ -26,14 +26,13 @@
     angular.mock.module(xosDataSources);
   });
 
-
   beforeEach(angular.mock.inject((
-    ModelDefs: IModeldefsService,
+    XosModelDefs: IXosModeldefsService,
     $httpBackend: ng.IHttpBackendService,
     _$resource_: ng.resource.IResourceService,
     _$rootScope_: ng.IRootScopeService
   ) => {
-    service = ModelDefs;
+    service = XosModelDefs;
     httpBackend = $httpBackend;
     $scope = _$rootScope_;
   }));
diff --git a/src/app/datasources/rest/modeldefs.rest.ts b/src/app/datasources/rest/modeldefs.rest.ts
index d927f8c..fdb4b99 100644
--- a/src/app/datasources/rest/modeldefs.rest.ts
+++ b/src/app/datasources/rest/modeldefs.rest.ts
@@ -1,17 +1,35 @@
 import {IXosModelDefsField} from '../../core/services/helpers/config.helpers';
 import {IXosAppConfig} from '../../../index';
 
-export interface IModeldef {
-  fields: IXosModelDefsField[];
-  relations?: string[];
+// Models interfaces
+export interface IXosModelDefsField {
   name: string;
+  type: string;
+  validators?: any;
+  hint?: string;
+  relation?: {
+    model: string;
+    type: string;
+  };
 }
 
-export interface IModeldefsService {
-  get(): Promise<IModeldef[]>;
+export interface IXosModelDefsRelation {
+  model: string; // model name
+  type: string; // relation type
 }
 
-export class ModeldefsService {
+export interface IXosModeldef {
+  fields: IXosModelDefsField[];
+  relations?: IXosModelDefsRelation[];
+  name: string;
+  app: string;
+}
+
+export interface IXosModeldefsService {
+  get(): Promise<IXosModeldef[]>;
+}
+
+export class XosModeldefsService implements IXosModeldefsService {
 
   static $inject = ['$http', '$q', 'AppConfig'];
 
@@ -24,9 +42,9 @@
 
   public get(): Promise<any> {
     const d = this.$q.defer();
-    this.$http.get(`${this.AppConfig.apiEndpoint}/utility/modeldefs/`)
-      .then((res) => {
-        d.resolve(res.data);
+    this.$http.get(`${this.AppConfig.apiEndpoint}/modeldefs`)
+      .then((res: any) => {
+        d.resolve(res.data.items);
       })
       .catch(e => {
         d.reject(e);
diff --git a/src/app/datasources/stores/model.store.spec.ts b/src/app/datasources/stores/model.store.spec.ts
index 7173658..533a53d 100644
--- a/src/app/datasources/stores/model.store.spec.ts
+++ b/src/app/datasources/stores/model.store.spec.ts
@@ -1,7 +1,7 @@
 import * as angular from 'angular';
 import 'angular-mocks';
 import 'angular-resource';
-import {IXosModelStoreService, ModelStore} from './model.store';
+import {IXosModelStoreService, XosModelStore} from './model.store';
 import {Subject} from 'rxjs';
 import {IWSEvent} from '../websocket/global';
 import {StoreHelpers} from '../helpers/store.helpers';
@@ -46,7 +46,7 @@
       .service('WebSocket', MockWs)
       .service('StoreHelpers', StoreHelpers) // TODO mock
       .service('ModelRest', ModelRest) // TODO mock
-      .service('ModelStore', ModelStore)
+      .service('XosModelStore', XosModelStore)
       .service('ConfigHelpers', ConfigHelpers) // TODO mock
       .service('AuthService', AuthService)
       .constant('AppConfig', MockAppCfg);
@@ -55,12 +55,12 @@
   });
 
   beforeEach(angular.mock.inject((
-    ModelStore: IXosModelStoreService,
+    XosModelStore: IXosModelStoreService,
     $httpBackend: ng.IHttpBackendService,
     _$rootScope_: ng.IRootScopeService,
     _WebSocket_: any
   ) => {
-    service = ModelStore;
+    service = XosModelStore;
     httpBackend = $httpBackend;
     $scope = _$rootScope_;
     WebSocket = _WebSocket_;
diff --git a/src/app/datasources/stores/model.store.ts b/src/app/datasources/stores/model.store.ts
index 291e7c0..4958015 100644
--- a/src/app/datasources/stores/model.store.ts
+++ b/src/app/datasources/stores/model.store.ts
@@ -6,11 +6,11 @@
 import {IStoreHelpersService} from '../helpers/store.helpers';
 
 export interface  IXosModelStoreService {
-  query(model: string): Observable<any>;
+  query(model: string, apiUrl?: string): Observable<any>;
   search(modelName: string): any[];
 }
 
-export class ModelStore implements IXosModelStoreService {
+export class XosModelStore implements IXosModelStoreService {
   static $inject = ['$log', 'WebSocket', 'StoreHelpers', 'ModelRest'];
   private _collections: any; // NOTE contains a map of {model: BehaviourSubject}
   constructor(
@@ -22,59 +22,76 @@
     this._collections = {};
   }
 
-  public query(model: string): Observable<any> {
+  public query(modelName: string, apiUrl: string): Observable<any> {
     // if there isn't already an observable for that item
-    if (!this._collections[model]) {
-      this._collections[model] = new BehaviorSubject([]); // NOTE maybe this can be created when we get response from the resource
-      this.loadInitialData(model);
+    if (!this._collections[modelName]) {
+      this._collections[modelName] = new BehaviorSubject([]); // NOTE maybe this can be created when we get response from the resource
+      this.loadInitialData(modelName, apiUrl);
     }
 
     this.webSocket.list()
-      .filter((e: IWSEvent) => e.model === model)
+      .filter((e: IWSEvent) => e.model === modelName)
       .subscribe(
         (event: IWSEvent) => {
-          this.storeHelpers.updateCollection(event, this._collections[model]);
+          this.storeHelpers.updateCollection(event, this._collections[modelName]);
         },
         err => console.error
       );
 
-    return this._collections[model].asObservable();
+    return this._collections[modelName].asObservable();
   }
 
   public search(modelName: string): any[] {
-    return _.reduce(Object.keys(this._collections), (results, k) => {
-      // console.log(k, this._collections[k].value)
-      const partialRes = _.filter(this._collections[k].value, i => {
-        if (i.humanReadableName) {
-          return i.humanReadableName.toLowerCase().indexOf(modelName) > -1;
+    try {
+      const res =  _.reduce(Object.keys(this._collections), (results, k) => {
+        let partialRes;
+        // NOTE wrapped in a try catch as some subject may be errored, due to not available REST endpoint
+        try {
+          partialRes = _.filter(this._collections[k].value, i => {
+            if (i && i.humanReadableName) {
+              return i.humanReadableName.toLowerCase().indexOf(modelName) > -1;
+            }
+            else if (i && i.name) {
+              return i.name.toLowerCase().indexOf(modelName) > -1;
+            }
+            return false;
+          });
+        } catch (e) {
+          partialRes = [];
         }
-        else if (i.name) {
-          return i.name.toLowerCase().indexOf(modelName) > -1;
-        }
-        return false;
-      })
-        .map(m => {
+        partialRes.map(m => {
           m.modelName = k;
           return m;
         });
-      return results.concat(partialRes);
-    }, []);
+        return results.concat(partialRes);
+      }, []);
+      return res;
+    } catch (e) {
+      return [];
+    }
   }
 
   public get(model: string, id: number) {
     // TODO implement a get method
   }
 
-  private loadInitialData(model: string) {
-    // NOTE check what is the correct pattern to pluralize this
-    const endpoint = this.storeHelpers.urlFromCoreModel(model);
-    this.ModelRest.getResource(endpoint).query().$promise
+  private loadInitialData(model: string, apiUrl?: string) {
+    // TODO provide alway the apiUrl togheter with the query() params
+    if (!angular.isDefined(apiUrl)) {
+      // NOTE check what is the correct pattern to pluralize this
+      apiUrl = this.storeHelpers.urlFromCoreModel(model);
+    }
+    this.ModelRest.getResource(apiUrl).query().$promise
       .then(
         res => {
           this._collections[model].next(res);
         })
       .catch(
-        err => this.$log.log(`Error retrieving ${model}`, err)
+        // TODO understand how to send an error to an observable
+        err => {
+          this._collections[model].error(err);
+          // this.$log.log(`Error retrieving ${model}`, err);
+        }
       );
   }
 }