intermediate commit

This commit is contained in:
lotte
2018-10-17 13:18:01 +02:00
parent b1c6d68cc5
commit 2330e96158
75 changed files with 387 additions and 1067 deletions

View File

@@ -1,5 +1,5 @@
import { getMockObjectCacheService } from '../../shared/mocks/mock-object-cache.service';
import { ErrorResponse, GenericSuccessResponse } from '../cache/response-cache.models';
import { ErrorResponse, GenericSuccessResponse } from '../cache/response.models';
import { DSpaceRESTV2Response } from '../dspace-rest-v2/dspace-rest-v2-response.model';
import { BrowseEntriesResponseParsingService } from './browse-entries-response-parsing.service';
import { BrowseEntriesRequest } from './request.models';

View File

@@ -7,7 +7,7 @@ import {
ErrorResponse,
GenericSuccessResponse,
RestResponse
} from '../cache/response-cache.models';
} from '../cache/response.models';
import { DSpaceRESTV2Response } from '../dspace-rest-v2/dspace-rest-v2-response.model';
import { DSpaceRESTv2Serializer } from '../dspace-rest-v2/dspace-rest-v2.serializer';
import { BrowseEntry } from '../shared/browse-entry.model';

View File

@@ -1,5 +1,5 @@
import { getMockObjectCacheService } from '../../shared/mocks/mock-object-cache.service';
import { ErrorResponse, GenericSuccessResponse } from '../cache/response-cache.models';
import { ErrorResponse, GenericSuccessResponse } from '../cache/response.models';
import { DSpaceRESTV2Response } from '../dspace-rest-v2/dspace-rest-v2-response.model';
import { BrowseEntriesResponseParsingService } from './browse-entries-response-parsing.service';
import { BrowseEntriesRequest, BrowseItemsRequest } from './request.models';

View File

@@ -7,7 +7,7 @@ import {
ErrorResponse,
GenericSuccessResponse,
RestResponse
} from '../cache/response-cache.models';
} from '../cache/response.models';
import { DSpaceRESTV2Response } from '../dspace-rest-v2/dspace-rest-v2-response.model';
import { DSpaceRESTv2Serializer } from '../dspace-rest-v2/dspace-rest-v2.serializer';
import { BaseResponseParsingService } from './base-response-parsing.service';

View File

@@ -1,6 +1,6 @@
import { BrowseResponseParsingService } from './browse-response-parsing.service';
import { BrowseEndpointRequest } from './request.models';
import { GenericSuccessResponse, ErrorResponse } from '../cache/response-cache.models';
import { GenericSuccessResponse, ErrorResponse } from '../cache/response.models';
import { BrowseDefinition } from '../shared/browse-definition.model';
import { DSpaceRESTV2Response } from '../dspace-rest-v2/dspace-rest-v2-response.model';

View File

@@ -2,7 +2,7 @@ import { Injectable } from '@angular/core';
import { ResponseParsingService } from './parsing.service';
import { RestRequest } from './request.models';
import { DSpaceRESTV2Response } from '../dspace-rest-v2/dspace-rest-v2-response.model';
import { GenericSuccessResponse, ErrorResponse, RestResponse } from '../cache/response-cache.models';
import { GenericSuccessResponse, ErrorResponse, RestResponse } from '../cache/response.models';
import { isNotEmpty } from '../../shared/empty.util';
import { DSpaceRESTv2Serializer } from '../dspace-rest-v2/dspace-rest-v2.serializer';
import { BrowseDefinition } from '../shared/browse-definition.model';

View File

