/
AbstractDAO.ts
294 lines (265 loc) · 9.53 KB
/
AbstractDAO.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
import {IFilterProcessor} from '../api/IFilterProcessor';
import {IHasHTTP} from '../api/IHasHTTP';
import {IOnmsHTTP} from '../api/IOnmsHTTP';
import {OnmsError} from '../api/OnmsError';
import {Filter} from '../api/Filter';
import {OnmsHTTPOptions} from '../api/OnmsHTTPOptions';
import {SearchProperty} from '../api/SearchProperty';
import {SearchPropertyType} from '../api/SearchPropertyType';
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');
/** @hidden */
// tslint:disable-next-line
import {Moment} from 'moment';
import {IValueProvider} from './IValueProvider';
/**
* An abstract data access layer API, meant to (somewhat) mirror the DAO interfaces
* inside OpenNMS. Used to retrieve model data like alarms, events, etc. from the
* OpenNMS ReST API in a consistent way.
*
* @module AbstractDAO
* @param K the ID/key type (number, string, etc.)
* @param T the model type (OnmsAlarm, OnmsEvent, etc.)
*/
export abstract class AbstractDAO<K, T> implements IValueProvider {
/**
* The [[IOnmsHTTP]] implementation to use internally when making DAO requests.
* @hidden
*/
private httpImpl: IOnmsHTTP;
/**
* Construct a DAO instance.
*
* @param impl - The HTTP implementation to use. It is also legal to pass any object
* conforming to the [[IHasHTTP]] interface (like a [[Client]]).
*/
constructor(impl: IOnmsHTTP | IHasHTTP) {
if ((impl as IHasHTTP).http) {
impl = (impl as IHasHTTP).http;
}
this.httpImpl = impl as IOnmsHTTP;
}
/**
* The HTTP implementation to use internally when making DAO requests.
*/
public get http() {
return this.httpImpl;
}
public set http(impl: IOnmsHTTP) {
this.httpImpl = impl;
}
/**
* Returns the Promise for a [[IFilterProcessor]].
* @returns {Promise}
*/
public async getFilterProcessor(): Promise<IFilterProcessor> {
switch (this.getApiVersion()) {
case 2:
return this.getPropertiesCache().then((cache) => {
return new V2FilterProcessor(cache);
});
default:
return Promise.resolve(new V1FilterProcessor());
}
}
/**
* Retrieve a model object.
* @param id - the ID of the object
*/
public abstract async get(id: K): Promise<T>;
/**
* Find all model objects given an optional filter.
* @param filter - the filter to use when retrieving a list of model objects
*/
public abstract async find(filter?: Filter): Promise<T[]>;
/**
* Get the list properties that can be used in queries.
* @version ReST v2
*/
public async searchProperties(): Promise<SearchProperty[]> {
return this.getPropertiesCache().then((cache) => {
return cache.getProperties();
});
}
/**
* 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);
});
}
/**
* 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.');
}
// 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) => {
const searchProperties = this.parseResultList(result, 'searchProperty',
this.searchPropertyPath(), (prop) => {
return this.toSearchProperty(prop);
});
PropertiesCache.put(this, searchProperties);
return Promise.resolve(PropertiesCache.get(this));
});
});
}
// Cache already initialized, use value
return Promise.resolve(PropertiesCache.get(this));
}
/**
* Finds the values for the given propertyId, if it exists.
*
* @param {string} propertyId The propertyId to find the values for
* @param options Some additional options. May be implementer dependent, such as limit, or value restrictions
* @returns {Promise<any>} A promise containing the values.
*/
public async findValues(propertyId: string, options?: any): Promise<any> {
return this.searchProperty(propertyId).then((property) => {
return this.getOptions().then((opts) => {
const path = this.searchPropertyPath() + '/' + property.id;
opts.headers.accept = 'application/json';
if (options) {
Object.assign(opts, options);
}
return this.http.get(path, opts).then((result) => {
return this.parseResultList(result, 'value', path, (value) => value);
});
});
});
}
/**
* The path to retrieve search properties for this DAO.
*/
protected abstract searchPropertyPath(): string;
/**
* Fetches the data from the result and verfifes that the <code>dataFieldName</code> exists in the data property.
* If it does not exist, an exception is thrown.
*
* @param result The result to fetch the data from
* @param dataFieldName The property name (basically <code>result.data[dataFieldName]</code>.
* @param path The path where the result was fetched from. This is for error handling
* @param mapCallbackFunction Callback function to convert each entry from <code>result.data[dataFieldName]</code>.
*/
protected parseResultList(result: any, dataFieldName: string, path: string, mapCallbackFunction: any): any {
let data = result.data;
if (this.getCount(data) > 0 && data[dataFieldName]) {
data = data[dataFieldName];
} else {
data = [];
}
if (!Array.isArray(data)) {
throw new OnmsError('Expected an array but got "' + (typeof data) + '" instead: ' + path);
}
if (mapCallbackFunction) {
return data.map(mapCallbackFunction);
}
return data;
}
/**
* A convenience method to make it easy for implementers to extract the count
* (or totalCount) values from response data.
*/
protected getCount(data: any): number {
let count = 0;
if (typeof(data) === 'number') {
count = data;
} else if (data.count !== undefined) {
count = parseInt(data.count, 10);
} else if (data.totalCount !== undefined) {
count = parseInt(data.totalCount, 10);
} else {
log.debug('data is missing count and totalCount properties', catDao);
}
return count;
}
/**
* Create an [[OnmsHTTPOptions]] object for DAO calls given an optional filter.
* @param filter - the filter to use
*/
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;
});
}
/**
* Convert the given value to a date, or undefined if it cannot be converted.
*/
protected toDate(from: any): Moment|undefined {
if (from === undefined || from === null || from === '') {
return undefined;
}
return moment(from);
}
/**
* Convert the given value to a number, or undefined if it cannot be converted.
*/
protected toNumber(from: any): number|undefined {
const ret = parseInt(from, 10);
return isNaN(ret) ? undefined : ret;
}
/**
* Whether or not to use JSON when making ReST requests.
*/
protected useJson(): boolean {
if (this.http === undefined || this.http.server === undefined || this.http.server.metadata === undefined) {
throw new OnmsError('Server meta-data must be populated prior to making DAO calls.');
}
return this.http.server.metadata.useJson();
}
/**
* Generate a [[SearchProperty]] from the given dictionary.
* @hidden
*/
protected toSearchProperty(data: any): SearchProperty {
if (!data) {
return null;
}
const prop = new SearchProperty(this);
prop.id = data.id;
prop.name = data.name;
prop.orderBy = !!data.orderBy;
prop.type = SearchPropertyType.forId(data.type);
prop.values = data.values;
return prop;
}
/**
* Retrieve the API version from the currently configured server.
*/
protected getApiVersion(): number {
if (this.http === undefined || this.http.server === undefined || this.http.server.metadata === undefined) {
throw new OnmsError('Server meta-data must be populated prior to making DAO calls.');
}
return this.http.server.metadata.apiVersion();
}
}