From 21a6a80d132cd81baf667641a855844b7dcb981c Mon Sep 17 00:00:00 2001 From: Art Lowel Date: Tue, 20 Jun 2017 15:04:42 +0200 Subject: [PATCH] fixed issues with related resources that have a single link for a hasMany relationship (e.g. item.bitstreams) --- .../core/cache/builders/build-decorators.ts | 6 ++-- .../builders/remote-data-build.service.ts | 32 ++++++++++++++++--- .../normalized-bitstream-format.model.ts | 11 +++++++ .../models/normalized-bitstream.model.ts | 8 ++--- .../cache/models/normalized-bundle.model.ts | 4 +-- .../models/normalized-collection.model.ts | 8 ++--- .../models/normalized-community.model.ts | 8 ++--- .../models/normalized-dspace-object.model.ts | 15 +++++++-- .../cache/models/normalized-item.model.ts | 6 ++-- .../cache/models/normalized-object-factory.ts | 9 +++++- .../cache/models/normalized-object.model.ts | 20 ++++++++++++ src/app/core/data/request.effects.ts | 17 ++++++++-- src/app/core/shared/bitstream.model.ts | 4 +-- src/app/core/shared/resource-type.ts | 1 + .../collections/collections.component.html | 2 +- .../collections/collections.component.ts | 4 +-- .../file-section/file-section.component.html | 2 +- 17 files changed, 119 insertions(+), 38 deletions(-) create mode 100644 src/app/core/cache/models/normalized-bitstream-format.model.ts create mode 100644 src/app/core/cache/models/normalized-object.model.ts diff --git a/src/app/core/cache/builders/build-decorators.ts b/src/app/core/cache/builders/build-decorators.ts index f00d8d87e5..5cd9a740dc 100644 --- a/src/app/core/cache/builders/build-decorators.ts +++ b/src/app/core/cache/builders/build-decorators.ts @@ -16,7 +16,7 @@ export const getMapsTo = function(target: any) { return Reflect.getOwnMetadata(mapsToMetadataKey, target); }; -export const relationship = function(value: ResourceType): any { +export const relationship = function(value: ResourceType, isList: boolean = false): any { return function (target: any, propertyKey: string, descriptor: PropertyDescriptor) { if (!target || !propertyKey) { return; @@ -28,11 +28,11 @@ export const relationship = function(value: ResourceType): any { } relationshipMap.set(target.constructor, metaDataList); - return Reflect.metadata(relationshipKey, value).apply(this, arguments); + return Reflect.metadata(relationshipKey, { resourceType: value, isList }).apply(this, arguments); }; }; -export const getResourceType = function(target: any, propertyKey: string) { +export const getRelationMetadata = function(target: any, propertyKey: string) { return Reflect.getMetadata(relationshipKey, target, propertyKey); }; 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 7caa48fc07..0901c8b69b 100644 --- a/src/app/core/cache/builders/remote-data-build.service.ts +++ b/src/app/core/cache/builders/remote-data-build.service.ts @@ -12,7 +12,7 @@ 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 { getMapsTo, getRelationMetadata, getRelationships } from "./build-decorators"; import { NormalizedObjectFactory } from "../models/normalized-object-factory"; import { Request } from "../../data/request.models"; @@ -64,11 +64,26 @@ export class RemoteDataBuildService { .map((entry: ResponseCacheEntry) => ( entry.response).pageInfo) .distinctUntilChanged(); - const payload = this.objectCache.getBySelfLink(href, normalizedType) - .map((normalized: TNormalized) => { + const payload = + Observable.race( + this.objectCache.getBySelfLink(href, normalizedType), + responseCacheObs + .filter((entry: ResponseCacheEntry) => 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) => { return this.build(normalized); }); + return new RemoteData( href, requestPending, @@ -143,7 +158,7 @@ export class RemoteDataBuildService { relationships.forEach((relationship: string) => { if (hasValue(normalized[relationship])) { - const resourceType = getResourceType(normalized, relationship); + const { resourceType, isList } = getRelationMetadata(normalized, relationship); const resourceConstructor = NormalizedObjectFactory.getConstructor(resourceType); if (Array.isArray(normalized[relationship])) { // without the setTimeout, the actions inside requestService.configure @@ -168,7 +183,14 @@ export class RemoteDataBuildService { this.requestService.configure(new Request(normalized[relationship])); },0); - links[relationship] = this.buildSingle(normalized[relationship], resourceConstructor); + // The rest API can return a single URL to represent a list of resources (e.g. /items/:id/bitstreams) + // in that case only 1 href will be stored in the normalized obj (so the isArray above fails), + // but it should still be built as a list + if (isList) { + links[relationship] = this.buildList(normalized[relationship], resourceConstructor); + } else { + links[relationship] = this.buildSingle(normalized[relationship], resourceConstructor); + } } } }); diff --git a/src/app/core/cache/models/normalized-bitstream-format.model.ts b/src/app/core/cache/models/normalized-bitstream-format.model.ts new file mode 100644 index 0000000000..188bd3d185 --- /dev/null +++ b/src/app/core/cache/models/normalized-bitstream-format.model.ts @@ -0,0 +1,11 @@ +import { NormalizedObject } from "./normalized-object.model"; +import { inheritSerialization } from "cerialize"; + +@inheritSerialization(NormalizedObject) +export class NormalizedBitstreamFormat extends NormalizedObject { + //TODO this class was created as a placeholder when we connected to the live rest api + + get uuid(): string { + return this.self; + } +} diff --git a/src/app/core/cache/models/normalized-bitstream.model.ts b/src/app/core/cache/models/normalized-bitstream.model.ts index 43a4b9f057..3b5c4fdc85 100644 --- a/src/app/core/cache/models/normalized-bitstream.model.ts +++ b/src/app/core/cache/models/normalized-bitstream.model.ts @@ -9,10 +9,10 @@ import { ResourceType } from "../../shared/resource-type"; export class NormalizedBitstream extends NormalizedDSpaceObject { /** - * The size of this bitstream in bytes(?) + * The size of this bitstream in bytes */ @autoserialize - size: number; + sizeBytes: number; /** * The relative path to this Bitstream's file @@ -42,14 +42,14 @@ export class NormalizedBitstream extends NormalizedDSpaceObject { * An array of Bundles that are direct parents of this Bitstream */ @autoserialize - @relationship(ResourceType.Item) + @relationship(ResourceType.Item, true) parents: Array; /** * The Bundle that owns this Bitstream */ @autoserialize - @relationship(ResourceType.Item) + @relationship(ResourceType.Item, false) owner: string; /** diff --git a/src/app/core/cache/models/normalized-bundle.model.ts b/src/app/core/cache/models/normalized-bundle.model.ts index 6333428227..839f09b247 100644 --- a/src/app/core/cache/models/normalized-bundle.model.ts +++ b/src/app/core/cache/models/normalized-bundle.model.ts @@ -11,7 +11,7 @@ export class NormalizedBundle extends NormalizedDSpaceObject { * The primary bitstream of this Bundle */ @autoserialize - @relationship(ResourceType.Bitstream) + @relationship(ResourceType.Bitstream, false) primaryBitstream: string; /** @@ -25,6 +25,6 @@ export class NormalizedBundle extends NormalizedDSpaceObject { owner: string; @autoserialize - @relationship(ResourceType.Bitstream) + @relationship(ResourceType.Bitstream, true) 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 cf30024d0b..669c02e383 100644 --- a/src/app/core/cache/models/normalized-collection.model.ts +++ b/src/app/core/cache/models/normalized-collection.model.ts @@ -18,25 +18,25 @@ export class NormalizedCollection extends NormalizedDSpaceObject { * The Bitstream that represents the logo of this Collection */ @autoserialize - @relationship(ResourceType.Bitstream) + @relationship(ResourceType.Bitstream, false) logo: string; /** * An array of Communities that are direct parents of this Collection */ @autoserialize - @relationship(ResourceType.Community) + @relationship(ResourceType.Community, true) parents: Array; /** * The Community that owns this Collection */ @autoserialize - @relationship(ResourceType.Community) + @relationship(ResourceType.Community, false) owner: string; @autoserialize - @relationship(ResourceType.Item) + @relationship(ResourceType.Item, true) items: Array; } diff --git a/src/app/core/cache/models/normalized-community.model.ts b/src/app/core/cache/models/normalized-community.model.ts index f0e7c5681d..1a6b0fea9e 100644 --- a/src/app/core/cache/models/normalized-community.model.ts +++ b/src/app/core/cache/models/normalized-community.model.ts @@ -18,25 +18,25 @@ export class NormalizedCommunity extends NormalizedDSpaceObject { * The Bitstream that represents the logo of this Community */ @autoserialize - @relationship(ResourceType.Bitstream) + @relationship(ResourceType.Bitstream, false) logo: string; /** * An array of Communities that are direct parents of this Community */ @autoserialize - @relationship(ResourceType.Community) + @relationship(ResourceType.Community, true) parents: Array; /** * The Community that owns this Community */ @autoserialize - @relationship(ResourceType.Community) + @relationship(ResourceType.Community, false) owner: string; @autoserialize - @relationship(ResourceType.Collection) + @relationship(ResourceType.Collection, true) collections: Array; } diff --git a/src/app/core/cache/models/normalized-dspace-object.model.ts b/src/app/core/cache/models/normalized-dspace-object.model.ts index e3712b0c2a..259bb5e097 100644 --- a/src/app/core/cache/models/normalized-dspace-object.model.ts +++ b/src/app/core/cache/models/normalized-dspace-object.model.ts @@ -1,13 +1,19 @@ -import { autoserialize, autoserializeAs } from "cerialize"; -import { CacheableObject } from "../object-cache.reducer"; +import { autoserialize, autoserializeAs, inheritSerialization } from "cerialize"; import { Metadatum } from "../../shared/metadatum.model"; import { ResourceType } from "../../shared/resource-type"; +import { NormalizedObject } from "./normalized-object.model"; /** * An abstract model class for a DSpaceObject. */ -export abstract class NormalizedDSpaceObject implements CacheableObject { +export abstract class NormalizedDSpaceObject extends NormalizedObject{ + /** + * The link to the rest endpoint where this object can be found + * + * Repeated here to make the serialization work, + * inheritSerialization doesn't seem to work for more than one level + */ @autoserialize self: string; @@ -22,6 +28,9 @@ export abstract class NormalizedDSpaceObject implements CacheableObject { /** * The universally unique identifier of this DSpaceObject + * + * Repeated here to make the serialization work, + * inheritSerialization doesn't seem to work for more than one level */ @autoserialize uuid: string; diff --git a/src/app/core/cache/models/normalized-item.model.ts b/src/app/core/cache/models/normalized-item.model.ts index 03cde47bda..6ed2ede8c2 100644 --- a/src/app/core/cache/models/normalized-item.model.ts +++ b/src/app/core/cache/models/normalized-item.model.ts @@ -42,17 +42,17 @@ export class NormalizedItem extends NormalizedDSpaceObject { * An array of Collections that are direct parents of this Item */ @autoserialize - @relationship(ResourceType.Collection) + @relationship(ResourceType.Collection, true) parents: Array; /** * The Collection that owns this Item */ @autoserializeAs(String, 'owningCollection') - @relationship(ResourceType.Collection) + @relationship(ResourceType.Collection, false) owner: string; @autoserialize - @relationship(ResourceType.Bitstream) + @relationship(ResourceType.Bitstream, true) bitstreams: Array; } diff --git a/src/app/core/cache/models/normalized-object-factory.ts b/src/app/core/cache/models/normalized-object-factory.ts index 45eaacbf5c..84bd2061cb 100644 --- a/src/app/core/cache/models/normalized-object-factory.ts +++ b/src/app/core/cache/models/normalized-object-factory.ts @@ -6,13 +6,20 @@ import { NormalizedCollection } from "./normalized-collection.model"; import { GenericConstructor } from "../../shared/generic-constructor"; import { NormalizedCommunity } from "./normalized-community.model"; import { ResourceType } from "../../shared/resource-type"; +import { NormalizedObject } from "./normalized-object.model"; +import { NormalizedBitstreamFormat } from "./normalized-bitstream-format.model"; export class NormalizedObjectFactory { - public static getConstructor(type: ResourceType): GenericConstructor { + public static getConstructor(type: ResourceType): GenericConstructor { switch (type) { case ResourceType.Bitstream: { return NormalizedBitstream } + // commented out for now, bitstreamformats aren't used in the UI yet + // and slow things down noticeably + // case ResourceType.BitstreamFormat: { + // return NormalizedBitstreamFormat + // } case ResourceType.Bundle: { return NormalizedBundle } diff --git a/src/app/core/cache/models/normalized-object.model.ts b/src/app/core/cache/models/normalized-object.model.ts new file mode 100644 index 0000000000..055996ab47 --- /dev/null +++ b/src/app/core/cache/models/normalized-object.model.ts @@ -0,0 +1,20 @@ +import { CacheableObject } from "../object-cache.reducer"; +import { autoserialize } from "cerialize"; +/** + * An abstract model class for a NormalizedObject. + */ +export abstract class NormalizedObject implements CacheableObject { + + /** + * The link to the rest endpoint where this object can be found + */ + @autoserialize + self: string; + + /** + * The universally unique identifier of this Object + */ + @autoserialize + uuid: string; + +} diff --git a/src/app/core/data/request.effects.ts b/src/app/core/data/request.effects.ts index 8e0de916f8..1fb325bb3c 100644 --- a/src/app/core/data/request.effects.ts +++ b/src/app/core/data/request.effects.ts @@ -72,7 +72,16 @@ export class RequestEffects { .filter(property => data.hasOwnProperty(property)) .filter(property => hasValue(data[property])) .forEach(property => { - uuids = [...uuids, ...this.deserializeAndCache(data[property], requestHref)]; + let propertyUUIDs; + + if (isPaginatedResponse(data[property])) { + propertyUUIDs = this.process(data[property], requestHref); + } + else { + propertyUUIDs = this.deserializeAndCache(data[property], requestHref); + } + + uuids = [...uuids, ...propertyUUIDs]; }); return uuids; } @@ -122,13 +131,15 @@ export class RequestEffects { } else { //TODO move check to Validator? - throw new Error(`The server returned an object with an unknown a known type: ${type}`); + // throw new Error(`The server returned an object with an unknown a known type: ${type}`); + return []; } } else { //TODO move check to Validator - throw new Error(`The server returned an object without a type: ${JSON.stringify(obj)}`); + // throw new Error(`The server returned an object without a type: ${JSON.stringify(obj)}`); + return []; } } diff --git a/src/app/core/shared/bitstream.model.ts b/src/app/core/shared/bitstream.model.ts index 86ff99b3eb..8e7f6204a3 100644 --- a/src/app/core/shared/bitstream.model.ts +++ b/src/app/core/shared/bitstream.model.ts @@ -5,9 +5,9 @@ import { Item } from "./item.model"; export class Bitstream extends DSpaceObject { /** - * The size of this bitstream in bytes(?) + * The size of this bitstream in bytes */ - size: number; + sizeBytes: number; /** * The mime type of this Bitstream diff --git a/src/app/core/shared/resource-type.ts b/src/app/core/shared/resource-type.ts index 2e180cba71..182397d87c 100644 --- a/src/app/core/shared/resource-type.ts +++ b/src/app/core/shared/resource-type.ts @@ -5,6 +5,7 @@ export enum ResourceType { Bundle = "bundle", Bitstream = "bitstream", + BitstreamFormat = "bitstreamformat", Item = "item", Collection = "collection", Community = "community" diff --git a/src/app/item-page/field-components/collections/collections.component.html b/src/app/item-page/field-components/collections/collections.component.html index 8f184817c2..bb7ab63341 100644 --- a/src/app/item-page/field-components/collections/collections.component.html +++ b/src/app/item-page/field-components/collections/collections.component.html @@ -1,6 +1,6 @@ diff --git a/src/app/item-page/field-components/collections/collections.component.ts b/src/app/item-page/field-components/collections/collections.component.ts index 0ff774b1ea..895fe79bf8 100644 --- a/src/app/item-page/field-components/collections/collections.component.ts +++ b/src/app/item-page/field-components/collections/collections.component.ts @@ -19,7 +19,7 @@ export class CollectionsComponent implements OnInit { label : string = "item.page.collections"; - separator: string = "
" + separator: string = "
"; collections: Observable; @@ -38,7 +38,7 @@ export class CollectionsComponent implements OnInit { //TODO this should use parents, but the collections // for an Item aren't returned by the REST API yet, // only the owning collection - this.collections = this.rdbs.aggregate([this.item.owner]).payload + this.collections = this.item.owner.payload.map(c => [c]); } diff --git a/src/app/item-page/simple/field-components/file-section/file-section.component.html b/src/app/item-page/simple/field-components/file-section/file-section.component.html index 1eba7d9f7f..f62e55edeb 100644 --- a/src/app/item-page/simple/field-components/file-section/file-section.component.html +++ b/src/app/item-page/simple/field-components/file-section/file-section.component.html @@ -2,7 +2,7 @@