Merge branch 'rest-relationships' into w2p-40416_simple-item-page

Conflicts:
	src/app/core/cache/models/bundle-builder.ts
	src/app/core/cache/models/collection-builder.ts
	src/app/core/cache/models/item-builder.ts
This commit is contained in:
Lotte Hofstede
2017-05-04 13:47:56 +02:00
18 changed files with 187 additions and 503 deletions

View File

@@ -0,0 +1,39 @@
import { GenericConstructor } from "../../shared/generic-constructor";
import { CacheableObject } from "../object-cache.reducer";
const mapsToMetadataKey = Symbol("mapsTo");
const relationshipKey = Symbol("relationship");
const relationshipMap = new Map();
export const mapsTo = function(value: GenericConstructor<CacheableObject>) {
return Reflect.metadata(mapsToMetadataKey, value);
};
export const getMapsTo = function(target: any) {
return Reflect.getOwnMetadata(mapsToMetadataKey, target);
};
export const relationship = function(value: any): any {
return function (target: any, propertyKey: string, descriptor: PropertyDescriptor) {
if (!target || !propertyKey) {
return;
}
let metaDataList : Array<string> = relationshipMap.get(target.constructor) || [];
if (metaDataList.indexOf(propertyKey) === -1) {
metaDataList.push(propertyKey);
}
relationshipMap.set(target.constructor, metaDataList);
return Reflect.metadata(relationshipKey, value).apply(this, arguments);
};
};
export const getResourceType = function(target: any, propertyKey: string) {
return Reflect.getMetadata(relationshipKey, target, propertyKey);
};
export const getRelationships = function(target: any) {
return relationshipMap.get(target);
};

View File

@@ -1,38 +1,35 @@
import { RemoteData } from "../../data/remote-data";
import { Observable } from "rxjs/Observable";
import { RequestEntry } from "../../data/request.reducer";
import { ResponseCacheEntry } from "../response-cache.reducer";
import { ErrorResponse, SuccessResponse } from "../response-cache.models";
import { Store } from "@ngrx/store";
import { CoreState } from "../../core.reducers";
import { ResponseCacheService } from "../response-cache.service";
import { Injectable } from "@angular/core";
import { CacheableObject } from "../object-cache.reducer";
import { ObjectCacheService } from "../object-cache.service";
import { RequestService } from "../../data/request.service";
import { CacheableObject } from "../object-cache.reducer";
import { GenericConstructor } from "../../shared/generic-constructor";
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";
export interface RemoteDataBuilder<T> {
build(): RemoteData<T>
}
export abstract class SingleRemoteDataBuilder<TDomain, TNormalized extends CacheableObject> implements RemoteDataBuilder<TDomain> {
@Injectable()
export class RemoteDataBuildService {
constructor(
protected objectCache: ObjectCacheService,
protected responseCache: ResponseCacheService,
protected requestService: RequestService,
protected store: Store<CoreState>,
protected href: string,
protected normalizedType: GenericConstructor<TNormalized>
) {
}
protected abstract normalizedToDomain(normalized: TNormalized): TDomain;
build(): RemoteData<TDomain> {
const requestObs = this.store.select<RequestEntry>('core', 'data', 'request', this.href);
const responseCacheObs = this.responseCache.get(this.href);
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();
@@ -48,23 +45,25 @@ export abstract class SingleRemoteDataBuilder<TDomain, TNormalized extends Cache
const payload =
Observable.race(
this.objectCache.getBySelfLink<TNormalized>(this.href, this.normalizedType),
this.objectCache.getBySelfLink<TNormalized>(href),
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], this.normalizedType);
return this.objectCache.get(resourceUUIDs[0]);
}
else {
return Observable.of(undefined);
}
})
.distinctUntilChanged()
).map((normalized: TNormalized) => this.normalizedToDomain(normalized));
).map((normalized: TNormalized) => {
return this.build<TNormalized, TDomain>(normalized);
});
return new RemoteData(
this.href,
href,
requestPending,
responsePending,
isSuccessFul,
@@ -73,25 +72,12 @@ export abstract class SingleRemoteDataBuilder<TDomain, TNormalized extends Cache
);
}
}
export abstract class ListRemoteDataBuilder<TDomain, TNormalized extends CacheableObject> implements RemoteDataBuilder<TDomain[]> {
constructor(
protected objectCache: ObjectCacheService,
protected responseCache: ResponseCacheService,
protected requestService: RequestService,
protected store: Store<CoreState>,
protected href: string,
protected normalizedType: GenericConstructor<TNormalized>
) {
}
protected abstract normalizedToDomain(normalized: TNormalized): TDomain;
build(): RemoteData<TDomain[]> {
const requestObs = this.store.select<RequestEntry>('core', 'data', 'request', this.href);
const responseCacheObs = this.responseCache.get(this.href);
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();
@@ -109,17 +95,17 @@ export abstract class ListRemoteDataBuilder<TDomain, TNormalized extends Cacheab
.filter((entry: ResponseCacheEntry) => hasValue(entry) && entry.response.isSuccessful)
.map((entry: ResponseCacheEntry) => (<SuccessResponse> entry.response).resourceUUIDs)
.flatMap((resourceUUIDs: Array<string>) => {
return this.objectCache.getList(resourceUUIDs, this.normalizedType)
return this.objectCache.getList(resourceUUIDs)
.map((normList: TNormalized[]) => {
return normList.map((normalized: TNormalized) => {
return this.normalizedToDomain(normalized);
return this.build<TNormalized, TDomain>(normalized);
});
});
})
.distinctUntilChanged();
return new RemoteData(
this.href,
href,
requestPending,
responsePending,
isSuccessFul,
@@ -128,4 +114,41 @@ export abstract class ListRemoteDataBuilder<TDomain, TNormalized extends Cacheab
);
}
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);
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)
});
}, 0);
links[relationship] = normalized[relationship].map((href: string) => {
return this.buildSingle(href, resourceType);
});
}
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);
},0);
links[relationship] = this.buildSingle(normalized[relationship], resourceType);
}
}
});
const constructor = getMapsTo(normalized.constructor);
return Object.assign(new constructor(), normalized, links);
}
}

