From f56eefab59ccf2f84d67113c1a1cc3d3ba62cb76 Mon Sep 17 00:00:00 2001 From: Art Lowel Date: Fri, 5 May 2017 15:37:43 +0200 Subject: [PATCH] Fixed an issue where deadlocks could occur when loading the relationship decorators --- .../core/cache/builders/build-decorators.ts | 3 +- .../builders/remote-data-build.service.ts | 20 ++++++----- .../cache/models/normalized-bundle.model.ts | 6 ++-- .../models/normalized-collection.model.ts | 4 +-- .../cache/models/normalized-item.model.ts | 4 +-- src/app/core/cache/object-cache.service.ts | 34 +++++++++++++++---- src/app/core/shared/bundle.model.ts | 2 -- src/app/core/shared/item.model.ts | 3 -- 8 files changed, 47 insertions(+), 29 deletions(-) diff --git a/src/app/core/cache/builders/build-decorators.ts b/src/app/core/cache/builders/build-decorators.ts index 93ef84cfa6..00cb50663a 100644 --- a/src/app/core/cache/builders/build-decorators.ts +++ b/src/app/core/cache/builders/build-decorators.ts @@ -1,6 +1,7 @@ import "reflect-metadata"; import { GenericConstructor } from "../../shared/generic-constructor"; import { CacheableObject } from "../object-cache.reducer"; +import { NormalizedDSOType } from "../models/normalized-dspace-object-type"; const mapsToMetadataKey = Symbol("mapsTo"); const relationshipKey = Symbol("relationship"); @@ -15,7 +16,7 @@ export const getMapsTo = function(target: any) { return Reflect.getOwnMetadata(mapsToMetadataKey, target); }; -export const relationship = function(value: any): any { +export const relationship = function(value: NormalizedDSOType): any { return function (target: any, propertyKey: string, descriptor: PropertyDescriptor) { if (!target || !propertyKey) { return; 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 511c5d594a..f37f0ce4f9 100644 --- a/src/app/core/cache/builders/remote-data-build.service.ts +++ b/src/app/core/cache/builders/remote-data-build.service.ts @@ -13,6 +13,7 @@ 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 { @@ -45,13 +46,13 @@ export class RemoteDataBuildService { const payload = Observable.race( - this.objectCache.getBySelfLink(href), + 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]); + return this.objectCache.get(resourceUUIDs[0], normalizedType); } else { return Observable.of(undefined); @@ -95,7 +96,7 @@ export class RemoteDataBuildService { .filter((entry: ResponseCacheEntry) => hasValue(entry) && entry.response.isSuccessful) .map((entry: ResponseCacheEntry) => ( entry.response).resourceUUIDs) .flatMap((resourceUUIDs: Array) => { - return this.objectCache.getList(resourceUUIDs) + return this.objectCache.getList(resourceUUIDs, normalizedType) .map((normList: TNormalized[]) => { return normList.map((normalized: TNormalized) => { return this.build(normalized); @@ -123,32 +124,33 @@ export class RemoteDataBuildService { 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, resourceType) + this.requestService.configure(href, resourceConstructor) }); }, 0); links[relationship] = normalized[relationship].map((href: string) => { - return this.buildSingle(href, resourceType); + return this.buildSingle(href, resourceConstructor); }); } 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], resourceType); + this.requestService.configure(normalized[relationship], resourceConstructor); },0); - links[relationship] = this.buildSingle(normalized[relationship], resourceType); + links[relationship] = this.buildSingle(normalized[relationship], resourceConstructor); } } }); - const constructor = getMapsTo(normalized.constructor); - return Object.assign(new constructor(), normalized, links); + const domainModel = getMapsTo(normalized.constructor); + return Object.assign(new domainModel(), normalized, links); } } diff --git a/src/app/core/cache/models/normalized-bundle.model.ts b/src/app/core/cache/models/normalized-bundle.model.ts index 834fd770f6..bb0e7b0708 100644 --- a/src/app/core/cache/models/normalized-bundle.model.ts +++ b/src/app/core/cache/models/normalized-bundle.model.ts @@ -2,7 +2,7 @@ import { autoserialize, inheritSerialization } from "cerialize"; import { NormalizedDSpaceObject } from "./normalized-dspace-object.model"; import { Bundle } from "../../shared/bundle.model"; import { mapsTo, relationship } from "../builders/build-decorators"; -import { NormalizedBitstream } from "./normalized-bitstream.model"; +import { NormalizedDSOType } from "./normalized-dspace-object-type"; @mapsTo(Bundle) @inheritSerialization(NormalizedDSpaceObject) @@ -11,7 +11,7 @@ export class NormalizedBundle extends NormalizedDSpaceObject { * The primary bitstream of this Bundle */ @autoserialize - @relationship(NormalizedBitstream) + @relationship(NormalizedDSOType.NormalizedBitstream) primaryBitstream: string; /** @@ -25,6 +25,6 @@ export class NormalizedBundle extends NormalizedDSpaceObject { owner: string; @autoserialize - @relationship(NormalizedBitstream) + @relationship(NormalizedDSOType.NormalizedBitstream) bitstreams: Array; } diff --git a/src/app/core/cache/models/normalized-collection.model.ts b/src/app/core/cache/models/normalized-collection.model.ts index 0584646ccb..9b31f34837 100644 --- a/src/app/core/cache/models/normalized-collection.model.ts +++ b/src/app/core/cache/models/normalized-collection.model.ts @@ -2,7 +2,7 @@ import { autoserialize, inheritSerialization, autoserializeAs } from "cerialize" import { NormalizedDSpaceObject } from "./normalized-dspace-object.model"; import { Collection } from "../../shared/collection.model"; import { mapsTo, relationship } from "../builders/build-decorators"; -import { NormalizedItem } from "./normalized-item.model"; +import { NormalizedDSOType } from "./normalized-dspace-object-type"; @mapsTo(Collection) @inheritSerialization(NormalizedDSpaceObject) @@ -30,7 +30,7 @@ export class NormalizedCollection extends NormalizedDSpaceObject { owner: string; @autoserialize - @relationship(NormalizedItem) + @relationship(NormalizedDSOType.NormalizedItem) items: Array; } diff --git a/src/app/core/cache/models/normalized-item.model.ts b/src/app/core/cache/models/normalized-item.model.ts index 9e1dca1a12..3a43d2338e 100644 --- a/src/app/core/cache/models/normalized-item.model.ts +++ b/src/app/core/cache/models/normalized-item.model.ts @@ -2,7 +2,7 @@ import { inheritSerialization, autoserialize } from "cerialize"; import { NormalizedDSpaceObject } from "./normalized-dspace-object.model"; import { Item } from "../../shared/item.model"; import { mapsTo, relationship } from "../builders/build-decorators"; -import { NormalizedBundle } from "./normalized-bundle.model"; +import { NormalizedDSOType } from "./normalized-dspace-object-type"; @mapsTo(Item) @inheritSerialization(NormalizedDSpaceObject) @@ -40,6 +40,6 @@ export class NormalizedItem extends NormalizedDSpaceObject { owner: string; @autoserialize - @relationship(NormalizedBundle) + @relationship(NormalizedDSOType.NormalizedBundle) bundles: Array; } diff --git a/src/app/core/cache/object-cache.service.ts b/src/app/core/cache/object-cache.service.ts index b801421ce3..ec0bea4a97 100644 --- a/src/app/core/cache/object-cache.service.ts +++ b/src/app/core/cache/object-cache.service.ts @@ -4,6 +4,7 @@ import { ObjectCacheState, ObjectCacheEntry, CacheableObject } from "./object-ca import { AddToObjectCacheAction, RemoveFromObjectCacheAction } from "./object-cache.actions"; import { Observable } from "rxjs"; import { hasNoValue } from "../../shared/empty.util"; +import { GenericConstructor } from "../shared/generic-constructor"; /** * A service to interact with the object cache @@ -39,34 +40,53 @@ export class ObjectCacheService { /** * Get an observable of the object with the specified UUID * + * The type needs to be specified as well, in order to turn + * the cached plain javascript object in to an instance of + * a class. + * + * e.g. get('c96588c6-72d3-425d-9d47-fa896255a695', Item) + * * @param uuid * The UUID of the object to get + * @param type + * The type of the object to get * @return Observable * An observable of the requested object */ - get(uuid: string): Observable { + get(uuid: string, type: GenericConstructor): Observable { return this.store.select('core', 'cache', 'object', uuid) .filter(entry => this.isValid(entry)) .distinctUntilChanged() - .map((entry: ObjectCacheEntry) => entry.data); + .map((entry: ObjectCacheEntry) => Object.assign(new type(), entry.data)); } - getBySelfLink(href: string): Observable { + getBySelfLink(href: string, type: GenericConstructor): Observable { return this.store.select('core', 'index', 'href', href) - .flatMap((uuid: string) => this.get(uuid)) + .flatMap((uuid: string) => this.get(uuid, type)) } /** - * Get an observable for an array of objects + * Get an observable for an array of objects of the same type * with the specified UUIDs * + * The type needs to be specified as well, in order to turn + * the cached plain javascript object in to an instance of + * a class. + * + * e.g. getList([ + * 'c96588c6-72d3-425d-9d47-fa896255a695', + * 'cff860da-cf5f-4fda-b8c9-afb7ec0b2d9e' + * ], Collection) + * * @param uuids * An array of UUIDs of the objects to get + * @param type + * The type of the objects to get * @return Observable> */ - getList(uuids: Array): Observable> { + getList(uuids: Array, type: GenericConstructor): Observable> { return Observable.combineLatest( - uuids.map((id: string) => this.get(id)) + uuids.map((id: string) => this.get(id, type)) ); } diff --git a/src/app/core/shared/bundle.model.ts b/src/app/core/shared/bundle.model.ts index f6fd8f3820..6d685cf6ad 100644 --- a/src/app/core/shared/bundle.model.ts +++ b/src/app/core/shared/bundle.model.ts @@ -1,10 +1,8 @@ -import { inheritSerialization } from "cerialize"; import { DSpaceObject } from "./dspace-object.model"; import { Bitstream } from "./bitstream.model"; import { Item } from "./item.model"; import { RemoteData } from "../data/remote-data"; -@inheritSerialization(DSpaceObject) export class Bundle extends DSpaceObject { /** * The primary bitstream of this Bundle diff --git a/src/app/core/shared/item.model.ts b/src/app/core/shared/item.model.ts index fe77068628..2f145e1e06 100644 --- a/src/app/core/shared/item.model.ts +++ b/src/app/core/shared/item.model.ts @@ -1,16 +1,13 @@ -import { inheritSerialization, autoserialize } from "cerialize"; import { DSpaceObject } from "./dspace-object.model"; import { Collection } from "./collection.model"; import { RemoteData } from "../data/remote-data"; import { Bundle } from "./bundle.model"; -@inheritSerialization(DSpaceObject) export class Item extends DSpaceObject { /** * A string representing the unique handle of this Item */ - @autoserialize handle: string; /**