1
0
Files
yel-dspace-angular/src/app/core/cache/builders/remote-data-build.service.ts

205 lines
7.8 KiB
TypeScript

import { Injectable } from "@angular/core";
import { CacheableObject } from "../object-cache.reducer";
import { ObjectCacheService } from "../object-cache.service";
import { RequestService } from "../../data/request.service";
import { ResponseCacheService } from "../response-cache.service";
import { Store } from "@ngrx/store";
import { CoreState } from "../../core.reducers";
import { RequestEntry } from "../../data/request.reducer";
import { hasValue, isNotEmpty } from "../../../shared/empty.util";
import { ResponseCacheEntry } from "../response-cache.reducer";
import { ErrorResponse, SuccessResponse } from "../response-cache.models";
import { Observable } from "rxjs/Observable";
import { RemoteData } from "../../data/remote-data";
import { GenericConstructor } from "../../shared/generic-constructor";
import { getMapsTo, getResourceType, getRelationships } from "./build-decorators";
import { NormalizedDSOFactory } from "../models/normalized-dspace-object-factory";
@Injectable()
export class RemoteDataBuildService {
constructor(
protected objectCache: ObjectCacheService,
protected responseCache: ResponseCacheService,
protected requestService: RequestService,
protected store: Store<CoreState>,
) {
}
buildSingle<TNormalized extends CacheableObject, TDomain>(
href: string,
normalizedType: GenericConstructor<TNormalized>
): RemoteData<TDomain> {
const requestObs = this.store.select<RequestEntry>('core', 'data', 'request', href);
const responseCacheObs = this.responseCache.get(href);
const requestPending = requestObs.map((entry: RequestEntry) => hasValue(entry) && entry.requestPending).distinctUntilChanged();
const responsePending = requestObs.map((entry: RequestEntry) => hasValue(entry) && entry.responsePending).distinctUntilChanged();
const isSuccessFul = responseCacheObs
.map((entry: ResponseCacheEntry) => hasValue(entry) && entry.response.isSuccessful).distinctUntilChanged();
const errorMessage = responseCacheObs
.filter((entry: ResponseCacheEntry) => hasValue(entry) && !entry.response.isSuccessful)
.map((entry: ResponseCacheEntry) => (<ErrorResponse> entry.response).errorMessage)
.distinctUntilChanged();
const payload =
Observable.race(
this.objectCache.getBySelfLink<TNormalized>(href, normalizedType),
responseCacheObs
.filter((entry: ResponseCacheEntry) => hasValue(entry) && entry.response.isSuccessful)
.map((entry: ResponseCacheEntry) => (<SuccessResponse> entry.response).resourceUUIDs)
.flatMap((resourceUUIDs: Array<string>) => {
if (isNotEmpty(resourceUUIDs)) {
return this.objectCache.get(resourceUUIDs[0], normalizedType);
}
else {
return Observable.of(undefined);
}
})
.distinctUntilChanged()
).map((normalized: TNormalized) => {
return this.build<TNormalized, TDomain>(normalized);
});
return new RemoteData(
href,
requestPending,
responsePending,
isSuccessFul,
errorMessage,
payload
);
}
buildList<TNormalized extends CacheableObject, TDomain>(
href: string,
normalizedType: GenericConstructor<TNormalized>
): RemoteData<TDomain[]> {
const requestObs = this.store.select<RequestEntry>('core', 'data', 'request', href);
const responseCacheObs = this.responseCache.get(href);
const requestPending = requestObs.map((entry: RequestEntry) => hasValue(entry) && entry.requestPending).distinctUntilChanged();
const responsePending = requestObs.map((entry: RequestEntry) => hasValue(entry) && entry.responsePending).distinctUntilChanged();
const isSuccessFul = responseCacheObs
.map((entry: ResponseCacheEntry) => hasValue(entry) && entry.response.isSuccessful).distinctUntilChanged();
const errorMessage = responseCacheObs
.filter((entry: ResponseCacheEntry) => hasValue(entry) && !entry.response.isSuccessful)
.map((entry: ResponseCacheEntry) => (<ErrorResponse> entry.response).errorMessage)
.distinctUntilChanged();
const payload = responseCacheObs
.filter((entry: ResponseCacheEntry) => hasValue(entry) && entry.response.isSuccessful)
.map((entry: ResponseCacheEntry) => (<SuccessResponse> entry.response).resourceUUIDs)
.flatMap((resourceUUIDs: Array<string>) => {
return this.objectCache.getList(resourceUUIDs, normalizedType)
.map((normList: TNormalized[]) => {
return normList.map((normalized: TNormalized) => {
return this.build<TNormalized, TDomain>(normalized);
});
});
})
.distinctUntilChanged();
return new RemoteData(
href,
requestPending,
responsePending,
isSuccessFul,
errorMessage,
payload
);
}
build<TNormalized extends CacheableObject, TDomain>(normalized: TNormalized): TDomain {
let links: any = {};
const relationships = getRelationships(normalized.constructor) || [];
relationships.forEach((relationship: string) => {
if (hasValue(normalized[relationship])) {
const resourceType = getResourceType(normalized, relationship);
const resourceConstructor = NormalizedDSOFactory.getConstructor(resourceType);
if (Array.isArray(normalized[relationship])) {
// without the setTimeout, the actions inside requestService.configure
// are dispatched, but sometimes don't arrive. I'm unsure why atm.
setTimeout(() => {
normalized[relationship].forEach((href: string) => {
this.requestService.configure(href, resourceConstructor)
});
}, 0);
let rdArr = [];
normalized[relationship].forEach((href: string) => {
rdArr.push(this.buildSingle(href, resourceConstructor));
});
links[relationship] = this.aggregate(rdArr);
}
else {
// without the setTimeout, the actions inside requestService.configure
// are dispatched, but sometimes don't arrive. I'm unsure why atm.
setTimeout(() => {
this.requestService.configure(normalized[relationship], resourceConstructor);
},0);
links[relationship] = this.buildSingle(normalized[relationship], resourceConstructor);
}
}
});
const domainModel = getMapsTo(normalized.constructor);
return Object.assign(new domainModel(), normalized, links);
}
aggregate<T>(input: RemoteData<T>[]): RemoteData<T[]> {
const requestPending = Observable.combineLatest(
...input.map(rd => rd.isRequestPending),
).map((...pendingArray) => pendingArray.every(e => e === true))
.distinctUntilChanged();
const responsePending = Observable.combineLatest(
...input.map(rd => rd.isResponsePending),
).map((...pendingArray) => pendingArray.every(e => e === true))
.distinctUntilChanged();
const isSuccessFul = Observable.combineLatest(
...input.map(rd => rd.hasSucceeded),
).map((...successArray) => successArray.every(e => e === true))
.distinctUntilChanged();
const errorMessage = Observable.combineLatest(
...input.map(rd => rd.errorMessage),
).map((...errors) => errors
.map((e, idx) => {
if (hasValue(e)) {
return `[${idx}]: ${e}`;
}
})
.filter(e => hasValue(e))
.join(", ")
);
const payload = <Observable<T[]>> Observable.combineLatest(
...input.map(rd => rd.payload)
);
return new RemoteData(
// This is an aggregated object, it doesn't necessarily correspond
// to a single REST endpoint, so instead of a self link, use the
// current time in ms for a somewhat unique id
`${new Date().getTime()}`,
requestPending,
responsePending,
isSuccessFul,
errorMessage,
payload
);
}
}