-
Notifications
You must be signed in to change notification settings - Fork 8
/
AbstractDAO.ts
246 lines (219 loc) · 8.49 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
import {OnmsError} from '../api/OnmsError';
import {Clause} from '../api/Clause';
import {Filter} from '../api/Filter';
import {IFilterProcessor} from '../api/IFilterProcessor';
import {IFilterVisitor} from '../api/IFilterVisitor';
import {IValueProvider} from './IValueProvider';
import {NestedRestriction} from '../api/NestedRestriction';
import {OnmsHTTPOptions, OnmsHTTPOptionsBuilder} from '../api/OnmsHTTPOptions';
import {OnmsServer} from '../api/OnmsServer';
import {Restriction} from '../api/Restriction';
import {SearchProperty} from '../api/SearchProperty';
import {SearchPropertyType} from '../api/SearchPropertyType';
import {log} from '../api/Log';
import {V1FilterProcessor} from './V1FilterProcessor';
import {V2FilterProcessor} from './V2FilterProcessor';
import {BaseDAO} from './BaseDAO';
/**
* 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.
*
* @category DAO
* @typeparam K the ID/key type (number, string, etc.)
* @typeparam T the model type (OnmsAlarm, OnmsEvent, etc.)
*/
export abstract class AbstractDAO<K, T> extends BaseDAO implements IValueProvider {
/** A local cache of v2 DAO properties (`api/v2/DAO/properties`) */
private propertiesCache: any;
/**
* Returns the Promise for a [[IFilterProcessor]].
* @returns {Promise}
*/
public async getFilterProcessor(): Promise<IFilterProcessor> {
switch (this.getApiVersion()) {
case 2:
const cache = await this.getPropertiesCache();
return new V2FilterProcessor(cache);
default:
return Promise.resolve(new V1FilterProcessor());
}
}
/**
* Retrieve a model object.
* @param id - the ID of the object
*/
public abstract 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 find(filter?: Filter): Promise<T[]>;
/**
* Get the list properties that can be used in queries.
* @version ReST v2
*/
public async searchProperties(): Promise<SearchProperty[] | undefined> {
return await this.getPropertiesCache();
}
/**
* 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 | undefined> {
const cache = await this.getPropertiesCache();
return cache.find((prop: any) => prop.id === id);
}
/**
* Returns or creates a cache of properties for this dao.
*
* @return the cache for this dao. It is created if it does not exist.
*/
public async getPropertiesCache(): Promise<any> {
if (this.getApiVersion() === 1) {
throw new OnmsError('Search property metadata is only available in OpenNMS ' +
'versions that support the ReSTv2 API.');
}
if (!this.propertiesCache) {
const opts = (await this.getOptions()).setHeader('Accept', 'application/json');
const result = await this.http.get(this.searchPropertyPath(), opts.build());
this.propertiesCache = this.parseResultList(result, 'searchProperty',
this.searchPropertyPath(), (prop: any) => this.toSearchProperty(prop));
}
return this.propertiesCache;
}
/**
* 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> {
// FIXME get rid of the "as" bit once https://github.com/microsoft/TypeScript/issues/33752 is closed
const [property, defaultOptions] = await Promise.all([
this.searchProperty(propertyId),
this.getOptions(options),
]) as [SearchProperty, OnmsHTTPOptionsBuilder];
if (!property || !property.id) {
throw new OnmsError('Unable to determine property for ID ' + propertyId);
}
const path = this.searchPropertyPath() + '/' + property.id;
const opts = defaultOptions.setHeader('Accept', 'application/json');
const result = await this.http.get(path, opts.build());
return this.parseResultList(result, 'value', path, (value: any) => value);
}
/** @inheritdoc */
protected onSetServer(server: OnmsServer) {
log.debug('Server has changed, invalidating DAO cache:' + JSON.stringify(server));
this.propertiesCache = undefined;
}
/**
* 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 ret = [] as any[];
const data = result.data;
if (this.getCount(data) > 0 && data[dataFieldName]) {
ret = data[dataFieldName];
}
if (!Array.isArray(ret)) {
throw new OnmsError('Expected an array but got "' + (typeof ret) + '" instead: ' + path);
}
if (mapCallbackFunction) {
return ret.map(mapCallbackFunction);
}
return ret;
}
/**
* "visits" a filter clause, applying it to the filter visitor
* @param clause the clause to visit
* @param visitor the visitor impl to invoke
*/
protected visitClause(clause: Clause, visitor: IFilterVisitor) {
const self = this;
if (visitor.onClause) { visitor.onClause(clause); }
const restriction = clause.restriction;
if (restriction instanceof Restriction) {
if (visitor.onRestriction) { visitor.onRestriction(restriction); }
} else if (restriction instanceof NestedRestriction) {
if (visitor.onNestedRestriction) { visitor.onNestedRestriction(restriction); }
if (restriction.clauses) {
restriction.clauses.forEach((c) => {
self.visitClause(c, visitor);
});
}
} else {
log.warn('Restriction is of an unknown type: ' + JSON.stringify(restriction));
}
}
/**
* Iterate over a Filter object and its children.
* @param filter the filter to visit
* @param visitor the class to invoke while visiting the filter
*/
protected visitFilter(filter: Filter, visitor: IFilterVisitor) {
const self = this;
if (visitor.onFilter) { visitor.onFilter(filter); }
if (filter.clauses) {
filter.clauses.forEach((clause) => {
self.visitClause(clause, visitor);
});
}
}
/**
* Create an [[OnmsHTTPOptions]] object for DAO calls given an optional filter.
* @param filter - the filter to use
*/
protected async getOptions(filter?: Filter): Promise<OnmsHTTPOptionsBuilder> {
const builder = OnmsHTTPOptions.newBuilder();
if (this.useJson()) {
builder.setHeader('Accept', 'application/json');
} else {
// always use application/xml in DAO calls when we're not sure how
// usable JSON output will be.
builder.setHeader('Accept', 'application/xml');
}
if (filter) {
const processor = await this.getFilterProcessor();
builder.setParameters(processor.getParameters(filter));
}
return builder;
}
/**
* Generate a [[SearchProperty]] from the given dictionary.
* @hidden
*/
protected toSearchProperty(data: any): SearchProperty | null {
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.server || this.server.metadata === null) {
throw new OnmsError('Server meta-data must be populated prior to making DAO calls.');
}
return this.server.metadata.apiVersion();
}
}