View File

@@ -1,65 +0,0 @@
import { Bitstream } from "../../shared/bitstream.model";
import { ObjectCacheService } from "../object-cache.service";
import { ResponseCacheService } from "../response-cache.service";
import { RequestService } from "../../data/request.service";
import { Store } from "@ngrx/store";
import { CoreState } from "../../core.reducers";
import { NormalizedBitstream } from "./normalized-bitstream.model";
import { ListRemoteDataBuilder, SingleRemoteDataBuilder } from "./remote-data-builder";
import { Request } from "../../data/request.models";
import { hasValue } from "../../../shared/empty.util";
import { RequestConfigureAction, RequestExecuteAction } from "../../data/request.actions";
export class BitstreamBuilder {
constructor(
protected objectCache: ObjectCacheService,
protected responseCache: ResponseCacheService,
protected requestService: RequestService,
protected store: Store<CoreState>,
protected href: string,
protected normalized: NormalizedBitstream
) {
}
build(): Bitstream {
let links: any = {};
//TODO
return Object.assign(new Bitstream(), this.normalized, links);
}
}
export class BitstreamRDBuilder extends SingleRemoteDataBuilder<Bitstream, NormalizedBitstream> {
constructor(
objectCache: ObjectCacheService,
responseCache: ResponseCacheService,
requestService: RequestService,
store: Store<CoreState>,
href: string
) {
super(objectCache, responseCache, requestService, store, href, NormalizedBitstream);
}
protected normalizedToDomain(normalized: NormalizedBitstream): Bitstream {
return new BitstreamBuilder(this.objectCache, this.responseCache, this.requestService, this.store, this.href, normalized).build();
}
}
export class BitstreamListRDBuilder extends ListRemoteDataBuilder<Bitstream, NormalizedBitstream> {
constructor(
objectCache: ObjectCacheService,
responseCache: ResponseCacheService,
requestService: RequestService,
store: Store<CoreState>,
href: string
) {
super(objectCache, responseCache, requestService, store, href, NormalizedBitstream);
}
protected normalizedToDomain(normalized: NormalizedBitstream): Bitstream {
return new BitstreamBuilder(this.objectCache, this.responseCache, this.requestService, this.store, this.href, normalized).build();
}
}

