mirror of
https://github.com/DSpace/dspace-angular.git
synced 2025-10-12 04:23:04 +00:00
fetch resource endpoints from HAL response instead of hardcoding them
This commit is contained in:
@@ -8,12 +8,12 @@ import { ResponseCacheService } from '../response-cache.service';
|
||||
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 { ErrorResponse, DSOSuccessResponse } from '../response-cache.models';
|
||||
import { RemoteData } from '../../data/remote-data';
|
||||
import { GenericConstructor } from '../../shared/generic-constructor';
|
||||
import { getMapsTo, getRelationMetadata, getRelationships } from './build-decorators';
|
||||
import { NormalizedObjectFactory } from '../models/normalized-object-factory';
|
||||
import { Request } from '../../data/request.models';
|
||||
import { RestRequest } from '../../data/request.models';
|
||||
|
||||
@Injectable()
|
||||
export class RemoteDataBuildService {
|
||||
@@ -25,19 +25,26 @@ export class RemoteDataBuildService {
|
||||
}
|
||||
|
||||
buildSingle<TNormalized extends CacheableObject, TDomain>(
|
||||
href: string,
|
||||
hrefObs: string | Observable<string>,
|
||||
normalizedType: GenericConstructor<TNormalized>
|
||||
): RemoteData<TDomain> {
|
||||
const requestHrefObs = this.objectCache.getRequestHrefBySelfLink(href);
|
||||
if (typeof hrefObs === 'string') {
|
||||
hrefObs = Observable.of(hrefObs);
|
||||
}
|
||||
|
||||
const requestHrefObs = hrefObs.flatMap((href: string) =>
|
||||
this.objectCache.getRequestHrefBySelfLink(href));
|
||||
|
||||
const requestObs = Observable.race(
|
||||
this.requestService.get(href).filter((entry) => hasValue(entry)),
|
||||
hrefObs.flatMap((href: string) => this.requestService.get(href))
|
||||
.filter((entry) => hasValue(entry)),
|
||||
requestHrefObs.flatMap((requestHref) =>
|
||||
this.requestService.get(requestHref)).filter((entry) => hasValue(entry))
|
||||
);
|
||||
|
||||
const responseCacheObs = Observable.race(
|
||||
this.responseCache.get(href).filter((entry) => hasValue(entry)),
|
||||
hrefObs.flatMap((href: string) => this.responseCache.get(href))
|
||||
.filter((entry) => hasValue(entry)),
|
||||
requestHrefObs.flatMap((requestHref) => this.responseCache.get(requestHref)).filter((entry) => hasValue(entry))
|
||||
);
|
||||
|
||||
@@ -60,17 +67,18 @@ export class RemoteDataBuildService {
|
||||
/* tslint:disable:no-string-literal */
|
||||
const pageInfo = responseCacheObs
|
||||
.filter((entry: ResponseCacheEntry) => hasValue(entry.response) && hasValue(entry.response['pageInfo']))
|
||||
.map((entry: ResponseCacheEntry) => (entry.response as SuccessResponse).pageInfo)
|
||||
.map((entry: ResponseCacheEntry) => (entry.response as DSOSuccessResponse).pageInfo)
|
||||
.distinctUntilChanged();
|
||||
/* tslint:enable:no-string-literal */
|
||||
|
||||
// always use self link if that is cached, only if it isn't, get it via the response.
|
||||
const payload =
|
||||
Observable.combineLatest(
|
||||
this.objectCache.getBySelfLink<TNormalized>(href, normalizedType).startWith(undefined),
|
||||
hrefObs.flatMap((href: string) => this.objectCache.getBySelfLink<TNormalized>(href, normalizedType))
|
||||
.startWith(undefined),
|
||||
responseCacheObs
|
||||
.filter((entry: ResponseCacheEntry) => entry.response.isSuccessful)
|
||||
.map((entry: ResponseCacheEntry) => (entry.response as SuccessResponse).resourceSelfLinks)
|
||||
.map((entry: ResponseCacheEntry) => (entry.response as DSOSuccessResponse).resourceSelfLinks)
|
||||
.flatMap((resourceSelfLinks: string[]) => {
|
||||
if (isNotEmpty(resourceSelfLinks)) {
|
||||
return this.objectCache.getBySelfLink(resourceSelfLinks[0], normalizedType);
|
||||
@@ -93,7 +101,7 @@ export class RemoteDataBuildService {
|
||||
}).distinctUntilChanged();
|
||||
|
||||
return new RemoteData(
|
||||
href,
|
||||
hrefObs,
|
||||
requestPending,
|
||||
responsePending,
|
||||
isSuccessFul,
|
||||
@@ -105,12 +113,17 @@ export class RemoteDataBuildService {
|
||||
}
|
||||
|
||||
buildList<TNormalized extends CacheableObject, TDomain>(
|
||||
href: string,
|
||||
hrefObs: string | Observable<string>,
|
||||
normalizedType: GenericConstructor<TNormalized>
|
||||
): RemoteData<TDomain[]> {
|
||||
const requestObs = this.requestService.get(href)
|
||||
if (typeof hrefObs === 'string') {
|
||||
hrefObs = Observable.of(hrefObs);
|
||||
}
|
||||
|
||||
const requestObs = hrefObs.flatMap((href: string) => this.requestService.get(href))
|
||||
.filter((entry) => hasValue(entry));
|
||||
const responseCacheObs = hrefObs.flatMap((href: string) => this.responseCache.get(href))
|
||||
.filter((entry) => hasValue(entry));
|
||||
const responseCacheObs = this.responseCache.get(href).filter((entry) => hasValue(entry));
|
||||
|
||||
const requestPending = requestObs.map((entry: RequestEntry) => entry.requestPending).distinctUntilChanged();
|
||||
|
||||
@@ -131,13 +144,13 @@ export class RemoteDataBuildService {
|
||||
/* tslint:disable:no-string-literal */
|
||||
const pageInfo = responseCacheObs
|
||||
.filter((entry: ResponseCacheEntry) => hasValue(entry.response) && hasValue(entry.response['pageInfo']))
|
||||
.map((entry: ResponseCacheEntry) => (entry.response as SuccessResponse).pageInfo)
|
||||
.map((entry: ResponseCacheEntry) => (entry.response as DSOSuccessResponse).pageInfo)
|
||||
.distinctUntilChanged();
|
||||
/* tslint:enable:no-string-literal */
|
||||
|
||||
const payload = responseCacheObs
|
||||
.filter((entry: ResponseCacheEntry) => entry.response.isSuccessful)
|
||||
.map((entry: ResponseCacheEntry) => (entry.response as SuccessResponse).resourceSelfLinks)
|
||||
.map((entry: ResponseCacheEntry) => (entry.response as DSOSuccessResponse).resourceSelfLinks)
|
||||
.flatMap((resourceUUIDs: string[]) => {
|
||||
return this.objectCache.getList(resourceUUIDs, normalizedType)
|
||||
.map((normList: TNormalized[]) => {
|
||||
@@ -149,7 +162,7 @@ export class RemoteDataBuildService {
|
||||
.distinctUntilChanged();
|
||||
|
||||
return new RemoteData(
|
||||
href,
|
||||
hrefObs,
|
||||
requestPending,
|
||||
responsePending,
|
||||
isSuccessFul,
|
||||
@@ -174,7 +187,7 @@ export class RemoteDataBuildService {
|
||||
// are dispatched, but sometimes don't arrive. I'm unsure why atm.
|
||||
setTimeout(() => {
|
||||
normalized[relationship].forEach((href: string) => {
|
||||
this.requestService.configure(new Request(href))
|
||||
this.requestService.configure(new RestRequest(href))
|
||||
});
|
||||
}, 0);
|
||||
|
||||
@@ -192,7 +205,7 @@ export class RemoteDataBuildService {
|
||||
// without the setTimeout, the actions inside requestService.configure
|
||||
// are dispatched, but sometimes don't arrive. I'm unsure why atm.
|
||||
setTimeout(() => {
|
||||
this.requestService.configure(new Request(normalized[relationship]));
|
||||
this.requestService.configure(new RestRequest(normalized[relationship]));
|
||||
}, 0);
|
||||
|
||||
// The rest API can return a single URL to represent a list of resources (e.g. /items/:id/bitstreams)
|
||||
@@ -259,7 +272,7 @@ export class RemoteDataBuildService {
|
||||
// This is an aggregated object, it doesn't necessarily correspond
|
||||
// to a single REST endpoint, so instead of a self link, use the
|
||||
// current time in ms for a somewhat unique id
|
||||
`${new Date().getTime()}`,
|
||||
Observable.of(`${new Date().getTime()}`),
|
||||
requestPending,
|
||||
responsePending,
|
||||
isSuccessFul,
|
||||
|
6
src/app/core/cache/response-cache.actions.ts
vendored
6
src/app/core/cache/response-cache.actions.ts
vendored
@@ -1,7 +1,7 @@
|
||||
import { Action } from '@ngrx/store';
|
||||
|
||||
import { type } from '../../shared/ngrx/type';
|
||||
import { Response } from './response-cache.models';
|
||||
import { RestResponse } from './response-cache.models';
|
||||
|
||||
/**
|
||||
* The list of ResponseCacheAction type definitions
|
||||
@@ -17,12 +17,12 @@ export class ResponseCacheAddAction implements Action {
|
||||
type = ResponseCacheActionTypes.ADD;
|
||||
payload: {
|
||||
key: string,
|
||||
response: Response
|
||||
response: RestResponse
|
||||
timeAdded: number;
|
||||
msToLive: number;
|
||||
};
|
||||
|
||||
constructor(key: string, response: Response, timeAdded: number, msToLive: number) {
|
||||
constructor(key: string, response: RestResponse, timeAdded: number, msToLive: number) {
|
||||
this.payload = { key, response, timeAdded, msToLive };
|
||||
}
|
||||
}
|
||||
|
15
src/app/core/cache/response-cache.models.ts
vendored
15
src/app/core/cache/response-cache.models.ts
vendored
@@ -2,14 +2,14 @@ import { RequestError } from '../data/request.models';
|
||||
import { PageInfo } from '../shared/page-info.model';
|
||||
|
||||
/* tslint:disable:max-classes-per-file */
|
||||
export class Response {
|
||||
export class RestResponse {
|
||||
constructor(
|
||||
public isSuccessful: boolean,
|
||||
public statusCode: string
|
||||
) { }
|
||||
}
|
||||
|
||||
export class SuccessResponse extends Response {
|
||||
export class DSOSuccessResponse extends RestResponse {
|
||||
constructor(
|
||||
public resourceSelfLinks: string[],
|
||||
public statusCode: string,
|
||||
@@ -19,7 +19,16 @@ export class SuccessResponse extends Response {
|
||||
}
|
||||
}
|
||||
|
||||
export class ErrorResponse extends Response {
|
||||
export class RootSuccessResponse extends RestResponse {
|
||||
constructor(
|
||||
public endpointMap: { [linkName: string]: string },
|
||||
public statusCode: string,
|
||||
) {
|
||||
super(true, statusCode);
|
||||
}
|
||||
}
|
||||
|
||||
export class ErrorResponse extends RestResponse {
|
||||
errorMessage: string;
|
||||
|
||||
constructor(error: RequestError) {
|
||||
|
4
src/app/core/cache/response-cache.reducer.ts
vendored
4
src/app/core/cache/response-cache.reducer.ts
vendored
@@ -5,14 +5,14 @@ import {
|
||||
} from './response-cache.actions';
|
||||
import { CacheEntry } from './cache-entry';
|
||||
import { hasValue } from '../../shared/empty.util';
|
||||
import { Response } from './response-cache.models';
|
||||
import { RestResponse } from './response-cache.models';
|
||||
|
||||
/**
|
||||
* An entry in the ResponseCache
|
||||
*/
|
||||
export class ResponseCacheEntry implements CacheEntry {
|
||||
key: string;
|
||||
response: Response;
|
||||
response: RestResponse;
|
||||
timeAdded: number;
|
||||
msToLive: number;
|
||||
}
|
||||
|
4
src/app/core/cache/response-cache.service.ts
vendored
4
src/app/core/cache/response-cache.service.ts
vendored
@@ -6,7 +6,7 @@ import { Observable } from 'rxjs/Observable';
|
||||
import { ResponseCacheEntry } from './response-cache.reducer';
|
||||
import { hasNoValue } from '../../shared/empty.util';
|
||||
import { ResponseCacheRemoveAction, ResponseCacheAddAction } from './response-cache.actions';
|
||||
import { Response } from './response-cache.models';
|
||||
import { RestResponse } from './response-cache.models';
|
||||
import { CoreState } from '../core.reducers';
|
||||
import { keySelector } from '../shared/selectors';
|
||||
|
||||
@@ -23,7 +23,7 @@ export class ResponseCacheService {
|
||||
private store: Store<CoreState>
|
||||
) { }
|
||||
|
||||
add(key: string, response: Response, msToLive: number): Observable<ResponseCacheEntry> {
|
||||
add(key: string, response: RestResponse, msToLive: number): Observable<ResponseCacheEntry> {
|
||||
if (!this.has(key)) {
|
||||
// this.store.dispatch(new ResponseCacheFindAllAction(key, service, scopeID, paginationOptions, sortOptions));
|
||||
this.store.dispatch(new ResponseCacheAddAction(key, response, new Date().getTime(), msToLive));
|
||||
|
@@ -18,6 +18,8 @@ import { coreEffects } from './core.effects';
|
||||
import { EffectsModule } from '@ngrx/effects';
|
||||
import { StoreModule } from '@ngrx/store';
|
||||
import { coreReducers } from './core.reducers';
|
||||
import { DSOResponseParsingService } from './data/dso-response-parsing.service';
|
||||
import { RootResponseParsingService } from './data/root-response-parsing.service';
|
||||
|
||||
const IMPORTS = [
|
||||
CommonModule,
|
||||
@@ -43,7 +45,9 @@ const PROVIDERS = [
|
||||
PaginationComponentOptions,
|
||||
ResponseCacheService,
|
||||
RequestService,
|
||||
RemoteDataBuildService
|
||||
RemoteDataBuildService,
|
||||
DSOResponseParsingService,
|
||||
RootResponseParsingService
|
||||
];
|
||||
|
||||
@NgModule({
|
||||
|
@@ -3,7 +3,6 @@ import { Store } from '@ngrx/store';
|
||||
|
||||
import { DataService } from './data.service';
|
||||
import { Collection } from '../shared/collection.model';
|
||||
import { ObjectCacheService } from '../cache/object-cache.service';
|
||||
import { ResponseCacheService } from '../cache/response-cache.service';
|
||||
import { NormalizedCollection } from '../cache/models/normalized-collection.model';
|
||||
import { CoreState } from '../core.reducers';
|
||||
@@ -13,11 +12,10 @@ import { GLOBAL_CONFIG, GlobalConfig } from '../../../config';
|
||||
|
||||
@Injectable()
|
||||
export class CollectionDataService extends DataService<NormalizedCollection, Collection> {
|
||||
protected resourceEndpoint = '/core/collections';
|
||||
protected linkName = 'collections';
|
||||
protected browseEndpoint = '/discover/browses/dateissued/collections';
|
||||
|
||||
constructor(
|
||||
protected objectCache: ObjectCacheService,
|
||||
protected responseCache: ResponseCacheService,
|
||||
protected requestService: RequestService,
|
||||
protected rdbService: RemoteDataBuildService,
|
||||
|
@@ -4,7 +4,6 @@ import { Store } from '@ngrx/store';
|
||||
|
||||
import { DataService } from './data.service';
|
||||
import { Community } from '../shared/community.model';
|
||||
import { ObjectCacheService } from '../cache/object-cache.service';
|
||||
import { ResponseCacheService } from '../cache/response-cache.service';
|
||||
import { NormalizedCommunity } from '../cache/models/normalized-community.model';
|
||||
import { CoreState } from '../core.reducers';
|
||||
@@ -14,11 +13,10 @@ import { GLOBAL_CONFIG, GlobalConfig } from '../../../config';
|
||||
|
||||
@Injectable()
|
||||
export class CommunityDataService extends DataService<NormalizedCommunity, Community> {
|
||||
protected resourceEndpoint = '/core/communities';
|
||||
protected linkName = 'communities';
|
||||
protected browseEndpoint = '/discover/browses/dateissued/communities';
|
||||
|
||||
constructor(
|
||||
protected objectCache: ObjectCacheService,
|
||||
protected responseCache: ResponseCacheService,
|
||||
protected requestService: RequestService,
|
||||
protected rdbService: RemoteDataBuildService,
|
||||
|
@@ -1,9 +1,11 @@
|
||||
import { ObjectCacheService } from '../cache/object-cache.service';
|
||||
import { ResponseCacheService } from '../cache/response-cache.service';
|
||||
import { CacheableObject } from '../cache/object-cache.reducer';
|
||||
import { hasValue, isNotEmpty } from '../../shared/empty.util';
|
||||
import { RemoteData } from './remote-data';
|
||||
import { FindAllOptions, FindAllRequest, FindByIDRequest, Request } from './request.models';
|
||||
import {
|
||||
FindAllOptions, FindAllRequest, FindByIDRequest, RestRequest,
|
||||
RootEndpointRequest
|
||||
} from './request.models';
|
||||
import { Store } from '@ngrx/store';
|
||||
import { CoreState } from '../core.reducers';
|
||||
import { RequestService } from './request.service';
|
||||
@@ -12,14 +14,15 @@ import { GenericConstructor } from '../shared/generic-constructor';
|
||||
import { GlobalConfig } from '../../../config';
|
||||
import { RESTURLCombiner } from '../url-combiner/rest-url-combiner';
|
||||
import { Observable } from 'rxjs/Observable';
|
||||
import { ResponseCacheEntry } from '../cache/response-cache.reducer';
|
||||
import { RootSuccessResponse } from '../cache/response-cache.models';
|
||||
|
||||
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 resourceEndpoint: string;
|
||||
protected abstract linkName: string;
|
||||
protected abstract browseEndpoint: string;
|
||||
|
||||
constructor(
|
||||
@@ -30,21 +33,23 @@ export abstract class DataService<TNormalized extends CacheableObject, TDomain>
|
||||
}
|
||||
|
||||
private getEndpoint(linkName: string): Observable<string> {
|
||||
const apiUrl = new RESTURLCombiner(this.EnvConfig, '/').toString();
|
||||
this.requestService.configure(new Request(apiUrl));
|
||||
// TODO fetch from store
|
||||
return Observable.of(undefined);
|
||||
const request = new RootEndpointRequest(this.EnvConfig);
|
||||
this.requestService.configure(request);
|
||||
return this.responseCache.get(request.href)
|
||||
.map((entry: ResponseCacheEntry) => entry.response)
|
||||
.filter((response: RootSuccessResponse) => isNotEmpty(response) && isNotEmpty(response.endpointMap))
|
||||
.map((response: RootSuccessResponse) => response.endpointMap[linkName])
|
||||
}
|
||||
|
||||
protected getFindAllHref(options: FindAllOptions = {}): string {
|
||||
protected getFindAllHref(endpoint, options: FindAllOptions = {}): string {
|
||||
let result;
|
||||
const args = [];
|
||||
|
||||
if (hasValue(options.scopeID)) {
|
||||
result = this.browseEndpoint;
|
||||
result = new RESTURLCombiner(this.EnvConfig, this.browseEndpoint).toString();
|
||||
args.push(`scope=${options.scopeID}`);
|
||||
} else {
|
||||
result = this.resourceEndpoint;
|
||||
result = endpoint;
|
||||
}
|
||||
|
||||
if (hasValue(options.currentPage) && typeof options.currentPage === 'number') {
|
||||
@@ -67,31 +72,41 @@ export abstract class DataService<TNormalized extends CacheableObject, TDomain>
|
||||
if (isNotEmpty(args)) {
|
||||
result = `${result}?${args.join('&')}`;
|
||||
}
|
||||
return new RESTURLCombiner(this.EnvConfig, result).toString();
|
||||
return result;
|
||||
}
|
||||
|
||||
findAll(options: FindAllOptions = {}): RemoteData<TDomain[]> {
|
||||
const href = this.getFindAllHref(options);
|
||||
const request = new FindAllRequest(href, options);
|
||||
this.requestService.configure(request);
|
||||
return this.rdbService.buildList<TNormalized, TDomain>(href, this.normalizedResourceType);
|
||||
// return this.rdbService.buildList(href);
|
||||
const hrefObs = this.getEndpoint(this.linkName)
|
||||
.map((endpoint: string) => this.getFindAllHref(endpoint, options));
|
||||
|
||||
hrefObs
|
||||
.subscribe((href: string) => {
|
||||
const request = new FindAllRequest(href, options);
|
||||
this.requestService.configure(request);
|
||||
});
|
||||
|
||||
return this.rdbService.buildList<TNormalized, TDomain>(hrefObs, this.normalizedResourceType);
|
||||
}
|
||||
|
||||
protected getFindByIDHref(resourceID): string {
|
||||
return new RESTURLCombiner(this.EnvConfig, `${this.resourceEndpoint}/${resourceID}`).toString();
|
||||
protected getFindByIDHref(endpoint, resourceID): string {
|
||||
return `${endpoint}/${resourceID}`;
|
||||
}
|
||||
|
||||
findById(id: string): RemoteData<TDomain> {
|
||||
const href = this.getFindByIDHref(id);
|
||||
const request = new FindByIDRequest(href, id);
|
||||
this.requestService.configure(request);
|
||||
return this.rdbService.buildSingle<TNormalized, TDomain>(href, this.normalizedResourceType);
|
||||
// return this.rdbService.buildSingle(href);
|
||||
const hrefObs = this.getEndpoint(this.linkName)
|
||||
.map((endpoint: string) => this.getFindByIDHref(endpoint, id));
|
||||
|
||||
hrefObs
|
||||
.subscribe((href: string) => {
|
||||
const request = new FindByIDRequest(href, id);
|
||||
this.requestService.configure(request);
|
||||
});
|
||||
|
||||
return this.rdbService.buildSingle<TNormalized, TDomain>(hrefObs, this.normalizedResourceType);
|
||||
}
|
||||
|
||||
findByHref(href: string): RemoteData<TDomain> {
|
||||
this.requestService.configure(new Request(href));
|
||||
this.requestService.configure(new RestRequest(href));
|
||||
return this.rdbService.buildSingle<TNormalized, TDomain>(href, this.normalizedResourceType);
|
||||
// return this.rdbService.buildSingle(href));
|
||||
}
|
||||
|
149
src/app/core/data/dso-response-parsing.service.ts
Normal file
149
src/app/core/data/dso-response-parsing.service.ts
Normal file
@@ -0,0 +1,149 @@
|
||||
import { ObjectCacheService } from '../cache/object-cache.service';
|
||||
import { Inject, Injectable } from '@angular/core';
|
||||
import { GlobalConfig } from '../../../config/global-config.interface';
|
||||
import { GLOBAL_CONFIG } from '../../../config';
|
||||
import { NormalizedObject } from '../cache/models/normalized-object.model';
|
||||
import { hasNoValue, hasValue, isNotEmpty } from '../../shared/empty.util';
|
||||
import { ResourceType } from '../shared/resource-type';
|
||||
import { NormalizedObjectFactory } from '../cache/models/normalized-object-factory';
|
||||
import { DSpaceRESTv2Serializer } from '../dspace-rest-v2/dspace-rest-v2.serializer';
|
||||
import { CacheableObject } from '../cache/object-cache.reducer';
|
||||
import { DSpaceRESTV2Response } from '../dspace-rest-v2/dspace-rest-v2-response.model';
|
||||
import { RestResponse, DSOSuccessResponse } from '../cache/response-cache.models';
|
||||
import { RestRequest } from './request.models';
|
||||
import { PageInfo } from '../shared/page-info.model';
|
||||
import { ResponseParsingService } from './parsing.service';
|
||||
|
||||
function isObjectLevel(halObj: any) {
|
||||
return isNotEmpty(halObj._links) && hasValue(halObj._links.self);
|
||||
}
|
||||
|
||||
function isPaginatedResponse(halObj: any) {
|
||||
return isNotEmpty(halObj.page) && hasValue(halObj._embedded);
|
||||
}
|
||||
|
||||
function flattenSingleKeyObject(obj: any): any {
|
||||
const keys = Object.keys(obj);
|
||||
if (keys.length !== 1) {
|
||||
throw new Error(`Expected an object with a single key, got: ${JSON.stringify(obj)}`);
|
||||
}
|
||||
return obj[keys[0]];
|
||||
}
|
||||
|
||||
/* tslint:disable:max-classes-per-file */
|
||||
class ProcessRequestDTO {
|
||||
[key: string]: NormalizedObject[]
|
||||
}
|
||||
|
||||
@Injectable()
|
||||
export class DSOResponseParsingService implements ResponseParsingService {
|
||||
constructor(
|
||||
@Inject(GLOBAL_CONFIG) private EnvConfig: GlobalConfig,
|
||||
private objectCache: ObjectCacheService,
|
||||
) {
|
||||
}
|
||||
|
||||
parse(request: RestRequest, data: DSpaceRESTV2Response): RestResponse {
|
||||
const processRequestDTO = this.process(data.payload, request.href);
|
||||
const selfLinks = flattenSingleKeyObject(processRequestDTO).map((no) => no.self);
|
||||
return new DSOSuccessResponse(selfLinks, data.statusCode, this.processPageInfo(data.payload.page))
|
||||
}
|
||||
|
||||
protected process(data: any, requestHref: string): ProcessRequestDTO {
|
||||
|
||||
if (isNotEmpty(data)) {
|
||||
if (isPaginatedResponse(data)) {
|
||||
return this.process(data._embedded, requestHref);
|
||||
} else if (isObjectLevel(data)) {
|
||||
return { topLevel: this.deserializeAndCache(data, requestHref) };
|
||||
} else {
|
||||
const result = new ProcessRequestDTO();
|
||||
if (Array.isArray(data)) {
|
||||
result.topLevel = [];
|
||||
data.forEach((datum) => {
|
||||
if (isPaginatedResponse(datum)) {
|
||||
const obj = this.process(datum, requestHref);
|
||||
result.topLevel = [...result.topLevel, ...flattenSingleKeyObject(obj)];
|
||||
} else {
|
||||
result.topLevel = [...result.topLevel, ...this.deserializeAndCache(datum, requestHref)];
|
||||
}
|
||||
});
|
||||
} else {
|
||||
Object.keys(data)
|
||||
.filter((property) => data.hasOwnProperty(property))
|
||||
.filter((property) => hasValue(data[property]))
|
||||
.forEach((property) => {
|
||||
if (isPaginatedResponse(data[property])) {
|
||||
const obj = this.process(data[property], requestHref);
|
||||
result[property] = flattenSingleKeyObject(obj);
|
||||
} else {
|
||||
result[property] = this.deserializeAndCache(data[property], requestHref);
|
||||
}
|
||||
});
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected deserializeAndCache(obj, requestHref: string): NormalizedObject[] {
|
||||
if (Array.isArray(obj)) {
|
||||
let result = [];
|
||||
obj.forEach((o) => result = [...result, ...this.deserializeAndCache(o, requestHref)])
|
||||
return result;
|
||||
}
|
||||
|
||||
const type: ResourceType = obj.type;
|
||||
if (hasValue(type)) {
|
||||
const normObjConstructor = NormalizedObjectFactory.getConstructor(type);
|
||||
|
||||
if (hasValue(normObjConstructor)) {
|
||||
const serializer = new DSpaceRESTv2Serializer(normObjConstructor);
|
||||
|
||||
let processed;
|
||||
if (isNotEmpty(obj._embedded)) {
|
||||
processed = this.process(obj._embedded, requestHref);
|
||||
}
|
||||
const normalizedObj = serializer.deserialize(obj);
|
||||
|
||||
if (isNotEmpty(processed)) {
|
||||
const linksOnly = {};
|
||||
Object.keys(processed).forEach((key) => {
|
||||
linksOnly[key] = processed[key].map((no: NormalizedObject) => no.self);
|
||||
});
|
||||
Object.assign(normalizedObj, linksOnly);
|
||||
}
|
||||
|
||||
this.addToObjectCache(normalizedObj, requestHref);
|
||||
return [normalizedObj];
|
||||
|
||||
} else {
|
||||
// TODO: move check to Validator?
|
||||
// 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)}`);
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
protected addToObjectCache(co: CacheableObject, requestHref: string): void {
|
||||
if (hasNoValue(co) || hasNoValue(co.self)) {
|
||||
throw new Error('The server returned an invalid object');
|
||||
}
|
||||
this.objectCache.add(co, this.EnvConfig.cache.msToLive, requestHref);
|
||||
}
|
||||
|
||||
protected processPageInfo(pageObj: any): PageInfo {
|
||||
if (isNotEmpty(pageObj)) {
|
||||
return new DSpaceRESTv2Serializer(PageInfo).deserialize(pageObj);
|
||||
} else {
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
/* tslint:enable:max-classes-per-file */
|
@@ -4,7 +4,6 @@ import { Store } from '@ngrx/store';
|
||||
|
||||
import { DataService } from './data.service';
|
||||
import { Item } from '../shared/item.model';
|
||||
import { ObjectCacheService } from '../cache/object-cache.service';
|
||||
import { ResponseCacheService } from '../cache/response-cache.service';
|
||||
import { CoreState } from '../core.reducers';
|
||||
import { NormalizedItem } from '../cache/models/normalized-item.model';
|
||||
@@ -14,11 +13,10 @@ import { GLOBAL_CONFIG, GlobalConfig } from '../../../config';
|
||||
|
||||
@Injectable()
|
||||
export class ItemDataService extends DataService<NormalizedItem, Item> {
|
||||
protected resourceEndpoint = '/core/items';
|
||||
protected linkName = 'items';
|
||||
protected browseEndpoint = '/discover/browses/dateissued/items';
|
||||
|
||||
constructor(
|
||||
protected objectCache: ObjectCacheService,
|
||||
protected responseCache: ResponseCacheService,
|
||||
protected requestService: RequestService,
|
||||
protected rdbService: RemoteDataBuildService,
|
||||
|
7
src/app/core/data/parsing.service.ts
Normal file
7
src/app/core/data/parsing.service.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
import { DSpaceRESTV2Response } from '../dspace-rest-v2/dspace-rest-v2-response.model';
|
||||
import { RestRequest } from './request.models';
|
||||
import { RestResponse } from '../cache/response-cache.models';
|
||||
|
||||
export interface ResponseParsingService {
|
||||
parse(request: RestRequest, data: DSpaceRESTV2Response): RestResponse;
|
||||
}
|
@@ -14,7 +14,7 @@ export enum RemoteDataState {
|
||||
*/
|
||||
export class RemoteData<T> {
|
||||
constructor(
|
||||
public self: string,
|
||||
public self: Observable<string>,
|
||||
private requestPending: Observable<boolean>,
|
||||
private responsePending: Observable<boolean>,
|
||||
private isSuccessFul: Observable<boolean>,
|
||||
|
@@ -1,7 +1,6 @@
|
||||
import { Action } from '@ngrx/store';
|
||||
import { type } from '../../shared/ngrx/type';
|
||||
import { CacheableObject } from '../cache/object-cache.reducer';
|
||||
import { Request } from './request.models';
|
||||
import { RestRequest } from './request.models';
|
||||
|
||||
/**
|
||||
* The list of RequestAction type definitions
|
||||
@@ -15,10 +14,10 @@ export const RequestActionTypes = {
|
||||
/* tslint:disable:max-classes-per-file */
|
||||
export class RequestConfigureAction implements Action {
|
||||
type = RequestActionTypes.CONFIGURE;
|
||||
payload: Request;
|
||||
payload: RestRequest;
|
||||
|
||||
constructor(
|
||||
request: Request
|
||||
request: RestRequest
|
||||
) {
|
||||
this.payload = request;
|
||||
}
|
||||
|
@@ -1,48 +1,18 @@
|
||||
import { Injectable, Inject } from '@angular/core';
|
||||
import { Inject, Injectable, Injector } from '@angular/core';
|
||||
import { Actions, Effect } from '@ngrx/effects';
|
||||
|
||||
// tslint:disable-next-line:import-blacklist
|
||||
import { Observable } from 'rxjs';
|
||||
|
||||
import { DSpaceRESTv2Service } from '../dspace-rest-v2/dspace-rest-v2.service';
|
||||
import { ObjectCacheService } from '../cache/object-cache.service';
|
||||
import { DSpaceRESTV2Response } from '../dspace-rest-v2/dspace-rest-v2-response.model';
|
||||
import { DSpaceRESTv2Serializer } from '../dspace-rest-v2/dspace-rest-v2.serializer';
|
||||
import { CacheableObject } from '../cache/object-cache.reducer';
|
||||
import { Response, SuccessResponse, ErrorResponse } from '../cache/response-cache.models';
|
||||
import { hasNoValue, hasValue, isEmpty, isNotEmpty } from '../../shared/empty.util';
|
||||
import { RequestEntry } from './request.reducer';
|
||||
import { RequestActionTypes, RequestExecuteAction, RequestCompleteAction } from './request.actions';
|
||||
import { GLOBAL_CONFIG, GlobalConfig } from '../../../config';
|
||||
import { ErrorResponse, RestResponse } from '../cache/response-cache.models';
|
||||
import { ResponseCacheService } from '../cache/response-cache.service';
|
||||
import { RequestService } from './request.service';
|
||||
import { NormalizedObjectFactory } from '../cache/models/normalized-object-factory';
|
||||
import { ResourceType } from '../shared/resource-type';
|
||||
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 { RequestError } from './request.models';
|
||||
import { PageInfo } from '../shared/page-info.model';
|
||||
import { NormalizedObject } from '../cache/models/normalized-object.model';
|
||||
|
||||
import { GlobalConfig, GLOBAL_CONFIG } from '../../../config';
|
||||
|
||||
function isObjectLevel(halObj: any) {
|
||||
return isNotEmpty(halObj._links) && hasValue(halObj._links.self);
|
||||
}
|
||||
|
||||
function isPaginatedResponse(halObj: any) {
|
||||
return isNotEmpty(halObj.page) && hasValue(halObj._embedded);
|
||||
}
|
||||
|
||||
function flattenSingleKeyObject(obj: any): any {
|
||||
const keys = Object.keys(obj);
|
||||
if (keys.length !== 1) {
|
||||
throw new Error(`Expected an object with a single key, got: ${JSON.stringify(obj)}`);
|
||||
}
|
||||
return obj[keys[0]];
|
||||
}
|
||||
|
||||
/* tslint:disable:max-classes-per-file */
|
||||
class ProcessRequestDTO {
|
||||
[key: string]: NormalizedObject[]
|
||||
}
|
||||
import { RequestEntry } from './request.reducer';
|
||||
import { RequestService } from './request.service';
|
||||
|
||||
@Injectable()
|
||||
export class RequestEffects {
|
||||
@@ -55,121 +25,23 @@ export class RequestEffects {
|
||||
})
|
||||
.flatMap((entry: RequestEntry) => {
|
||||
return this.restApi.get(entry.request.href)
|
||||
.map((data: DSpaceRESTV2Response) => {
|
||||
const processRequestDTO = this.process(data.payload, entry.request.href);
|
||||
const selfLinks = flattenSingleKeyObject(processRequestDTO).map((no) => no.self);
|
||||
return new SuccessResponse(selfLinks, data.statusCode, this.processPageInfo(data.payload.page))
|
||||
}).do((response: Response) => this.responseCache.add(entry.request.href, response, this.EnvConfig.cache.msToLive))
|
||||
.map((response: Response) => new RequestCompleteAction(entry.request.href))
|
||||
.map((data: DSpaceRESTV2Response) =>
|
||||
this.injector.get(entry.request.getResponseParser()).parse(entry.request, data))
|
||||
.do((response: RestResponse) => this.responseCache.add(entry.request.href, response, this.EnvConfig.cache.msToLive))
|
||||
.map((response: RestResponse) => new RequestCompleteAction(entry.request.href))
|
||||
.catch((error: RequestError) => Observable.of(new ErrorResponse(error))
|
||||
.do((response: Response) => this.responseCache.add(entry.request.href, response, this.EnvConfig.cache.msToLive))
|
||||
.map((response: Response) => new RequestCompleteAction(entry.request.href)));
|
||||
.do((response: RestResponse) => this.responseCache.add(entry.request.href, response, this.EnvConfig.cache.msToLive))
|
||||
.map((response: RestResponse) => new RequestCompleteAction(entry.request.href)));
|
||||
});
|
||||
|
||||
constructor(
|
||||
@Inject(GLOBAL_CONFIG) private EnvConfig: GlobalConfig,
|
||||
private actions$: Actions,
|
||||
private restApi: DSpaceRESTv2Service,
|
||||
private objectCache: ObjectCacheService,
|
||||
private injector: Injector,
|
||||
private responseCache: ResponseCacheService,
|
||||
protected requestService: RequestService
|
||||
) { }
|
||||
|
||||
protected process(data: any, requestHref: string): ProcessRequestDTO {
|
||||
|
||||
if (isNotEmpty(data)) {
|
||||
if (isPaginatedResponse(data)) {
|
||||
return this.process(data._embedded, requestHref);
|
||||
} else if (isObjectLevel(data)) {
|
||||
return { topLevel: this.deserializeAndCache(data, requestHref) };
|
||||
} else {
|
||||
const result = new ProcessRequestDTO();
|
||||
if (Array.isArray(data)) {
|
||||
result.topLevel = [];
|
||||
data.forEach((datum) => {
|
||||
if (isPaginatedResponse(datum)) {
|
||||
const obj = this.process(datum, requestHref);
|
||||
result.topLevel = [...result.topLevel, ...flattenSingleKeyObject(obj)];
|
||||
} else {
|
||||
result.topLevel = [...result.topLevel, ...this.deserializeAndCache(datum, requestHref)];
|
||||
}
|
||||
});
|
||||
} else {
|
||||
Object.keys(data)
|
||||
.filter((property) => data.hasOwnProperty(property))
|
||||
.filter((property) => hasValue(data[property]))
|
||||
.forEach((property) => {
|
||||
if (isPaginatedResponse(data[property])) {
|
||||
const obj = this.process(data[property], requestHref);
|
||||
result[property] = flattenSingleKeyObject(obj);
|
||||
} else {
|
||||
result[property] = this.deserializeAndCache(data[property], requestHref);
|
||||
}
|
||||
});
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected deserializeAndCache(obj, requestHref: string): NormalizedObject[] {
|
||||
if (Array.isArray(obj)) {
|
||||
let result = [];
|
||||
obj.forEach((o) => result = [...result, ...this.deserializeAndCache(o, requestHref)])
|
||||
return result;
|
||||
}
|
||||
|
||||
const type: ResourceType = obj.type;
|
||||
if (hasValue(type)) {
|
||||
const normObjConstructor = NormalizedObjectFactory.getConstructor(type);
|
||||
|
||||
if (hasValue(normObjConstructor)) {
|
||||
const serializer = new DSpaceRESTv2Serializer(normObjConstructor);
|
||||
|
||||
let processed;
|
||||
if (isNotEmpty(obj._embedded)) {
|
||||
processed = this.process(obj._embedded, requestHref);
|
||||
}
|
||||
const normalizedObj = serializer.deserialize(obj);
|
||||
|
||||
if (isNotEmpty(processed)) {
|
||||
const linksOnly = {};
|
||||
Object.keys(processed).forEach((key) => {
|
||||
linksOnly[key] = processed[key].map((no: NormalizedObject) => no.self);
|
||||
});
|
||||
Object.assign(normalizedObj, linksOnly);
|
||||
}
|
||||
|
||||
this.addToObjectCache(normalizedObj, requestHref);
|
||||
return [normalizedObj];
|
||||
|
||||
} else {
|
||||
// TODO: move check to Validator?
|
||||
// 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)}`);
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
protected addToObjectCache(co: CacheableObject, requestHref: string): void {
|
||||
if (hasNoValue(co) || hasNoValue(co.self)) {
|
||||
throw new Error('The server returned an invalid object');
|
||||
}
|
||||
this.objectCache.add(co, this.EnvConfig.cache.msToLive, requestHref);
|
||||
}
|
||||
|
||||
protected processPageInfo(pageObj: any): PageInfo {
|
||||
if (isNotEmpty(pageObj)) {
|
||||
return new DSpaceRESTv2Serializer(PageInfo).deserialize(pageObj);
|
||||
} else {
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
/* tslint:enable:max-classes-per-file */
|
||||
|
@@ -1,15 +1,23 @@
|
||||
import { SortOptions } from '../cache/models/sort-options.model';
|
||||
import { PaginationComponentOptions } from '../../shared/pagination/pagination-component-options.model';
|
||||
import { GenericConstructor } from '../shared/generic-constructor';
|
||||
import { GlobalConfig } from '../../../config/global-config.interface';
|
||||
import { RESTURLCombiner } from '../url-combiner/rest-url-combiner';
|
||||
import { DSOResponseParsingService } from './dso-response-parsing.service';
|
||||
import { ResponseParsingService } from './parsing.service';
|
||||
import { RootResponseParsingService } from './root-response-parsing.service';
|
||||
|
||||
/* tslint:disable:max-classes-per-file */
|
||||
export class Request {
|
||||
export class RestRequest {
|
||||
constructor(
|
||||
public href: string,
|
||||
) { }
|
||||
|
||||
getResponseParser(): GenericConstructor<ResponseParsingService> {
|
||||
return DSOResponseParsingService;
|
||||
}
|
||||
}
|
||||
|
||||
export class FindByIDRequest extends Request {
|
||||
export class FindByIDRequest extends RestRequest {
|
||||
constructor(
|
||||
href: string,
|
||||
public resourceID: string
|
||||
@@ -25,7 +33,7 @@ export class FindAllOptions {
|
||||
sort?: SortOptions;
|
||||
}
|
||||
|
||||
export class FindAllRequest extends Request {
|
||||
export class FindAllRequest extends RestRequest {
|
||||
constructor(
|
||||
href: string,
|
||||
public options?: FindAllOptions,
|
||||
@@ -34,6 +42,17 @@ export class FindAllRequest extends Request {
|
||||
}
|
||||
}
|
||||
|
||||
export class RootEndpointRequest extends RestRequest {
|
||||
constructor(EnvConfig: GlobalConfig) {
|
||||
const href = new RESTURLCombiner(EnvConfig, '/').toString();
|
||||
super(href);
|
||||
}
|
||||
|
||||
getResponseParser(): GenericConstructor<ResponseParsingService> {
|
||||
return RootResponseParsingService;
|
||||
}
|
||||
}
|
||||
|
||||
export class RequestError extends Error {
|
||||
statusText: string;
|
||||
}
|
||||
|
@@ -1,12 +1,11 @@
|
||||
import { CacheableObject } from '../cache/object-cache.reducer';
|
||||
import {
|
||||
RequestActionTypes, RequestAction, RequestConfigureAction,
|
||||
RequestExecuteAction, RequestCompleteAction
|
||||
} from './request.actions';
|
||||
import { Request } from './request.models';
|
||||
import { RestRequest } from './request.models';
|
||||
|
||||
export class RequestEntry {
|
||||
request: Request;
|
||||
request: RestRequest;
|
||||
requestPending: boolean;
|
||||
responsePending: boolean;
|
||||
completed: boolean;
|
||||
|
@@ -3,18 +3,18 @@ import { Injectable } from '@angular/core';
|
||||
import { MemoizedSelector, Store } from '@ngrx/store';
|
||||
|
||||
import { Observable } from 'rxjs/Observable';
|
||||
|
||||
import { RequestEntry } from './request.reducer';
|
||||
import { Request } from './request.models';
|
||||
import { hasValue } from '../../shared/empty.util';
|
||||
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 { ObjectCacheService } from '../cache/object-cache.service';
|
||||
import { DSOSuccessResponse } from '../cache/response-cache.models';
|
||||
import { ResponseCacheEntry } from '../cache/response-cache.reducer';
|
||||
import { SuccessResponse } from '../cache/response-cache.models';
|
||||
import { ResponseCacheService } from '../cache/response-cache.service';
|
||||
import { CoreState } from '../core.reducers';
|
||||
import { keySelector } from '../shared/selectors';
|
||||
import { RequestConfigureAction, RequestExecuteAction } from './request.actions';
|
||||
import { RestRequest } from './request.models';
|
||||
|
||||
import { RequestEntry } from './request.reducer';
|
||||
|
||||
function entryFromHrefSelector(href: string): MemoizedSelector<CoreState, RequestEntry> {
|
||||
return keySelector<RequestEntry>('data/request', href);
|
||||
@@ -45,18 +45,25 @@ export class RequestService {
|
||||
return this.store.select(entryFromHrefSelector(href));
|
||||
}
|
||||
|
||||
configure<T extends CacheableObject>(request: Request): void {
|
||||
configure<T extends CacheableObject>(request: RestRequest): void {
|
||||
let isCached = this.objectCache.hasBySelfLink(request.href);
|
||||
|
||||
if (!isCached && this.responseCache.has(request.href)) {
|
||||
// if it isn't cached it may be a list endpoint, if so verify
|
||||
// every object included in the response is still cached
|
||||
this.responseCache.get(request.href)
|
||||
const [dsoSuccessResponse, otherSuccessResponse] = this.responseCache.get(request.href)
|
||||
.take(1)
|
||||
.filter((entry: ResponseCacheEntry) => entry.response.isSuccessful)
|
||||
.map((entry: ResponseCacheEntry) => (entry.response as SuccessResponse).resourceSelfLinks)
|
||||
.map((resourceSelfLinks: string[]) => resourceSelfLinks.every((selfLink) => this.objectCache.hasBySelfLink(selfLink)))
|
||||
.subscribe((c) => isCached = c);
|
||||
.map((entry: ResponseCacheEntry) => entry.response)
|
||||
.share()
|
||||
.partition((response: DSOSuccessResponse) => hasValue(response.resourceSelfLinks));
|
||||
|
||||
Observable.merge(
|
||||
otherSuccessResponse.map(() => true),
|
||||
dsoSuccessResponse // a DSOSuccessResponse should only be considered cached if all its resources are cached
|
||||
.map((response: DSOSuccessResponse) => response.resourceSelfLinks)
|
||||
.map((resourceSelfLinks: string[]) => resourceSelfLinks
|
||||
.every((selfLink) => this.objectCache.hasBySelfLink(selfLink))
|
||||
)
|
||||
).subscribe((c) => isCached = c);
|
||||
}
|
||||
|
||||
const isPending = this.isPending(request.href);
|
||||
|
37
src/app/core/data/root-response-parsing.service.ts
Normal file
37
src/app/core/data/root-response-parsing.service.ts
Normal file
@@ -0,0 +1,37 @@
|
||||
import { Inject, Injectable } from '@angular/core';
|
||||
import { GLOBAL_CONFIG } from '../../../config';
|
||||
import { GlobalConfig } from '../../../config/global-config.interface';
|
||||
import { ErrorResponse, RestResponse, RootSuccessResponse } from '../cache/response-cache.models';
|
||||
import { DSpaceRESTV2Response } from '../dspace-rest-v2/dspace-rest-v2-response.model';
|
||||
import { ResponseParsingService } from './parsing.service';
|
||||
import { RestRequest } from './request.models';
|
||||
import { isNotEmpty } from '../../shared/empty.util';
|
||||
import { RESTURLCombiner } from '../url-combiner/rest-url-combiner';
|
||||
|
||||
@Injectable()
|
||||
export class RootResponseParsingService implements ResponseParsingService {
|
||||
constructor(
|
||||
@Inject(GLOBAL_CONFIG) private EnvConfig: GlobalConfig,
|
||||
) {
|
||||
}
|
||||
|
||||
parse(request: RestRequest, data: DSpaceRESTV2Response): RestResponse {
|
||||
if (isNotEmpty(data.payload) && isNotEmpty(data.payload._links)) {
|
||||
const links = data.payload._links;
|
||||
for (const link of Object.keys(links)) {
|
||||
let href = links[link].href;
|
||||
// TODO temporary workaround as these endpoint paths are relative, but should be absolute
|
||||
href = new RESTURLCombiner(this.EnvConfig, href.substring(this.EnvConfig.rest.nameSpace.length)).toString();
|
||||
links[link] = href;
|
||||
}
|
||||
return new RootSuccessResponse(links, data.statusCode);
|
||||
} else {
|
||||
return new ErrorResponse(
|
||||
Object.assign(
|
||||
new Error('Unexpected response from root endpoint'),
|
||||
{ statusText: data.statusCode }
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
@@ -78,7 +78,7 @@ export class SearchService {
|
||||
});
|
||||
|
||||
return new RemoteData(
|
||||
self,
|
||||
Observable.of(self),
|
||||
requestPending,
|
||||
responsePending,
|
||||
isSuccessFul,
|
||||
|
Reference in New Issue
Block a user