@@ -1,10 +1,8 @@
import { Inject, Injectable } from '@angular/core';
import { Store } from '@ngrx/store';
import { GLOBAL_CONFIG, GlobalConfig } from '../../../config';
import { RemoteDataBuildService } from '../cache/builders/remote-data-build.service';
import { NormalizedCollection } from '../cache/models/normalized-collection.model';
import { ObjectCacheService } from '../cache/object-cache.service';
import { ResponseCacheService } from '../cache/response-cache.service';
import { CoreState } from '../core.reducers';
import { Collection } from '../shared/collection.model';
import { ComColDataService } from './comcol-data.service';
@@ -17,7 +15,6 @@ export class CollectionDataService extends ComColDataService<NormalizedCollectio
protected linkPath = 'collections';
constructor(
protected responseCache: ResponseCacheService,
protected requestService: RequestService,
protected rdbService: RemoteDataBuildService,
protected store: Store<CoreState>,

View File

@@ -5,7 +5,6 @@ import { GlobalConfig } from '../../../config';
import { getMockRequestService } from '../../shared/mocks/mock-request.service';
import { RemoteDataBuildService } from '../cache/builders/remote-data-build.service';
import { ObjectCacheService } from '../cache/object-cache.service';
import { ResponseCacheService } from '../cache/response-cache.service';
import { CoreState } from '../core.reducers';
import { ComColDataService } from './comcol-data.service';
import { CommunityDataService } from './community-data.service';
@@ -23,7 +22,6 @@ class NormalizedTestObject extends NormalizedObject {
class TestService extends ComColDataService<NormalizedTestObject, any> {
constructor(
protected responseCache: ResponseCacheService,
protected requestService: RequestService,
protected rdbService: RemoteDataBuildService,
protected store: Store<CoreState>,
@@ -41,7 +39,6 @@ class TestService extends ComColDataService<NormalizedTestObject, any> {
describe('ComColDataService', () => {
let scheduler: TestScheduler;
let service: TestService;
let responseCache: ResponseCacheService;
let requestService: RequestService;
let cds: CommunityDataService;
let objectCache: ObjectCacheService;
@@ -68,14 +65,6 @@ describe('ComColDataService', () => {
});
}
function initMockResponseCacheService(isSuccessful: boolean): ResponseCacheService {
return jasmine.createSpyObj('responseCache', {
get: cold('c-', {
c: { response: { isSuccessful } }
})
});
}
function initMockObjectCacheService(): ObjectCacheService {
return jasmine.createSpyObj('objectCache', {
getByUUID: cold('d-', {
@@ -90,7 +79,6 @@ describe('ComColDataService', () => {
function initTestService(): TestService {
return new TestService(
responseCache,
requestService,
rdbService,
store,
@@ -111,7 +99,6 @@ describe('ComColDataService', () => {
cds = initMockCommunityDataService();
requestService = getMockRequestService();
objectCache = initMockObjectCacheService();
responseCache = initMockResponseCacheService(true);
service = initTestService();
const expected = new FindByIDRequest(requestService.generateRequestId(), communityEndpoint, scopeID);
@@ -127,7 +114,6 @@ describe('ComColDataService', () => {
cds = initMockCommunityDataService();
requestService = getMockRequestService();
objectCache = initMockObjectCacheService();
responseCache = initMockResponseCacheService(true);
service = initTestService();
});
@@ -150,7 +136,6 @@ describe('ComColDataService', () => {
cds = initMockCommunityDataService();
requestService = getMockRequestService();
objectCache = initMockObjectCacheService();
responseCache = initMockResponseCacheService(false);
service = initTestService();
});

View File

@@ -1,15 +1,16 @@
import { distinctUntilChanged, filter, map, mergeMap, take, tap } from 'rxjs/operators';
import { distinctUntilChanged, filter, map, mergeMap, share, take, tap } from 'rxjs/operators';
import { merge as observableMerge, Observable, throwError as observableThrowError } from 'rxjs';
import { isEmpty, isNotEmpty } from '../../shared/empty.util';
import { hasValue, isEmpty, isNotEmpty } from '../../shared/empty.util';
import { NormalizedCommunity } from '../cache/models/normalized-community.model';
import { ObjectCacheService } from '../cache/object-cache.service';
import { ResponseCacheEntry } from '../cache/response-cache.reducer';
import { CommunityDataService } from './community-data.service';
import { DataService } from './data.service';
import { FindAllOptions, FindByIDRequest } from './request.models';
import { NormalizedObject } from '../cache/models/normalized-object.model';
import { HALEndpointService } from '../shared/hal-endpoint.service';
import { RequestEntry } from './request.reducer';
import { getResponseFromEntry } from '../shared/operators';
export abstract class ComColDataService<TNormalized extends NormalizedObject, TDomain> extends DataService<TNormalized, TDomain> {
protected abstract cds: CommunityDataService;
@@ -26,9 +27,9 @@ export abstract class ComColDataService<TNormalized extends NormalizedObject, TD
* @return { Observable<string> }
* an Observable<string> containing the scoped URL
*/
public getBrowseEndpoint(options: FindAllOptions = {}): Observable<string> {
public getBrowseEndpoint(options: FindAllOptions = {}, linkPath: string = this.linkPath): Observable<string> {
if (isEmpty(options.scopeID)) {
return this.halService.getEndpoint(this.linkPath);
return this.halService.getEndpoint(linkPath);
} else {
const scopeCommunityHrefObs = this.cds.getEndpoint().pipe(
mergeMap((endpoint: string) => this.cds.getFindByIDHref(endpoint, options.scopeID)),
@@ -37,7 +38,7 @@ export abstract class ComColDataService<TNormalized extends NormalizedObject, TD
tap((href: string) => {
const request = new FindByIDRequest(this.requestService.generateRequestId(), href, options.scopeID);
this.requestService.configure(request);
}),);
}));
// return scopeCommunityHrefObs.pipe(
// mergeMap((href: string) => this.responseCache.get(href)),
@@ -46,7 +47,7 @@ export abstract class ComColDataService<TNormalized extends NormalizedObject, TD
// if (response.isSuccessful) {
// const community$: Observable<NormalizedCommunity> = this.objectCache.getByUUID(scopeID);
// return community$.pipe(
// map((community) => community._links[this.linkPath]),
// map((community) => community._links[linkPath]),
// filter((href) => isNotEmpty(href)),
// distinctUntilChanged()
// );
@@ -57,8 +58,8 @@ export abstract class ComColDataService<TNormalized extends NormalizedObject, TD
// distinctUntilChanged()
// );
const responses = scopeCommunityHrefObs.pipe(
mergeMap((href: string) => this.responseCache.get(href)),
map((entry: ResponseCacheEntry) => entry.response));
mergeMap((href: string) => this.requestService.getByHref(href)),
getResponseFromEntry());
const errorResponses = responses.pipe(
filter((response) => !response.isSuccessful),
mergeMap(() => observableThrowError(new Error(`The Community with scope ${options.scopeID} couldn't be retrieved`)))
@@ -66,11 +67,11 @@ export abstract class ComColDataService<TNormalized extends NormalizedObject, TD
const successResponses = responses.pipe(
filter((response) => response.isSuccessful),
mergeMap(() => this.objectCache.getByUUID(options.scopeID)),
map((nc: NormalizedCommunity) => nc._links[this.linkPath]),
map((nc: NormalizedCommunity) => nc._links[linkPath]),
filter((href) => isNotEmpty(href))
);
return observableMerge(errorResponses, successResponses).pipe(distinctUntilChanged());
return observableMerge(errorResponses, successResponses).pipe(distinctUntilChanged(), share());
}
}
}

View File

@@ -1,12 +1,10 @@
import {mergeMap, filter, take} from 'rxjs/operators';
import { filter, mergeMap, take } from 'rxjs/operators';
import { Injectable } from '@angular/core';
import { Store } from '@ngrx/store';
import { RemoteDataBuildService } from '../cache/builders/remote-data-build.service';
import { NormalizedCommunity } from '../cache/models/normalized-community.model';
import { ObjectCacheService } from '../cache/object-cache.service';
import { ResponseCacheService } from '../cache/response-cache.service';
import { CoreState } from '../core.reducers';
import { Community } from '../shared/community.model';
import { ComColDataService } from './comcol-data.service';
@@ -25,7 +23,6 @@ export class CommunityDataService extends ComColDataService<NormalizedCommunity,
protected cds = this;
constructor(
protected responseCache: ResponseCacheService,
protected requestService: RequestService,
protected rdbService: RemoteDataBuildService,
protected store: Store<CoreState>,
@@ -40,12 +37,10 @@ export class CommunityDataService extends ComColDataService<NormalizedCommunity,
}
findTop(options: FindAllOptions = {}): Observable<RemoteData<PaginatedList<Community>>> {
const hrefObs = this.halService.getEndpoint(this.topLinkPath).pipe(filter((href: string) => isNotEmpty(href)),
mergeMap((endpoint: string) => this.getFindAllHref(options)),);
const hrefObs = this.getFindAllHref(options, this.topLinkPath);
hrefObs.pipe(
filter((href: string) => hasValue(href)),
take(1),)
take(1))
.subscribe((href: string) => {
const request = new FindAllRequest(this.requestService.generateRequestId(), href, options);
this.requestService.configure(request);

View File

@@ -1,4 +1,4 @@
import { ConfigSuccessResponse, ErrorResponse } from '../cache/response-cache.models';
import { ConfigSuccessResponse, ErrorResponse } from '../cache/response.models';
import { ConfigResponseParsingService } from './config-response-parsing.service';
import { ObjectCacheService } from '../cache/object-cache.service';
import { GlobalConfig } from '../../../config/global-config.interface';

View File

@@ -3,7 +3,7 @@ import { Inject, Injectable } from '@angular/core';
import { ResponseParsingService } from './parsing.service';
import { RestRequest } from './request.models';
import { DSpaceRESTV2Response } from '../dspace-rest-v2/dspace-rest-v2-response.model';
import { ConfigSuccessResponse, ErrorResponse, RestResponse } from '../cache/response-cache.models';
import { ConfigSuccessResponse, ErrorResponse, RestResponse } from '../cache/response.models';
import { isNotEmpty } from '../../shared/empty.util';
import { ConfigObjectFactory } from '../shared/config/config-object-factory';

View File

@@ -1,6 +1,5 @@
import { DataService } from './data.service';
import { NormalizedObject } from '../cache/models/normalized-object.model';
import { ResponseCacheService } from '../cache/response-cache.service';
import { RequestService } from './request.service';
import { RemoteDataBuildService } from '../cache/builders/remote-data-build.service';
import { CoreState } from '../core.reducers';
@@ -22,7 +21,6 @@ class NormalizedTestObject extends NormalizedObject {
class TestService extends DataService<NormalizedTestObject, any> {
constructor(
protected responseCache: ResponseCacheService,
protected requestService: RequestService,
protected rdbService: RemoteDataBuildService,
protected store: Store<CoreState>,
@@ -33,7 +31,7 @@ class TestService extends DataService<NormalizedTestObject, any> {
super();
}
public getBrowseEndpoint(options: FindAllOptions): Observable<string> {
public getBrowseEndpoint(options: FindAllOptions = {}, linkPath: string = this.linkPath): Observable<string> {
return observableOf(endpoint);
}
}
@@ -41,7 +39,6 @@ class TestService extends DataService<NormalizedTestObject, any> {
describe('DataService', () => {
let service: TestService;
let options: FindAllOptions;
const responseCache = {} as ResponseCacheService;
const requestService = {} as RequestService;
const halService = {} as HALEndpointService;
const rdbService = {} as RemoteDataBuildService;
@@ -57,7 +54,6 @@ describe('DataService', () => {
function initTestService(): TestService {
return new TestService(
responseCache,
requestService,
rdbService,
store,

View File

@@ -1,9 +1,8 @@
import { distinctUntilChanged, filter, first, map, take } from 'rxjs/operators';
import { delay, distinctUntilChanged, filter, first, map, take, tap } from 'rxjs/operators';
import { Observable } from 'rxjs';
import { Store } from '@ngrx/store';
import { hasValue, isNotEmpty } from '../../shared/empty.util';
import { RemoteDataBuildService } from '../cache/builders/remote-data-build.service';
import { ResponseCacheService } from '../cache/response-cache.service';
import { CoreState } from '../core.reducers';
import { HALEndpointService } from '../shared/hal-endpoint.service';
import { URLCombiner } from '../url-combiner/url-combiner';
@@ -15,9 +14,9 @@ import { NormalizedObject } from '../cache/models/normalized-object.model';
import { compare, Operation } from 'fast-json-patch';
import { ObjectCacheService } from '../cache/object-cache.service';
import { DSpaceObject } from '../shared/dspace-object.model';
import { of } from 'rxjs/internal/observable/of';
export abstract class DataService<TNormalized extends NormalizedObject, TDomain> {
protected abstract responseCache: ResponseCacheService;
protected abstract requestService: RequestService;
protected abstract rdbService: RemoteDataBuildService;
protected abstract store: Store<CoreState>;
@@ -25,34 +24,31 @@ export abstract class DataService<TNormalized extends NormalizedObject, TDomain>
protected abstract halService: HALEndpointService;
protected abstract objectCache: ObjectCacheService;
public abstract getBrowseEndpoint(options: FindAllOptions): Observable<string>
public abstract getBrowseEndpoint(options: FindAllOptions, linkPath?: string): Observable<string>
protected getFindAllHref(options: FindAllOptions = {}): Observable<string> {
protected getFindAllHref(options: FindAllOptions = {}, linkPath?: string): Observable<string> {
let result: Observable<string>;
const args = [];
result = this.getBrowseEndpoint(options).pipe(distinctUntilChanged());
result = this.getBrowseEndpoint(options, linkPath);
if (hasValue(options.currentPage) && typeof options.currentPage === 'number') {
/* TODO: this is a temporary fix for the pagination start index (0 or 1) discrepancy between the rest and the frontend respectively */
args.push(`page=${options.currentPage - 1}`);
}
if (hasValue(options.elementsPerPage)) {
args.push(`size=${options.elementsPerPage}`);
}
if (hasValue(options.sort)) {
args.push(`sort=${options.sort.field},${options.sort.direction}`);
}
if (hasValue(options.startsWith)) {
args.push(`startsWith=${options.startsWith}`);
}
if (isNotEmpty(args)) {
return result.pipe(map((href: string) => new URLCombiner(href, `?${args.join('&')}`).toString()));
} else {
result.subscribe((t) => console.log(t));
return result;
}
}
@@ -115,6 +111,7 @@ export abstract class DataService<TNormalized extends NormalizedObject, TDomain>
this.objectCache.addPatch(object.self, operations);
}
}
// TODO implement, after the structure of the REST server's POST response is finalized
// create(dso: DSpaceObject): Observable<RemoteData<TDomain>> {
// const postHrefObs = this.getEndpoint();

View File

@@ -1,5 +1,5 @@
import { Injectable } from '@angular/core';
import { RestResponse } from '../cache/response-cache.models';
import { RestResponse } from '../cache/response.models';
import { DSpaceRESTV2Response } from '../dspace-rest-v2/dspace-rest-v2-response.model';
import { ResponseParsingService } from './parsing.service';
import { RestRequest } from './request.models';

View File

@@ -7,7 +7,7 @@ import { NormalizedObject } from '../cache/models/normalized-object.model';
import { ResourceType } from '../shared/resource-type';
import { NormalizedObjectFactory } from '../cache/models/normalized-object-factory';
import { DSpaceRESTV2Response } from '../dspace-rest-v2/dspace-rest-v2-response.model';
import { RestResponse, DSOSuccessResponse } from '../cache/response-cache.models';
import { RestResponse, DSOSuccessResponse } from '../cache/response.models';
import { RestRequest } from './request.models';
import { ResponseParsingService } from './parsing.service';
@@ -23,12 +23,14 @@ export class DSOResponseParsingService extends BaseResponseParsingService implem
constructor(
@Inject(GLOBAL_CONFIG) protected EnvConfig: GlobalConfig,
protected objectCache: ObjectCacheService,
) { super();
) {
super();
}
parse(request: RestRequest, data: DSpaceRESTV2Response): RestResponse {
const processRequestDTO = this.process<NormalizedObject,ResourceType>(data.payload, request.href);
const processRequestDTO = this.process<NormalizedObject, ResourceType>(data.payload, request.href);
let objectList = processRequestDTO;
if (hasNoValue(processRequestDTO)) {
return new DSOSuccessResponse([], data.statusCode, undefined)
}

View File

@@ -3,7 +3,6 @@ import { Store } from '@ngrx/store';
import { Observable } from 'rxjs';
import { RemoteDataBuildService } from '../cache/builders/remote-data-build.service';
import { NormalizedDSpaceObject } from '../cache/models/normalized-dspace-object.model';
import { ResponseCacheService } from '../cache/response-cache.service';
import { CoreState } from '../core.reducers';
import { DSpaceObject } from '../shared/dspace-object.model';
import { HALEndpointService } from '../shared/hal-endpoint.service';
@@ -18,7 +17,6 @@ class DataServiceImpl extends DataService<NormalizedDSpaceObject, DSpaceObject>
protected linkPath = 'dso';
constructor(
protected responseCache: ResponseCacheService,
protected requestService: RequestService,
protected rdbService: RemoteDataBuildService,
protected store: Store<CoreState>,
@@ -27,8 +25,8 @@ class DataServiceImpl extends DataService<NormalizedDSpaceObject, DSpaceObject>
super();
}
getBrowseEndpoint(options: FindAllOptions): Observable<string> {
return this.halService.getEndpoint(this.linkPath);
getBrowseEndpoint(options: FindAllOptions = {}, linkPath: string = this.linkPath): Observable<string> {
return this.halService.getEndpoint(linkPath);
}
getFindByIDHref(endpoint, resourceID): string {
@@ -46,7 +44,7 @@ export class DSpaceObjectDataService {
protected rdbService: RemoteDataBuildService,
protected halService: HALEndpointService,
protected objectCache: ObjectCacheService) {
this.dataService = new DataServiceImpl(null, requestService, rdbService, null, halService, objectCache);
this.dataService = new DataServiceImpl(requestService, rdbService, null, halService, objectCache);
}
findById(uuid: string): Observable<RemoteData<DSpaceObject>> {

View File

@@ -1,7 +1,7 @@
import { Inject, Injectable } from '@angular/core';
import { GLOBAL_CONFIG } from '../../../config';
import { GlobalConfig } from '../../../config/global-config.interface';
import { ErrorResponse, RestResponse, EndpointMapSuccessResponse } from '../cache/response-cache.models';
import { ErrorResponse, RestResponse, EndpointMapSuccessResponse } from '../cache/response.models';
import { DSpaceRESTV2Response } from '../dspace-rest-v2/dspace-rest-v2-response.model';
import { ResponseParsingService } from './parsing.service';
import { RestRequest } from './request.models';

View File

@@ -2,7 +2,7 @@ import { Inject, Injectable } from '@angular/core';
import {
FacetConfigSuccessResponse,
RestResponse
} from '../cache/response-cache.models';
} from '../cache/response.models';
import { ResponseParsingService } from './parsing.service';
import { RestRequest } from './request.models';
import { DSpaceRESTV2Response } from '../dspace-rest-v2/dspace-rest-v2-response.model';

View File

@@ -4,7 +4,7 @@ import {
FacetValueMapSuccessResponse,
FacetValueSuccessResponse,
RestResponse
} from '../cache/response-cache.models';
} from '../cache/response.models';
import { ResponseParsingService } from './parsing.service';
import { RestRequest } from './request.models';
import { DSpaceRESTV2Response } from '../dspace-rest-v2/dspace-rest-v2-response.model';

View File

@@ -4,7 +4,7 @@ import {
FacetValueMapSuccessResponse,
FacetValueSuccessResponse,
RestResponse
} from '../cache/response-cache.models';
} from '../cache/response.models';
import { ResponseParsingService } from './parsing.service';
import { RestRequest } from './request.models';
import { DSpaceRESTV2Response } from '../dspace-rest-v2/dspace-rest-v2-response.model';

View File

@@ -3,7 +3,6 @@ import { cold, getTestScheduler } from 'jasmine-marbles';
import { TestScheduler } from 'rxjs/testing';
import { BrowseService } from '../browse/browse.service';
import { RemoteDataBuildService } from '../cache/builders/remote-data-build.service';
import { ResponseCacheService } from '../cache/response-cache.service';
import { CoreState } from '../core.reducers';
import { ItemDataService } from './item-data.service';
import { RequestService } from './request.service';
@@ -16,7 +15,6 @@ describe('ItemDataService', () => {
let service: ItemDataService;
let bs: BrowseService;
const requestService = {} as RequestService;
const responseCache = {} as ResponseCacheService;
const rdbService = {} as RemoteDataBuildService;
const objectCache = {} as ObjectCacheService;
const store = {} as Store<CoreState>;
@@ -48,7 +46,6 @@ describe('ItemDataService', () => {
function initTestService() {
return new ItemDataService(
responseCache,
requestService,
rdbService,
store,

View File

@@ -7,7 +7,6 @@ import { isNotEmpty } from '../../shared/empty.util';
import { BrowseService } from '../browse/browse.service';
import { RemoteDataBuildService } from '../cache/builders/remote-data-build.service';
import { NormalizedItem } from '../cache/models/normalized-item.model';
import { ResponseCacheService } from '../cache/response-cache.service';
import { CoreState } from '../core.reducers';
import { Item } from '../shared/item.model';
import { URLCombiner } from '../url-combiner/url-combiner';
@@ -23,7 +22,6 @@ export class ItemDataService extends DataService<NormalizedItem, Item> {
protected linkPath = 'items';
constructor(
protected responseCache: ResponseCacheService,
protected requestService: RequestService,
protected rdbService: RemoteDataBuildService,
protected store: Store<CoreState>,
@@ -39,12 +37,12 @@ export class ItemDataService extends DataService<NormalizedItem, Item> {
* @param {FindAllOptions} options
* @returns {Observable<string>}
*/
public getBrowseEndpoint(options: FindAllOptions = {}): Observable<string> {
public getBrowseEndpoint(options: FindAllOptions = {}, linkPath: string = this.linkPath): Observable<string> {
let field = 'dc.date.issued';
if (options.sort && options.sort.field) {
field = options.sort.field;
}
return this.bs.getBrowseURLFor(field, this.linkPath).pipe(
return this.bs.getBrowseURLFor(field, linkPath).pipe(
filter((href: string) => isNotEmpty(href)),
map((href: string) => new URLCombiner(href, `?scope=${options.scopeID}`).toString()),
distinctUntilChanged(),);

View File

@@ -4,7 +4,7 @@ import { DSpaceRESTV2Response } from '../dspace-rest-v2/dspace-rest-v2-response.
import { RestRequest } from './request.models';
import { ResponseParsingService } from './parsing.service';
import { Injectable } from '@angular/core';
import { MetadataschemaSuccessResponse, RestResponse } from '../cache/response-cache.models';
import { MetadataschemaSuccessResponse, RestResponse } from '../cache/response.models';
@Injectable()
export class MetadataschemaParsingService implements ResponseParsingService {

View File

@@ -1,6 +1,6 @@
import { DSpaceRESTV2Response } from '../dspace-rest-v2/dspace-rest-v2-response.model';
import { RestRequest } from './request.models';
import { RestResponse } from '../cache/response-cache.models';
import { RestResponse } from '../cache/response.models';
export interface ResponseParsingService {
parse(request: RestRequest, data: DSpaceRESTV2Response): RestResponse;

View File

@@ -1,4 +1,4 @@
import { RegistryBitstreamformatsSuccessResponse, RestResponse } from '../cache/response-cache.models';
import { RegistryBitstreamformatsSuccessResponse, RestResponse } from '../cache/response.models';
import { RegistryBitstreamformatsResponse } from '../registry/registry-bitstreamformats-response.model';
import { DSpaceRESTv2Serializer } from '../dspace-rest-v2/dspace-rest-v2.serializer';
import { DSpaceRESTV2Response } from '../dspace-rest-v2/dspace-rest-v2-response.model';

View File

@@ -1,7 +1,7 @@
import {
RegistryMetadatafieldsSuccessResponse,
RestResponse
} from '../cache/response-cache.models';
} from '../cache/response.models';
import { DSpaceRESTV2Response } from '../dspace-rest-v2/dspace-rest-v2-response.model';
import { RestRequest } from './request.models';
import { ResponseParsingService } from './parsing.service';

View File

@@ -1,4 +1,4 @@
import { RegistryMetadataschemasSuccessResponse, RestResponse } from '../cache/response-cache.models';
import { RegistryMetadataschemasSuccessResponse, RestResponse } from '../cache/response.models';
import { DSpaceRESTV2Response } from '../dspace-rest-v2/dspace-rest-v2-response.model';
import { RestRequest } from './request.models';
import { ResponseParsingService } from './parsing.service';

View File

@@ -1,6 +1,7 @@
import { Action } from '@ngrx/store';
import { type } from '../../shared/ngrx/type';
import { RestRequest } from './request.models';
import { RestResponse } from '../cache/response.models';
/**
* The list of RequestAction type definitions
@@ -8,7 +9,8 @@ import { RestRequest } from './request.models';
export const RequestActionTypes = {
CONFIGURE: type('dspace/core/data/request/CONFIGURE'),
EXECUTE: type('dspace/core/data/request/EXECUTE'),
COMPLETE: type('dspace/core/data/request/COMPLETE')
COMPLETE: type('dspace/core/data/request/COMPLETE'),
RESET_TIMESTAMPS: type('dspace/core/data/request/RESET_TIMESTAMPS')
};
/* tslint:disable:max-classes-per-file */
@@ -43,7 +45,10 @@ export class RequestExecuteAction implements Action {
*/
export class RequestCompleteAction implements Action {
type = RequestActionTypes.COMPLETE;
payload: string;
payload: {
uuid: string,
response: RestResponse
};
/**
* Create a new RequestCompleteAction
@@ -51,10 +56,32 @@ export class RequestCompleteAction implements Action {
* @param uuid
* the request's uuid
*/
constructor(uuid: string) {
this.payload = uuid;
constructor(uuid: string, response: RestResponse) {
this.payload = {
uuid,
response
};
}
}
/**
* An ngrx action to reset the timeAdded property of all responses in the cached objects
*/
export class ResetResponseTimestampsAction implements Action {
type = RequestActionTypes.RESET_TIMESTAMPS;
payload: number;
/**
* Create a new ResetResponseTimestampsAction
*
* @param newTimestamp
* the new timeAdded all objects should get
*/
constructor(newTimestamp: number) {
this.payload = newTimestamp;
}
}
/* tslint:enable:max-classes-per-file */
/**
@@ -63,4 +90,5 @@ export class RequestCompleteAction implements Action {
export type RequestAction
= RequestConfigureAction
| RequestExecuteAction
| RequestCompleteAction;
| RequestCompleteAction
| ResetResponseTimestampsAction;

View File

@@ -1,30 +1,33 @@
import {of as observableOf, Observable } from 'rxjs';
import { Observable, of as observableOf } from 'rxjs';
import { Inject, Injectable, Injector } from '@angular/core';
import { Request } from '@angular/http';
import { RequestArgs } from '@angular/http/src/interfaces';
import { Actions, Effect, ofType } from '@ngrx/effects';
import { GLOBAL_CONFIG, GlobalConfig } from '../../../config';
import { isNotEmpty } from '../../shared/empty.util';
import { ErrorResponse, RestResponse } from '../cache/response-cache.models';
import { ResponseCacheService } from '../cache/response-cache.service';
import { DSpaceRESTV2Response } from '../dspace-rest-v2/dspace-rest-v2-response.model';
import { DSpaceRESTv2Service } from '../dspace-rest-v2/dspace-rest-v2.service';
import { RequestActionTypes, RequestCompleteAction, RequestExecuteAction } from './request.actions';
import {
RequestActionTypes,
RequestCompleteAction,
RequestExecuteAction,
ResetResponseTimestampsAction
} from './request.actions';
import { RequestError, RestRequest } from './request.models';
import { RequestEntry } from './request.reducer';
import { RequestService } from './request.service';
import { DSpaceRESTv2Serializer } from '../dspace-rest-v2/dspace-rest-v2.serializer';
import { NormalizedObjectFactory } from '../cache/models/normalized-object-factory';
import { catchError, flatMap, map, take, tap } from 'rxjs/operators';
import { ErrorResponse, RestResponse } from '../cache/response.models';
import { StoreActionTypes } from '../../store.actions';
export const addToResponseCacheAndCompleteAction = (request: RestRequest, responseCache: ResponseCacheService, envConfig: GlobalConfig) =>
(source: Observable<ErrorResponse>): Observable<RequestCompleteAction> =>
export const addToResponseCacheAndCompleteAction = (request: RestRequest, envConfig: GlobalConfig) =>
(source: Observable<RestResponse>): Observable<RequestCompleteAction> =>
source.pipe(
tap((response: RestResponse) => responseCache.add(request.href, response, request.responseMsToLive ? request.responseMsToLive : envConfig.cache.msToLive.default)),
map((response: RestResponse) => new RequestCompleteAction(request.uuid))
map((response: RestResponse) => {
return new RequestCompleteAction(request.uuid, response)
})
);
@Injectable()
@@ -46,20 +49,32 @@ export class RequestEffects {
}
return this.restApi.request(request.method, request.href, body, request.options).pipe(
map((data: DSpaceRESTV2Response) => this.injector.get(request.getResponseParser()).parse(request, data)),
addToResponseCacheAndCompleteAction(request, this.responseCache, this.EnvConfig),
addToResponseCacheAndCompleteAction(request, this.EnvConfig),
catchError((error: RequestError) => observableOf(new ErrorResponse(error)).pipe(
addToResponseCacheAndCompleteAction(request, this.responseCache, this.EnvConfig)
addToResponseCacheAndCompleteAction(request, this.EnvConfig)
))
);
})
);
/**
* When the store is rehydrated in the browser, set all cache
* timestamps to 'now', because the time zone of the server can
* differ from the client.
*
* This assumes that the server cached everything a negligible
* time ago, and will likely need to be revisited later
*/
@Effect() fixTimestampsOnRehydrate = this.actions$
.pipe(ofType(StoreActionTypes.REHYDRATE),
map(() => new ResetResponseTimestampsAction(new Date().getTime()))
);
constructor(
@Inject(GLOBAL_CONFIG) private EnvConfig: GlobalConfig,
private actions$: Actions,
private restApi: DSpaceRESTv2Service,
private injector: Injector,
private responseCache: ResponseCacheService,
protected requestService: RequestService
) { }

View File

@@ -14,32 +14,36 @@ import { BrowseItemsResponseParsingService } from './browse-items-response-parsi
/* tslint:disable:max-classes-per-file */
export abstract class RestRequest {
public responseMsToLive = 0;
constructor(
public uuid: string,
public href: string,
public method: RestRequestMethod = RestRequestMethod.GET,
public body?: any,
public options?: HttpOptions,
public responseMsToLive?: number
) {
}
getResponseParser(): GenericConstructor<ResponseParsingService> {
return DSOResponseParsingService;
}
get toCache(): boolean {
return this.responseMsToLive > 0;
}
}
export class GetRequest extends RestRequest {
public responseMsToLive = 60 * 15 * 1000;
constructor(
public uuid: string,
public href: string,
public body?: any,
public options?: HttpOptions,
public responseMsToLive?: number
) {
super(uuid, href, RestRequestMethod.GET, body, options, responseMsToLive)
super(uuid, href, RestRequestMethod.GET, body, options)
}
}
@@ -212,6 +216,7 @@ export class IntegrationRequest extends GetRequest {
return IntegrationResponseParsingService;
}
}
export class RequestError extends Error {
statusText: string;
}

View File

@@ -1,14 +1,16 @@
import {
RequestActionTypes, RequestAction, RequestConfigureAction,
RequestExecuteAction, RequestCompleteAction
RequestExecuteAction, RequestCompleteAction, ResetResponseTimestampsAction
} from './request.actions';
import { RestRequest } from './request.models';
import { RestResponse } from '../cache/response.models';
export class RequestEntry {
request: RestRequest;
requestPending: boolean;
responsePending: boolean;
completed: boolean;
response: RestResponse
}
export interface RequestState {
@@ -32,6 +34,9 @@ export function requestReducer(state = initialState, action: RequestAction): Req
case RequestActionTypes.COMPLETE: {
return completeRequest(state, action as RequestCompleteAction);
}
case RequestActionTypes.RESET_TIMESTAMPS: {
return resetResponseTimestamps(state, action as ResetResponseTimestampsAction);
}
default: {
return state;
@@ -45,7 +50,7 @@ function configureRequest(state: RequestState, action: RequestConfigureAction):
request: action.payload,
requestPending: true,
responsePending: false,
completed: false
completed: false,
}
});
}
@@ -70,10 +75,25 @@ function executeRequest(state: RequestState, action: RequestExecuteAction): Requ
* the new state, with the response added to the request
*/
function completeRequest(state: RequestState, action: RequestCompleteAction): RequestState {
return Object.assign({}, state, {
[action.payload]: Object.assign({}, state[action.payload], {
const time = new Date().getTime();
const ob = Object.assign({}, state, {
[action.payload.uuid]: Object.assign({}, state[action.payload.uuid], {
responsePending: false,
completed: true
completed: true,
response: Object.assign({}, action.payload.response, { timeAdded: time })
})
});
console.log(ob);
return ob;
}
function resetResponseTimestamps(state: RequestState, action: ResetResponseTimestampsAction) {
const newState = Object.create(null);
Object.keys(state).forEach((key) => {
newState[key] = Object.assign({}, state[key],
{ response: Object.assign({}, state[key].response, { timeAdded: action.payload }) }
);
});
return newState;
}

View File

@@ -1,10 +1,8 @@
import { cold, getTestScheduler, hot } from 'jasmine-marbles';
import { of as observableOf } from 'rxjs';
import { getMockObjectCacheService } from '../../shared/mocks/mock-object-cache.service';
import { getMockResponseCacheService } from '../../shared/mocks/mock-response-cache.service';
import { defaultUUID, getMockUUIDService } from '../../shared/mocks/mock-uuid.service';
import { ObjectCacheService } from '../cache/object-cache.service';
import { ResponseCacheService } from '../cache/response-cache.service';
import { CoreState } from '../core.reducers';
import { UUIDService } from '../shared/uuid.service';
import { RequestConfigureAction, RequestExecuteAction } from './request.actions';
@@ -29,7 +27,6 @@ describe('RequestService', () => {
let service: RequestService;
let serviceAsAny: any;
let objectCache: ObjectCacheService;
let responseCache: ResponseCacheService;
let uuidService: UUIDService;
let store: Store<CoreState>;
@@ -49,7 +46,6 @@ describe('RequestService', () => {
objectCache = getMockObjectCacheService();
(objectCache.hasBySelfLink as any).and.returnValue(false);
responseCache = getMockResponseCacheService();
(responseCache.has as any).and.returnValue(false);
(responseCache.get as any).and.returnValue(observableOf(undefined));
@@ -65,7 +61,6 @@ describe('RequestService', () => {
service = new RequestService(
objectCache,
responseCache,
uuidService,
store
);

View File

@@ -1,14 +1,23 @@
import { Observable, merge as observableMerge } from 'rxjs';
import { filter, first, map, mergeMap, partition, take } from 'rxjs/operators';
import { merge as observableMerge, Observable, of as observableOf } from 'rxjs';
import {
filter,
find,
first,
map,
mergeMap,
reduce,
startWith,
switchMap,
take,
tap
} from 'rxjs/operators';
import { Injectable } from '@angular/core';
import { MemoizedSelector, select, Store } from '@ngrx/store';
import { hasValue } from '../../shared/empty.util';
import { hasNoValue, hasValue } from '../../shared/empty.util';
import { CacheableObject } from '../cache/object-cache.reducer';
import { ObjectCacheService } from '../cache/object-cache.service';
import { DSOSuccessResponse, RestResponse } from '../cache/response-cache.models';
import { ResponseCacheEntry } from '../cache/response-cache.reducer';
import { ResponseCacheService } from '../cache/response-cache.service';
import { DSOSuccessResponse, RestResponse } from '../cache/response.models';
import { coreSelector, CoreState } from '../core.reducers';
import { IndexName } from '../index/index.reducer';
import { pathSelector } from '../shared/selectors';
@@ -19,13 +28,13 @@ import { GetRequest, RestRequest } from './request.models';
import { RequestEntry } from './request.reducer';
import { CommitSSBAction } from '../cache/server-sync-buffer.actions';
import { RestRequestMethod } from './rest-request-method';
import { getResponseFromEntry } from '../shared/operators';
@Injectable()
export class RequestService {
private requestsOnTheirWayToTheStore: string[] = [];
constructor(private objectCache: ObjectCacheService,
private responseCache: ResponseCacheService,
private uuidService: UUIDService,
private store: Store<CoreState>) {
}
@@ -83,23 +92,27 @@ export class RequestService {
private isCachedOrPending(request: GetRequest) {
let isCached = this.objectCache.hasBySelfLink(request.href);
if (!isCached && this.responseCache.has(request.href)) {
const responses = this.responseCache.get(request.href).pipe(
take(1),
map((entry: ResponseCacheEntry) => entry.response)
);
const responses: Observable<RestResponse> = this.isReusable(request.uuid).pipe(
filter((reusable: boolean) => !isCached && reusable),
switchMap(() => {
return this.getByHref(request.href).pipe(
getResponseFromEntry(),
take(1)
);
}
));
const errorResponses = responses.pipe(filter((response) => !response.isSuccessful), map(() => true)); // TODO add a configurable number of retries in case of an error.
const dsoSuccessResponses = responses.pipe(
filter((response) => response.isSuccessful && hasValue((response as DSOSuccessResponse).resourceSelfLinks)),
map((response: DSOSuccessResponse) => response.resourceSelfLinks),
map((resourceSelfLinks: string[]) => resourceSelfLinks
.every((selfLink) => this.objectCache.hasBySelfLink(selfLink))
));
const otherSuccessResponses = responses.pipe(filter((response) => response.isSuccessful && !hasValue((response as DSOSuccessResponse).resourceSelfLinks)), map(() => true));
const errorResponses = responses.pipe(filter((response) => !response.isSuccessful), map(() => true)); // TODO add a configurable number of retries in case of an error.
const dsoSuccessResponses = responses.pipe(
filter((response) => response.isSuccessful && hasValue((response as DSOSuccessResponse).resourceSelfLinks)),
map((response: DSOSuccessResponse) => response.resourceSelfLinks),
map((resourceSelfLinks: string[]) => resourceSelfLinks
.every((selfLink) => this.objectCache.hasBySelfLink(selfLink))
));
const otherSuccessResponses = responses.pipe(filter((response) => response.isSuccessful && !hasValue((response as DSOSuccessResponse).resourceSelfLinks)), map(() => true));
observableMerge(errorResponses, otherSuccessResponses, dsoSuccessResponses).subscribe((c) => isCached = c);
observableMerge(errorResponses, otherSuccessResponses, dsoSuccessResponses).subscribe((c) => isCached = c);
}
const isPending = this.isPending(request);
return isCached || isPending;
}
@@ -129,4 +142,34 @@ export class RequestService {
commit(method?: RestRequestMethod) {
this.store.dispatch(new CommitSSBAction(method))
}
/**
* Check whether a ResponseCacheEntry should still be cached
*
* @param entry
* the entry to check
* @return boolean
* false if the entry is null, undefined, or its time to
* live has been exceeded, true otherwise
*/
private isReusable(uuid: string): Observable<boolean> {
if (hasNoValue(uuid)) {
return observableOf(false);
} else {
const requestEntry$ = this.getByUUID(uuid);
return requestEntry$.pipe(
filter((entry: RequestEntry) => hasValue(entry) && hasValue(entry.response)),
map((entry: RequestEntry) => {
if (hasValue(entry) && entry.response.isSuccessful) {
const timeOutdated = entry.response.timeAdded + entry.request.responseMsToLive;
const isOutDated = new Date().getTime() > timeOutdated;
return !isOutDated;
} else {
return false;
}
})
);
return observableOf(false);
}
}
}

View File

@@ -1,5 +1,5 @@
import { Injectable } from '@angular/core';
import { RestResponse, SearchSuccessResponse } from '../cache/response-cache.models';
import { RestResponse, SearchSuccessResponse } from '../cache/response.models';
import { DSOResponseParsingService } from './dso-response-parsing.service';
import { ResponseParsingService } from './parsing.service';
import { RestRequest } from './request.models';