diff --git a/src/storage/RoutingResourceStore.ts b/src/storage/RoutingResourceStore.ts new file mode 100644 index 000000000..ca31c6e17 --- /dev/null +++ b/src/storage/RoutingResourceStore.ts @@ -0,0 +1,45 @@ +import type { Patch } from '../ldp/http/Patch'; +import type { Representation } from '../ldp/representation/Representation'; +import type { RepresentationPreferences } from '../ldp/representation/RepresentationPreferences'; +import type { ResourceIdentifier } from '../ldp/representation/ResourceIdentifier'; +import type { Conditions } from './Conditions'; +import type { ResourceStore } from './ResourceStore'; +import type { RouterRule } from './routing/RouterRule'; + +/** + * Store that calls a specific store based on certain routing defined by the ResourceRouter. + */ +export class RoutingResourceStore implements ResourceStore { + private readonly rule: RouterRule; + + public constructor(rule: RouterRule) { + this.rule = rule; + } + + public async getRepresentation(identifier: ResourceIdentifier, preferences: RepresentationPreferences, + conditions?: Conditions): Promise { + return (await this.rule.getMatchingResourceStore(identifier)) + .getRepresentation(identifier, preferences, conditions); + } + + public async addResource(container: ResourceIdentifier, representation: Representation, + conditions?: Conditions): Promise { + return (await this.rule.getMatchingResourceStore(container, representation)) + .addResource(container, representation, conditions); + } + + public async setRepresentation(identifier: ResourceIdentifier, representation: Representation, + conditions?: Conditions): Promise { + return (await this.rule.getMatchingResourceStore(identifier, representation)) + .setRepresentation(identifier, representation, conditions); + } + + public async deleteResource(identifier: ResourceIdentifier, conditions?: Conditions): Promise { + return (await this.rule.getMatchingResourceStore(identifier)).deleteResource(identifier, conditions); + } + + public async modifyResource(identifier: ResourceIdentifier, patch: Patch, conditions?: Conditions): + Promise { + return (await this.rule.getMatchingResourceStore(identifier)).modifyResource(identifier, patch, conditions); + } +} diff --git a/src/storage/routing/PathRouterRule.ts b/src/storage/routing/PathRouterRule.ts new file mode 100644 index 000000000..4eafa09cb --- /dev/null +++ b/src/storage/routing/PathRouterRule.ts @@ -0,0 +1,35 @@ +import type { Representation } from '../../ldp/representation/Representation'; +import type { ResourceIdentifier } from '../../ldp/representation/ResourceIdentifier'; +import { NotFoundHttpError } from '../../util/errors/NotFoundHttpError'; +import { UnsupportedHttpError } from '../../util/errors/UnsupportedHttpError'; +import type { ResourceStore } from '../ResourceStore'; +import type { RouterRule } from './RouterRule'; + +// TODO: +export class PathRouterRule implements RouterRule { + private readonly pathMap: { [path: string]: ResourceStore }; + + public constructor(pathMap: { [path: string]: ResourceStore }) { + this.pathMap = pathMap; + } + + public async getMatchingResourceStore(identifier: ResourceIdentifier, representation?: Representation): + Promise { + const paths = Object.keys(this.pathMap); + const matches = paths.filter((path): boolean => identifier.path.includes(path)); + if (matches.length !== 1) { + // Incoming data, need to reject + if (representation) { + throw new UnsupportedHttpError( + `Identifiers need to have exactly 1 of the following in them: [${paths.join(', ')}]`, + ); + + // Because of the above requirement, we know this will always be a 404 for requests + } else { + throw new NotFoundHttpError(); + } + } + + return this.pathMap[matches[0]]; + } +} diff --git a/src/storage/routing/RdfConvertingRouterRule.ts b/src/storage/routing/RdfConvertingRouterRule.ts new file mode 100644 index 000000000..cf3a363a3 --- /dev/null +++ b/src/storage/routing/RdfConvertingRouterRule.ts @@ -0,0 +1,42 @@ +import type { Representation } from '../../ldp/representation/Representation'; +import type { ResourceIdentifier } from '../../ldp/representation/ResourceIdentifier'; +import { INTERNAL_QUADS } from '../../util/ContentTypes'; +import type { RepresentationConverter } from '../conversion/RepresentationConverter'; +import type { ResourceStore } from '../ResourceStore'; +import type { RouterRule } from './RouterRule'; + +// TODO: +export class RdfConvertingRouterRule implements RouterRule { + private readonly rdfStore: ResourceStore; + private readonly binaryStore: ResourceStore; + private readonly converter: RepresentationConverter; + + public constructor(rdfStore: ResourceStore, binaryStore: ResourceStore, converter: RepresentationConverter) { + this.rdfStore = rdfStore; + this.binaryStore = binaryStore; + this.converter = converter; + } + + public async getMatchingResourceStore(identifier: ResourceIdentifier, representation?: Representation): + Promise { + if (representation) { + try { + const preferences = { type: [{ value: INTERNAL_QUADS, weight: 1 }]}; + await this.converter.canHandle({ identifier, representation, preferences }); + return this.rdfStore; + } catch { + return this.binaryStore; + } + } else { + // No content-type given so we can only check if one of the stores has data for the identifier + // Any of the two stores can be used. Using the binary one here since that one would be faster in current cases. + try { + const response = await this.binaryStore.getRepresentation(identifier, {}); + response.data.destroy(); + return this.binaryStore; + } catch { + return this.rdfStore; + } + } + } +} diff --git a/src/storage/routing/RouterRule.ts b/src/storage/routing/RouterRule.ts new file mode 100644 index 000000000..88ae4b841 --- /dev/null +++ b/src/storage/routing/RouterRule.ts @@ -0,0 +1,20 @@ +import type { Representation } from '../../ldp/representation/Representation'; +import type { ResourceIdentifier } from '../../ldp/representation/ResourceIdentifier'; +import type { ResourceStore } from '../ResourceStore'; + +/** + * A RouterRule represents a rule that decides which instance of a + * ResourceStore should be used to handle the incoming request. + */ +export interface RouterRule { + + /** + * Find the appropriate ResourceStore to which the request should be routed based on the incoming parameters. + * @param identifier - Incoming ResourceIdentifier. + * @param representation - Optional incoming Representation. + */ + getMatchingResourceStore: ( + identifier: ResourceIdentifier, + representation?: Representation, + ) => Promise; +}