View File

@@ -1,111 +0,0 @@
import { Bundle } from "../../shared/bundle.model";
import { ObjectCacheService } from "../object-cache.service";
import { ResponseCacheService } from "../response-cache.service";
import { RequestService } from "../../data/request.service";
import { Store } from "@ngrx/store";
import { CoreState } from "../../core.reducers";
import { NormalizedBundle } from "./normalized-bundle.model";
import { ListRemoteDataBuilder, SingleRemoteDataBuilder } from "./remote-data-builder";
import { Request } from "../../data/request.models";
import { hasValue } from "../../../shared/empty.util";
import { RequestConfigureAction, RequestExecuteAction } from "../../data/request.actions";
import { BitstreamRDBuilder } from "./bitstream-builder";
import { NormalizedBitstream } from "./normalized-bitstream.model";
export class BundleBuilder {
constructor(protected objectCache: ObjectCacheService,
protected responseCache: ResponseCacheService,
protected requestService: RequestService,
protected store: Store<CoreState>,
protected href: string,
protected normalized: NormalizedBundle) {
}
build(): Bundle {
let links: any = {};
if (hasValue(this.normalized.bitstreams)) {
//for some reason the dispatches in the forEach don't
//fire without this timeout. A zone issue?
setTimeout(() => {
this.normalized.bitstreams.forEach((href: string) => {
const isCached = this.objectCache.hasBySelfLink(href);
const isPending = this.requestService.isPending(href);
if (!(isCached || isPending)) {
const request = new Request(href, NormalizedBitstream);
this.store.dispatch(new RequestConfigureAction(request));
this.store.dispatch(new RequestExecuteAction(href));
}
});
}, 0);
links.bitstreams = this.normalized.bitstreams.map((href: string) => {
return new BitstreamRDBuilder(
this.objectCache,
this.responseCache,
this.requestService,
this.store,
href
).build();
});
}
if (hasValue(this.normalized.primaryBitstream)) {
const href = this.normalized.primaryBitstream;
//for some reason the dispatches in the forEach don't
//fire without this timeout. A zone issue?
setTimeout(() => {
const isCached = this.objectCache.hasBySelfLink(href);
const isPending = this.requestService.isPending(href);
if (!(isCached || isPending)) {
const request = new Request(href, NormalizedBitstream);
this.store.dispatch(new RequestConfigureAction(request));
this.store.dispatch(new RequestExecuteAction(href));
}
}, 0);
links.primaryBitstream =
new BitstreamRDBuilder(
this.objectCache,
this.responseCache,
this.requestService,
this.store,
href
).build();
}
return Object.assign(new Bundle(), this.normalized, links);
}
}
export class BundleRDBuilder extends SingleRemoteDataBuilder<Bundle, NormalizedBundle> {
constructor(objectCache: ObjectCacheService,
responseCache: ResponseCacheService,
requestService: RequestService,
store: Store<CoreState>,
href: string) {
super(objectCache, responseCache, requestService, store, href, NormalizedBundle);
}
protected normalizedToDomain(normalized: NormalizedBundle): Bundle {
return new BundleBuilder(this.objectCache, this.responseCache, this.requestService, this.store, this.href, normalized).build();
}
}
export class BundleListRDBuilder extends ListRemoteDataBuilder<Bundle, NormalizedBundle> {
constructor(objectCache: ObjectCacheService,
responseCache: ResponseCacheService,
requestService: RequestService,
store: Store<CoreState>,
href: string) {
super(objectCache, responseCache, requestService, store, href, NormalizedBundle);
}
protected normalizedToDomain(normalized: NormalizedBundle): Bundle {
return new BundleBuilder(this.objectCache, this.responseCache, this.requestService, this.store, this.href, normalized).build();
}
}

