libs/caching/src/lib/get-data-cache/local-http-cache-interval.ts
Properties |
|
Methods |
|
Public clearCache |
clearCache()
|
Remove every entry in cache.
Returns :
void
|
Public get | ||||||||||||
get(url: string, generalize: boolean)
|
||||||||||||
Get all objects cached with a given key (url).
Parameters :
Returns :
CachedObject[]
|
Private getByCache | ||||||||||||
getByCache(selectedCache: Map
|
||||||||||||
Get all objects from cache.
Parameters :
Returns :
CachedObject[]
|
Private getCachedInterval | ||||||||||||||||||||
getCachedInterval(obj: CachedObject, tsDiff: Timespan, ts: Timespan, pos: string)
|
||||||||||||||||||||
Function to filter cached values by given timespan.
Parameters :
Returns :
CachedObject
|
Public getIntersection | ||||||||||||||||
getIntersection(url: string, timespan: Timespan, generalize: boolean)
|
||||||||||||||||
Get all objects intersecting with a given timespan. Further return all timespans that are not covered.
Parameters :
Returns :
CachedIntersection | null
|
Private identifyCachedIntersection | ||||||||||||
identifyCachedIntersection(objs: CachedObject[], ts: Timespan)
|
||||||||||||
Identify relevant objects inside cache and return timespans that are not covered with data.
Parameters :
Returns :
CachedIntersection | null
|
Public put | ||||||||||||||||||||
put(url: string, obj: CachedObject, generalize: boolean, originReq?: boolean)
|
||||||||||||||||||||
Cache a new object under a given key (url).
Parameters :
Returns :
void
|
Private putByCache | ||||||||||||||||||||
putByCache(selectedCache: Map
|
||||||||||||||||||||
Check for intersection before putting a new object into cache.
Parameters :
Returns :
void
|
Public Abstract get |
get(url: string, generalized: boolean)
|
Inherited from
HttpCacheInterval
|
Defined in
HttpCacheInterval:31
|
Returns a cached response, if any, or null, if not present.
Returns :
[] | null
|
Public Abstract getIntersection |
getIntersection(url: string, timespan: Timespan, generalized: boolean)
|
Inherited from
HttpCacheInterval
|
Defined in
HttpCacheInterval:37
|
Returns a cached response with intersecting timespans, if any, or null, if not present. Further returns an array of timespans that need to be requested (not covered by cached timespans), if any, or null, if only one cached object covers the requested timespan.
Returns :
CachedIntersection | null
|
Public Abstract put | |||||||||||||||
put(url: string, obj: CachedObject, generalized: boolean, originReq?: boolean)
|
|||||||||||||||
Inherited from
HttpCacheInterval
|
|||||||||||||||
Defined in
HttpCacheInterval:42
|
|||||||||||||||
Saves new object into cache. 'originReq' indicates, if the request is the original request (e.g. forceUpdate), so the cache can be filtered
Parameters :
Returns :
void
|
Private cache |
Type : Map<string | CachedObject[]>
|
Default value : new Map()
|
Private generalizedCache |
Type : Map<string | CachedObject[]>
|
Default value : new Map()
|
import { HttpResponse } from '@angular/common/http';
import { Injectable } from '@angular/core';
import { Data, Timespan, TimeValueTuple } from '@helgoland/core';
import * as lodash from 'lodash';
import { HttpCacheInterval } from '../model';
export interface CachedObject {
values: Data<TimeValueTuple>;
expirationDate: Date;
expirationAtMs: number;
httpResponse: HttpResponse<any>;
requestTs: Timespan;
}
export interface CachedIntersection {
cachedObjects: CachedObject[];
timespans?: Timespan[];
}
@Injectable()
export class LocalHttpCacheInterval extends HttpCacheInterval {
private cache: Map<string, CachedObject[]> = new Map();
private generalizedCache: Map<string, CachedObject[]> = new Map();
/**
* Get all objects cached with a given key (url).
* @param url {string} key
* @param generalize {boolean} indicate if request has parameter generalized true or false to safe to different caches
*/
public get(url: string, generalize: boolean): CachedObject[] {
if (generalize) {
return this.getByCache(this.generalizedCache, url);
} else {
return this.getByCache(this.cache, url);
}
}
/**
* Get all objects from cache.
* @param selectedCache {Map<string, CachedObject[]>} selected cache (generalized or not generalized)
* @param url {string} key
*/
private getByCache(selectedCache: Map<string, CachedObject[]>, url: string): CachedObject[] {
const objs = selectedCache.get(url);
if (objs) {
const filteredObjs = objs.filter(el => {
return (new Date() < el.expirationDate);
});
const newCachedObjs = this.tidyUpCache(filteredObjs);
selectedCache.set(url, newCachedObjs);
return newCachedObjs;
} else {
return objs;
}
}
/**
* Get all objects intersecting with a given timespan.
* Further return all timespans that are not covered.
* @param url {string} key
* @param timespan {Timespan} timespan
* @param generalize {boolean} generalized or not
*/
public getIntersection(url: string, timespan: Timespan, generalize: boolean): CachedIntersection | null {
const objs = this.get(url, generalize);
if (objs && objs.length > 0) {
return this.identifyCachedIntersection(objs, timespan);
}
return null;
}
/**
* Cache a new object under a given key (url).
* @param url {string} key
* @param obj {CachedObject} object to be cached
* @param generalize {boolean} generalized or not
* @param originReq {boolean} indicating if original request or manipulated
*/
public put(url: string, obj: CachedObject, generalize: boolean, originReq?: boolean) {
if (generalize) {
this.putByCache(this.generalizedCache, url, obj, originReq);
} else {
this.putByCache(this.cache, url, obj, originReq);
}
}
/**
* Check for intersection before putting a new object into cache.
* @param selectedCache {Map<string, CachedObject[]>} current cached objects
* @param url {string} key
* @param obj {CachedObject} object to be put into cache
* @param originReq {boolean} indicating if original request or manipulated
*/
private putByCache(selectedCache: Map<string, CachedObject[]>, url: string, obj: CachedObject, originReq?: boolean) {
if (selectedCache.has(url)) {
let cachedObjs = selectedCache.get(url);
// add new obj to current key
const cachedObjsManip: CachedObject[] = [];
if (originReq) {
// update timespan boundaries with incoming forceUpdate or origin request
const objTs = new Timespan(obj.requestTs.from, obj.requestTs.to);
// filter cachedObjs without any intersection
cachedObjs.forEach(el => {
const elTs = new Timespan(el.requestTs.from, el.requestTs.to);
if (elTs.from >= objTs.from && elTs.to <= objTs.to) {
// do not push cached element into new cache - cached obj completely covered by new element
} else if (elTs.from > objTs.to) {
// el right of obj
cachedObjsManip.push(el);
} else if (elTs.to < objTs.from) {
// el left of obj
cachedObjsManip.push(el);
} else if (elTs.from > objTs.from && elTs.to >= objTs.to) {
// el partly right of obj
el.values.values = el.values.values.filter(val => val[0] > objTs.to);
el.requestTs = new Timespan(objTs.to + 1, el.requestTs.to);
cachedObjsManip.push(el);
} else if (elTs.from <= objTs.from && elTs.to < objTs.to) {
// el partly left of obj
el.values.values = el.values.values.filter(val => val[0] < objTs.from);
el.requestTs = new Timespan(el.requestTs.from, objTs.from - 1);
cachedObjsManip.push(el);
} else if (elTs.from <= objTs.from && elTs.to >= objTs.to) {
// el over obj # do partly right and partly left
const elLeft = lodash.cloneDeep(el);
elLeft.values.values = elLeft.values.values.filter(val => val[0] < objTs.from);
elLeft.requestTs = new Timespan(elLeft.requestTs.from, objTs.from - 1);
cachedObjsManip.push(elLeft);
el.values.values = el.values.values.filter(val => val[0] > objTs.to);
el.requestTs = new Timespan(objTs.to + 1, el.requestTs.to);
cachedObjsManip.push(el);
}
});
cachedObjs = cachedObjsManip;
}
cachedObjs.push(obj);
// sort by timespan
const objsSorted = cachedObjs.sort((a, b) => (a.requestTs.from > b.requestTs.from) ? 1 : ((b.requestTs.from > a.requestTs.from) ? -1 : 0));
selectedCache.set(url, objsSorted);
} else {
selectedCache.set(url, [obj]);
}
}
/**
* Remove every entry in cache.
*/
public clearCache() {
this.cache.clear();
this.generalizedCache.clear();
}
/**
* Identify relevant objects inside cache and return timespans that are not covered with data.
* @param objs {CachedObject[]} objects to be checked
* @param ts {Timespan} timespan that might be intersected
*/
private identifyCachedIntersection(objs: CachedObject[], ts: Timespan): CachedIntersection | null {
const intersectedObjs: CachedObject[] = [];
const differedIntervals: Timespan[] = [];
for (let i = 0; i < objs.length; i++) {
const el = objs[i];
const cachedTs = new Timespan(el.requestTs.from, el.requestTs.to);
if (ts.from > cachedTs.to) {
// ts right of cached # no overlapping
if (i === objs.length - 1) {
differedIntervals.push(ts);
}
continue;
} else if (ts.to < cachedTs.from) {
// ts left of cached # no overlapping
differedIntervals.push(ts);
break;
} else if (ts.from >= cachedTs.from && ts.to <= cachedTs.to) {
// ts inside cached # cached completely overlaps ts
const intVals = this.getCachedInterval(el, null, ts, 'inside');
if (intVals.values.values.length > 0) { intersectedObjs.push(intVals); }
break;
} else if (ts.from > cachedTs.from && ts.to >= cachedTs.to) {
// ts partly right of cached # ts and cached overlap, ts ends after cached
const diffTs = new Timespan(Math.max(ts.from, cachedTs.to) + 1, ts.to);
if (i === objs.length - 1) {
differedIntervals.push(ts);
}
const intVals = this.getCachedInterval(el, diffTs, ts, 'right');
if (intVals.values.values.length > 0) { intersectedObjs.push(intVals); }
} else if (ts.from <= cachedTs.from && ts.to < cachedTs.to) {
// ts partly left of cached # ts and cached overlap, cached ends after ts
const diffTs = new Timespan(ts.from, Math.min(ts.to, cachedTs.from) - 1);
differedIntervals.push(diffTs);
const intVals = this.getCachedInterval(el, diffTs, ts, 'left');
if (intVals.values.values.length > 0) { intersectedObjs.push(intVals); }
break;
} else if (ts.from <= cachedTs.from && ts.to >= cachedTs.to) {
// ts over cached # ts completely overlaps cached
let pushedLeft = false;
// partly left
if (ts.from < cachedTs.from) {
const diffTs = new Timespan(ts.from, Math.min(ts.to, cachedTs.from) - 1);
differedIntervals.push(diffTs);
const intVals = this.getCachedInterval(el, diffTs, ts, 'left');
if (intVals.values.values.length > 0) { intersectedObjs.push(intVals); }
pushedLeft = true;
} else {
// completely inside
const intVals = this.getCachedInterval(el, null, ts, 'inside');
if (intVals.values.values.length > 0) { intersectedObjs.push(intVals); }
pushedLeft = true;
}
// partly right
if (i === objs.length - 1) {
const diffTsRight = new Timespan(Math.max(ts.from, cachedTs.to) + 1, ts.to);
differedIntervals.push(diffTsRight);
if (!pushedLeft) {
const intValsRight = this.getCachedInterval(el, diffTsRight, ts, 'right');
if (intValsRight.values.values.length > 0) { intersectedObjs.push(intValsRight); }
}
break;
}
}
ts.from = Math.min(ts.to, cachedTs.to) + 1;
// break if ts is negative now
if (ts.from > ts.to) { break; }
}
return {
cachedObjects: intersectedObjs,
timespans: differedIntervals
};
}
/**
* Function to filter cached values by given timespan.
* @param obj {CachedObject} cached object with values
* @param tsDiff {Timespan} updated timespan for different cached objects and time periods
* @param ts {Timespan} requested timespan
* @param pos {string} indicates point in time where values should be taken from
*/
private getCachedInterval(obj: CachedObject, tsDiff: Timespan, ts: Timespan, pos: string): CachedObject {
const clonedObj: CachedObject = lodash.cloneDeep(obj);
if (pos === 'left') {
clonedObj.values.values = obj.values.values.filter(el => el[0] <= ts.to && el[0] >= tsDiff.to);
}
if (pos === 'right') {
clonedObj.values.values = obj.values.values.filter(el => el[0] >= ts.from && el[0] <= tsDiff.from);
}
if (pos === 'inside') {
clonedObj.values.values = obj.values.values.filter(el => el[0] >= ts.from && el[0] <= ts.to);
}
// set valueBeforeTimespan and valueAfterTimespan, if possible
if (clonedObj.values.values.length > 0 && obj.values.values.length > 0) {
const idx = obj.values.values.findIndex(el => el[0] === clonedObj.values.values[0][0]);
if (idx > 0 && obj.values.values[idx - 1]) {
clonedObj.values.valueBeforeTimespan = obj.values.values[idx - 1];
}
const idxj = obj.values.values.findIndex(el => el[0] === clonedObj.values.values[clonedObj.values.values.length - 1][0]);
if (idxj >= 0 && obj.values.values[idxj + 1]) {
clonedObj.values.valueAfterTimespan = obj.values.values[idxj + 1];
}
}
return clonedObj;
}
/**
* Filter cached objects and remove duplicates.
* @param filteredObjs {CachedObject[]} objects to be filtered
*/
private tidyUpCache(filteredObjs: CachedObject[]): CachedObject[] {
// tidy up to avoid duplicate timespans in cache
for (let i = 0; i < filteredObjs.length - 1; i++) {
const obj = filteredObjs[i];
filteredObjs = filteredObjs.filter(el => !(el.requestTs.from >= obj.requestTs.from && el.requestTs.to <= obj.requestTs.to));
filteredObjs.splice(i, 0, obj);
}
return filteredObjs;
}
}