diff --git a/src/app/core/cache/builders/remote-data-build.service.ts b/src/app/core/cache/builders/remote-data-build.service.ts index 2096d47459..00e99940e1 100644 --- a/src/app/core/cache/builders/remote-data-build.service.ts +++ b/src/app/core/cache/builders/remote-data-build.service.ts @@ -30,37 +30,33 @@ export class RemoteDataBuildService { href: string, normalizedType: GenericConstructor ): RemoteData { - const requestObs = this.store.select('core', 'data', 'request', href); - const responseCacheObs = this.responseCache.get(href); + const requestHrefObs = this.objectCache.getRequestHrefBySelfLink(href); - const requestPending = requestObs.map((entry: RequestEntry) => hasValue(entry) && entry.requestPending).distinctUntilChanged(); + const requestObs = Observable.race( + this.store.select('core', 'data', 'request', href).filter(entry => hasValue(entry)), + requestHrefObs.flatMap(requestHref => + this.store.select('core', 'data', 'request', requestHref)).filter(entry => hasValue(entry)) + ); - const responsePending = requestObs.map((entry: RequestEntry) => hasValue(entry) && entry.responsePending).distinctUntilChanged(); + const responseCacheObs = Observable.race( + this.responseCache.get(href).filter(entry => hasValue(entry)), + requestHrefObs.flatMap(requestHref => this.responseCache.get(requestHref)).filter(entry => hasValue(entry)) + ); + + const requestPending = requestObs.map((entry: RequestEntry) => entry.requestPending).distinctUntilChanged(); + + const responsePending = requestObs.map((entry: RequestEntry) => entry.responsePending).distinctUntilChanged(); const isSuccessFul = responseCacheObs - .map((entry: ResponseCacheEntry) => hasValue(entry) && entry.response.isSuccessful).distinctUntilChanged(); + .map((entry: ResponseCacheEntry) => entry.response.isSuccessful).distinctUntilChanged(); const errorMessage = responseCacheObs - .filter((entry: ResponseCacheEntry) => hasValue(entry) && !entry.response.isSuccessful) + .filter((entry: ResponseCacheEntry) => !entry.response.isSuccessful) .map((entry: ResponseCacheEntry) => ( entry.response).errorMessage) .distinctUntilChanged(); - const payload = - Observable.race( - this.objectCache.getBySelfLink(href, normalizedType), - responseCacheObs - .filter((entry: ResponseCacheEntry) => hasValue(entry) && entry.response.isSuccessful) - .map((entry: ResponseCacheEntry) => ( entry.response).resourceUUIDs) - .flatMap((resourceUUIDs: Array) => { - if (isNotEmpty(resourceUUIDs)) { - return this.objectCache.get(resourceUUIDs[0], normalizedType); - } - else { - return Observable.of(undefined); - } - }) - .distinctUntilChanged() - ).map((normalized: TNormalized) => { + const payload = this.objectCache.getBySelfLink(href, normalizedType) + .map((normalized: TNormalized) => { return this.build(normalized); }); @@ -78,23 +74,24 @@ export class RemoteDataBuildService { href: string, normalizedType: GenericConstructor ): RemoteData { - const requestObs = this.store.select('core', 'data', 'request', href); - const responseCacheObs = this.responseCache.get(href); + const requestObs = this.store.select('core', 'data', 'request', href) + .filter(entry => hasValue(entry)); + const responseCacheObs = this.responseCache.get(href).filter(entry => hasValue(entry)); - const requestPending = requestObs.map((entry: RequestEntry) => hasValue(entry) && entry.requestPending).distinctUntilChanged(); + const requestPending = requestObs.map((entry: RequestEntry) => entry.requestPending).distinctUntilChanged(); - const responsePending = requestObs.map((entry: RequestEntry) => hasValue(entry) && entry.responsePending).distinctUntilChanged(); + const responsePending = requestObs.map((entry: RequestEntry) => entry.responsePending).distinctUntilChanged(); const isSuccessFul = responseCacheObs - .map((entry: ResponseCacheEntry) => hasValue(entry) && entry.response.isSuccessful).distinctUntilChanged(); + .map((entry: ResponseCacheEntry) => entry.response.isSuccessful).distinctUntilChanged(); const errorMessage = responseCacheObs - .filter((entry: ResponseCacheEntry) => hasValue(entry) && !entry.response.isSuccessful) + .filter((entry: ResponseCacheEntry) => !entry.response.isSuccessful) .map((entry: ResponseCacheEntry) => ( entry.response).errorMessage) .distinctUntilChanged(); const payload = responseCacheObs - .filter((entry: ResponseCacheEntry) => hasValue(entry) && entry.response.isSuccessful) + .filter((entry: ResponseCacheEntry) => entry.response.isSuccessful) .map((entry: ResponseCacheEntry) => ( entry.response).resourceUUIDs) .flatMap((resourceUUIDs: Array) => { return this.objectCache.getList(resourceUUIDs, normalizedType) diff --git a/src/app/core/cache/object-cache.actions.ts b/src/app/core/cache/object-cache.actions.ts index cc9e557de4..9d34c01219 100644 --- a/src/app/core/cache/object-cache.actions.ts +++ b/src/app/core/cache/object-cache.actions.ts @@ -20,6 +20,7 @@ export class AddToObjectCacheAction implements Action { objectToCache: CacheableObject; timeAdded: number; msToLive: number; + requestHref: string; }; /** @@ -31,9 +32,13 @@ export class AddToObjectCacheAction implements Action { * the time it was added * @param msToLive * the amount of milliseconds before it should expire + * @param requestHref + * The href of the request that resulted in this object + * This isn't necessarily the same as the object's self + * link, it could have been part of a list for example */ - constructor(objectToCache: CacheableObject, timeAdded: number, msToLive: number) { - this.payload = { objectToCache, timeAdded, msToLive }; + constructor(objectToCache: CacheableObject, timeAdded: number, msToLive: number, requestHref: string) { + this.payload = { objectToCache, timeAdded, msToLive, requestHref }; } } diff --git a/src/app/core/cache/object-cache.reducer.spec.ts b/src/app/core/cache/object-cache.reducer.spec.ts index 4d8e116e4f..3331be569e 100644 --- a/src/app/core/cache/object-cache.reducer.spec.ts +++ b/src/app/core/cache/object-cache.reducer.spec.ts @@ -24,7 +24,8 @@ describe("objectCacheReducer", () => { foo: "bar" }, timeAdded: new Date().getTime(), - msToLive: 900000 + msToLive: 900000, + requestHref: "https://rest.api/endpoint/uuid1" }, [uuid2]: { data: { @@ -32,7 +33,8 @@ describe("objectCacheReducer", () => { foo: "baz" }, timeAdded: new Date().getTime(), - msToLive: 900000 + msToLive: 900000, + requestHref: "https://rest.api/endpoint/uuid2" } }; deepFreeze(testState); @@ -56,7 +58,8 @@ describe("objectCacheReducer", () => { const objectToCache = {uuid: uuid1}; const timeAdded = new Date().getTime(); const msToLive = 900000; - const action = new AddToObjectCacheAction(objectToCache, timeAdded, msToLive); + const requestHref = "https://rest.api/endpoint/uuid1"; + const action = new AddToObjectCacheAction(objectToCache, timeAdded, msToLive, requestHref); const newState = objectCacheReducer(state, action); expect(newState[uuid1].data).toEqual(objectToCache); @@ -68,7 +71,8 @@ describe("objectCacheReducer", () => { const objectToCache = {uuid: uuid1, foo: "baz", somethingElse: true}; const timeAdded = new Date().getTime(); const msToLive = 900000; - const action = new AddToObjectCacheAction(objectToCache, timeAdded, msToLive); + const requestHref = "https://rest.api/endpoint/uuid1"; + const action = new AddToObjectCacheAction(objectToCache, timeAdded, msToLive, requestHref); const newState = objectCacheReducer(testState, action); expect(newState[uuid1].data['foo']).toBe("baz"); @@ -80,7 +84,8 @@ describe("objectCacheReducer", () => { const objectToCache = {uuid: uuid1}; const timeAdded = new Date().getTime(); const msToLive = 900000; - const action = new AddToObjectCacheAction(objectToCache, timeAdded, msToLive); + const requestHref = "https://rest.api/endpoint/uuid1"; + const action = new AddToObjectCacheAction(objectToCache, timeAdded, msToLive, requestHref); deepFreeze(state); objectCacheReducer(state, action); diff --git a/src/app/core/cache/object-cache.reducer.ts b/src/app/core/cache/object-cache.reducer.ts index 85e1fdc2b3..113a06cd06 100644 --- a/src/app/core/cache/object-cache.reducer.ts +++ b/src/app/core/cache/object-cache.reducer.ts @@ -22,6 +22,7 @@ export class ObjectCacheEntry implements CacheEntry { data: CacheableObject; timeAdded: number; msToLive: number; + requestHref: string; } /** @@ -83,7 +84,8 @@ function addToObjectCache(state: ObjectCacheState, action: AddToObjectCacheActio [action.payload.objectToCache.uuid]: { data: action.payload.objectToCache, timeAdded: action.payload.timeAdded, - msToLive: action.payload.msToLive + msToLive: action.payload.msToLive, + requestHref: action.payload.requestHref } }); } diff --git a/src/app/core/cache/object-cache.service.ts b/src/app/core/cache/object-cache.service.ts index ec0bea4a97..63bfd44c82 100644 --- a/src/app/core/cache/object-cache.service.ts +++ b/src/app/core/cache/object-cache.service.ts @@ -22,9 +22,13 @@ export class ObjectCacheService { * The object to add * @param msToLive * The number of milliseconds it should be cached for + * @param requestHref + * The href of the request that resulted in this object + * This isn't necessarily the same as the object's self + * link, it could have been part of a list for example */ - add(objectToCache: CacheableObject, msToLive: number): void { - this.store.dispatch(new AddToObjectCacheAction(objectToCache, new Date().getTime(), msToLive)); + add(objectToCache: CacheableObject, msToLive: number, requestHref: string): void { + this.store.dispatch(new AddToObjectCacheAction(objectToCache, new Date().getTime(), msToLive, requestHref)); } /** @@ -54,9 +58,7 @@ export class ObjectCacheService { * An observable of the requested object */ get(uuid: string, type: GenericConstructor): Observable { - return this.store.select('core', 'cache', 'object', uuid) - .filter(entry => this.isValid(entry)) - .distinctUntilChanged() + return this.getEntry(uuid) .map((entry: ObjectCacheEntry) => Object.assign(new type(), entry.data)); } @@ -65,6 +67,23 @@ export class ObjectCacheService { .flatMap((uuid: string) => this.get(uuid, type)) } + private getEntry(uuid: string): Observable { + return this.store.select('core', 'cache', 'object', uuid) + .filter(entry => this.isValid(entry)) + .distinctUntilChanged(); + } + + getRequestHref(uuid: string): Observable { + return this.getEntry(uuid) + .map((entry: ObjectCacheEntry) => entry.requestHref) + .distinctUntilChanged(); + } + + getRequestHrefBySelfLink(self: string): Observable { + return this.store.select('core', 'index', 'href', self) + .flatMap((uuid: string) => this.getRequestHref(uuid)); + } + /** * Get an observable for an array of objects of the same type * with the specified UUIDs diff --git a/src/app/core/data/request.effects.ts b/src/app/core/data/request.effects.ts index 64e7aec1fd..f010f2e59c 100644 --- a/src/app/core/data/request.effects.ts +++ b/src/app/core/data/request.effects.ts @@ -45,7 +45,7 @@ export class RequestEffects { }) .flatMap((entry: RequestEntry) => { return this.restApi.get(entry.request.href) - .map((data: DSpaceRESTV2Response) => this.processEmbedded(data._embedded)) + .map((data: DSpaceRESTV2Response) => this.processEmbedded(data._embedded, entry.request.href)) .map((ids: Array) => new SuccessResponse(ids)) .do((response: Response) => this.responseCache.add(entry.request.href, response, this.EnvConfig.cache.msToLive)) .map((response: Response) => new RequestCompleteAction(entry.request.href)) @@ -54,25 +54,25 @@ export class RequestEffects { .map((response: Response) => new RequestCompleteAction(entry.request.href))); }); - protected processEmbedded(_embedded: any): Array { + protected processEmbedded(_embedded: any, requestHref): Array { if (isNotEmpty(_embedded)) { if (isObjectLevel(_embedded)) { - return this.deserializeAndCache(_embedded); + return this.deserializeAndCache(_embedded, requestHref); } else { let uuids = []; Object.keys(_embedded) .filter(property => _embedded.hasOwnProperty(property)) .forEach(property => { - uuids = [...uuids, ...this.deserializeAndCache(_embedded[property])]; + uuids = [...uuids, ...this.deserializeAndCache(_embedded[property], requestHref)]; }); return uuids; } } } - protected deserializeAndCache(obj): Array { + protected deserializeAndCache(obj, requestHref): Array { let type: ResourceType; const isArray = Array.isArray(obj); @@ -96,19 +96,19 @@ export class RequestEffects { if (isArray) { obj.forEach(o => { if (isNotEmpty(o._embedded)) { - this.processEmbedded(o._embedded); + this.processEmbedded(o._embedded, requestHref); } }); const normalizedObjArr = serializer.deserializeArray(obj); - normalizedObjArr.forEach(t => this.addToObjectCache(t)); + normalizedObjArr.forEach(t => this.addToObjectCache(t, requestHref)); return normalizedObjArr.map(t => t.uuid); } else { if (isNotEmpty(obj._embedded)) { - this.processEmbedded(obj._embedded); + this.processEmbedded(obj._embedded, requestHref); } const normalizedObj = serializer.deserialize(obj); - this.addToObjectCache(normalizedObj); + this.addToObjectCache(normalizedObj, requestHref); return [normalizedObj.uuid]; } @@ -125,10 +125,10 @@ export class RequestEffects { } } - protected addToObjectCache(co: CacheableObject): void { + protected addToObjectCache(co: CacheableObject, requestHref: string): void { if (hasNoValue(co) || hasNoValue(co.uuid)) { throw new Error('The server returned an invalid object'); } - this.objectCache.add(co, this.EnvConfig.cache.msToLive); + this.objectCache.add(co, this.EnvConfig.cache.msToLive, requestHref); } } diff --git a/src/app/core/data/request.service.ts b/src/app/core/data/request.service.ts index 35ce8ea078..47e4574955 100644 --- a/src/app/core/data/request.service.ts +++ b/src/app/core/data/request.service.ts @@ -8,6 +8,9 @@ import { RequestConfigureAction, RequestExecuteAction } from "./request.actions" import { ResponseCacheService } from "../cache/response-cache.service"; import { ObjectCacheService } from "../cache/object-cache.service"; import { CacheableObject } from "../cache/object-cache.reducer"; +import { ResponseCacheEntry } from "../cache/response-cache.reducer"; +import { request } from "http"; +import { SuccessResponse } from "../cache/response-cache.models"; @Injectable() export class RequestService { @@ -35,7 +38,19 @@ export class RequestService { } configure(request: Request): void { - const isCached = this.objectCache.hasBySelfLink(request.href); + let isCached = this.objectCache.hasBySelfLink(request.href); + + if (!isCached && this.responseCache.has(request.href)) { + //if it isn't cached it may be a list endpoint, if so verify + //every object included in the response is still cached + this.responseCache.get(request.href) + .take(1) + .filter((entry: ResponseCacheEntry) => entry.response.isSuccessful) + .map((entry: ResponseCacheEntry) => ( entry.response).resourceUUIDs) + .map((resourceUUIDs: Array) => resourceUUIDs.every(uuid => this.objectCache.has(uuid))) + .subscribe(c => isCached = c); + } + const isPending = this.isPending(request.href); if (!(isCached || isPending)) { diff --git a/src/backend/communities.ts b/src/backend/communities.ts index d301936cdf..35a86aeaf9 100644 --- a/src/backend/communities.ts +++ b/src/backend/communities.ts @@ -35,7 +35,7 @@ export const COMMUNITIES = { ], "_links": { "self": { - "href": "http://dspace7.4science.it/dspace-spring-rest/api/core/community/9076bd16-e69a-48d6-9e41-0238cb40d863" + "href": "/communities/6631" }, "collections": [ { "href": "/collections/5179" } @@ -78,7 +78,7 @@ export const COMMUNITIES = { ], "_links": { "self": { - "href": "http://dspace7.4science.it/dspace-spring-rest/api/core/community/9076bd16-e69a-48d6-9e41-0238cb40d863" + "href": "/communities/2365" }, "collections": [ { "href": "/collections/6547" } @@ -86,4 +86,4 @@ export const COMMUNITIES = { } } ] -}; \ No newline at end of file +};