View File

@@ -1,94 +0,0 @@
import { Collection } from "../../shared/collection.model";
import { hasValue } from "../../../shared/empty.util";
import { Item } from "../../shared/item.model";
import { RequestConfigureAction, RequestExecuteAction } from "../../data/request.actions";
import { ObjectCacheService } from "../object-cache.service";
import { ResponseCacheService } from "../response-cache.service";
import { RequestService } from "../../data/request.service";
import { Store } from "@ngrx/store";
import { CoreState } from "../../core.reducers";
import { NormalizedCollection } from "./normalized-collection.model";
import { Request } from "../../data/request.models";
import { ListRemoteDataBuilder, SingleRemoteDataBuilder } from "./remote-data-builder";
import { ItemRDBuilder } from "./item-builder";
import { NormalizedItem } from "./normalized-item.model";
export class CollectionBuilder {
constructor(
protected objectCache: ObjectCacheService,
protected responseCache: ResponseCacheService,
protected requestService: RequestService,
protected store: Store<CoreState>,
protected href: string,
protected normalized: NormalizedCollection
) {
}
build(): Collection {
let links: any = {};
if (hasValue(this.normalized.items)) {
setTimeout(() => {
this.normalized.items.forEach((href: string) => {
const isCached = this.objectCache.hasBySelfLink(href);
const isPending = this.requestService.isPending(href);
if (!(isCached || isPending)) {
const request = new Request(href, NormalizedItem);
this.store.dispatch(new RequestConfigureAction(request));
this.store.dispatch(new RequestExecuteAction(href));
}
});
}, 0);
links.items = this.normalized.items.map((href: string) => {
return new ItemRDBuilder(
this.objectCache,
this.responseCache,
this.requestService,
this.store,
href
).build();
});
}
return Object.assign(new Collection(), this.normalized, links);
}
}
export class CollectionRDBuilder extends SingleRemoteDataBuilder<Collection, NormalizedCollection> {
constructor(
objectCache: ObjectCacheService,
responseCache: ResponseCacheService,
requestService: RequestService,
store: Store<CoreState>,
href: string
) {
super(objectCache, responseCache, requestService, store, href, NormalizedCollection);
}
protected normalizedToDomain(normalized: NormalizedCollection): Collection {
return new CollectionBuilder(this.objectCache, this.responseCache, this.requestService, this.store, this.href, normalized).build();
}
}
export class CollectionListRDBuilder extends ListRemoteDataBuilder<Collection, NormalizedCollection> {
constructor(
objectCache: ObjectCacheService,
responseCache: ResponseCacheService,
requestService: RequestService,
store: Store<CoreState>,
href: string
) {
super(objectCache, responseCache, requestService, store, href, NormalizedCollection);
}
protected normalizedToDomain(normalized: NormalizedCollection): Collection {
return new CollectionBuilder(this.objectCache, this.responseCache, this.requestService, this.store, this.href, normalized).build();
}
}

View File

