Skip to content

Commit

Permalink
feat(dao): Add more complex null value handling for v2 (JS-20)
Browse files Browse the repository at this point in the history
  • Loading branch information
Markus von Rüden committed Aug 14, 2017
1 parent 53fad98 commit 7817b4a
Show file tree
Hide file tree
Showing 12 changed files with 434 additions and 244 deletions.
131 changes: 80 additions & 51 deletions src/dao/AbstractDAO.ts
Expand Up @@ -13,6 +13,8 @@ import {log, catDao} from '../api/Log';
import {V1FilterProcessor} from './V1FilterProcessor';
import {V2FilterProcessor} from './V2FilterProcessor';

import {PropertiesCache} from './PropertiesCache';

/** @hidden */
// tslint:disable-next-line
const moment = require('moment');
Expand All @@ -37,12 +39,6 @@ export abstract class AbstractDAO<K, T> {
*/
private httpImpl: IOnmsHTTP;

/**
* The [[IFilterProcessor]] to use internally when making DAO requests.
* @hidden
*/
private filterProcessorImpl;

/**
* Construct a DAO instance.
*
Expand All @@ -67,21 +63,19 @@ export abstract class AbstractDAO<K, T> {
this.httpImpl = impl;
}

public get filterProcessor() {
if (!this.filterProcessorImpl) {
/**
* Returns the Promise for a [[IFilterProcessor]].
* @returns {Promise}
*/
public async getFilterProcessor(): Promise<IFilterProcessor> {
switch (this.getApiVersion()) {
case 2:
this.filterProcessorImpl = new V2FilterProcessor();
break;
default:
this.filterProcessorImpl = new V1FilterProcessor();
case 2:
return this.getPropertiesCache().then((cache) => {
return new V2FilterProcessor(cache);
});
default:
return Promise.resolve(new V1FilterProcessor());
}
}
return this.filterProcessorImpl;
}

public set filterProcessor(impl: IFilterProcessor) {
this.filterProcessorImpl = impl;
}

/**
Expand All @@ -101,30 +95,60 @@ export abstract class AbstractDAO<K, T> {
* @version ReST v2
*/
public async searchProperties(): Promise<SearchProperty[]> {
if (this.getApiVersion() === 1) {
throw new OnmsError('Search property metadata is only available in OpenNMS ' +
'versions that support the ReSTv2 API.');
}
return this.getPropertiesCache().then((cache) => {
return cache.getProperties();
});
}

