Fixed an issue where deadlocks could occur when loading the relationship decorators

This commit is contained in:
Art Lowel
2017-05-05 15:37:43 +02:00
parent b9de162e00
commit f56eefab59
8 changed files with 47 additions and 29 deletions

View File

@@ -1,6 +1,7 @@
import "reflect-metadata"; import "reflect-metadata";
import { GenericConstructor } from "../../shared/generic-constructor"; import { GenericConstructor } from "../../shared/generic-constructor";
import { CacheableObject } from "../object-cache.reducer"; import { CacheableObject } from "../object-cache.reducer";
import { NormalizedDSOType } from "../models/normalized-dspace-object-type";
const mapsToMetadataKey = Symbol("mapsTo"); const mapsToMetadataKey = Symbol("mapsTo");
const relationshipKey = Symbol("relationship"); const relationshipKey = Symbol("relationship");
@@ -15,7 +16,7 @@ export const getMapsTo = function(target: any) {
return Reflect.getOwnMetadata(mapsToMetadataKey, target); 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) { return function (target: any, propertyKey: string, descriptor: PropertyDescriptor) {
if (!target || !propertyKey) { if (!target || !propertyKey) {
return; return;

View File

@@ -13,6 +13,7 @@ import { Observable } from "rxjs/Observable";
import { RemoteData } from "../../data/remote-data"; import { RemoteData } from "../../data/remote-data";
import { GenericConstructor } from "../../shared/generic-constructor"; import { GenericConstructor } from "../../shared/generic-constructor";
import { getMapsTo, getResourceType, getRelationships } from "./build-decorators"; import { getMapsTo, getResourceType, getRelationships } from "./build-decorators";
import { NormalizedDSOFactory } from "../models/normalized-dspace-object-factory";
@Injectable() @Injectable()
export class RemoteDataBuildService { export class RemoteDataBuildService {
@@ -45,13 +46,13 @@ export class RemoteDataBuildService {
const payload = const payload =
Observable.race( Observable.race(
this.objectCache.getBySelfLink<TNormalized>(href), this.objectCache.getBySelfLink<TNormalized>(href, normalizedType),
responseCacheObs responseCacheObs
.filter((entry: ResponseCacheEntry) => hasValue(entry) && entry.response.isSuccessful) .filter((entry: ResponseCacheEntry) => hasValue(entry) && entry.response.isSuccessful)
.map((entry: ResponseCacheEntry) => (<SuccessResponse> entry.response).resourceUUIDs) .map((entry: ResponseCacheEntry) => (<SuccessResponse> entry.response).resourceUUIDs)
.flatMap((resourceUUIDs: Array<string>) => { .flatMap((resourceUUIDs: Array<string>) => {
if (isNotEmpty(resourceUUIDs)) { if (isNotEmpty(resourceUUIDs)) {
return this.objectCache.get(resourceUUIDs[0]); return this.objectCache.get(resourceUUIDs[0], normalizedType);
} }
else { else {
return Observable.of(undefined); return Observable.of(undefined);
@@ -95,7 +96,7 @@ export class RemoteDataBuildService {
.filter((entry: ResponseCacheEntry) => hasValue(entry) && entry.response.isSuccessful) .filter((entry: ResponseCacheEntry) => hasValue(entry) && entry.response.isSuccessful)
.map((entry: ResponseCacheEntry) => (<SuccessResponse> entry.response).resourceUUIDs) .map((entry: ResponseCacheEntry) => (<SuccessResponse> entry.response).resourceUUIDs)
.flatMap((resourceUUIDs: Array<string>) => { .flatMap((resourceUUIDs: Array<string>) => {
return this.objectCache.getList(resourceUUIDs) return this.objectCache.getList(resourceUUIDs, normalizedType)
.map((normList: TNormalized[]) => { .map((normList: TNormalized[]) => {
return normList.map((normalized: TNormalized) => { return normList.map((normalized: TNormalized) => {
return this.build<TNormalized, TDomain>(normalized); return this.build<TNormalized, TDomain>(normalized);
@@ -123,32 +124,33 @@ export class RemoteDataBuildService {
relationships.forEach((relationship: string) => { relationships.forEach((relationship: string) => {
if (hasValue(normalized[relationship])) { if (hasValue(normalized[relationship])) {
const resourceType = getResourceType(normalized, relationship); const resourceType = getResourceType(normalized, relationship);
const resourceConstructor = NormalizedDSOFactory.getConstructor(resourceType);
if (Array.isArray(normalized[relationship])) { if (Array.isArray(normalized[relationship])) {
// without the setTimeout, the actions inside requestService.configure // without the setTimeout, the actions inside requestService.configure
// are dispatched, but sometimes don't arrive. I'm unsure why atm. // are dispatched, but sometimes don't arrive. I'm unsure why atm.
setTimeout(() => { setTimeout(() => {
normalized[relationship].forEach((href: string) => { normalized[relationship].forEach((href: string) => {
this.requestService.configure(href, resourceType) this.requestService.configure(href, resourceConstructor)
}); });
}, 0); }, 0);
links[relationship] = normalized[relationship].map((href: string) => { links[relationship] = normalized[relationship].map((href: string) => {
return this.buildSingle(href, resourceType); return this.buildSingle(href, resourceConstructor);
}); });
} }
else { else {
// without the setTimeout, the actions inside requestService.configure // without the setTimeout, the actions inside requestService.configure
// are dispatched, but sometimes don't arrive. I'm unsure why atm. // are dispatched, but sometimes don't arrive. I'm unsure why atm.
setTimeout(() => { setTimeout(() => {
this.requestService.configure(normalized[relationship], resourceType); this.requestService.configure(normalized[relationship], resourceConstructor);
},0); },0);
links[relationship] = this.buildSingle(normalized[relationship], resourceType); links[relationship] = this.buildSingle(normalized[relationship], resourceConstructor);
} }
} }
}); });
const constructor = getMapsTo(normalized.constructor); const domainModel = getMapsTo(normalized.constructor);
return Object.assign(new constructor(), normalized, links); return Object.assign(new domainModel(), normalized, links);
} }
} }

View File

@@ -2,7 +2,7 @@ import { autoserialize, inheritSerialization } from "cerialize";
import { NormalizedDSpaceObject } from "./normalized-dspace-object.model"; import { NormalizedDSpaceObject } from "./normalized-dspace-object.model";
import { Bundle } from "../../shared/bundle.model"; import { Bundle } from "../../shared/bundle.model";
import { mapsTo, relationship } from "../builders/build-decorators"; import { mapsTo, relationship } from "../builders/build-decorators";
import { NormalizedBitstream } from "./normalized-bitstream.model"; import { NormalizedDSOType } from "./normalized-dspace-object-type";
@mapsTo(Bundle) @mapsTo(Bundle)
@inheritSerialization(NormalizedDSpaceObject) @inheritSerialization(NormalizedDSpaceObject)
@@ -11,7 +11,7 @@ export class NormalizedBundle extends NormalizedDSpaceObject {
* The primary bitstream of this Bundle * The primary bitstream of this Bundle
*/ */
@autoserialize @autoserialize
@relationship(NormalizedBitstream) @relationship(NormalizedDSOType.NormalizedBitstream)
primaryBitstream: string; primaryBitstream: string;
/** /**
@@ -25,6 +25,6 @@ export class NormalizedBundle extends NormalizedDSpaceObject {
owner: string; owner: string;
@autoserialize @autoserialize
@relationship(NormalizedBitstream) @relationship(NormalizedDSOType.NormalizedBitstream)
bitstreams: Array<string>; bitstreams: Array<string>;
} }

View File

@@ -2,7 +2,7 @@ import { autoserialize, inheritSerialization, autoserializeAs } from "cerialize"
import { NormalizedDSpaceObject } from "./normalized-dspace-object.model"; import { NormalizedDSpaceObject } from "./normalized-dspace-object.model";
import { Collection } from "../../shared/collection.model"; import { Collection } from "../../shared/collection.model";
import { mapsTo, relationship } from "../builders/build-decorators"; import { mapsTo, relationship } from "../builders/build-decorators";
import { NormalizedItem } from "./normalized-item.model"; import { NormalizedDSOType } from "./normalized-dspace-object-type";
@mapsTo(Collection) @mapsTo(Collection)
@inheritSerialization(NormalizedDSpaceObject) @inheritSerialization(NormalizedDSpaceObject)
@@ -30,7 +30,7 @@ export class NormalizedCollection extends NormalizedDSpaceObject {
owner: string; owner: string;
@autoserialize @autoserialize
@relationship(NormalizedItem) @relationship(NormalizedDSOType.NormalizedItem)
items: Array<string>; items: Array<string>;
} }

View File

@@ -2,7 +2,7 @@ import { inheritSerialization, autoserialize } from "cerialize";
import { NormalizedDSpaceObject } from "./normalized-dspace-object.model"; import { NormalizedDSpaceObject } from "./normalized-dspace-object.model";
import { Item } from "../../shared/item.model"; import { Item } from "../../shared/item.model";
import { mapsTo, relationship } from "../builders/build-decorators"; import { mapsTo, relationship } from "../builders/build-decorators";
import { NormalizedBundle } from "./normalized-bundle.model"; import { NormalizedDSOType } from "./normalized-dspace-object-type";
@mapsTo(Item) @mapsTo(Item)
@inheritSerialization(NormalizedDSpaceObject) @inheritSerialization(NormalizedDSpaceObject)
@@ -40,6 +40,6 @@ export class NormalizedItem extends NormalizedDSpaceObject {
owner: string; owner: string;
@autoserialize @autoserialize
@relationship(NormalizedBundle) @relationship(NormalizedDSOType.NormalizedBundle)
bundles: Array<string>; bundles: Array<string>;
} }

View File

@@ -4,6 +4,7 @@ import { ObjectCacheState, ObjectCacheEntry, CacheableObject } from "./object-ca
import { AddToObjectCacheAction, RemoveFromObjectCacheAction } from "./object-cache.actions"; import { AddToObjectCacheAction, RemoveFromObjectCacheAction } from "./object-cache.actions";
import { Observable } from "rxjs"; import { Observable } from "rxjs";
import { hasNoValue } from "../../shared/empty.util"; import { hasNoValue } from "../../shared/empty.util";
import { GenericConstructor } from "../shared/generic-constructor";
/** /**
* A service to interact with the object cache * 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 * 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 * @param uuid
* The UUID of the object to get * The UUID of the object to get
* @param type
* The type of the object to get
* @return Observable<T> * @return Observable<T>
* An observable of the requested object * An observable of the requested object
*/ */
get<T extends CacheableObject>(uuid: string): Observable<T> { get<T extends CacheableObject>(uuid: string, type: GenericConstructor<T>): Observable<T> {
return this.store.select<ObjectCacheEntry>('core', 'cache', 'object', uuid) return this.store.select<ObjectCacheEntry>('core', 'cache', 'object', uuid)
.filter(entry => this.isValid(entry)) .filter(entry => this.isValid(entry))
.distinctUntilChanged() .distinctUntilChanged()
.map((entry: ObjectCacheEntry) => <T> entry.data); .map((entry: ObjectCacheEntry) => <T> Object.assign(new type(), entry.data));
} }
getBySelfLink<T extends CacheableObject>(href: string): Observable<T> { getBySelfLink<T extends CacheableObject>(href: string, type: GenericConstructor<T>): Observable<T> {
return this.store.select<string>('core', 'index', 'href', href) return this.store.select<string>('core', 'index', 'href', href)
.flatMap((uuid: string) => this.get<T>(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 * 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 * @param uuids
* An array of UUIDs of the objects to get * An array of UUIDs of the objects to get
* @param type
* The type of the objects to get
* @return Observable<Array<T>> * @return Observable<Array<T>>
*/ */
getList<T extends CacheableObject>(uuids: Array<string>): Observable<Array<T>> { getList<T extends CacheableObject>(uuids: Array<string>, type: GenericConstructor<T>): Observable<Array<T>> {
return Observable.combineLatest( return Observable.combineLatest(
uuids.map((id: string) => this.get<T>(id)) uuids.map((id: string) => this.get<T>(id, type))
); );
} }

View File

@@ -1,10 +1,8 @@
import { inheritSerialization } from "cerialize";
import { DSpaceObject } from "./dspace-object.model"; import { DSpaceObject } from "./dspace-object.model";
import { Bitstream } from "./bitstream.model"; import { Bitstream } from "./bitstream.model";
import { Item } from "./item.model"; import { Item } from "./item.model";
import { RemoteData } from "../data/remote-data"; import { RemoteData } from "../data/remote-data";
@inheritSerialization(DSpaceObject)
export class Bundle extends DSpaceObject { export class Bundle extends DSpaceObject {
/** /**
* The primary bitstream of this Bundle * The primary bitstream of this Bundle

View File

@@ -1,16 +1,13 @@
import { inheritSerialization, autoserialize } from "cerialize";
import { DSpaceObject } from "./dspace-object.model"; import { DSpaceObject } from "./dspace-object.model";
import { Collection } from "./collection.model"; import { Collection } from "./collection.model";
import { RemoteData } from "../data/remote-data"; import { RemoteData } from "../data/remote-data";
import { Bundle } from "./bundle.model"; import { Bundle } from "./bundle.model";
@inheritSerialization(DSpaceObject)
export class Item extends DSpaceObject { export class Item extends DSpaceObject {
/** /**
* A string representing the unique handle of this Item * A string representing the unique handle of this Item
*/ */
@autoserialize
handle: string; handle: string;
/** /**