Files
dspace-angular/src/app/core/cache/builders/link.service.ts
2021-11-18 09:09:00 -06:00

121 lines
5.1 KiB
TypeScript

import { Inject, Injectable, Injector } from '@angular/core';
import { hasNoValue, hasValue, isNotEmpty } from '../../../shared/empty.util';
import { FollowLinkConfig } from '../../../shared/utils/follow-link-config.model';
import { GenericConstructor } from '../../shared/generic-constructor';
import { HALResource } from '../../shared/hal-resource.model';
import {
DATA_SERVICE_FACTORY,
LINK_DEFINITION_FACTORY,
LINK_DEFINITION_MAP_FACTORY,
LinkDefinition
} from './build-decorators';
import { RemoteData } from '../../data/remote-data';
import { EMPTY, Observable } from 'rxjs';
import { ResourceType } from '../../shared/resource-type';
/**
* A Service to handle the resolving and removing
* of resolved {@link HALLink}s on HALResources
*/
@Injectable({
providedIn: 'root'
})
export class LinkService {
constructor(
protected parentInjector: Injector,
@Inject(DATA_SERVICE_FACTORY) private getDataServiceFor: (resourceType: ResourceType) => GenericConstructor<any>,
@Inject(LINK_DEFINITION_FACTORY) private getLinkDefinition: <T extends HALResource>(source: GenericConstructor<T>, linkName: keyof T['_links']) => LinkDefinition<T>,
@Inject(LINK_DEFINITION_MAP_FACTORY) private getLinkDefinitions: <T extends HALResource>(source: GenericConstructor<T>) => Map<keyof T['_links'], LinkDefinition<T>>,
) {
}
/**
* Resolve the given {@link FollowLinkConfig}s for the given model
*
* @param model the {@link HALResource} to resolve the links for
* @param linksToFollow the {@link FollowLinkConfig}s to resolve
*/
public resolveLinks<T extends HALResource>(model: T, ...linksToFollow: FollowLinkConfig<T>[]): T {
linksToFollow.forEach((linkToFollow: FollowLinkConfig<T>) => {
this.resolveLink(model, linkToFollow);
});
return model;
}
/**
* Resolve the given {@link FollowLinkConfig} for the given model and return the result. This does
* not attach the link result to the property on the model. Useful when you're working with a
* readonly object
*
* @param model the {@link HALResource} to resolve the link for
* @param linkToFollow the {@link FollowLinkConfig} to resolve
*/
public resolveLinkWithoutAttaching<T extends HALResource, U extends HALResource>(model, linkToFollow: FollowLinkConfig<T>): Observable<RemoteData<U>> {
const matchingLinkDef = this.getLinkDefinition(model.constructor, linkToFollow.name);
if (hasValue(matchingLinkDef)) {
const provider = this.getDataServiceFor(matchingLinkDef.resourceType);
if (hasNoValue(provider)) {
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({
providers: [],
parent: this.parentInjector
}).get(provider);
const link = model._links[matchingLinkDef.linkName];
if (hasValue(link)) {
const href = link.href;
try {
if (matchingLinkDef.isList) {
return service.findAllByHref(href, linkToFollow.findListOptions, linkToFollow.useCachedVersionIfAvailable, linkToFollow.reRequestOnStale, ...linkToFollow.linksToFollow);
} else {
return service.findByHref(href, linkToFollow.useCachedVersionIfAvailable, linkToFollow.reRequestOnStale, ...linkToFollow.linksToFollow);
}
} catch (e) {
console.error(`Something went wrong when using @dataService(${matchingLinkDef.resourceType.value}) ${hasValue(service) ? '' : '(undefined) '}to resolve link ${linkToFollow.name} at ${href}`);
throw e;
}
}
} else if (!linkToFollow.isOptional) {
throw new Error(`followLink('${linkToFollow.name}') was used as a required link for a ${model.constructor.name}, but there is no property on ${model.constructor.name} models with an @link() for ${linkToFollow.name}`);
}
return EMPTY;
}
/**
* Resolve the given {@link FollowLinkConfig} for the given model and return the model with the
* link property attached.
*
* @param model the {@link HALResource} to resolve the link for
* @param linkToFollow the {@link FollowLinkConfig} to resolve
*/
public resolveLink<T extends HALResource>(model, linkToFollow: FollowLinkConfig<T>): T {
model[linkToFollow.name] = this.resolveLinkWithoutAttaching(model, linkToFollow);
return model;
}
/**
* Remove any resolved links that the model may have.
*
* @param model the {@link HALResource} to remove the links from
* @returns a copy of the given model, without resolved links.
*/
public removeResolvedLinks<T extends HALResource>(model: T): T {
const result = Object.assign(new (model.constructor as GenericConstructor<T>)(), model);
const linkDefs = this.getLinkDefinitions(model.constructor as GenericConstructor<T>);
if (isNotEmpty(linkDefs)) {
linkDefs.forEach((linkDef: LinkDefinition<T>) => {
result[linkDef.propertyName] = undefined;
});
}
return result;
}
}