From 6393ecbff5018c014b0c0d92963ff23c0da2b95d Mon Sep 17 00:00:00 2001 From: Denys Vuika Date: Thu, 15 Jun 2017 16:30:20 +0100 Subject: [PATCH] [ADF-814] application configuration service (#1969) * app configuration service * api improvements and readme update * extend readme and add defaults * unit tests for app config service --- demo-shell-ng2/app.config.json | 7 ++ demo-shell-ng2/config/webpack.common.js | 3 + ng2-components/ng2-alfresco-core/README.md | 71 +++++++++++++++ ng2-components/ng2-alfresco-core/index.ts | 16 +++- .../src/services/app-config.service.spec.ts | 91 +++++++++++++++++++ .../src/services/app-config.service.ts | 88 ++++++++++++++++++ .../ng2-alfresco-core/src/services/index.ts | 1 + 7 files changed, 273 insertions(+), 4 deletions(-) create mode 100644 demo-shell-ng2/app.config.json create mode 100644 ng2-components/ng2-alfresco-core/src/services/app-config.service.spec.ts create mode 100644 ng2-components/ng2-alfresco-core/src/services/app-config.service.ts diff --git a/demo-shell-ng2/app.config.json b/demo-shell-ng2/app.config.json new file mode 100644 index 0000000000..0e8f560881 --- /dev/null +++ b/demo-shell-ng2/app.config.json @@ -0,0 +1,7 @@ +{ + "ecmHost": "http://localhost:3000/ecm", + "bpmHost": "http://localhost:3000/bpm", + "application": { + "name": "Alfresco" + } +} diff --git a/demo-shell-ng2/config/webpack.common.js b/demo-shell-ng2/config/webpack.common.js index 267c816ffd..658d4878a4 100644 --- a/demo-shell-ng2/config/webpack.common.js +++ b/demo-shell-ng2/config/webpack.common.js @@ -101,6 +101,9 @@ module.exports = { from: '**/*.json', to: 'resources/i18n' }, + { + from: 'app.config.json' + }, { from: 'favicon-96x96.png' }, diff --git a/ng2-components/ng2-alfresco-core/README.md b/ng2-components/ng2-alfresco-core/README.md index 8a0f598115..84c4e79e55 100644 --- a/ng2-components/ng2-alfresco-core/README.md +++ b/ng2-components/ng2-alfresco-core/README.md @@ -22,6 +22,7 @@ npm install ng2-alfresco-core - [Upload](#upload-directive) - [Context Menu](#context-menu-directive) - Services + - [AppConfigService](#appconfigservice), application configuration - **LogService**, log service implementation - [NotificationService](#notification-service), Notification service implementation - [AlfrescoApiService](#alfresco-api-service), provides access to Alfresco JS API instance @@ -240,6 +241,76 @@ let api: any = this.apiService.getInstance(); api.nodes.addNode('-root-', body, {}); ``` +## AppConfigService + +The `AppConfigService` service provides support for loading and accessing global application configuration settings that you store on the server side in the form of a JSON file. + +> You may need this service when deploying your ADF-based application to production servers. +> There can be more than one server running web apps with different settings, like different addresses for Alfreco Content/Process services. +> Or there is a need to change global settings for all the clients. + +The service is already pre-configured to look for the "app.config.json" file in the application root address. + +That allows deploying ADF-based web applications to multiple servers together with different settings files, for example having development, staging or production environments. + +Example of the default settings file content: + +**app.config.json** + +```json +{ + "ecmHost": "http://localhost:3000/ecm", + "bpmHost": "http://localhost:3000/bpm", + "application": { + "name": "Alfresco" + } +} +``` + +Please note that settings above are default ones coming with the server. +You can override the values in your custom `app.config.json` file if needed. + +You can also change the path or name of the configuration file when importing the CoreModule in your main application. + +```ts +... +@NgModule({ + imports: [ + ... + CoreModule.forRoot({ + appConfigFile: 'app.production.config.json' + }) + ], + ... +} +export class AppModule { } +``` + +Below is a simple example of using the AppConfigService in practice. + +**app.component.ts** + +```ts +import { AppConfigService } from 'ng2-alfresco-core'; + +@Component({...}) +export class AppComponent { + + constructor(appConfig: AppConfigService) { + + // get nested properties by the path + console.log(appConfig.get('application.name')); + + // use generics for type safety + let version: number = appConfig.get('version'); + console.log(version); + } +} +``` + +You custom components can also benefit from the `AppConfigService`, +you can put an unlimited number of settings and optionally a nested JSON hierarchy. + ## Notification Service The Notification Service is implemented on top of the Angular 2 Material Design snackbar. diff --git a/ng2-components/ng2-alfresco-core/index.ts b/ng2-components/ng2-alfresco-core/index.ts index 56ddef5541..888ca8e5a9 100644 --- a/ng2-components/ng2-alfresco-core/index.ts +++ b/ng2-components/ng2-alfresco-core/index.ts @@ -22,6 +22,7 @@ import { CommonModule } from '@angular/common'; import { FormsModule, ReactiveFormsModule } from '@angular/forms'; import { TranslateModule, TranslateLoader } from 'ng2-translate/ng2-translate'; import { MaterialModule } from './src/material.module'; +import { AppConfigModule } from './src/services/app-config.service'; import { AdfToolbarComponent } from './src/components/toolbar/toolbar.component'; import { @@ -40,7 +41,8 @@ import { LogService, LogServiceMock, NotificationService, - ContentService + ContentService, + AppConfigService, InitAppConfigServiceProvider } from './src/services/index'; import { FileSizePipe } from './src/pipes/file-size.pipe'; @@ -98,7 +100,8 @@ export function createTranslateLoader(http: Http, logService: LogService) { useFactory: (createTranslateLoader), deps: [Http, LogService] }), - MaterialModule + MaterialModule, + AppConfigModule ], declarations: [ ...MATERIAL_DESIGN_DIRECTIVES, @@ -132,11 +135,16 @@ export function createTranslateLoader(http: Http, logService: LogService) { ] }) export class CoreModule { - static forRoot(): ModuleWithProviders { + static forRoot(opts: any = {}): ModuleWithProviders { + + const appConfigFile = opts.appConfigFile || 'app.config.json'; + return { ngModule: CoreModule, providers: [ - ...ALFRESCO_CORE_PROVIDERS + ...ALFRESCO_CORE_PROVIDERS, + AppConfigService, + InitAppConfigServiceProvider(appConfigFile) ] }; } diff --git a/ng2-components/ng2-alfresco-core/src/services/app-config.service.spec.ts b/ng2-components/ng2-alfresco-core/src/services/app-config.service.spec.ts new file mode 100644 index 0000000000..d930745c7b --- /dev/null +++ b/ng2-components/ng2-alfresco-core/src/services/app-config.service.spec.ts @@ -0,0 +1,91 @@ +/*! + * @license + * Copyright 2016 Alfresco Software, Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { TestBed, inject } from '@angular/core/testing'; +import { HttpModule, XHRBackend, Response, ResponseOptions } from '@angular/http'; +import { MockBackend, MockConnection } from '@angular/http/testing'; +import { AppConfigModule, AppConfigService } from './app-config.service'; + +describe('AppConfigService', () => { + + let appConfigService: AppConfigService; + const mockResponse = { + 'ecmHost': 'http://localhost:4000/ecm', + 'bpmHost': 'http://localhost:4000/ecm', + 'application': { + 'name': 'Custom Name' + } + }; + + beforeEach(() => { + TestBed.configureTestingModule({ + imports: [ + HttpModule, + AppConfigModule + ], + providers: [ + { provide: XHRBackend, useClass: MockBackend } + ] + }); + }); + + beforeEach( + inject([AppConfigService, XHRBackend], (appConfig: AppConfigService, mockBackend) => { + appConfigService = appConfig; + mockBackend.connections.subscribe((connection: MockConnection) => { + connection.mockRespond(new Response(new ResponseOptions({ + body: JSON.stringify(mockResponse) + }))); + }); + }) + ); + + it('should export service in the module', () => { + const service = TestBed.get(AppConfigService); + expect(service).toBeDefined(); + }); + + it('should load external settings', () => { + appConfigService.load().then(config => { + expect(config).toEqual(mockResponse); + }); + }); + + it('should retrieve settings', () => { + appConfigService.load().then(() => { + expect(appConfigService.get('ecmHost')).toBe(mockResponse.ecmHost); + expect(appConfigService.get('bpmHost')).toBe(mockResponse.bpmHost); + expect(appConfigService.get('application.name')).toBe(mockResponse.application.name); + }); + }); + + it('should use default config file', () => { + expect(appConfigService.configFile).toBeNull(); + appConfigService.load().then(() => { + expect(appConfigService.configFile).toBe('app.config.json'); + }); + }); + + it('should take custom config file', () => { + expect(appConfigService.configFile).toBeNull(); + + const name = 'custom.config.json'; + appConfigService.load(name).then(() => { + expect(appConfigService.configFile).toBe(name); + }); + }); +}); diff --git a/ng2-components/ng2-alfresco-core/src/services/app-config.service.ts b/ng2-components/ng2-alfresco-core/src/services/app-config.service.ts new file mode 100644 index 0000000000..398137568f --- /dev/null +++ b/ng2-components/ng2-alfresco-core/src/services/app-config.service.ts @@ -0,0 +1,88 @@ +/*! + * @license + * Copyright 2016 Alfresco Software, Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { Injectable, APP_INITIALIZER, NgModule, ModuleWithProviders } from '@angular/core'; +import { Http } from '@angular/http'; +import { ObjectUtils } from '../utils/object-utils'; + +@Injectable() +export class AppConfigService { + + private config: any = { + 'ecmHost': 'http://localhost:3000/ecm', + 'bpmHost': 'http://localhost:3000/bpm', + 'application': { + 'name': 'Alfresco' + } + }; + + configFile: string = null; + + constructor(private http: Http) {} + + get(key: string): T { + return ObjectUtils.getValue(this.config, key); + }; + + load(resource: string = 'app.config.json'): Promise { + console.log('Loading app config: ' + resource); + this.configFile = resource; + return new Promise((resolve, reject) => { + this.http.get(resource).subscribe( + data => { + this.config = Object.assign({}, this.config, data.json() || {}); + resolve(this.config); + }, + (err) => { + const errorMessage = `Error loading ${resource}`; + console.log(errorMessage); + resolve(this.config); + } + ); + }); + } +} + +export function InitAppConfigServiceProvider(resource: string): any { + return { + provide: APP_INITIALIZER, + useFactory: (configService: AppConfigService) => { + return () => configService.load(resource); + }, + deps: [ + AppConfigService + ], + multi: true + }; +} + +@NgModule({ + providers: [ + AppConfigService + ] +}) +export class AppConfigModule { + static forRoot(resource: string): ModuleWithProviders { + return { + ngModule: AppConfigModule, + providers: [ + AppConfigService, + InitAppConfigServiceProvider(resource) + ] + }; + } +} diff --git a/ng2-components/ng2-alfresco-core/src/services/index.ts b/ng2-components/ng2-alfresco-core/src/services/index.ts index 2f3d9d80e3..bb45242230 100644 --- a/ng2-components/ng2-alfresco-core/src/services/index.ts +++ b/ng2-components/ng2-alfresco-core/src/services/index.ts @@ -30,3 +30,4 @@ export * from './log.service'; export * from './alfresco-authentication.service'; export * from './alfresco-translation.service'; export * from './alfresco-translate-loader.service'; +export * from './app-config.service';