const opts = this.getOptions();
opts.headers.accept = 'application/json';
return this.http.get(this.searchPropertyPath(), opts).then((result) => {
let data = result.data;
/**
* Gets the property identified by the id if it exists.
*
* @param id The id to search the property by.
*/
public async searchProperty(id: string): Promise<SearchProperty> {
return this.getPropertiesCache().then((cache) => {
return cache.getProperty(id);
});
}

if (this.getCount(data) > 0 && data.searchProperty) {
data = data.searchProperty;
} else {
data = [];
/**
* Returns or creates the [[PropertiesCache]] for this dao.
*
* @return the [[PropertiesCache]] for this dao. It is created if it does not exist.
*/
public async getPropertiesCache(): Promise<PropertiesCache> {
if (this.getApiVersion() === 1) {
throw new OnmsError('Search property metadata is only available in OpenNMS ' +
'versions that support the ReSTv2 API.');
}

if (!Array.isArray(data)) {
throw new OnmsError('Expected an array of search properties but got "' +
(typeof data) + '" instead: ' + this.searchPropertyPath());
// Cache not yet initialized
if (!PropertiesCache.get(this)) {
return this.getOptions().then((opts) => {
opts.headers.accept = 'application/json';
return this.http.get(this.searchPropertyPath(), opts).then((result) => {
let data = result.data;

if (this.getCount(data) > 0 && data.searchProperty) {
data = data.searchProperty;
} else {
data = [];
}

if (!Array.isArray(data)) {
throw new OnmsError('Expected an array of search properties but got "' +
(typeof data) + '" instead: ' + this.searchPropertyPath());
}
const searchProperties = data.map((prop) => {
return this.toSearchProperty(prop);
});
PropertiesCache.put(this, searchProperties);
return Promise.resolve(PropertiesCache.get(this));
});
});
}
return data.map((prop) => {
return this.toSearchProperty(prop);
});
});
// Cache already initialized, use value
return Promise.resolve(PropertiesCache.get(this));
}

/**
Expand Down Expand Up @@ -154,19 +178,24 @@ export abstract class AbstractDAO<K, T> {
* Create an [[OnmsHTTPOptions]] object for DAO calls given an optional filter.
* @param filter - the filter to use
*/
protected getOptions(filter?: Filter): OnmsHTTPOptions {
const ret = new OnmsHTTPOptions();
if (this.useJson()) {
ret.headers.accept = 'application/json';
} else {
// always use application/xml in DAO calls when we're not sure how
// usable JSON output will be.
ret.headers.accept = 'application/xml';
}
if (filter) {
ret.parameters = this.filterProcessor.getParameters(filter);
}
return ret;
protected async getOptions(filter?: Filter): Promise<OnmsHTTPOptions> {
return Promise.resolve(new OnmsHTTPOptions())
.then((options) => {
if (this.useJson()) {
options.headers.accept = 'application/json';
} else {
// always use application/xml in DAO calls when we're not sure how
// usable JSON output will be.
options.headers.accept = 'application/xml';
}
if (filter) {
return this.getFilterProcessor().then((processor) => {
options.parameters = processor.getParameters(filter);
return options;
});
}
return options;
});
}

/**
Expand Down
73 changes: 39 additions & 34 deletions src/dao/AlarmDAO.ts
Expand Up @@ -48,9 +48,10 @@ export class AlarmDAO extends AbstractDAO<number, OnmsAlarm> {
* @return An [[OnmsAlarm]].
*/
public async get(id: number): Promise<OnmsAlarm> {
const opts = this.getOptions();
return this.http.get(this.pathToAlarmsEndpoint() + '/' + id, opts).then((result) => {
return this.fromData(result.data);
return this.getOptions().then((opts) => {
return this.http.get(this.pathToAlarmsEndpoint() + '/' + id, opts).then((result) => {
return this.fromData(result.data);
});
});
}

Expand All @@ -62,12 +63,13 @@ export class AlarmDAO extends AbstractDAO<number, OnmsAlarm> {
* @return An array of [[OnmsAlarm]] objects.
*/
public async find(filter?: Filter): Promise<OnmsAlarm[]> {
const opts = this.getOptions(filter);
return this.http.get(this.pathToAlarmsEndpoint(), opts).then((result) => {
const data = this.getData(result);
return data.map((alarmData) => {
return this.fromData(alarmData);
});
return this.getOptions(filter).then((opts) => {
return this.http.get(this.pathToAlarmsEndpoint(), opts).then((result) => {
const data = this.getData(result);
return data.map((alarmData) => {
return this.fromData(alarmData);
});
});
});
}

Expand Down Expand Up @@ -408,29 +410,31 @@ export class AlarmDAO extends AbstractDAO<number, OnmsAlarm> {
* Given an optional filter, generate an [[OnmsHTTPOptions]] object for DAO calls.
* @hidden
*/
protected getOptions(filter?: Filter): OnmsHTTPOptions {
const options = super.getOptions(filter);
// always use application/json for v2 calls
if (this.getApiVersion() === 2) {
options.headers.accept = 'application/json';
}
return options;
protected async getOptions(filter?: Filter): Promise<OnmsHTTPOptions> {
return super.getOptions(filter).then((options) => {
// always use application/json for v2 calls
if (this.getApiVersion() === 2) {
options.headers.accept = 'application/json';
}
return options;
});
}

/**
* Call a PUT request in the format the alarm ack API expects.
* @hidden
*/
private async put(url: string, parameters = {} as IHash<string>): Promise<void> {
const opts = this.getOptions();
opts.headers['content-type'] = 'application/x-www-form-urlencoded';
opts.headers.accept = null;
opts.parameters = parameters;
return this.http.put(url, opts).then((result) => {
if (!result.isSuccess) {
throw result;
}
return;
return this.getOptions().then((opts) => {
opts.headers['content-type'] = 'application/x-www-form-urlencoded';
opts.headers.accept = null;
opts.parameters = parameters;
return this.http.put(url, opts).then((result) => {
if (!result.isSuccess) {
throw result;
}
return;
});
});
}

Expand All @@ -439,15 +443,16 @@ export class AlarmDAO extends AbstractDAO<number, OnmsAlarm> {
* @hidden
*/
private async httpDelete(url: string, parameters = {} as IHash<string>): Promise<void> {
const opts = this.getOptions();
opts.headers['content-type'] = 'application/x-www-form-urlencoded';
opts.headers.accept = null;
opts.parameters = parameters;
return this.http.httpDelete(url, opts).then((result) => {
if (!result.isSuccess) {
throw result;
}
return;
return this.getOptions().then((opts) => {
opts.headers['content-type'] = 'application/x-www-form-urlencoded';
opts.headers.accept = null;
opts.parameters = parameters;
return this.http.httpDelete(url, opts).then((result) => {
if (!result.isSuccess) {
throw result;
}
return;
});
});
}

Expand Down
48 changes: 25 additions & 23 deletions src/dao/EventDAO.ts
Expand Up @@ -29,34 +29,36 @@ export class EventDAO extends AbstractDAO<number, OnmsEvent> {

/** Get an event, given the event's ID. */
public async get(id: number): Promise<OnmsEvent> {
const opts = this.getOptions();
return this.http.get(this.pathToEventsEndpoint() + '/' + id, opts).then((result) => {
return this.fromData(result.data);
return this.getOptions().then((opts) => {
return this.http.get(this.pathToEventsEndpoint() + '/' + id, opts).then((result) => {
return this.fromData(result.data);
});
});
}

/** Get an event, given a filter. */
public async find(filter?: Filter): Promise<OnmsEvent[]> {
const opts = this.getOptions(filter);
return this.http.get(this.pathToEventsEndpoint(), opts).then((result) => {
let data = result.data;

if (data !== null && this.getCount(data) > 0 && data.event) {
data = data.event;
} else {
data = [];
}

if (!Array.isArray(data)) {
if (data.id) {
data = [data];
} else {
throw new OnmsError('Expected an array of events but got "' + (typeof data) + '" instead.');
}
}
return data.map((eventData) => {
return this.fromData(eventData);
});
return this.getOptions(filter).then((opts) => {
return this.http.get(this.pathToEventsEndpoint(), opts).then((result) => {
let data = result.data;

if (data !== null && this.getCount(data) > 0 && data.event) {
data = data.event;
} else {
data = [];
}

if (!Array.isArray(data)) {
if (data.id) {
data = [data];
} else {
throw new OnmsError('Expected an array of events but got "' + (typeof data) + '" instead.');
}
}
return data.map((eventData) => {
return this.fromData(eventData);
});
});
});
}

Expand Down
22 changes: 22 additions & 0 deletions src/dao/ISearchPropertyAccessor.ts
@@ -0,0 +1,22 @@
import {SearchProperty} from '../api/SearchProperty';

/**
* Convenient interface to access [[SearchProperty]]s.
* Mainly used for caching purposes.
*/
export interface ISearchPropertyAccessor {

/**
* Returns all available [[SearchProperty]]s.
* @returns {SearchProperty[]}
*/
getProperties(): SearchProperty[];

/**
* Returns a certain [[SearchProperty]].
*
* @param {string} id The property id to find the property for.
* @returns {SearchProperty} The property found.
*/
getProperty(id: string): SearchProperty;
}

0 comments on commit 7817b4a

Please sign in to comment.