@@ -1,91 +0,0 @@
import { Item } from "../../shared/item.model";
import { ObjectCacheService } from "../object-cache.service";
import { ResponseCacheService } from "../response-cache.service";
import { RequestService } from "../../data/request.service";
import { Store } from "@ngrx/store";
import { CoreState } from "../../core.reducers";
import { NormalizedItem } from "./normalized-item.model";
import { ListRemoteDataBuilder, SingleRemoteDataBuilder } from "./remote-data-builder";
import { Request } from "../../data/request.models";
import { hasValue } from "../../../shared/empty.util";
import { RequestConfigureAction, RequestExecuteAction } from "../../data/request.actions";
import { BundleRDBuilder } from "./bundle-builder";
import { NormalizedBundle } from "./normalized-bundle.model";
export class ItemBuilder {
constructor(
protected objectCache: ObjectCacheService,
protected responseCache: ResponseCacheService,
protected requestService: RequestService,
protected store: Store<CoreState>,
protected href: string,
protected normalized: NormalizedItem
) {
}
build(): Item {
let links: any = {};
if (hasValue(this.normalized.bundles)) {
setTimeout(() => {
this.normalized.bundles.forEach((href: string) => {
const isCached = this.objectCache.hasBySelfLink(href);
const isPending = this.requestService.isPending(href);
if (!(isCached || isPending)) {
const request = new Request(href, NormalizedBundle);
this.store.dispatch(new RequestConfigureAction(request));
this.store.dispatch(new RequestExecuteAction(href));
}
});
}, 0);
links.bundles = this.normalized.bundles.map((href: string) => {
return new BundleRDBuilder(
this.objectCache,
this.responseCache,
this.requestService,
this.store,
href
).build();
});
}
return Object.assign(new Item(), this.normalized, links);
}
}
export class ItemRDBuilder extends SingleRemoteDataBuilder<Item, NormalizedItem> {
constructor(
objectCache: ObjectCacheService,
responseCache: ResponseCacheService,
requestService: RequestService,
store: Store<CoreState>,
href: string
) {
super(objectCache, responseCache, requestService, store, href, NormalizedItem);
}
protected normalizedToDomain(normalized: NormalizedItem): Item {
return new ItemBuilder(this.objectCache, this.responseCache, this.requestService, this.store, this.href, normalized).build();
}
}
export class ItemListRDBuilder extends ListRemoteDataBuilder<Item, NormalizedItem> {
constructor(
objectCache: ObjectCacheService,
responseCache: ResponseCacheService,
requestService: RequestService,
store: Store<CoreState>,
href: string
) {
super(objectCache, responseCache, requestService, store, href, NormalizedItem);
}
protected normalizedToDomain(normalized: NormalizedItem): Item {
return new ItemBuilder(this.objectCache, this.responseCache, this.requestService, this.store, this.href, normalized).build();
}
}

View File

