File

libs/caching/src/lib/get-data-cache/local-http-cache-interval.ts

Extends

HttpCacheInterval

Index

Properties
Methods

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 :
Name Type Optional Description
url string No

key

generalize boolean No

indicate if request has parameter generalized true or false to safe to different caches

Returns : CachedObject[]
Private getByCache
getByCache(selectedCache: Map, url: string)

Get all objects from cache.

Parameters :
Name Type Optional Description
selectedCache Map<string | CachedObject[]> No

selected cache (generalized or not generalized)

url string No

key

Returns : CachedObject[]
Private getCachedInterval
getCachedInterval(obj: CachedObject, tsDiff: Timespan, ts: Timespan, pos: string)

Function to filter cached values by given timespan.

Parameters :
Name Type Optional Description
obj CachedObject No

cached object with values

tsDiff Timespan No

updated timespan for different cached objects and time periods

ts Timespan No

requested timespan

pos string No

indicates point in time where values should be taken from

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 :
Name Type Optional Description
url string No

key

timespan Timespan No

timespan

generalize boolean No

generalized or not

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 :
Name Type Optional Description
objs CachedObject[] No

objects to be checked

ts Timespan No

timespan that might be intersected

Returns : CachedIntersection | null
Public put
put(url: string, obj: CachedObject, generalize: boolean, originReq?: boolean)

Cache a new object under a given key (url).

Parameters :
Name Type Optional Description
url string No

key

obj CachedObject No

object to be cached

generalize boolean No

generalized or not

originReq boolean Yes

indicating if original request or manipulated

Returns : void
Private putByCache
putByCache(selectedCache: Map, url: string, obj: CachedObject, originReq?: boolean)

Check for intersection before putting a new object into cache.

Parameters :
Name Type Optional Description
selectedCache Map<string | CachedObject[]> No

current cached objects

url string No

key

obj CachedObject No

object to be put into cache

originReq boolean Yes

indicating if original request or manipulated

Returns : void
Private tidyUpCache
tidyUpCache(filteredObjs: CachedObject[])

Filter cached objects and remove duplicates.

Parameters :
Name Type Optional Description
filteredObjs CachedObject[] No

objects to be filtered

Returns : CachedObject[]
Public Abstract get
get(url: string, generalized: boolean)
Inherited from HttpCacheInterval

Returns a cached response, if any, or null, if not present.

Parameters :
Name Type Optional
url string No
generalized boolean No
Returns : [] | null
Public Abstract getIntersection
getIntersection(url: string, timespan: Timespan, generalized: boolean)
Inherited from HttpCacheInterval

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.

Parameters :
Name Type Optional
url string No
timespan Timespan No
generalized boolean No
Returns : CachedIntersection | null
Public Abstract put
put(url: string, obj: CachedObject, generalized: boolean, originReq?: boolean)
Inherited from HttpCacheInterval

Saves new object into cache. 'originReq' indicates, if the request is the original request (e.g. forceUpdate), so the cache can be filtered

Parameters :
Name Type Optional
url string No
obj CachedObject No
generalized boolean No
originReq boolean Yes
Returns : void

Properties

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;
    }

}

result-matching ""

    No results matching ""