diff --git a/src/app/core/cache/builders/build-decorators.spec.ts b/src/app/core/cache/builders/build-decorators.spec.ts index 064a2b3f83..e397ca6516 100644 --- a/src/app/core/cache/builders/build-decorators.spec.ts +++ b/src/app/core/cache/builders/build-decorators.spec.ts @@ -2,12 +2,21 @@ import { HALLink } from '../../shared/hal-link.model'; import { HALResource } from '../../shared/hal-resource.model'; import { ResourceType } from '../../shared/resource-type'; -import { dataService, getDataServiceFor, getLinkDefinition, link, } from './build-decorators'; +import { dataService, getDataServiceFor, getLinkDefinition, link } from './build-decorators'; +import { HALDataService } from '../../data/base/hal-data-service.interface'; +import { BaseDataService } from '../../data/base/base-data.service'; -class TestService { +class TestService extends BaseDataService { } -class AnotherTestService { +class AnotherTestService implements HALDataService { + public findAllByHref(href$, findListOptions, useCachedVersionIfAvailable, reRequestOnStale, ...linksToFollow): any { + return undefined; + } + + public findByHref(href$, useCachedVersionIfAvailable, reRequestOnStale, ...linksToFollow): any { + return undefined; + } } class TestHALResource implements HALResource { diff --git a/src/app/core/cache/builders/build-decorators.ts b/src/app/core/cache/builders/build-decorators.ts index 193eeb57e8..b421c927b3 100644 --- a/src/app/core/cache/builders/build-decorators.ts +++ b/src/app/core/cache/builders/build-decorators.ts @@ -3,20 +3,19 @@ import { hasNoValue, hasValue } from '../../../shared/empty.util'; import { GenericConstructor } from '../../shared/generic-constructor'; import { HALResource } from '../../shared/hal-resource.model'; import { ResourceType } from '../../shared/resource-type'; -import { - getResourceTypeValueFor -} from '../object-cache.reducer'; +import { getResourceTypeValueFor } from '../object-cache.reducer'; import { InjectionToken } from '@angular/core'; import { CacheableObject } from '../cacheable-object.model'; import { TypedObject } from '../typed-object.model'; +import { HALDataService } from '../../data/base/hal-data-service.interface'; -export const DATA_SERVICE_FACTORY = new InjectionToken<(resourceType: ResourceType) => GenericConstructor>('getDataServiceFor', { +export const DATA_SERVICE_FACTORY = new InjectionToken<(resourceType: ResourceType) => GenericConstructor>>('getDataServiceFor', { providedIn: 'root', - factory: () => getDataServiceFor + factory: () => getDataServiceFor, }); export const LINK_DEFINITION_FACTORY = new InjectionToken<(source: GenericConstructor, linkName: keyof T['_links']) => LinkDefinition>('getLinkDefinition', { providedIn: 'root', - factory: () => getLinkDefinition + factory: () => getLinkDefinition, }); export const LINK_DEFINITION_MAP_FACTORY = new InjectionToken<(source: GenericConstructor) => Map>>('getLinkDefinitions', { providedIn: 'root', @@ -47,16 +46,15 @@ export function getClassForType(type: string | ResourceType) { } /** - * A class decorator to indicate that this class is a dataservice - * for a given resource type. + * A class decorator to indicate that this class is a data service for a given HAL resource type. * - * "dataservice" in this context means that it has findByHref and - * findAllByHref methods. + * In most cases, a data service should extend {@link BaseDataService}. + * At the very least it must implement {@link HALDataService} in order for it to work with {@link LinkService}. * * @param resourceType the resource type the class is a dataservice for */ -export function dataService(resourceType: ResourceType): any { - return (target: any) => { +export function dataService(resourceType: ResourceType) { + return (target: GenericConstructor>): void => { if (hasNoValue(resourceType)) { throw new Error(`Invalid @dataService annotation on ${target}, resourceType needs to be defined`); } @@ -75,7 +73,7 @@ export function dataService(resourceType: ResourceType): any { * * @param resourceType the resource type you want the matching dataservice for */ -export function getDataServiceFor(resourceType: ResourceType) { +export function getDataServiceFor(resourceType: ResourceType): GenericConstructor> { return dataServiceMap.get(resourceType.value); } diff --git a/src/app/core/cache/builders/link.service.ts b/src/app/core/cache/builders/link.service.ts index af616332c0..8d54d46d2d 100644 --- a/src/app/core/cache/builders/link.service.ts +++ b/src/app/core/cache/builders/link.service.ts @@ -7,24 +7,26 @@ import { DATA_SERVICE_FACTORY, LINK_DEFINITION_FACTORY, LINK_DEFINITION_MAP_FACTORY, - LinkDefinition + LinkDefinition, } from './build-decorators'; import { RemoteData } from '../../data/remote-data'; import { EMPTY, Observable } from 'rxjs'; import { ResourceType } from '../../shared/resource-type'; +import { HALDataService } from '../../data/base/hal-data-service.interface'; +import { PaginatedList } from '../../data/paginated-list.model'; /** * A Service to handle the resolving and removing * of resolved {@link HALLink}s on HALResources */ @Injectable({ - providedIn: 'root' + providedIn: 'root', }) export class LinkService { constructor( protected parentInjector: Injector, - @Inject(DATA_SERVICE_FACTORY) private getDataServiceFor: (resourceType: ResourceType) => GenericConstructor, + @Inject(DATA_SERVICE_FACTORY) private getDataServiceFor: (resourceType: ResourceType) => GenericConstructor>, @Inject(LINK_DEFINITION_FACTORY) private getLinkDefinition: (source: GenericConstructor, linkName: keyof T['_links']) => LinkDefinition, @Inject(LINK_DEFINITION_MAP_FACTORY) private getLinkDefinitions: (source: GenericConstructor) => Map>, ) { @@ -51,7 +53,7 @@ export class LinkService { * @param model the {@link HALResource} to resolve the link for * @param linkToFollow the {@link FollowLinkConfig} to resolve */ - public resolveLinkWithoutAttaching(model, linkToFollow: FollowLinkConfig): Observable> { + public resolveLinkWithoutAttaching(model, linkToFollow: FollowLinkConfig): Observable>> { const matchingLinkDef = this.getLinkDefinition(model.constructor, linkToFollow.name); if (hasValue(matchingLinkDef)) { @@ -61,9 +63,9 @@ export class LinkService { throw new Error(`The @link() for ${linkToFollow.name} on ${model.constructor.name} models uses the resource type ${matchingLinkDef.resourceType.value.toUpperCase()}, but there is no service with an @dataService(${matchingLinkDef.resourceType.value.toUpperCase()}) annotation in order to retrieve it`); } - const service = Injector.create({ + const service: HALDataService = Injector.create({ providers: [], - parent: this.parentInjector + parent: this.parentInjector, }).get(provider); const link = model._links[matchingLinkDef.linkName]; diff --git a/src/app/core/data/base/base-data.service.ts b/src/app/core/data/base/base-data.service.ts index 9679bddf1b..faf8a4bead 100644 --- a/src/app/core/data/base/base-data.service.ts +++ b/src/app/core/data/base/base-data.service.ts @@ -22,6 +22,7 @@ import { FindListOptions } from '../find-list-options.model'; import { PaginatedList } from '../paginated-list.model'; import { ObjectCacheEntry } from '../../cache/object-cache.reducer'; import { ObjectCacheService } from '../../cache/object-cache.service'; +import { HALDataService } from './hal-data-service.interface'; /** * Common functionality for data services. @@ -47,7 +48,7 @@ import { ObjectCacheService } from '../../cache/object-cache.service'; * } * ``` */ -export class BaseDataService { +export class BaseDataService implements HALDataService { constructor( protected linkPath: string, protected requestService: RequestService, diff --git a/src/app/core/data/base/hal-data-service.interface.ts b/src/app/core/data/base/hal-data-service.interface.ts new file mode 100644 index 0000000000..af39edd7cf --- /dev/null +++ b/src/app/core/data/base/hal-data-service.interface.ts @@ -0,0 +1,41 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ +import { Observable } from 'rxjs'; +import { FollowLinkConfig } from '../../../shared/utils/follow-link-config.model'; +import { RemoteData } from '../remote-data'; +import { FindListOptions } from '../find-list-options.model'; +import { PaginatedList } from '../paginated-list.model'; +import { HALResource } from '../../shared/hal-resource.model'; + +/** + * An interface defining the minimum functionality needed for a data service to resolve HAL resources. + */ +export interface HALDataService { + /** + * Returns an Observable of {@link RemoteData} of an object, based on an href, + * with a list of {@link FollowLinkConfig}, to automatically resolve {@link HALLink}s of the object + * + * @param href$ The url of object we want to retrieve. Can be a string or an Observable + * @param useCachedVersionIfAvailable If this is true, the request will only be sent if there's no valid cached version. + * @param reRequestOnStale Whether or not the request should automatically be re-requested after the response becomes stale + * @param linksToFollow List of {@link FollowLinkConfig} that indicate which {@link HALLink}s should be automatically resolved + */ + findByHref(href$: string | Observable, useCachedVersionIfAvailable?: boolean, reRequestOnStale?: boolean, ...linksToFollow: FollowLinkConfig[]): Observable>; + + /** + * Returns an Observable of a {@link RemoteData} of a {@link PaginatedList} of objects, based on an href, + * with a list of {@link FollowLinkConfig}, to automatically resolve {@link HALLink}s of the object + * + * @param href$ The url of list we want to retrieve. Can be a string or an Observable + * @param findListOptions The options for to use for this find list request. + * @param useCachedVersionIfAvailable If this is true, the request will only be sent if there's no valid cached version. + * @param reRequestOnStale Whether or not the request should automatically be re-requested after the response becomes stale + * @param linksToFollow List of {@link FollowLinkConfig} that indicate which {@link HALLink}s should be automatically resolved + */ + findAllByHref(href$: string | Observable, findListOptions?: FindListOptions, useCachedVersionIfAvailable?: boolean, reRequestOnStale?: boolean, ...linksToFollow: FollowLinkConfig[]): Observable>>; +} diff --git a/src/app/core/data/href-only-data.service.ts b/src/app/core/data/href-only-data.service.ts index ba5abe9340..9a36dcc128 100644 --- a/src/app/core/data/href-only-data.service.ts +++ b/src/app/core/data/href-only-data.service.ts @@ -14,6 +14,7 @@ import { LICENSE } from '../shared/license.resource-type'; import { CacheableObject } from '../cache/cacheable-object.model'; import { FindListOptions } from './find-list-options.model'; import { BaseDataService } from './base/base-data.service'; +import { HALDataService } from './base/hal-data-service.interface'; /** * A DataService with only findByHref methods. Its purpose is to be used for resources that don't @@ -21,6 +22,15 @@ import { BaseDataService } from './base/base-data.service'; * for their links to be resolved by the LinkService. * * an @dataService annotation can be added for any number of these resource types + * + * + * Additionally, this service may be used to retrieve objects by `href` regardless of their type + * For example + * ``` + * const items$: Observable>> = hrefOnlyDataService.findAllByHref(href); + * const sites$: Observable>> = hrefOnlyDataService.findAllByHref(href); + * ``` + * This means we cannot extend from {@link BaseDataService} directly because the method signatures would not match. */ @Injectable({ providedIn: 'root', @@ -28,9 +38,10 @@ import { BaseDataService } from './base/base-data.service'; @dataService(VOCABULARY_ENTRY) @dataService(ITEM_TYPE) @dataService(LICENSE) -export class HrefOnlyDataService { +export class HrefOnlyDataService implements HALDataService { /** - * Not all BaseDataService methods should be exposed, so + * Works with a {@link BaseDataService} internally, but only exposes two of its methods + * with altered signatures to (optionally) constrain the arbitrary return type. * @private */ private dataService: BaseDataService;