@@ -1,6 +1,9 @@
import { inheritSerialization, autoserialize } from "cerialize";
import { NormalizedDSpaceObject } from "./normalized-dspace-object.model";
import { Bitstream } from "../../shared/bitstream.model";
import { mapsTo } from "../builders/build-decorators";
@mapsTo(Bitstream)
@inheritSerialization(NormalizedDSpaceObject)
export class NormalizedBitstream extends NormalizedDSpaceObject {

View File

@@ -1,12 +1,17 @@
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";
@mapsTo(Bundle)
@inheritSerialization(NormalizedDSpaceObject)
export class NormalizedBundle extends NormalizedDSpaceObject {
/**
* The primary bitstream of this Bundle
*/
@autoserialize
@relationship(NormalizedBitstream)
primaryBitstream: string;
/**
@@ -20,5 +25,6 @@ export class NormalizedBundle extends NormalizedDSpaceObject {
owner: string;
@autoserialize
@relationship(NormalizedBitstream)
bitstreams: Array<string>;
}

View File

@@ -1,6 +1,10 @@
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";
@mapsTo(Collection)
@inheritSerialization(NormalizedDSpaceObject)
export class NormalizedCollection extends NormalizedDSpaceObject {
@@ -26,6 +30,7 @@ export class NormalizedCollection extends NormalizedDSpaceObject {
owner: string;
@autoserialize
@relationship(NormalizedItem)
items: Array<string>;
}

View File

@@ -1,6 +1,10 @@
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";
@mapsTo(Item)
@inheritSerialization(NormalizedDSpaceObject)
export class NormalizedItem extends NormalizedDSpaceObject {
@@ -36,5 +40,6 @@ export class NormalizedItem extends NormalizedDSpaceObject {
owner: string;
@autoserialize
@relationship(NormalizedBundle)
bundles: Array<string>;
}

View File

@@ -57,12 +57,12 @@ describe("ObjectCacheService", () => {
});
describe("get", () => {
it("should return an observable of the cached object with the specified UUID and type", () => {
it("should return an observable of the cached object with the specified UUID", () => {
spyOn(store, 'select').and.returnValue(Observable.of(cacheEntry));
let testObj: any;
//due to the implementation of spyOn above, this subscribe will be synchronous
service.get(uuid, TestClass).take(1).subscribe(o => testObj = o);
service.get(uuid).take(1).subscribe(o => testObj = o);
expect(testObj.uuid).toBe(uuid);
expect(testObj.foo).toBe("bar");
// this only works if testObj is an instance of TestClass
@@ -73,18 +73,18 @@ describe("ObjectCacheService", () => {
spyOn(store, 'select').and.returnValue(Observable.of(invalidCacheEntry));
let getObsHasFired = false;
const subscription = service.get(uuid, TestClass).subscribe(o => getObsHasFired = true);
const subscription = service.get(uuid).subscribe(o => getObsHasFired = true);
expect(getObsHasFired).toBe(false);
subscription.unsubscribe();
});
});
describe("getList", () => {
it("should return an observable of the array of cached objects with the specified UUID and type", () => {
it("should return an observable of the array of cached objects with the specified UUID", () => {
spyOn(service, 'get').and.returnValue(Observable.of(new TestClass(uuid, "bar")));
let testObjs: Array<any>;
service.getList([uuid, uuid], TestClass).take(1).subscribe(arr => testObjs = arr);
service.getList([uuid, uuid]).take(1).subscribe(arr => testObjs = arr);
expect(testObjs[0].uuid).toBe(uuid);
expect(testObjs[0].foo).toBe("bar");
expect(testObjs[0].test()).toBe("bar" + uuid);

View File

@@ -4,7 +4,6 @@ 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
@@ -40,53 +39,34 @@ 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<T>
* An observable of the requested object
*/
get<T extends CacheableObject>(uuid: string, type: GenericConstructor<T>): Observable<T> {
get<T extends CacheableObject>(uuid: string): Observable<T> {
return this.store.select<ObjectCacheEntry>('core', 'cache', 'object', uuid)
.filter(entry => this.isValid(entry))
.distinctUntilChanged()
.map((entry: ObjectCacheEntry) => <T> Object.assign(new type(), entry.data));
.map((entry: ObjectCacheEntry) => <T> entry.data);
}
getBySelfLink<T extends CacheableObject>(href: string, type: GenericConstructor<T>): Observable<T> {
getBySelfLink<T extends CacheableObject>(href: string): Observable<T> {
return this.store.select<string>('core', 'index', 'href', href)
.flatMap((uuid: string) => this.get(uuid, type))
.flatMap((uuid: string) => this.get<T>(uuid))
}
/**
* Get an observable for an array of objects of the same type
* Get an observable for an array of objects
* 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<Array<T>>
*/
getList<T extends CacheableObject>(uuids: Array<string>, type: GenericConstructor<T>): Observable<Array<T>> {
getList<T extends CacheableObject>(uuids: Array<string>): Observable<Array<T>> {
return Observable.combineLatest(
uuids.map((id: string) => this.get<T>(id, type))
uuids.map((id: string) => this.get<T>(id))
);
}

View File

@@ -9,6 +9,7 @@ import { ResponseCacheService } from "./cache/response-cache.service";
import { CollectionDataService } from "./data/collection-data.service";
import { ItemDataService } from "./data/item-data.service";
import { RequestService } from "./data/request.service";
import { RemoteDataBuildService } from "./cache/builders/remote-data-build.service";
const IMPORTS = [
CommonModule,
@@ -29,7 +30,8 @@ const PROVIDERS = [
DSpaceRESTv2Service,
ObjectCacheService,
ResponseCacheService,
RequestService
RequestService,
RemoteDataBuildService
];
@NgModule({

View File

@@ -7,39 +7,20 @@ import { Store } from "@ngrx/store";
import { NormalizedCollection } from "../cache/models/normalized-collection.model";
import { CoreState } from "../core.reducers";
import { RequestService } from "./request.service";
import { CollectionListRDBuilder, CollectionRDBuilder } from "../cache/models/collection-builder";
import { RemoteDataBuildService } from "../cache/builders/remote-data-build.service";
@Injectable()
export class CollectionDataService extends DataService<Collection, NormalizedCollection> {
export class CollectionDataService extends DataService<NormalizedCollection, Collection> {
protected endpoint = '/collections';
constructor(
protected objectCache: ObjectCacheService,
protected responseCache: ResponseCacheService,
protected requestService: RequestService,
protected rdbService: RemoteDataBuildService,
protected store: Store<CoreState>
) {
super(NormalizedCollection);
}
protected getListDataBuilder(href: string): CollectionListRDBuilder {
return new CollectionListRDBuilder (
this.objectCache,
this.responseCache,
this.requestService,
this.store,
href,
);
}
protected getSingleDataBuilder(href: string): CollectionRDBuilder {
return new CollectionRDBuilder (
this.objectCache,
this.responseCache,
this.requestService,
this.store,
href,
);
}
}

View File

@@ -2,29 +2,27 @@ import { ObjectCacheService } from "../cache/object-cache.service";
import { ResponseCacheService } from "../cache/response-cache.service";
import { CacheableObject } from "../cache/object-cache.reducer";
import { hasValue } from "../../shared/empty.util";
import { GenericConstructor } from "../shared/generic-constructor";
import { RemoteData } from "./remote-data";
import { FindAllRequest, FindByIDRequest, Request } from "./request.models";
import { Store } from "@ngrx/store";
import { RequestConfigureAction, RequestExecuteAction } from "./request.actions";
import { CoreState } from "../core.reducers";
import { RemoteDataBuilder } from "../cache/models/remote-data-builder";
import { RequestService } from "./request.service";
import { RemoteDataBuildService } from "../cache/builders/remote-data-build.service";
import { GenericConstructor } from "../shared/generic-constructor";
export abstract class DataService<T, U extends CacheableObject> {
export abstract class DataService<TNormalized extends CacheableObject, TDomain> {
protected abstract objectCache: ObjectCacheService;
protected abstract responseCache: ResponseCacheService;
protected abstract requestService: RequestService;
protected abstract rdbService: RemoteDataBuildService;
protected abstract store: Store<CoreState>;
protected abstract endpoint: string;
constructor(private normalizedResourceType: GenericConstructor<U>) {
constructor(private normalizedResourceType: GenericConstructor<TNormalized>) {
}
protected abstract getListDataBuilder(href: string): RemoteDataBuilder<T[]>;
protected abstract getSingleDataBuilder(href: string): RemoteDataBuilder<T>;
protected getFindAllHref(scopeID?): string {
let result = this.endpoint;
if (hasValue(scopeID)) {
@@ -33,37 +31,40 @@ export abstract class DataService<T, U extends CacheableObject> {
return result;
}
findAll(scopeID?: string): RemoteData<Array<T>> {
findAll(scopeID?: string): RemoteData<Array<TDomain>> {
const href = this.getFindAllHref(scopeID);
if (!this.responseCache.has(href) && !this.requestService.isPending(href)) {
const request = new FindAllRequest(href, this.normalizedResourceType, scopeID);
this.store.dispatch(new RequestConfigureAction(request));
this.store.dispatch(new RequestExecuteAction(href));
}
return this.getListDataBuilder(href).build();
return this.rdbService.buildList<TNormalized, TDomain>(href, this.normalizedResourceType);
// return this.rdbService.buildList(href);
}
protected getFindByIDHref(resourceID): string {
return `${this.endpoint}/${resourceID}`;
}
findById(id: string): RemoteData<T> {
findById(id: string): RemoteData<TDomain> {
const href = this.getFindByIDHref(id);
if (!this.objectCache.hasBySelfLink(href) && !this.requestService.isPending(href)) {
const request = new FindByIDRequest(href, this.normalizedResourceType, id);
this.store.dispatch(new RequestConfigureAction(request));
this.store.dispatch(new RequestExecuteAction(href));
}
return this.getSingleDataBuilder(href).build();
return this.rdbService.buildSingle<TNormalized, TDomain>(href, this.normalizedResourceType);
// return this.rdbService.buildSingle(href);
}
findByHref(href: string): RemoteData<T> {
findByHref(href: string): RemoteData<TDomain> {
if (!this.objectCache.hasBySelfLink(href) && !this.requestService.isPending(href)) {
const request = new Request(href, this.normalizedResourceType);
this.store.dispatch(new RequestConfigureAction(request));
this.store.dispatch(new RequestExecuteAction(href));
}
return this.getSingleDataBuilder(href).build();
return this.rdbService.buildSingle<TNormalized, TDomain>(href, this.normalizedResourceType);
// return this.rdbService.buildSingle(href));
}
}

View File

@@ -7,38 +7,19 @@ import { Store } from "@ngrx/store";
import { CoreState } from "../core.reducers";
import { NormalizedItem } from "../cache/models/normalized-item.model";
import { RequestService } from "./request.service";
import { ItemListRDBuilder, ItemRDBuilder } from "../cache/models/item-builder";
import { RemoteDataBuildService } from "../cache/builders/remote-data-build.service";
@Injectable()
export class ItemDataService extends DataService<Item, NormalizedItem> {
export class ItemDataService extends DataService<NormalizedItem, Item> {
protected endpoint = '/items';
constructor(
protected objectCache: ObjectCacheService,
protected responseCache: ResponseCacheService,
protected requestService: RequestService,
protected rdbService: RemoteDataBuildService,
protected store: Store<CoreState>
) {
super(NormalizedItem);
}
protected getListDataBuilder(href: string): ItemListRDBuilder {
return new ItemListRDBuilder(
this.objectCache,
this.responseCache,
this.requestService,
this.store,
href,
);
}
protected getSingleDataBuilder(href: string): ItemRDBuilder {
return new ItemRDBuilder(
this.objectCache,
this.responseCache,
this.requestService,
this.store,
href,
);
}
}

View File

@@ -1,13 +1,23 @@
import { Injectable } from "@angular/core";
import { RequestEntry, RequestState } from "./request.reducer";
import { Store } from "@ngrx/store";
import { Request } from "./request.models";
import { hasValue } from "../../shared/empty.util";
import { Observable } from "rxjs/Observable";
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 { GenericConstructor } from "../shared/generic-constructor";
@Injectable()
export class RequestService {
constructor(private store: Store<RequestState>) {
constructor(
private objectCache: ObjectCacheService,
private responseCache: ResponseCacheService,
private store: Store<RequestState>
) {
}
isPending(href: string): boolean {
@@ -24,4 +34,15 @@ export class RequestService {
get(href: string): Observable<RequestEntry> {
return this.store.select<RequestEntry>('core', 'data', 'request', href);
}
configure<T extends CacheableObject>(href: string, normalizedType: GenericConstructor<T>): void {
const isCached = this.objectCache.hasBySelfLink(href);
const isPending = this.isPending(href);
if (!(isCached || isPending)) {
const request = new Request(href, normalizedType);
this.store.dispatch(new RequestConfigureAction(request));
this.store.dispatch(new RequestExecuteAction(href));
}
}
}

View File

@@ -1,8 +1,6 @@
import { inheritSerialization } from "cerialize";
import { DSpaceObject } from "./dspace-object.model";
import { Bundle } from "./bundle.model";
@inheritSerialization(DSpaceObject)
export class Bitstream extends DSpaceObject {
/**