From 26ab3f60714a9bb142da5102e40111bf92d217ce Mon Sep 17 00:00:00 2001 From: Eric Prud'hommeaux Date: Wed, 24 Nov 2021 00:27:23 +0100 Subject: [PATCH 01/15] ~ javatots first pass --- .../client-core/src/ShapeTreeClient.ts | 136 +++++ .../packages/client-http/src/HttpClient.ts | 29 + .../client-http/src/HttpClientFactory.ts | 19 + .../src/HttpClientFactoryManager.ts | 21 + .../packages/client-http/src/HttpRequest.ts | 24 + .../client-http/src/HttpResourceAccessor.ts | 531 ++++++++++++++++++ .../client-http/src/HttpShapeTreeClient.ts | 249 ++++++++ .../packages/core/src/DocumentResponse.ts | 40 ++ .../packages/core/src/InstanceResource.ts | 94 ++++ .../packages/core/src/ManageableInstance.ts | 116 ++++ .../packages/core/src/ManageableResource.ts | 61 ++ .../packages/core/src/ManagedResource.ts | 22 + .../packages/core/src/ManagerResource.ts | 56 ++ .../core/src/MissingManageableResource.ts | 27 + .../core/src/MissingManagerResource.ts | 35 ++ .../packages/core/src/ResourceAccessor.ts | 113 ++++ .../packages/core/src/ResourceAttributes.ts | 195 +++++++ asTypescript/packages/core/src/SchemaCache.ts | 72 +++ asTypescript/packages/core/src/ShapeTree.ts | 295 ++++++++++ .../packages/core/src/ShapeTreeAssignment.ts | 131 +++++ .../packages/core/src/ShapeTreeContext.ts | 13 + .../packages/core/src/ShapeTreeFactory.ts | 212 +++++++ .../packages/core/src/ShapeTreeManager.ts | 224 ++++++++ .../core/src/ShapeTreeManagerDelta.ts | 120 ++++ .../packages/core/src/ShapeTreeReference.ts | 48 ++ .../packages/core/src/ShapeTreeRequest.ts | 27 + .../core/src/ShapeTreeRequestHandler.ts | 437 ++++++++++++++ .../packages/core/src/ShapeTreeResource.ts | 90 +++ .../packages/core/src/UnmanagedResource.ts | 22 + .../packages/core/src/ValidationResult.ts | 101 ++++ .../ResourceTypeAssignmentPriority.ts | 15 + .../comparators/ShapeTreeContainsPriority.ts | 32 ++ .../contentloaders/DocumentLoaderManager.ts | 31 + .../contentloaders/ExternalDocumentLoader.ts | 20 + .../HttpExternalDocumentLoader.ts | 38 ++ .../packages/core/src/enums/HttpHeaders.ts | 22 + .../packages/core/src/enums/LinkRelations.ts | 15 + .../core/src/enums/RecursionMethods.ts | 5 + .../core/src/enums/ShapeTreeResourceType.ts | 17 + .../core/src/exceptions/ShapeTreeException.ts | 21 + .../packages/core/src/helpers/GraphHelper.ts | 192 +++++++ .../core/src/helpers/RequestHelper.ts | 222 ++++++++ .../AbstractValidatingMethodHandler.ts | 22 + .../ValidatingDeleteMethodHandler.ts | 31 + .../methodhandlers/ValidatingMethodHandler.ts | 10 + .../ValidatingPatchMethodHandler.ts | 50 ++ .../ValidatingPostMethodHandler.ts | 36 ++ .../ValidatingPutMethodHandler.ts | 44 ++ .../core/src/vocabularies/LdpVocabulary.ts | 12 + .../core/src/vocabularies/Namespaces.ts | 12 + .../core/src/vocabularies/RdfVocabulary.ts | 9 + .../src/vocabularies/ShapeTreeVocabulary.ts | 40 ++ .../packages/javahttp/src/JavaHttpClient.ts | 147 +++++ .../javahttp/src/JavaHttpClientFactory.ts | 61 ++ .../JavaHttpValidatingShapeTreeInterceptor.ts | 201 +++++++ 55 files changed, 4865 insertions(+) create mode 100644 asTypescript/packages/client-core/src/ShapeTreeClient.ts create mode 100644 asTypescript/packages/client-http/src/HttpClient.ts create mode 100644 asTypescript/packages/client-http/src/HttpClientFactory.ts create mode 100644 asTypescript/packages/client-http/src/HttpClientFactoryManager.ts create mode 100644 asTypescript/packages/client-http/src/HttpRequest.ts create mode 100644 asTypescript/packages/client-http/src/HttpResourceAccessor.ts create mode 100644 asTypescript/packages/client-http/src/HttpShapeTreeClient.ts create mode 100644 asTypescript/packages/core/src/DocumentResponse.ts create mode 100644 asTypescript/packages/core/src/InstanceResource.ts create mode 100644 asTypescript/packages/core/src/ManageableInstance.ts create mode 100644 asTypescript/packages/core/src/ManageableResource.ts create mode 100644 asTypescript/packages/core/src/ManagedResource.ts create mode 100644 asTypescript/packages/core/src/ManagerResource.ts create mode 100644 asTypescript/packages/core/src/MissingManageableResource.ts create mode 100644 asTypescript/packages/core/src/MissingManagerResource.ts create mode 100644 asTypescript/packages/core/src/ResourceAccessor.ts create mode 100644 asTypescript/packages/core/src/ResourceAttributes.ts create mode 100644 asTypescript/packages/core/src/SchemaCache.ts create mode 100644 asTypescript/packages/core/src/ShapeTree.ts create mode 100644 asTypescript/packages/core/src/ShapeTreeAssignment.ts create mode 100644 asTypescript/packages/core/src/ShapeTreeContext.ts create mode 100644 asTypescript/packages/core/src/ShapeTreeFactory.ts create mode 100644 asTypescript/packages/core/src/ShapeTreeManager.ts create mode 100644 asTypescript/packages/core/src/ShapeTreeManagerDelta.ts create mode 100644 asTypescript/packages/core/src/ShapeTreeReference.ts create mode 100644 asTypescript/packages/core/src/ShapeTreeRequest.ts create mode 100644 asTypescript/packages/core/src/ShapeTreeRequestHandler.ts create mode 100644 asTypescript/packages/core/src/ShapeTreeResource.ts create mode 100644 asTypescript/packages/core/src/UnmanagedResource.ts create mode 100644 asTypescript/packages/core/src/ValidationResult.ts create mode 100644 asTypescript/packages/core/src/comparators/ResourceTypeAssignmentPriority.ts create mode 100644 asTypescript/packages/core/src/comparators/ShapeTreeContainsPriority.ts create mode 100644 asTypescript/packages/core/src/contentloaders/DocumentLoaderManager.ts create mode 100644 asTypescript/packages/core/src/contentloaders/ExternalDocumentLoader.ts create mode 100644 asTypescript/packages/core/src/contentloaders/HttpExternalDocumentLoader.ts create mode 100644 asTypescript/packages/core/src/enums/HttpHeaders.ts create mode 100644 asTypescript/packages/core/src/enums/LinkRelations.ts create mode 100644 asTypescript/packages/core/src/enums/RecursionMethods.ts create mode 100644 asTypescript/packages/core/src/enums/ShapeTreeResourceType.ts create mode 100644 asTypescript/packages/core/src/exceptions/ShapeTreeException.ts create mode 100644 asTypescript/packages/core/src/helpers/GraphHelper.ts create mode 100644 asTypescript/packages/core/src/helpers/RequestHelper.ts create mode 100644 asTypescript/packages/core/src/methodhandlers/AbstractValidatingMethodHandler.ts create mode 100644 asTypescript/packages/core/src/methodhandlers/ValidatingDeleteMethodHandler.ts create mode 100644 asTypescript/packages/core/src/methodhandlers/ValidatingMethodHandler.ts create mode 100644 asTypescript/packages/core/src/methodhandlers/ValidatingPatchMethodHandler.ts create mode 100644 asTypescript/packages/core/src/methodhandlers/ValidatingPostMethodHandler.ts create mode 100644 asTypescript/packages/core/src/methodhandlers/ValidatingPutMethodHandler.ts create mode 100644 asTypescript/packages/core/src/vocabularies/LdpVocabulary.ts create mode 100644 asTypescript/packages/core/src/vocabularies/Namespaces.ts create mode 100644 asTypescript/packages/core/src/vocabularies/RdfVocabulary.ts create mode 100644 asTypescript/packages/core/src/vocabularies/ShapeTreeVocabulary.ts create mode 100644 asTypescript/packages/javahttp/src/JavaHttpClient.ts create mode 100644 asTypescript/packages/javahttp/src/JavaHttpClientFactory.ts create mode 100644 asTypescript/packages/javahttp/src/JavaHttpValidatingShapeTreeInterceptor.ts diff --git a/asTypescript/packages/client-core/src/ShapeTreeClient.ts b/asTypescript/packages/client-core/src/ShapeTreeClient.ts new file mode 100644 index 00000000..f4b4ca20 --- /dev/null +++ b/asTypescript/packages/client-core/src/ShapeTreeClient.ts @@ -0,0 +1,136 @@ +// Corresponding shapetrees-java package: com.janeirodigital.shapetrees.client.core +import { DocumentResponse } from '@shapetrees/ocumentResponse'; +import { ShapeTreeContext } from '@shapetrees/hapeTreeContext'; +import { ShapeTreeManager } from '@shapetrees/hapeTreeManager'; +import { ShapeTreeException } from '@shapetrees/xceptions/ShapeTreeException'; +import * as URL from 'java/net'; +import * as Optional from 'java/util'; + +/** + * This interface defines a proposed API to be used for any client-side implementations of + * a shape tree client + */ +export interface ShapeTreeClient { + + /** + * Shape Trees, §4.1: This operation is used by a client-side agent to discover any shape trees associated + * with a given resource. If URL is a managed resource, the associated Shape Tree Manager will be returned. + * + * https://shapetrees.org/TR/specification/#discover + * + * @param context ShapeTreeContext that would be used for authentication purposes + * @param targetResource The URL of the target resource for shape tree discovery + * @return A ShapeTreeManager associated with targetResource + * @throws ShapeTreeException ShapeTreeException + */ + discoverShapeTree(context: ShapeTreeContext, targetResource: URL): Optional /* throws ShapeTreeException */; + + /** + * Shape Trees, §4.2: This operation marks an existing resource as being managed by one or more shape trees, + * by associating a shape tree manager with the resource, and turning it into a managed resource. + * + * If the resource is already managed, the associated shape tree manager will be updated with another + * shape tree assignment for the planted shape tree. + * + * If the resource is a container that already contains existing resources, this operation will + * perform a depth first traversal through the containment hierarchy, validating + * and assigning as it works its way back up to the target resource of this operation. + * + * https://shapetrees.org/TR/specification/#plant-shapetree + * + * Plants one or more shape trees at a given container + * @param context ShapeTreeContext that would be used for authentication purposes + * @param targetResource The URL of the resource to plant on + * @param targetShapeTree A URL representing the shape tree to plant for targetResource + * @param focusNode An optional URL representing the target subject within targetResource used for shape validation + * @return DocumentResponse containing status and response headers/attributes + * @throws ShapeTreeException ShapeTreeException + */ + plantShapeTree(context: ShapeTreeContext, targetResource: URL, targetShapeTree: URL, focusNode: URL): DocumentResponse /* throws ShapeTreeException */; + + /** + * Shape Trees, §4.3: This operation unassigns a planted root shape tree from a root shape tree instance. If + * the root shape tree instance is a managed container, it will also unassign contained resources. + * If there are no remaining shape trees managing the resource, it would no longer be considered as managed. + * + * https://shapetrees.org/TR/specification/#unplant-shapetree + * + * @param context ShapeTreeContext that would be used for authentication purposes + * @param targetResource URL of target resource to unplant shape tree from + * @param targetShapeTree URL of shape tree being unplanted + */ + unplantShapeTree(context: ShapeTreeContext, targetResource: URL, targetShapeTree: URL): DocumentResponse /* throws ShapeTreeException */; + + /** + * Creates a resource via HTTP POST that has been validated against the provided shape tree + * @param context ShapeTreeContext that would be used for authentication purposes + * @param parentContainer The container the created resource should be created within + * @param focusNodes One or more nodes/subjects to use as the focus for shape validation + * @param targetShapeTrees One or more target shape trees the resource should be validated by + * @param proposedName Proposed resource name (aka Slug) for the resulting resource + * @param isContainer Specifies whether the newly created resource should be created as a container or not + * @param bodyString String representation of body of the created resource + * @param contentType Content type to parse the bodyString parameter as + * @return DocumentResponse containing status and response headers/attributes + * @throws ShapeTreeException ShapeTreeException + */ + postManagedInstance(context: ShapeTreeContext, parentContainer: URL, focusNodes: Array, targetShapeTrees: Array, proposedName: string, isContainer: Boolean, bodyString: string, contentType: string): DocumentResponse /* throws ShapeTreeException */; + + /** + * Creates a resource via HTTP PUT that has been validated against the provided target shape tree + * @param context ShapeTreeContext that would be used for authentication purposes + * @param targetResource The target resource to be created or updated + * @param focusNodes One or more nodes/subjects to use as the focus for shape validation + * @param targetShapeTrees The shape trees that a proposed resource to be created should be validated against + * @param isContainer Specifies whether a newly created resource should be created as a container or not + * @param bodyString String representation of the body of the resource to create or update + * @param contentType Content type to parse the bodyString parameter as + * @return DocumentResponse containing status and response header / attributes + * @throws ShapeTreeException + */ + putManagedInstance(context: ShapeTreeContext, targetResource: URL, focusNodes: Array, targetShapeTrees: Array, isContainer: Boolean, bodyString: string, contentType: string): DocumentResponse /* throws ShapeTreeException */; + + /** + * Updates a resource via HTTP PUT that has been validated against an associated shape tree + * @param context ShapeTreeContext that would be used for authentication purposes + * @param targetResource The target resource to be created or updated + * @param focusNodes One or more nodes/subjects to use as the focus for shape validation + * @param bodyString String representation of the body of the resource to create or update + * @param contentType Content type to parse the bodyString parameter as + * @return DocumentResponse containing status and response header / attributes + * @throws ShapeTreeException + */ + putManagedInstance(context: ShapeTreeContext, targetResource: URL, focusNodes: Array, bodyString: string, contentType: string): DocumentResponse /* throws ShapeTreeException */; + + /** + * Updates a resource via HTTP PATCH that has been validated against an associated shape tree + * @param context ShapeTreeContext that would be used for authentication purposes + * @param targetResource The target resource to be created or updated + * @param focusNodes One or more nodes/subjects to use as the focus for shape validation + * @param patchString SPARQL Update statement to use in patching the resource + * @return DocumentResponse containing status and response header / attributes + * @throws ShapeTreeException + */ + patchManagedInstance(context: ShapeTreeContext, targetResource: URL, focusNodes: Array, patchString: string): DocumentResponse /* throws ShapeTreeException */; + + /** + * Deletes an existing resource. Provided as a convenience - no validation is performed + * @param context ShapeTreeContext that would be used for authentication purposes + * @param resourceUrl The URL of the resource being deleted + * @return DocumentResponse containing status and response headers/attributes + * @throws ShapeTreeException ShapeTreeException + */ + deleteManagedInstance(context: ShapeTreeContext, resourceUrl: URL): DocumentResponse /* throws ShapeTreeException */; + + /** + * Indicates whether validation is currently being applied on the client + * @return boolean of whether client-side validation is being performed + */ + isShapeTreeValidationSkipped(): boolean; + + /** + * Determines whether validation should be performed on the client + * @param skipValidation boolean indicating whether validation should be performed on the client + */ + skipShapeTreeValidation(skipValidation: boolean): void; +} diff --git a/asTypescript/packages/client-http/src/HttpClient.ts b/asTypescript/packages/client-http/src/HttpClient.ts new file mode 100644 index 00000000..1967322e --- /dev/null +++ b/asTypescript/packages/client-http/src/HttpClient.ts @@ -0,0 +1,29 @@ +// Corresponding shapetrees-java package: com.janeirodigital.shapetrees.client.http +import { DocumentResponse } from '@shapetrees/ocumentResponse'; +import { ShapeTreeException } from '@shapetrees/xceptions/ShapeTreeException'; +import { HttpRequest } from './HttpRequest'; + +/** + * abstract base class for ShapeTree library network drivers + */ +export interface HttpClient { + + GET: string = "GET"; + + PUT: string = "PUT"; + + POST: string = "POST"; + + PATCH: string = "PATCH"; + + DELETE: string = "DELETE"; + + /** + * Execute an HTTP request to create a DocumentResponse object + * Implements `HttpClient` interface + * @param request an HTTP request with appropriate headers for ShapeTree interactions + * @return new DocumentResponse with response headers and contents + * @throws ShapeTreeException + */ + fetchShapeTreeResponse(request: HttpRequest): DocumentResponse /* throws ShapeTreeException */; +} diff --git a/asTypescript/packages/client-http/src/HttpClientFactory.ts b/asTypescript/packages/client-http/src/HttpClientFactory.ts new file mode 100644 index 00000000..9fa2a0d8 --- /dev/null +++ b/asTypescript/packages/client-http/src/HttpClientFactory.ts @@ -0,0 +1,19 @@ +// Corresponding shapetrees-java package: com.janeirodigital.shapetrees.client.http +import { ShapeTreeException } from '@shapetrees/xceptions/ShapeTreeException'; +import { HttpClient } from './HttpClient'; + +/** + * Constructs HttpClients based on the passed configuration. + * + *

See the Usage section in README. + */ +export interface HttpClientFactory { + + /** + * Reuses or constructs a new HttpClient tailored to the passed configuration + * @param useShapeTreeValidation whether or not the returned HttpClient must do ShapeTree validation (and Shape validation) on dereferenced resources + * @return an implementation of HttpClient that can be used to map HTTP library (e.g. OkHttp) + * requests and responses to `shapetrees-java-client-http` classes. + */ + get(useShapeTreeValidation: boolean): HttpClient /* throws ShapeTreeException */; +} diff --git a/asTypescript/packages/client-http/src/HttpClientFactoryManager.ts b/asTypescript/packages/client-http/src/HttpClientFactoryManager.ts new file mode 100644 index 00000000..86343cf9 --- /dev/null +++ b/asTypescript/packages/client-http/src/HttpClientFactoryManager.ts @@ -0,0 +1,21 @@ +// Corresponding shapetrees-java package: com.janeirodigital.shapetrees.client.http +import { ShapeTreeException } from '@shapetrees/xceptions/ShapeTreeException'; +import { HttpClientFactory } from './HttpClientFactory'; + +export abstract class HttpClientFactoryManager { + + @Setter(onMethod_ = { @Synchronized }) + private static factory: HttpClientFactory; + + private constructor() { + throw new IllegalStateException("Utility class"); + } + + // @Synchronized + public static getFactory(): HttpClientFactory /* throws ShapeTreeException */ { + if (factory === null) { + throw new ShapeTreeException(500, "Must provide a valid HTTP client factory"); + } + return HttpClientFactoryManager.factory; + } +} diff --git a/asTypescript/packages/client-http/src/HttpRequest.ts b/asTypescript/packages/client-http/src/HttpRequest.ts new file mode 100644 index 00000000..0193e2f4 --- /dev/null +++ b/asTypescript/packages/client-http/src/HttpRequest.ts @@ -0,0 +1,24 @@ +// Corresponding shapetrees-java package: com.janeirodigital.shapetrees.client.http +import { ResourceAttributes } from '@shapetrees/esourceAttributes'; +import * as URL from 'java/net'; + +export class HttpRequest { + + public method: string; + + public resourceURL: URL; + + public headers: ResourceAttributes; + + public body: string; + + public contentType: string; + + public constructor(method: string, resourceURL: URL, headers: ResourceAttributes, body: string, contentType: string) { + this.method = method; + this.resourceURL = resourceURL; + this.headers = headers; + this.body = body; + this.contentType = contentType; + } +} diff --git a/asTypescript/packages/client-http/src/HttpResourceAccessor.ts b/asTypescript/packages/client-http/src/HttpResourceAccessor.ts new file mode 100644 index 00000000..d7d1e199 --- /dev/null +++ b/asTypescript/packages/client-http/src/HttpResourceAccessor.ts @@ -0,0 +1,531 @@ +// Corresponding shapetrees-java package: com.janeirodigital.shapetrees.client.http +import * as core from 'com/janeirodigital/shapetrees'; +import { HttpHeaders } from '@shapetrees/nums/HttpHeaders'; +import { LinkRelations } from '@shapetrees/nums/LinkRelations'; +import { ShapeTreeResourceType } from '@shapetrees/nums/ShapeTreeResourceType'; +import { ShapeTreeException } from '@shapetrees/xceptions/ShapeTreeException'; +import { ShapeTreeContext } from '@shapetrees/hapeTreeContext'; +import { LdpVocabulary } from '@shapetrees/ocabularies/LdpVocabulary'; +import * as Slf4j from 'lombok/extern/slf4j'; +import * as Graph from 'org/apache/jena/graph'; +import * as Node from 'org/apache/jena/graph'; +import * as NodeFactory from 'org/apache/jena/graph'; +import * as Triple from 'org/apache/jena/graph'; +import * as MalformedURLException from 'java/net'; +import * as URL from 'java/net'; +import * as util from 'java'; +import { readStringIntoGraph } from '@shapetrees/elpers/GraphHelper/readStringIntoGraph'; +import { urlToUri } from '@shapetrees/elpers/GraphHelper/urlToUri'; +import { HttpRequest } from './HttpRequest'; +import { HttpClient } from './HttpClient'; + +/** + * Allows the {@link com.janeirodigital.shapetrees.core shapetrees-core} to access + * {@link ManageableInstance}s and {@link InstanceResource}s over the network via HTTP. This is + * particularly effective when employing client-side shape-tree validation in a + * proxy scenario. + * + *

Given the fact that resources are accessed via HTTP, some inferences must be made on + * resource state based on responses to HTTP requests.

+ */ +@Slf4j +export class HttpResourceAccessor implements ResourceAccessor { + + private static readonly supportedRDFContentTypes: Set = Set.of("text/turtle", "application/rdf+xml", "application/n-triples", "application/ld+json"); + + /** + * Return a {@link ManageableInstance} constructed based on the provided resourceUrl, + * which could target either a {@link ManageableResource} or a {@link ManagerResource}. + * Both are retrieved via HTTP and loaded as specifically + * typed sub-classes that indicate whether they exist, or (in the case of manageable resource) + * whether they are managed. + * + * @param context {@link ShapeTreeContext} + * @param resourceUrl URL of the target resource + * @return {@link ManageableInstance} including {@link ManageableResource} and {@link ManagerResource} + */ + override public getInstance(context: ShapeTreeContext, resourceUrl: URL): ManageableInstance /* throws ShapeTreeException */ { + let resource: InstanceResource = this.getResource(context, resourceUrl); + if (resource instanceof MissingManageableResource) { + // Get is for a manageable resource that doesn't exist + return getInstanceFromMissingManageableResource(context, (MissingManageableResource) resource); + } else if (resource instanceof MissingManagerResource) { + // Get is for a manager resource that doesn't exist + return getInstanceFromMissingManagerResource(context, (MissingManagerResource) resource); + } else if (resource instanceof ManageableResource) { + // Get is for an existing manageable resource + return getInstanceFromManageableResource(context, (ManageableResource) resource); + } else if (resource instanceof ManagerResource) { + // Get is for an existing manager resource + return getInstanceFromManagerResource(context, (ManagerResource) resource); + } + throw new ShapeTreeException(500, "Can get instance from resource of unsupported type: " + resource.getUrl()); + } + + /** + * Gets a {@link ManageableInstance} given a {@link MissingManageableResource}, which means that + * a corresponding {@link ManagerResource} cannot exist, so a {@link MissingManagerResource} is + * constructed and included as part of instance construction. + * @param context {@link ShapeTreeContext} + * @param missing {@link MissingManageableResource} + * @return {@link ManageableInstance} including {@link MissingManageableResource} and {@link MissingManagerResource} + */ + private getInstanceFromMissingManageableResource(context: ShapeTreeContext, missing: MissingManageableResource): ManageableInstance { + let missingManager: MissingManagerResource = new MissingManagerResource(missing, null); + return new ManageableInstance(context, this, false, missing, missingManager); + } + + /** + * Gets a {@link ManageableInstance} given a {@link MissingManagerResource}, which means that + * a {@link ManagerResource} doesn't exist, but an {@link UnmanagedResource} that would be associated + * with it may, so it is looked up over HTTP and populated with the appropriate resulting type + * based on its existence. + * @param context {@link ShapeTreeContext} + * @param missing {@link MissingManagerResource} + * @return {@link ManageableInstance} including {@link UnmanagedResource}|{@link MissingManageableResource} and {@link MissingManagerResource} + * @throws ShapeTreeException + */ + private getInstanceFromMissingManagerResource(context: ShapeTreeContext, missing: MissingManagerResource): ManageableInstance /* throws ShapeTreeException */ { + let manageable: InstanceResource = this.getResource(context, calculateManagedUrl(missing.getUrl(), missing.getAttributes())); + if (manageable.isExists()) { + let unmanaged: UnmanagedResource = new UnmanagedResource((ManageableResource) manageable, Optional.of(missing.getUrl())); + return new ManageableInstance(context, this, true, unmanaged, missing); + } else { + throw new ShapeTreeException(500, "Cannot have a shape tree manager " + missing.getUrl() + " for a missing manageable resource " + manageable.getUrl()); + } + } + + /** + * Gets a {@link ManageableInstance} given a {@link ManageableResource}, which could be a + * {@link ManagedResource} or an {@link UnmanagedResource}. Which type is determined by + * the presence of the {@link ManagerResource}, which is looked up and the instance is + * populated with the appropriate resulting types.* + * @param context {@link ShapeTreeContext} + * @param manageable {@link ManagedResource} or {@link UnmanagedResource} + * @return {@link ManageableInstance} including {@link UnmanagedResource}|{@link ManagedResource} and {@link ManagerResource}|{@link MissingManagerResource} + * @throws ShapeTreeException + */ + private getInstanceFromManageableResource(context: ShapeTreeContext, manageable: ManageableResource): ManageableInstance /* throws ShapeTreeException */ { + let managerResourceUrl: URL = manageable.getManagerResourceUrl().orElseThrow(() -> new ShapeTreeException(500, "Cannot discover shape tree manager for " + manageable.getUrl())); + let manager: InstanceResource = this.getResource(context, managerResourceUrl); + if (manager instanceof MissingManagerResource) { + // If the manager does exist it is unmanaged - Get and store both in instance + let unmanaged: UnmanagedResource = new UnmanagedResource(manageable, Optional.of(manager.getUrl())); + return new ManageableInstance(context, this, false, unmanaged, (ManagerResource) manager); + } else if (manager instanceof ManagerResource) { + // If the manager exists then it is managed - get and store manager and managed resource in instance + let managed: ManagedResource = new ManagedResource(manageable, Optional.of(manager.getUrl())); + return new ManageableInstance(context, this, false, managed, (ManagerResource) manager); + } else { + throw new ShapeTreeException(500, "Error looking up corresponding shape tree manager for " + manageable.getUrl()); + } + } + + /** + * Gets a {@link ManageableInstance} given a {@link ManagerResource}. The corresponding + * {@link ManagedResource} is looked up and the instance is populated with it. + * @param context {@link ShapeTreeContext} + * @param manager Existing {@link ManagerResource} + * @return {@link ManageableInstance} including {@link ManagerResource} and {@link ManagedResource} + * @throws ShapeTreeException + */ + private getInstanceFromManagerResource(context: ShapeTreeContext, manager: ManagerResource): ManageableInstance /* throws ShapeTreeException */ { + let manageable: InstanceResource = this.getResource(context, manager.getManagedResourceUrl()); + if (manageable instanceof MissingManageableResource) { + throw new ShapeTreeException(500, "Cannot have a shape tree manager at " + manager.getUrl() + " without a corresponding managed resource"); + } + let managed: ManagedResource = new ManagedResource((ManageableResource) manageable, Optional.of(manager.getUrl())); + return new ManageableInstance(context, this, true, managed, manager); + } + + /** + * Gets a {@link ManageableInstance} by first creating the provided resourceUrl, which could + * mean creating either a {@link ManageableResource} or a {@link ManagerResource}. The newly created resource + * is loaded into the instance, and the corresponding {@link ManageableResource} or {@link ManagerResource} is + * looked up and loaded into the instance alongside it. They are loaded as specifically + * typed sub-classes that indicate whether they exist, or (in the case of a {@link ManageableResource}), + * whether they are managed. + * @param context {@link ShapeTreeContext} + * @param method HTTP method used for creation + * @param resourceUrl URL of the resource to create + * @param headers HTTP headers used for creation + * @param body Body of the created resource + * @param contentType Content-type of the created resource + * @return {@link ManageableInstance} with {@link ManageableResource} and {@link ManagerResource} + * @throws ShapeTreeException + */ + override public createInstance(context: ShapeTreeContext, method: string, resourceUrl: URL, headers: ResourceAttributes, body: string, contentType: string): ManageableInstance /* throws ShapeTreeException */ { + let resource: InstanceResource = this.createResource(context, method, resourceUrl, headers, body, contentType); + if (resource instanceof ManageableResource) { + // Managed or unmanaged resource was created + return createInstanceFromManageableResource(context, (ManageableResource) resource); + } else if (resource instanceof ManagerResource) { + // Manager resource was created + return createInstanceFromManagerResource(context, (ManagerResource) resource); + } + throw new ShapeTreeException(500, "Invalid resource type returned from resource creation"); + } + + /** + * Gets a {@link ManageableInstance} given a newly created {@link ManageableResource}. A corresponding + * {@link ManagerResource} is looked up. If it exists, a {@link ManagedResource} is initialized and loaded + * into the instance. If it doesn't, an {@link UnmanagedResource} is initialized and loaded instead. + * @param context {@link ShapeTreeContext} + * @param manageable Newly created {@link ManageableResource} + * @return {@link ManageableInstance} including {@link ManagedResource}|{@link UnmanagedResource} and {@link ManagerResource}|{@link MissingManagerResource} + * @throws ShapeTreeException + */ + private createInstanceFromManageableResource(context: ShapeTreeContext, manageable: ManageableResource): ManageableInstance /* throws ShapeTreeException */ { + // Lookup the corresponding ManagerResource for the ManageableResource + let managerResourceUrl: URL = manageable.getManagerResourceUrl().orElseThrow(() -> new ShapeTreeException(500, "Cannot discover shape tree manager for " + manageable.getUrl())); + let manager: InstanceResource = this.getResource(context, managerResourceUrl); + if (manager instanceof MissingManagerResource) { + // Create and store an UnmanagedResource in instance - if the create was a resource in an unmanaged container + let unmanaged: UnmanagedResource = new UnmanagedResource(manageable, Optional.of(manager.getUrl())); + return new ManageableInstance(context, this, false, unmanaged, (ManagerResource) manager); + } else if (manager instanceof ManagerResource) { + // Create and store a ManagedResource in instance - if the create was a resource in a managed container + let managed: ManagedResource = new ManagedResource(manageable, Optional.of(manager.getUrl())); + return new ManageableInstance(context, this, false, managed, (ManagerResource) manager); + } + throw new ShapeTreeException(500, "Error lookup up corresponding shape tree manager for " + manageable.getUrl()); + } + + /** + * Gets a {@link ManageableInstance} given a newly created {@link ManagerResource}. A corresponding + * {@link ManagedResource} is looked up (and which must exist and be associated with this + * manager). + * @param context {@link ShapeTreeContext} + * @param manager Newly created {@link ManagerResource} + * @return {@link ManageableInstance} including {@link ManagerResource} and {@link ManagedResource} + * @throws ShapeTreeException + */ + private createInstanceFromManagerResource(context: ShapeTreeContext, manager: ManagerResource): ManageableInstance /* throws ShapeTreeException */ { + // Lookup the corresponding ManagedResource for the ManagerResource + let resource: InstanceResource = this.getResource(context, manager.getManagedResourceUrl()); + if (resource instanceof MissingManageableResource) { + throw new ShapeTreeException(500, "Cannot have an existing manager resource " + manager.getUrl() + " with a non-existing managed resource " + resource.getUrl()); + } else if (resource instanceof ManagerResource) { + throw new ShapeTreeException(500, "Invalid manager resource " + resource.getUrl() + " seems to be associated with another manager resource " + manager.getUrl()); + } + let managed: ManagedResource = new ManagedResource((ManageableResource) resource, Optional.of(manager.getUrl())); + return new ManageableInstance(context, this, true, managed, manager); + } + + /** + * Get a {@link InstanceResource} at the provided url, which may or may not exist. + * Most of the work happens in {@link #generateResource(URL, DocumentResponse)}, which + * processes the response and returns the corresponding typed resource. + * @param context {@link ShapeTreeContext} + * @param url Url of the resource to get + * @return {@link InstanceResource} + * @throws ShapeTreeException + */ + override public getResource(context: ShapeTreeContext, url: URL): InstanceResource /* throws ShapeTreeException */ { + log.debug("HttpResourceAccessor#getResource({})", url); + let headers: ResourceAttributes = new ResourceAttributes(); + headers.maybeSet(HttpHeaders.AUTHORIZATION.getValue(), context.getAuthorizationHeaderValue()); + let fetcher: HttpClient = HttpClientFactoryManager.getFactory().get(false); + let req: HttpRequest = new HttpRequest("GET", url, headers, null, null); + let response: DocumentResponse = fetcher.fetchShapeTreeResponse(req); + return generateResource(url, response); + } + + /** + * Create a {@link InstanceResource} at the provided url via the provided HTTP + * method. Most of the work happens in {@link #generateResource(URL, DocumentResponse)}, + * which processes the response and returns the corresponding typed resource. + * @param context {@link ShapeTreeContext} + * @param method HTTP method to use for resource creation + * @param url Url of the resource to create + * @param headers HTTP headers to use for resource creation + * @param body Body of resource to create + * @param contentType HTTP content-type + * @return {@link InstanceResource} + * @throws ShapeTreeException + */ + override public createResource(context: ShapeTreeContext, method: string, url: URL, headers: ResourceAttributes, body: string, contentType: string): InstanceResource /* throws ShapeTreeException */ { + log.debug("createResource via {}: URL [{}], headers [{}]", method, url, headers.toString()); + let fetcher: HttpClient = HttpClientFactoryManager.getFactory().get(false); + let allHeaders: ResourceAttributes = headers.maybePlus(HttpHeaders.AUTHORIZATION.getValue(), context.getAuthorizationHeaderValue()); + let response: DocumentResponse = fetcher.fetchShapeTreeResponse(new HttpRequest(method, url, allHeaders, body, contentType)); + if (!response.isExists()) { + throw new ShapeTreeException(500, "Unable to create resource <" + url + ">"); + } + return generateResource(url, response); + } + + /** + * Generates a typed {@link InstanceResource} based on the response from {@link #getResource(ShapeTreeContext, URL)} or + * {@link #createResource(ShapeTreeContext, String, URL, ResourceAttributes, String, String)}. + * Determines whether the resource is an existing {@link ManageableResource} or {@link ManagerResource}. + * @param url Url of the resource to generate + * @param response Response from a create or update of url + * @return Generated {@link InstanceResource}, either {@link ManageableResource} or {@link ManagerResource} + * @throws ShapeTreeException + */ + private generateResource(url: URL, response: DocumentResponse): InstanceResource /* throws ShapeTreeException */ { + // If a resource was created, ensure the URL returned in the Location header is valid + let location: Optional = response.getResourceAttributes().firstValue(HttpHeaders.LOCATION.getValue()); + if (location.isPresent()) { + try { + url = new URL(location.get()); + } catch (e: MalformedURLException) { + throw new ShapeTreeException(500, "Retrieving <" + url + "> yielded a Location header \"" + location.get() + "\" which doesn't parse as a URL: " + e.getMessage()); + } + } + // Determine whether the resource exists based on the response. Even if the resource + // doesn't exist, additional context and processing is done to provide the appropriate + // typed resource with adequate context to the caller + const exists: boolean = response.isExists(); + const container: boolean = isContainerFromHeaders(response.getResourceAttributes(), url); + const attributes: ResourceAttributes = response.getResourceAttributes(); + const resourceType: ShapeTreeResourceType = getResourceTypeFromHeaders(response.getResourceAttributes()); + const name: string = calculateName(url); + const body: string = response.getBody(); + if (response.getBody() === null) { + log.error("Could not retrieve the body string from response for " + url); + } + // Parse Link headers from response and populate ResourceAttributes + const linkHeaders: List = attributes.allValues(HttpHeaders.LINK.getValue()); + let parsedLinkHeaders: ResourceAttributes = // !! + linkHeaders.isEmpty() ? new ResourceAttributes() : ResourceAttributes.parseLinkHeaders(linkHeaders); + // Determine if the resource is a shape tree manager based on the response + const isManager: boolean = calculateIsManager(url, exists, parsedLinkHeaders); + if (Boolean.TRUE === isManager) { + const managedResourceUrl: URL = calculateManagedUrl(url, parsedLinkHeaders); + if (exists) { + return new ManagerResource(url, resourceType, attributes, body, name, true, managedResourceUrl); + } else { + return new MissingManagerResource(url, resourceType, attributes, body, name, managedResourceUrl); + } + } else { + // Look for presence of st:managedBy in link headers from response and get the target manager URL + const managerUrl: Optional = calculateManagerUrl(url, parsedLinkHeaders); + if (exists) { + return new ManageableResource(url, resourceType, attributes, body, name, true, managerUrl, container); + } else { + return new MissingManageableResource(url, resourceType, attributes, body, name, managerUrl, container); + } + } + } + + /** + * Gets a List of contained {@link ManageableInstance}s from a given container specified by containerUrl + * @param context {@link ShapeTreeContext} + * @param containerUrl URL of target container resource + * @return List of {@link ManageableInstance}s from the target container + * @throws ShapeTreeException + */ + override public getContainedInstances(context: ShapeTreeContext, containerUrl: URL): List /* throws ShapeTreeException */ { + try { + let resource: InstanceResource = this.getResource(context, containerUrl); + if (!(resource instanceof ManageableResource)) { + throw new ShapeTreeException(500, "Cannot get contained resources for a manager resource <" + containerUrl + ">"); + } + let containerResource: ManageableResource = (ManageableResource) resource; + if (Boolean.FALSE === containerResource.isContainer()) { + throw new ShapeTreeException(500, "Cannot get contained resources for a resource that is not a Container <" + containerUrl + ">"); + } + let containerGraph: Graph = readStringIntoGraph(urlToUri(containerUrl), containerResource.getBody(), containerResource.getAttributes().firstValue(HttpHeaders.CONTENT_TYPE.getValue()).orElse(null)); + if (containerGraph.isEmpty()) { + return Collections.emptyList(); + } + let containerTriples: List = containerGraph.find(NodeFactory.createURI(containerUrl.toString()), NodeFactory.createURI(LdpVocabulary.CONTAINS), Node.ANY).toList(); + if (containerTriples.isEmpty()) { + return Collections.emptyList(); + } + let containedInstances: ArrayList = new ArrayList<>(); + for (let containerTriple: Triple : containerTriples) { + let containedInstance: ManageableInstance = this.getInstance(context, new URL(containerTriple.getObject().getURI())); + containedInstances.add(containedInstance); + } + return containedInstances; + } catch (ex: Exception) { + throw new ShapeTreeException(500, ex.getMessage()); + } + } + + /** + * Updates the provided {@link InstanceResource} updateResource with body via the supplied + * method + * @param context Shape tree context + * @param method HTTP method to use for update + * @param updateResource {@link InstanceResource} to update + * @param body Body to use for update + * @return {@link DocumentResponse} of the result + * @throws ShapeTreeException + */ + override public updateResource(context: ShapeTreeContext, method: string, updateResource: InstanceResource, body: string): DocumentResponse /* throws ShapeTreeException */ { + log.debug("updateResource: URL [{}]", updateResource.getUrl()); + let contentType: string = updateResource.getAttributes().firstValue(HttpHeaders.CONTENT_TYPE.getValue()).orElse(null); + // [careful] updateResource attributes may contain illegal client headers (connection, content-length, date, expect, from, host, upgrade, via, warning) + let allHeaders: ResourceAttributes = updateResource.getAttributes().maybePlus(HttpHeaders.AUTHORIZATION.getValue(), context.getAuthorizationHeaderValue()); + let fetcher: HttpClient = HttpClientFactoryManager.getFactory().get(false); + return fetcher.fetchShapeTreeResponse(new HttpRequest(method, updateResource.getUrl(), allHeaders, body, contentType)); + } + + /** + * Deletes the provided {@link InstanceResource }deleteResource + * @param context {@link ShapeTreeContext} + * @param deleteResource {@link InstanceResource} to delete + * @return {@link DocumentResponse} of the result + * @throws ShapeTreeException + */ + override public deleteResource(context: ShapeTreeContext, deleteResource: ManagerResource): DocumentResponse /* throws ShapeTreeException */ { + log.debug("deleteResource: URL [{}]", deleteResource.getUrl()); + let fetcher: HttpClient = HttpClientFactoryManager.getFactory().get(false); + let allHeaders: ResourceAttributes = deleteResource.getAttributes().maybePlus(HttpHeaders.AUTHORIZATION.getValue(), context.getAuthorizationHeaderValue()); + let response: DocumentResponse = fetcher.fetchShapeTreeResponse(new HttpRequest("DELETE", deleteResource.getUrl(), allHeaders, null, null)); + let respCode: number = response.getStatusCode(); + if (respCode < 200 || respCode >= 400) { + log.error("Error deleting resource {}, Status {}", deleteResource.getUrl(), respCode); + } + return response; + } + + /** + * Look for a Link rel=type of ldp:Container or ldp:BasicContainer + * @param headers to parse + * @return True if headers indicating a container are found + */ + private isContainerFromHeaders(headers: ResourceAttributes, url: URL): boolean { + let linkHeaders: List = headers.allValues(HttpHeaders.LINK.getValue()); + if (linkHeaders.isEmpty()) { + return url.getPath().endsWith("/"); + } + let parsedLinkHeaders: ResourceAttributes = ResourceAttributes.parseLinkHeaders(linkHeaders); + let typeLinks: List = parsedLinkHeaders.allValues(LinkRelations.TYPE.getValue()); + if (!typeLinks.isEmpty()) { + return typeLinks.contains(LdpVocabulary.CONTAINER) || typeLinks.contains(LdpVocabulary.BASIC_CONTAINER); + } + return false; + } + + /** + * Determine a resource type by parsing Link rel=type headers + * @param headers to parse + * @return Type of resource + */ + private getResourceTypeFromHeaders(headers: ResourceAttributes): ShapeTreeResourceType { + let linkHeaders: List = headers.allValues(HttpHeaders.LINK.getValue()); + if (linkHeaders === null) { + return null; + } + let parsedLinkHeaders: ResourceAttributes = ResourceAttributes.parseLinkHeaders(linkHeaders); + let typeLinks: List = parsedLinkHeaders.allValues(LinkRelations.TYPE.getValue()); + if (typeLinks != null && (typeLinks.contains(LdpVocabulary.CONTAINER) || typeLinks.contains(LdpVocabulary.BASIC_CONTAINER))) { + return ShapeTreeResourceType.CONTAINER; + } + if (supportedRDFContentTypes.contains(headers.firstValue(HttpHeaders.CONTENT_TYPE.getValue()).orElse(""))) { + // orElse("") because contains(null) throw NPE + return ShapeTreeResourceType.RESOURCE; + } + return ShapeTreeResourceType.NON_RDF; + } + + /** + * Looks for the presence of the http://www.w3.org/ns/shapetrees#managedBy HTTP Link Relation in the + * provided parsedLinkHeaders, with a valid target URL of a {@link ShapeTreeManager} associated + * with the provided url. + * @param url URL of the (potentially) managed resource + * @param parsedLinkHeaders Parsed HTTP Link headers to evaluate + * @return + * @throws ShapeTreeException + */ + private calculateManagerUrl(url: URL, parsedLinkHeaders: ResourceAttributes): Optional /* throws ShapeTreeException */ { + const optManagerString: Optional = parsedLinkHeaders.firstValue(LinkRelations.MANAGED_BY.getValue()); + if (optManagerString.isEmpty()) { + log.info("The resource {} does not contain a link header of {}", url, LinkRelations.MANAGED_BY.getValue()); + return Optional.empty(); + } + let managerUrlString: string = optManagerString.get(); + try { + return Optional.of(new URL(url, managerUrlString)); + } catch (e: MalformedURLException) { + throw new ShapeTreeException(500, "Malformed relative URL <" + managerUrlString + "> (resolved from <" + url + ">)"); + } + } + + /** + * Looks for the presence of the http://www.w3.org/ns/shapetrees#manages HTTP Link Relation in the + * provided parsedLinkHeaders, with a valid target URL of a {@link ManagedResource}. Falls + * back to a relatively crude inference when the more reliable header isn't available + * @param managerUrl URL of the {@link ShapeTreeManager} + * @param parsedLinkHeaders Parsed link headers from {@link ManagerResource} response + * @return URL of {@link ManagedResource} + * @throws ShapeTreeException + */ + private calculateManagedUrl(managerUrl: URL, parsedLinkHeaders: ResourceAttributes): URL /* throws ShapeTreeException */ { + let managedUrlString: string; + let managedResourceUrl: URL; + const optManagedString: Optional = parsedLinkHeaders.firstValue(LinkRelations.MANAGES.getValue()); + if (!optManagedString.isEmpty()) { + managedUrlString = optManagedString.get(); + } else { + // Attempt to (crudely) infer based on path calculation + // If this implementation uses a dot notation for meta, trim it from the path + // Rebuild without the query string in case that was employed + managedUrlString = managerUrl.getPath().replaceAll("\\.shapetree$", ""); + } + try { + managedResourceUrl = new URL(managerUrl, managedUrlString); + } catch (e: MalformedURLException) { + throw new ShapeTreeException(500, "Can't calculate managed resource for shape tree manager <" + managerUrl + ">"); + } + return managedResourceUrl; + } + + /** + * Calculates the name of the resource itself, removing any leading path and any trailing slash. In + * the event that the resource is '/', then '/' will be returned. + * @param url URL of the resource to evaluate + * @return Name of resource + */ + private calculateName(url: URL): string { + let path: string = url.getPath(); + if (path === "/") + return "/"; + // if this is a container, trim the trailing slash + if (path.endsWith("/")) { + path = path.substring(0, path.length() - 1); + } + let pathIndex: number = path.lastIndexOf('/'); + // No slashes in the path + if (pathIndex === -1) { + return path; + } + return path.substring(path.lastIndexOf('/') + 1); + } + + /** + * Determine whether url is a {@link ManagerResource}. Since this is a completely HTTP based + * resource processor, this determination can't be made with special server-side knowledge about + * the nature of the resources it serves. Instead, this must be derived based on information + * present in the HTTP response from the server. + * @param url URL of the resource that is being evaluated + * @param exists whether the resource at url exists + * @param parsedLinkHeaders Parsed HTTP Link headers from the response for url + * @return True if {@link ManagerResource} + */ + private calculateIsManager(url: URL, exists: boolean, parsedLinkHeaders: ResourceAttributes): boolean { + // If the resource has an HTTP Link header of type of https://www.w3.org/ns/shapetrees#managedBy + // with a manager target, it is not a manager resource (because it is managed by one) + if (Boolean.TRUE === exists && parsedLinkHeaders.firstValue(LinkRelations.MANAGED_BY.getValue()).isPresent()) { + return false; + } + // If the resource has an HTTP Link header of type of https://www.w3.org/ns/shapetrees#manages + // it is a manager resource (because it manages another one). + if (Boolean.TRUE === exists && parsedLinkHeaders.firstValue(LinkRelations.MANAGES.getValue()).isPresent()) { + return true; + } + // If the resource doesn't exist, attempt to infer based on the URL + if (url.getPath() != null && url.getPath().matches(".*\\.shapetree$")) { + return true; + } + return url.getQuery() != null && url.getQuery().matches(".*ext\\=shapetree$"); + } + + public constructor() { + } +} diff --git a/asTypescript/packages/client-http/src/HttpShapeTreeClient.ts b/asTypescript/packages/client-http/src/HttpShapeTreeClient.ts new file mode 100644 index 00000000..6e5be1e4 --- /dev/null +++ b/asTypescript/packages/client-http/src/HttpShapeTreeClient.ts @@ -0,0 +1,249 @@ +// Corresponding shapetrees-java package: com.janeirodigital.shapetrees.client.http +import { ShapeTreeClient } from '@shapetrees/hapeTreeClient'; +import * as core from 'com/janeirodigital/shapetrees'; +import { HttpHeaders } from '@shapetrees/nums/HttpHeaders'; +import { LinkRelations } from '@shapetrees/nums/LinkRelations'; +import { ShapeTreeException } from '@shapetrees/xceptions/ShapeTreeException'; +import * as Slf4j from 'lombok/extern/slf4j'; +import * as Lang from 'org/apache/jena/riot'; +import * as RDFDataMgr from 'org/apache/jena/riot'; +import { Writable } from 'stream'; +import * as URL from 'java/net'; +import * as Optional from 'java/util'; +import { HttpRequest } from './HttpRequest'; +import { HttpResourceAccessor } from './HttpResourceAccessor'; +import { HttpClient } from './HttpClient'; + +@Slf4j +export class HttpShapeTreeClient implements ShapeTreeClient { + + private useClientShapeTreeValidation: boolean = true; + + override public isShapeTreeValidationSkipped(): boolean { + return !this.useClientShapeTreeValidation; + } + + override public skipShapeTreeValidation(skipValidation: boolean): void { + this.useClientShapeTreeValidation = !skipValidation; + } + + /** + * Discover the ShapeTreeManager associated with a given target resource. + * Implements {@link ShapeTreeClient#discoverShapeTree} + * + * Shape Trees, §4.1: This operation is used by a client-side agent to discover any shape trees associated + * with a given resource. If URL is a managed resource, the associated Shape Tree Manager will be returned. + * https://shapetrees.org/TR/specification/#discover + * + * @param context ShapeTreeContext that would be used for authentication purposes + * @param targetResource The URL of the target resource for shape tree discovery + * @return + * @throws ShapeTreeException + */ + override public discoverShapeTree(context: ShapeTreeContext, targetResource: URL): Optional /* throws ShapeTreeException */ { + if (targetResource === null) { + throw new ShapeTreeException(500, "Must provide a value target resource for discovery"); + } + log.debug("Discovering shape tree manager managing {}", targetResource); + // Lookup the target resource for pointer to associated shape tree manager + const resourceAccessor: HttpResourceAccessor = new HttpResourceAccessor(); + let instance: ManageableInstance = resourceAccessor.getInstance(context, targetResource); + let manageableResource: ManageableResource = instance.getManageableResource(); + if (!manageableResource.isExists()) { + log.debug("Target resource for discovery {} does not exist", targetResource); + return Optional.empty(); + } + if (instance.wasRequestForManager()) { + throw new ShapeTreeException(500, "Discovery target must not be a shape tree manager resource"); + } + if (instance.isUnmanaged()) { + return Optional.empty(); + } + return Optional.of(instance.getManagerResource().getManager()); + } + + /** + * Shape Trees, §4.2: This operation marks an existing resource as being managed by one or more shape trees, + * by associating a shape tree manager with the resource, and turning it into a managed resource. + * + * If the resource is already managed, the associated shape tree manager will be updated with another + * shape tree assignment for the planted shape tree. + * + * If the resource is a container that already contains existing resources, and a recursive plant is requested, + * this operation will perform a depth first traversal through the containment hierarchy, validating + * and assigning as it works its way back up to the target root resource of this operation. + * + * https://shapetrees.org/TR/specification/#plant-shapetree + * + * @param context ShapeTreeContext that would be used for authentication purposes + * @param targetResource The URL of the resource to plant on + * @param targetShapeTree A URL representing the shape tree to plant for targetResource + * @param focusNode An optional URL representing the target subject within targetResource used for shape validation + * @return The URL of the Shape Tree Manager that was planted for targetResource + * @throws ShapeTreeException + */ + override public plantShapeTree(context: ShapeTreeContext, targetResource: URL, targetShapeTree: URL, focusNode: URL): DocumentResponse /* throws ShapeTreeException */ { + if (context === null || targetResource === null || targetShapeTree === null) { + throw new ShapeTreeException(500, "Must provide a valid context, target resource, and target shape tree to the plant shape tree"); + } + log.debug("Planting shape tree {} on {}: ", targetShapeTree, targetResource); + log.debug("Focus node: {}", focusNode === null ? "None provided" : focusNode); + const resourceAccessor: HttpResourceAccessor = new HttpResourceAccessor(); + // Lookup the shape tree + let shapeTree: ShapeTree = ShapeTreeFactory.getShapeTree(targetShapeTree); + // Lookup the target resource + let instance: ManageableInstance = resourceAccessor.getInstance(context, targetResource); + let manageableResource: ManageableResource = instance.getManageableResource(); + if (!manageableResource.isExists()) { + return new DocumentResponse(null, "Cannot find target resource to plant: " + targetResource, 404); + } + let manager: ShapeTreeManager; + let managerResourceUrl: URL = instance.getManagerResource().getUrl(); + if (instance.isManaged()) { + manager = instance.getManagerResource().getManager(); + } else { + manager = new ShapeTreeManager(managerResourceUrl); + } + // Initialize a shape tree assignment based on the supplied parameters + let assignmentUrl: URL = manager.mintAssignmentUrl(); + let assignment: ShapeTreeAssignment = new ShapeTreeAssignment(targetShapeTree, targetResource, assignmentUrl, focusNode, shapeTree.getShape(), assignmentUrl); + // Add the assignment to the manager + manager.addAssignment(assignment); + // Get an RDF version of the manager stored in a turtle string + let sw: Writable = new Writable(); + RDFDataMgr.write(sw, manager.getGraph(), Lang.TURTLE); + // Build an HTTP PUT request with the manager graph in turtle as the content body + link header + let fetcher: HttpClient = HttpClientFactoryManager.getFactory().get(this.useClientShapeTreeValidation); + let headers: ResourceAttributes = new ResourceAttributes(); + headers.maybeSet(HttpHeaders.AUTHORIZATION.getValue(), context.getAuthorizationHeaderValue()); + return fetcher.fetchShapeTreeResponse(new HttpRequest("PUT", managerResourceUrl, headers, sw.toString(), "text/turtle")); + } + + override public postManagedInstance(context: ShapeTreeContext, parentContainer: URL, focusNodes: Array, targetShapeTrees: Array, proposedResourceName: string, isContainer: Boolean, bodyString: string, contentType: string): DocumentResponse /* throws ShapeTreeException */ { + if (context === null || parentContainer === null) { + throw new ShapeTreeException(500, "Must provide a valid context and parent container to post shape tree instance"); + } + log.debug("POST-ing shape tree instance to {}", parentContainer); + log.debug("Proposed name: {}", proposedResourceName === null ? "None provided" : proposedResourceName); + log.debug("Target Shape Tree: {}", targetShapeTrees === null || targetShapeTrees.isEmpty() ? "None provided" : targetShapeTrees.toString()); + log.debug("Focus Node: {}", focusNodes === null || focusNodes.isEmpty() ? "None provided" : focusNodes.toString()); + let fetcher: HttpClient = HttpClientFactoryManager.getFactory().get(this.useClientShapeTreeValidation); + let headers: ResourceAttributes = getCommonHeaders(context, focusNodes, targetShapeTrees, isContainer, proposedResourceName, contentType); + return fetcher.fetchShapeTreeResponse(new HttpRequest("POST", parentContainer, headers, bodyString, contentType)); + } + + // Create via HTTP PUT + override public putManagedInstance(context: ShapeTreeContext, resourceUrl: URL, focusNodes: Array, targetShapeTrees: Array, isContainer: Boolean, bodyString: string, contentType: string): DocumentResponse /* throws ShapeTreeException */ { + if (context === null || resourceUrl === null) { + throw new ShapeTreeException(500, "Must provide a valid context and target resource to create shape tree instance via PUT"); + } + log.debug("Creating shape tree instance via PUT at {}", resourceUrl); + log.debug("Target Shape Tree: {}", targetShapeTrees === null || targetShapeTrees.isEmpty() ? "None provided" : targetShapeTrees.toString()); + log.debug("Focus Node: {}", focusNodes === null || focusNodes.isEmpty() ? "None provided" : focusNodes.toString()); + let fetcher: HttpClient = HttpClientFactoryManager.getFactory().get(this.useClientShapeTreeValidation); + let headers: ResourceAttributes = getCommonHeaders(context, focusNodes, targetShapeTrees, isContainer, null, contentType); + return fetcher.fetchShapeTreeResponse(new HttpRequest("PUT", resourceUrl, headers, bodyString, contentType)); + } + + // Update via HTTP PUT + override public putManagedInstance(context: ShapeTreeContext, resourceUrl: URL, focusNodes: Array, bodyString: string, contentType: string): DocumentResponse /* throws ShapeTreeException */ { + if (context === null || resourceUrl === null) { + throw new ShapeTreeException(500, "Must provide a valid context and target resource to update shape tree instance via PUT"); + } + log.debug("Updating shape tree instance via PUT at {}", resourceUrl); + log.debug("Focus Node: {}", focusNodes === null || focusNodes.isEmpty() ? "None provided" : focusNodes.toString()); + let fetcher: HttpClient = HttpClientFactoryManager.getFactory().get(this.useClientShapeTreeValidation); + let headers: ResourceAttributes = getCommonHeaders(context, focusNodes, null, null, null, contentType); + return fetcher.fetchShapeTreeResponse(new HttpRequest("PUT", resourceUrl, headers, bodyString, contentType)); + } + + override public patchManagedInstance(context: ShapeTreeContext, resourceUrl: URL, focusNodes: Array, patchString: string): DocumentResponse /* throws ShapeTreeException */ { + if (context === null || resourceUrl === null || patchString === null) { + throw new ShapeTreeException(500, "Must provide a valid context, target resource, and PATCH expression to PATCH shape tree instance"); + } + log.debug("PATCH-ing shape tree instance at {}", resourceUrl); + log.debug("PATCH String: {}", patchString); + log.debug("Focus Node: {}", focusNodes === null || focusNodes.isEmpty() ? "None provided" : focusNodes.toString()); + let contentType: string = "application/sparql-update"; + let fetcher: HttpClient = HttpClientFactoryManager.getFactory().get(this.useClientShapeTreeValidation); + let headers: ResourceAttributes = getCommonHeaders(context, focusNodes, null, null, null, contentType); + return fetcher.fetchShapeTreeResponse(new HttpRequest("PATCH", resourceUrl, headers, patchString, contentType)); + } + + override public deleteManagedInstance(context: ShapeTreeContext, resourceUrl: URL): DocumentResponse /* throws ShapeTreeException */ { + if (context === null || resourceUrl === null) { + throw new ShapeTreeException(500, "Must provide a valid context and target resource to DELETE shape tree instance"); + } + log.debug("DELETE-ing shape tree instance at {}", resourceUrl); + let fetcher: HttpClient = HttpClientFactoryManager.getFactory().get(this.useClientShapeTreeValidation); + let headers: ResourceAttributes = getCommonHeaders(context, null, null, null, null, null); + return fetcher.fetchShapeTreeResponse(new HttpRequest("DELETE", resourceUrl, headers, null, null)); + } + + override public unplantShapeTree(context: ShapeTreeContext, targetResource: URL, targetShapeTree: URL): DocumentResponse /* throws ShapeTreeException */ { + if (context === null || targetResource === null || targetShapeTree === null) { + throw new ShapeTreeException(500, "Must provide a valid context, target resource, and target shape tree to unplant"); + } + log.debug("Unplanting shape tree {} managing {}: ", targetShapeTree, targetResource); + // Lookup the target resource + const resourceAccessor: HttpResourceAccessor = new HttpResourceAccessor(); + let instance: ManageableInstance = resourceAccessor.getInstance(context, targetResource); + let manageableResource: ManageableResource = instance.getManageableResource(); + if (!manageableResource.isExists()) { + return new DocumentResponse(null, "Cannot find target resource to unplant: " + targetResource, 404); + } + if (instance.isUnmanaged()) { + return new DocumentResponse(null, "Cannot unplant target resource that is not managed by a shapetree: " + targetResource, 500); + } + // Remove assignment from manager that corresponds with the provided shape tree + let manager: ShapeTreeManager = instance.getManagerResource().getManager(); + manager.removeAssignmentForShapeTree(targetShapeTree); + let method: string; + let body: string; + let contentType: string; + if (manager.getAssignments().isEmpty()) { + method = "DELETE"; + body = null; + contentType = null; + } else { + // Build an HTTP PUT request with the manager graph in turtle as the content body + link header + method = "PUT"; + // Get a RDF version of the manager stored in a turtle string + let sw: Writable = new Writable(); + RDFDataMgr.write(sw, manager.getGraph(), Lang.TURTLE); + body = sw.toString(); + contentType = "text/turtle"; + } + let fetcher: HttpClient = HttpClientFactoryManager.getFactory().get(this.useClientShapeTreeValidation); + return fetcher.fetchShapeTreeResponse(new HttpRequest(method, manager.getId(), // why no getCommonHeaders(context, null, null, null, null, null) + null, body, contentType)); + } + + private getCommonHeaders(context: ShapeTreeContext, focusNodes: Array, targetShapeTrees: Array, isContainer: Boolean, proposedResourceName: string, contentType: string): ResourceAttributes { + let ret: ResourceAttributes = new ResourceAttributes(); + if (context.getAuthorizationHeaderValue() != null) { + ret.maybeSet(HttpHeaders.AUTHORIZATION.getValue(), context.getAuthorizationHeaderValue()); + } + if (isContainer != null) { + let resourceTypeUrl: string = Boolean.TRUE === isContainer ? "http://www.w3.org/ns/ldp#Container" : "http://www.w3.org/ns/ldp#Resource"; + ret.maybeSet(HttpHeaders.LINK.getValue(), "<" + resourceTypeUrl + ">; rel=\"type\""); + } + if (focusNodes != null && !focusNodes.isEmpty()) { + for (let focusNode: URL : focusNodes) { + ret.maybeSet(HttpHeaders.LINK.getValue(), "<" + focusNode + ">; rel=\"" + LinkRelations.FOCUS_NODE.getValue() + "\""); + } + } + if (targetShapeTrees != null && !targetShapeTrees.isEmpty()) { + for (let targetShapeTree: URL : targetShapeTrees) { + ret.maybeSet(HttpHeaders.LINK.getValue(), "<" + targetShapeTree + ">; rel=\"" + LinkRelations.TARGET_SHAPETREE.getValue() + "\""); + } + } + if (proposedResourceName != null) { + ret.maybeSet(HttpHeaders.SLUG.getValue(), proposedResourceName); + } + if (contentType != null) { + ret.maybeSet(HttpHeaders.CONTENT_TYPE.getValue(), contentType); + } + return ret; + } +} diff --git a/asTypescript/packages/core/src/DocumentResponse.ts b/asTypescript/packages/core/src/DocumentResponse.ts new file mode 100644 index 00000000..b9a4f44f --- /dev/null +++ b/asTypescript/packages/core/src/DocumentResponse.ts @@ -0,0 +1,40 @@ +// Corresponding shapetrees-java package: com.janeirodigital.shapetrees.core +import { HttpHeaders } from './enums/HttpHeaders'; +import * as Optional from 'java/util'; +import { ResourceAttributes } from './ResourceAttributes'; + +export class DocumentResponse { + + private readonly resourceAttributes: ResourceAttributes; + + private readonly body: string; + + private readonly statusCode: number; + + public getContentType(): Optional { + return this.resourceAttributes.firstValue(HttpHeaders.CONTENT_TYPE.getValue()); + } + + // TODO: lots of choices re non-404, not >= 4xx, not 3xx. not 201 (meaning there's no body) + public isExists(): boolean { + return this.statusCode / 100 === 2; + } + + public constructor(resourceAttributes: ResourceAttributes, body: string, statusCode: number) { + this.resourceAttributes = resourceAttributes; + this.body = body; + this.statusCode = statusCode; + } + + public getResourceAttributes(): ResourceAttributes { + return this.resourceAttributes; + } + + public getBody(): string { + return this.body; + } + + public getStatusCode(): number { + return this.statusCode; + } +} diff --git a/asTypescript/packages/core/src/InstanceResource.ts b/asTypescript/packages/core/src/InstanceResource.ts new file mode 100644 index 00000000..496f9809 --- /dev/null +++ b/asTypescript/packages/core/src/InstanceResource.ts @@ -0,0 +1,94 @@ +// Corresponding shapetrees-java package: com.janeirodigital.shapetrees.core +import { HttpHeaders } from './enums/HttpHeaders'; +import { ShapeTreeResourceType } from './enums/ShapeTreeResourceType'; +import { ShapeTreeException } from './exceptions/ShapeTreeException'; +import { GraphHelper } from './helpers/GraphHelper'; +import * as Graph from 'org/apache/jena/graph'; +import * as URI from 'java/net'; +import * as URL from 'java/net'; +import { urlToUri } from './helpers/GraphHelper/urlToUri'; +import { ResourceAttributes } from './ResourceAttributes'; + +/** + * InstanceResource is a base class which may represent either a ManageableResource + * (a resource that can be managed by a shape tree), or a ManagerResource (a resource + * which assigns shape trees to a ManageableResource). This class is only meant to be + * extended by ManageableResource and ManagerResource, or used to indicate either + * of the two. + */ +export class InstanceResource { + + private readonly url: URL; + + private readonly resourceType: ShapeTreeResourceType; + + private readonly attributes: ResourceAttributes; + + private readonly body: string; + + private readonly name: string; + + private readonly exists: boolean; + + /** + * Construct an InstanceResource by providing essential attributes. This constructor is meant + * to be called by sub-class constructors. + * @param url URL of the instance resource + * @param resourceType Identified shape tree resource type + * @param attributes Associated resource attributes + * @param body Body of the resource + * @param name Name of the resource + * @param exists Whether the resource exists + */ + constructor(url: URL, resourceType: ShapeTreeResourceType, attributes: ResourceAttributes, body: string, name: string, exists: boolean) { + this.url = url; + this.resourceType = resourceType; + this.attributes = attributes; + this.body = body; + this.name = name; + this.exists = exists; + } + + /** + * Get an RDF graph of the body of the InstanceResource. If baseUrl is not + * provided, the URL of the InstanceResource will be used. An exception is thrown if + * the body cannot be processed (e.g. if it isn't a valid RDF resource). + * @param baseUrl Base URL to use for the graph + * @return RDF graph of the InstanceResource body + * @throws ShapeTreeException + */ + public getGraph(baseUrl: URL): Graph /* throws ShapeTreeException */ { + if (!this.isExists()) { + return null; + } + if (baseUrl === null) { + baseUrl = this.url; + } + const baseUri: URI = urlToUri(baseUrl); + return GraphHelper.readStringIntoGraph(baseUri, this.getBody(), this.getAttributes().firstValue(HttpHeaders.CONTENT_TYPE.getValue()).orElse(null)); + } + + public getUrl(): URL { + return this.url; + } + + public getResourceType(): ShapeTreeResourceType { + return this.resourceType; + } + + public getAttributes(): ResourceAttributes { + return this.attributes; + } + + public getBody(): string { + return this.body; + } + + public getName(): string { + return this.name; + } + + public getExists(): boolean { + return this.exists; + } +} diff --git a/asTypescript/packages/core/src/ManageableInstance.ts b/asTypescript/packages/core/src/ManageableInstance.ts new file mode 100644 index 00000000..fdee8694 --- /dev/null +++ b/asTypescript/packages/core/src/ManageableInstance.ts @@ -0,0 +1,116 @@ +// Corresponding shapetrees-java package: com.janeirodigital.shapetrees.core +import * as Slf4j from 'lombok/extern/slf4j'; +import * as URL from 'java/net'; +import * as Objects from 'java/util'; +import { ResourceAccessor } from './ResourceAccessor'; +import { ShapeTreeContext } from './ShapeTreeContext'; +import { ManageableResource } from './ManageableResource'; +import { ManagerResource } from './ManagerResource'; +import { MissingManagerResource } from './MissingManagerResource'; + +/** + * A ManageableInstance represents a pairing of a shape tree ManagerResource + * and a ManageableResource. + * + * The ManageableInstance may represent a managed + * state, where the ManageableResource is a ManagedResource that is + * managed by one or more shape trees assigned by the ShapeTreeManager + * in the ManagedResource.Conversely, it could represent an unmanaged + * state, where the ManageableResource is an UnmanagedResource and the + * ManagedResource is a MissingManagedResource. Lastly, it may + * represent other state combinations where one or both of the + * ManageableResource or ManagedResource are missing. + * + * Both ManageableResource and ManagedResource are looked up and loaded + * upon construction of the ManageableInstance, which should be done + * through a ResourceAccessor. Once constructed, the ManageableInstance + * is immutable. + */ +@Slf4j +export class ManageableInstance { + + public static readonly TEXT_TURTLE: string = "text/turtle"; + + private readonly resourceAccessor: ResourceAccessor; + + private readonly shapeTreeContext: ShapeTreeContext; + + private readonly wasRequestForManager: boolean; + + private readonly manageableResource: ManageableResource; + + private readonly managerResource: ManagerResource; + + /** + * Indicates whether the HTTP request that triggered the initialization of the + * ManageableInstance was targeted towards the ManagerResource or the + * ManageableResource. + * @return True when the request targeted the ManagerResource + */ + public wasRequestForManager(): boolean { + return isWasRequestForManager(); + } + + /** + * Indicates whether the ManageableInstance represents an unmanaged state, with a + * UnmanagedResource and a MissingManagerResource + * @return True when the instance is in an unmanaged state + */ + public isUnmanaged(): boolean { + return managerResource instanceof MissingManagerResource; + } + + /** + * Indicates whether the ManageableInstance represents a managed state, with a + * ManagedResource assigned one or more shape trees by a ShapeTreeManager in + * a ManagerResource + * @return True when the instance is in an managed state + */ + public isManaged(): boolean { + return !isUnmanaged(); + } + + /** + * Constructor for a ManageableInstance. Since a ManageableInstance is immutable, all + * elements must be provided, and cannot be null. ManageableInstances should be + * constructed through a ResourceAccessor: + * {@link ResourceAccessor#createInstance(ShapeTreeContext, String, URL, ResourceAttributes, String, String)} + * {@link ResourceAccessor#getInstance(ShapeTreeContext, URL)} + * @param context Shape tree context + * @param resourceAccessor Resource accessor in use + * @param wasRequestForManager True if the manager resource was the target of the associated request + * @param manageableResource Initialized manageable resource, which may be a typed sub-class + * @param managerResource Initialized manager resource, which may be a typed sub-class + */ + public constructor(context: ShapeTreeContext, resourceAccessor: ResourceAccessor, wasRequestForManager: boolean, manageableResource: ManageableResource, managerResource: ManagerResource) { + this.shapeTreeContext = Objects.requireNonNull(context, "Must provide a shape tree context"); + this.resourceAccessor = Objects.requireNonNull(resourceAccessor, "Must provide a resource accessor"); + this.wasRequestForManager = wasRequestForManager; + this.manageableResource = Objects.requireNonNull(manageableResource, "Must provide a manageable resource"); + this.managerResource = Objects.requireNonNull(managerResource, "Must provide a manager resource"); + } + + public getTEXT_TURTLE(): string { + return this.TEXT_TURTLE; + } + + public getResourceAccessor(): ResourceAccessor { + return this.resourceAccessor; + } + + public getShapeTreeContext(): ShapeTreeContext { + return this.shapeTreeContext; + } + + public getWasRequestForManager(): boolean { + return this.wasRequestForManager; + } + + public getManageableResource(): ManageableResource { + return this.manageableResource; + } + + public getManagerResource(): ManagerResource { + return this.managerResource; + } +} diff --git a/asTypescript/packages/core/src/ManageableResource.ts b/asTypescript/packages/core/src/ManageableResource.ts new file mode 100644 index 00000000..6d96847e --- /dev/null +++ b/asTypescript/packages/core/src/ManageableResource.ts @@ -0,0 +1,61 @@ +// Corresponding shapetrees-java package: com.janeirodigital.shapetrees.core +import { ShapeTreeResourceType } from './enums/ShapeTreeResourceType'; +import { ShapeTreeException } from './exceptions/ShapeTreeException'; +import * as MalformedURLException from 'java/net'; +import * as URL from 'java/net'; +import * as Optional from 'java/util'; +import { InstanceResource } from './InstanceResource'; +import { ResourceAttributes } from './ResourceAttributes'; + +/** + * A ManageableResource represents a regular resource that could be managed by + * one or more shape trees. Each possible state is represented by typed + * subclasses; ManagedResource, UnmanagedResource, and + * MissingManageableResource. When the state is known, the appropriate + * typed subclass should be used. + */ +export class ManageableResource extends InstanceResource { + + private readonly managerResourceUrl: Optional; + + private readonly isContainer: boolean; + + /** + * Construct a manageable resource. + * @param url URL of the resource + * @param resourceType Identified shape tree resource type + * @param attributes Associated resource attributes + * @param body Body of the resource + * @param name Name of the resource + * @param exists Whether the resource exists + * @param managerResourceUrl URL of the shape tree manager resource + * @param isContainer Whether the resource is a container + */ + public constructor(url: URL, resourceType: ShapeTreeResourceType, attributes: ResourceAttributes, body: string, name: string, exists: boolean, managerResourceUrl: Optional, isContainer: boolean) { + super(url, resourceType, attributes, body, name, exists); + this.managerResourceUrl = managerResourceUrl; + this.isContainer = isContainer; + } + + /** + * Get the URL of the resource's parent container + * @return URL of the parent container + * @throws ShapeTreeException + */ + public getParentContainerUrl(): URL /* throws ShapeTreeException */ { + const rel: string = this.isContainer() ? ".." : "."; + try { + return new URL(this.getUrl(), rel); + } catch (e: MalformedURLException) { + throw new ShapeTreeException(500, "Malformed focus node when resolving <" + rel + "> against <" + this.getUrl() + ">"); + } + } + + public getManagerResourceUrl(): Optional { + return this.managerResourceUrl; + } + + public getIsContainer(): boolean { + return this.isContainer; + } +} diff --git a/asTypescript/packages/core/src/ManagedResource.ts b/asTypescript/packages/core/src/ManagedResource.ts new file mode 100644 index 00000000..441e79ee --- /dev/null +++ b/asTypescript/packages/core/src/ManagedResource.ts @@ -0,0 +1,22 @@ +// Corresponding shapetrees-java package: com.janeirodigital.shapetrees.core +import * as URL from 'java/net'; +import * as Optional from 'java/util'; +import { ManageableResource } from './ManageableResource'; + +/** + * A ManagedResource indicates that a given ManageableResource + * is managed by a shape tree. This means that is has an associated + * ManagerResource that exists and contains a valid ShapeTreeManager. + */ +export class ManagedResource extends ManageableResource { + + /** + * Construct a ManagedResource based on a provided ManageableResource + * manageable and managerUrl + * @param manageable ManageableResource to construct the ManagedResource from + * @param managerUrl URL of the associated shape tree manager resource + */ + public constructor(manageable: ManageableResource, managerUrl: Optional) { + super(manageable.getUrl(), manageable.getResourceType(), manageable.getAttributes(), manageable.getBody(), manageable.getName(), manageable.isExists(), managerUrl, manageable.isContainer()); + } +} diff --git a/asTypescript/packages/core/src/ManagerResource.ts b/asTypescript/packages/core/src/ManagerResource.ts new file mode 100644 index 00000000..c51cd263 --- /dev/null +++ b/asTypescript/packages/core/src/ManagerResource.ts @@ -0,0 +1,56 @@ +// Corresponding shapetrees-java package: com.janeirodigital.shapetrees.core +import { ShapeTreeResourceType } from './enums/ShapeTreeResourceType'; +import { ShapeTreeException } from './exceptions/ShapeTreeException'; +import * as Graph from 'org/apache/jena/graph'; +import * as URL from 'java/net'; +import { InstanceResource } from './InstanceResource'; +import { ShapeTreeManager } from './ShapeTreeManager'; +import { ResourceAttributes } from './ResourceAttributes'; + +/** + * A ManagerResource represents a resource that is associated with + * a regular MangeableResource, and contains metadata in the form + * of a ShapeTreeManager that assigns one or more shape trees to the + * associated ManageableResource. When it exists, the associated + * resource is considered to be managed. When it doesn't, the associated + * resource is considered to be unmanaged. + */ +export class ManagerResource extends InstanceResource { + + private readonly managedResourceUrl: URL; + + /** + * Construct a manager resource + * @param url URL of the resource + * @param resourceType Identified shape tree resource type + * @param attributes Associated resource attributes + * @param body Body of the resource + * @param name Name of the resource + * @param exists Whether the resource exists + * @param managedResourceUrl URL of the associated managed resource + */ + public constructor(url: URL, resourceType: ShapeTreeResourceType, attributes: ResourceAttributes, body: string, name: string, exists: boolean, managedResourceUrl: URL) { + super(url, resourceType, attributes, body, name, exists); + this.managedResourceUrl = managedResourceUrl; + } + + /** + * Get a ShapeTreeManager from the body of the ManagerResource + * @return Shape tree manager + * @throws ShapeTreeException + */ + public getManager(): ShapeTreeManager /* throws ShapeTreeException */ { + if (!this.isExists()) { + return null; + } + let managerGraph: Graph = this.getGraph(this.getUrl()); + if (managerGraph === null) { + return null; + } + return ShapeTreeManager.getFromGraph(this.getUrl(), managerGraph); + } + + public getManagedResourceUrl(): URL { + return this.managedResourceUrl; + } +} diff --git a/asTypescript/packages/core/src/MissingManageableResource.ts b/asTypescript/packages/core/src/MissingManageableResource.ts new file mode 100644 index 00000000..bc9413ad --- /dev/null +++ b/asTypescript/packages/core/src/MissingManageableResource.ts @@ -0,0 +1,27 @@ +// Corresponding shapetrees-java package: com.janeirodigital.shapetrees.core +import { ShapeTreeResourceType } from './enums/ShapeTreeResourceType'; +import * as URL from 'java/net'; +import * as Optional from 'java/util'; +import { ManageableResource } from './ManageableResource'; +import { ResourceAttributes } from './ResourceAttributes'; + +/** + * A MissingManageableResource represents a state where a given + * ManageableResource at a URL does not exist. + */ +export class MissingManageableResource extends ManageableResource { + + /** + * Construct a missing manageable resource. + * @param url URL of the resource + * @param resourceType Identified shape tree resource type + * @param attributes Associated resource attributes + * @param body Body of the resource + * @param name Name of the resource + * @param managerResourceUrl URL of the shape tree manager resource + * @param isContainer Whether the resource is a container + */ + public constructor(url: URL, resourceType: ShapeTreeResourceType, attributes: ResourceAttributes, body: string, name: string, managerResourceUrl: Optional, isContainer: boolean) { + super(url, resourceType, attributes, body, name, false, managerResourceUrl, isContainer); + } +} diff --git a/asTypescript/packages/core/src/MissingManagerResource.ts b/asTypescript/packages/core/src/MissingManagerResource.ts new file mode 100644 index 00000000..946f000b --- /dev/null +++ b/asTypescript/packages/core/src/MissingManagerResource.ts @@ -0,0 +1,35 @@ +// Corresponding shapetrees-java package: com.janeirodigital.shapetrees.core +import { ShapeTreeResourceType } from './enums/ShapeTreeResourceType'; +import * as URL from 'java/net'; +import { MissingManageableResource } from './MissingManageableResource'; +import { ManagerResource } from './ManagerResource'; +import { ResourceAttributes } from './ResourceAttributes'; + +/** + * A MissingManagerResource represents a state where a given + * ManagerResource at a URL does not exist. + */ +export class MissingManagerResource extends ManagerResource { + + /** + * Construct a missing manager resource based on a MissingManageableResource + * @param manageable Missing manageable resource + * @param managedUrl Corresponding URL of the resource that would be managed + */ + public constructor(manageable: MissingManageableResource, managedUrl: URL) { + super(manageable.getUrl(), manageable.getResourceType(), manageable.getAttributes(), manageable.getBody(), manageable.getName(), manageable.isExists(), managedUrl); + } + + /** + * Construct a missing manager resource. + * @param url URL of the resource + * @param resourceType Identified shape tree resource type + * @param attributes Associated resource attributes + * @param body Body of the resource + * @param name Name of the resource + * @param managedResourceUrl URL of the resource that would be managed + */ + public constructor(url: URL, resourceType: ShapeTreeResourceType, attributes: ResourceAttributes, body: string, name: string, managedResourceUrl: URL) { + super(url, resourceType, attributes, body, name, false, managedResourceUrl); + } +} diff --git a/asTypescript/packages/core/src/ResourceAccessor.ts b/asTypescript/packages/core/src/ResourceAccessor.ts new file mode 100644 index 00000000..0b1977a2 --- /dev/null +++ b/asTypescript/packages/core/src/ResourceAccessor.ts @@ -0,0 +1,113 @@ +// Corresponding shapetrees-java package: com.janeirodigital.shapetrees.core +import { ShapeTreeException } from './exceptions/ShapeTreeException'; +import * as URL from 'java/net'; +import { ManageableInstance } from './ManageableInstance'; +import { InstanceResource } from './InstanceResource'; +import { ShapeTreeContext } from './ShapeTreeContext'; +import { DocumentResponse } from './DocumentResponse'; +import { ManagerResource } from './ManagerResource'; +import { ResourceAttributes } from './ResourceAttributes'; + +/** + * Interface used by the shape trees core for accessing {@link ManageableInstance}s + * and individual {@link InstanceResource}s. + * + *

Depending upon the context, this could be implemented by a ResourceAccessor implementation + * accessing a database or filesystem (typical of server-side processing), or by a ResourceAccessor + * implementation that is working with remote resources over http (typical of client-side processing).

+ * + *

Note that create and update methods make the assumption that requests to do so are + * originating from HTTP requests regardless of context (hence the inclusion of method, + * headers, and contentType).

+ * + *

Deletion and Update of {@link ManageableInstance}s aren't supported, as both should be targeted + * specifically to either a {@link ManageableResource} or {@link ManagerResource} with + * {@link #deleteResource(ShapeTreeContext, ManagerResource) deleteResource} or + * {@link #updateResource(ShapeTreeContext, String, InstanceResource, String) updateResource}.

+ */ +export interface ResourceAccessor { + + /** + * Return a {@link ManageableInstance} constructed starting with the resource identified by the provided + * resourceUrl. The resourceUrl may target either a {@link ManageableResource}, + * or a {@link ManagerResource}. + * + *

Both the {@link ManageableResource} and {@link ManagerResource} are retrieved and loaded as specifically + * typed sub-classes that indicate whether they exist, or (in the case of {@link ManageableResource}) + * whether they are managed.

+ * @param context {@link ShapeTreeContext} + * @param resourceUrl URL of the resource to get + * @return {@link ManageableInstance} including {@link ManageableResource} and {@link ManagerResource} + * @throws ShapeTreeException + */ + getInstance(context: ShapeTreeContext, resourceUrl: URL): ManageableInstance /* throws ShapeTreeException */; + + /** + * Gets a {@link ManageableInstance} by first creating the resource identified by the provided + * resourceUrl, which could mean creating either a {@link ManageableResource} or a {@link ManagerResource}. + * The newly created resource is loaded into the instance, and the corresponding {@link ManageableResource} or + * {@link ManagerResource} is looked up and loaded into the instance alongside it. They are loaded as specifically + * typed sub-classes that indicate whether they exist, or (in the case of {@link ManageableResource}), + * whether they are managed. + * @param context {@link ShapeTreeContext} + * @param method Incoming HTTP method triggering resource creation + * @param resourceUrl URL of the resource to create + * @param headers Incoming HTTP headers + * @param body Body of the resource to create + * @param contentType Content-type of the resource to create + * @return {@link ManageableInstance} including {@link ManageableResource} and {@link ManagerResource} + * @throws ShapeTreeException + */ + createInstance(context: ShapeTreeContext, method: string, resourceUrl: URL, headers: ResourceAttributes, body: string, contentType: string): ManageableInstance /* throws ShapeTreeException */; + + /** + * Gets a list of {@link ManageableInstance}s contained in the container at the containerResourceUrl. + * @param context {@link ShapeTreeContext} + * @param containerResourceUrl URL of the target container + * @return List of contained {@link ManageableInstance}s + * @throws ShapeTreeException + */ + getContainedInstances(context: ShapeTreeContext, containerResourceUrl: URL): Array /* throws ShapeTreeException */; + + /** + * Gets a specific {@link InstanceResource} identified by the provided resourceUrl. + * @param context {@link ShapeTreeContext} + * @param resourceUrl URL of the target resource to get + * @return {@link InstanceResource} + * @throws ShapeTreeException + */ + getResource(context: ShapeTreeContext, resourceUrl: URL): InstanceResource /* throws ShapeTreeException */; + + /** + * Creates a specific {@link InstanceResource} identified by the provided resourceUrl. + * @param context {@link ShapeTreeContext} + * @param method Incoming HTTP method triggering resource creation + * @param resourceUrl URL of the resource to create + * @param headers Incoming HTTP headers + * @param body Body of the resource to create + * @param contentType Content-type of the resource to create + * @return {@link InstanceResource} + * @throws ShapeTreeException + */ + createResource(context: ShapeTreeContext, method: string, resourceUrl: URL, headers: ResourceAttributes, body: string, contentType: string): InstanceResource /* throws ShapeTreeException */; + + /** + * Updates a specific {@link InstanceResource} identified by the provided updatedResource + * @param context {@link ShapeTreeContext} + * @param method Incoming HTTP method triggering resource update + * @param updatedResource {@link InstanceResource} to update + * @param body Updated body of the {@link InstanceResource} + * @return Updated {@link InstanceResource} + * @throws ShapeTreeException + */ + updateResource(context: ShapeTreeContext, method: string, updatedResource: InstanceResource, body: string): DocumentResponse /* throws ShapeTreeException */; + + /** + * Deletes a specific {@link InstanceResource} identified by the provided updatedResource + * @param context {@link ShapeTreeContext} + * @param deleteResource {@link InstanceResource} to delete + * @return Resultant {@link DocumentResponse} + * @throws ShapeTreeException + */ + deleteResource(context: ShapeTreeContext, deleteResource: ManagerResource): DocumentResponse /* throws ShapeTreeException */; +} diff --git a/asTypescript/packages/core/src/ResourceAttributes.ts b/asTypescript/packages/core/src/ResourceAttributes.ts new file mode 100644 index 00000000..1d1a3957 --- /dev/null +++ b/asTypescript/packages/core/src/ResourceAttributes.ts @@ -0,0 +1,195 @@ +// Corresponding shapetrees-java package: com.janeirodigital.shapetrees.core +import { ShapeTreeException } from './exceptions/ShapeTreeException'; +import * as Slf4j from 'lombok/extern/slf4j'; +import * as util from 'java'; +import * as Matcher from 'java/util/regex'; +import * as Pattern from 'java/util/regex'; +import * as requireNonNull from 'java/util/Objects'; + +/** + * The HttpClientHeaders object is a multi-map with some constructors and put-ers tailored to the + * shapetrees-java libraries. The only behavior that's at all HTTP-specific is the + * parseLinkHeaders factory which includes logic for HTTP Link headers. + */ +@Slf4j +export class ResourceAttributes { + + myMapOfLists: Map>; + + /** + * construct a case-insensitive ResourceAttributes container + */ + public constructor() { + this.myMapOfLists = new TreeMap<>(String.CASE_INSENSITIVE_ORDER); + } + + /** + * construct a case-insensitive ResourceAttributes container and set attr to value if both are not null. + * @param attr attribute (header) name to set + * @param value String value to assign to attr + */ + public constructor(attr: string, value: string) throws ShapeTreeException { + this.myMapOfLists = new TreeMap<>(String.CASE_INSENSITIVE_ORDER); + this.maybeSet(attr, value); + } + + /** + * Construct ResourceAttributes with passed map, which may be case-sensitive. + * @param newMap replacement for myMapOfLists + */ + public constructor(newMap: Map>) { + this.myMapOfLists = newMap; + } + + // copy constructor + private copy(): ResourceAttributes { + let ret: ResourceAttributes = new ResourceAttributes(); + for (let entry: Map.Entry> : this.myMapOfLists.entrySet()) { + ret.myMapOfLists.put(entry.getKey(), new ArrayList<>(entry.getValue())); + } + return ret; + } + + /** + * Re-use HttpClientHeaders to capture link headers as a mapping from link relation to list of values + * This is really a constructor but a named static function clarifies its intention. + * @param headerValues Header values for Link headers + * @return subset of this matching the pattern + */ + public static parseLinkHeaders(headerValues: List): ResourceAttributes { + let linkHeaderMap: ResourceAttributes = new ResourceAttributes(); + for (let headerValue: string : headerValues) { + let matcher: Matcher = LINK_HEADER_PATTERN.matcher(headerValue); + // if (matcher.matches() && matcher.groupCount() >= 2) { + if (matcher.matches()) { + let uri: string = matcher.group(1); + let rel: string = matcher.group(2); + linkHeaderMap.myMapOfLists.computeIfAbsent(rel, k -> new ArrayList<>()); + linkHeaderMap.myMapOfLists.get(rel).add(uri); + } else { + log.warn("Unable to parse link header: [{}]", headerValue); + } + } + return linkHeaderMap; + } + + /** + * make a new HttpClientHeaders with the additional attr/value set. + * @param attr attribute (header) name to set + * @param value String value to assign to attr + * @return original HttpClientHeaders if no change is made; otherwise a new copy. + */ + public maybePlus(attr: string, value: string): ResourceAttributes { + if (attr === null || value === null) { + return this; + } + let ret: ResourceAttributes = copy(); + ret.maybeSet(attr, value); + return ret; + } + + /** + * set attr to value if both are not null. + * @param attr attribute (header) name to set + * @param value String value to assign to attr + */ + /*@SneakyThrows*/ + public maybeSet(attr: string, value: string): void { + if (attr === null || value === null) { + return; + } + if (this.myMapOfLists.containsKey(attr)) { + let existingValues: List = this.myMapOfLists.get(attr); + let alreadySet: boolean = existingValues.stream().anyMatch(s -> s === value); + if (!alreadySet) { + existingValues.add(value); + } + /* else { + throw new Exception(attr + ": " + value + " already set."); + }*/ + } else { + let list: ArrayList = new ArrayList(); + list.add(value); + this.myMapOfLists.put(attr, list); + } + } + + /** + * replaces the list of attrs (without regard to nulls) + * @param attr attribute (header) name to set + * @param values String values to assign to attr + */ + public setAll(attr: string, values: List): void { + this.myMapOfLists.put(attr, values); + } + + /** + * Returns a map of attributes to lists of values + */ + public toMultimap(): Map> { + return this.myMapOfLists; + } + + /** + * Returns an array with alternating attributes and values. + * @param exclusions set of headers to exclude from returned array. + * (This is useful for HttpRequest.Builder().) + */ + public toList(...exclusions: string): string[] { + let ret: List = new ArrayList<>(); + for (let entry: Map.Entry> : this.myMapOfLists.entrySet()) { + let attr: string = entry.getKey(); + if (!Arrays.stream(exclusions).anyMatch(s -> s === attr)) { + for (let value: string : entry.getValue()) { + ret.add(attr); + ret.add(value); + } + } + } + return ret.stream().toArray(string[]::new); + } + + /** + * Returns an {@link Optional} containing the first header string value of + * the given named (and possibly multi-valued) header. If the header is not + * present, then the returned {@code Optional} is empty. + * + * @param name the header name + * @return an {@code Optional} containing the first named header + * string value, if present + */ + public firstValue(name: string): Optional { + return allValues(name).stream().findFirst(); + } + + /** + * Returns an unmodifiable List of all of the header string values of the + * given named header. Always returns a List, which may be empty if the + * header is not present. + * + * @param name the header name + * @return a List of headers string values + */ + public allValues(name: string): List { + requireNonNull(name); + let values: List = toMultimap().get(name); + // Making unmodifiable list out of empty in order to make a list which + // throws UOE unconditionally + return values != null ? values : List.of(); + } + + public toString(): string { + let sb: StringBuilder = new StringBuilder(); + for (let entry: Map.Entry> : this.myMapOfLists.entrySet()) { + for (let value: string : entry.getValue()) { + if (sb.length() != 0) { + sb.append(","); + } + sb.append(entry.getKey()).append("=").append(value); + } + } + return sb.toString(); + } + + private static readonly LINK_HEADER_PATTERN: Pattern = Pattern.compile("^<(.*?)>\\s*;\\s*rel\\s*=\"(.*?)\"\\s*"); +} diff --git a/asTypescript/packages/core/src/SchemaCache.ts b/asTypescript/packages/core/src/SchemaCache.ts new file mode 100644 index 00000000..462458c5 --- /dev/null +++ b/asTypescript/packages/core/src/SchemaCache.ts @@ -0,0 +1,72 @@ +// Corresponding shapetrees-java package: com.janeirodigital.shapetrees.core +import { ShapeTreeException } from './exceptions/ShapeTreeException'; +import * as ShexSchema from 'fr/inria/lille/shexjava/schema'; +import * as Slf4j from 'lombok/extern/slf4j'; +import * as URL from 'java/net'; +import * as HashMap from 'java/util'; + +/** + * Optional, static cache for pre-compiled ShEx schemas + */ +@Slf4j +export class SchemaCache { + + private constructor() { + } + + public static readonly CACHE_IS_NOT_INITIALIZED: string = "Cache is not initialized"; + + private static cache: Map = null; + + public static initializeCache(): void { + cache = new HashMap<>(); + } + + public static initializeCache(existingCache: Map): void { + cache = existingCache; + } + + public static isInitialized(): boolean { + let initialized: boolean = cache != null; + log.debug("Cache initialized set to {}", initialized); + return initialized; + } + + public static containsSchema(schemaUrl: URL): boolean /* throws ShapeTreeException */ { + log.debug("Determining if cache contains schema {}", schemaUrl); + if (cache === null) { + throw new ShapeTreeException(500, CACHE_IS_NOT_INITIALIZED); + } + return cache.containsKey(schemaUrl); + } + + public static getSchema(schemaUrl: URL): ShexSchema /* throws ShapeTreeException */ { + log.debug("Getting schema {}", schemaUrl); + if (cache === null) { + throw new ShapeTreeException(500, CACHE_IS_NOT_INITIALIZED); + } + return cache.get(schemaUrl); + } + + public static putSchema(schemaUrl: URL, schema: ShexSchema): void /* throws ShapeTreeException */ { + log.debug("Caching schema {}", schemaUrl.toString()); + if (cache === null) { + throw new ShapeTreeException(500, CACHE_IS_NOT_INITIALIZED); + } + cache.put(schemaUrl, schema); + } + + public static clearCache(): void /* throws ShapeTreeException */ { + if (cache === null) { + throw new ShapeTreeException(500, CACHE_IS_NOT_INITIALIZED); + } + cache.clear(); + } + + public static unInitializeCache(): void /* throws ShapeTreeException */ { + if (cache != null) { + cache.clear(); + } + cache = null; + } +} diff --git a/asTypescript/packages/core/src/ShapeTree.ts b/asTypescript/packages/core/src/ShapeTree.ts new file mode 100644 index 00000000..cf96b4c1 --- /dev/null +++ b/asTypescript/packages/core/src/ShapeTree.ts @@ -0,0 +1,295 @@ +// Corresponding shapetrees-java package: com.janeirodigital.shapetrees.core +import { ShapeTreeContainsPriority } from './comparators/ShapeTreeContainsPriority'; +import { DocumentLoaderManager } from './contentloaders/DocumentLoaderManager'; +import { HttpHeaders } from './enums/HttpHeaders'; +import { RecursionMethods } from './enums/RecursionMethods'; +import { ShapeTreeResourceType } from './enums/ShapeTreeResourceType'; +import { ShapeTreeException } from './exceptions/ShapeTreeException'; +import { GraphHelper } from './helpers/GraphHelper'; +import * as GlobalFactory from 'fr/inria/lille/shexjava'; +import * as Label from 'fr/inria/lille/shexjava/schema'; +import * as ShexSchema from 'fr/inria/lille/shexjava/schema'; +import * as ShExCParser from 'fr/inria/lille/shexjava/schema/parsing'; +import * as RecursiveValidation from 'fr/inria/lille/shexjava/validation'; +import * as ValidationAlgorithm from 'fr/inria/lille/shexjava/validation'; +import * as Slf4j from 'lombok/extern/slf4j'; +import * as IRI from 'org/apache/commons/rdf/api'; +import * as JenaRDF from 'org/apache/commons/rdf/jena'; +import * as Graph from 'org/apache/jena/graph'; +import * as GraphUtil from 'org/apache/jena/graph'; +import * as Node from 'org/apache/jena/graph'; +import * as NotNull from 'org/jetbrains/annotations'; +import * as MalformedURLException from 'java/net'; +import * as URL from 'java/net'; +import * as StandardCharsets from 'java/nio/charset'; +import * as util from 'java'; +import { urlToUri } from './helpers/GraphHelper/urlToUri'; +import { ShapeTreeReference } from './ShapeTreeReference'; +import { DocumentResponse } from './DocumentResponse'; +import { ManageableResource } from './ManageableResource'; +import { ValidationResult } from './ValidationResult'; + +@Slf4j +export class ShapeTree { + + @NotNull + private readonly id: URL; + + @NotNull + private readonly expectedResourceType: URL; + + private readonly shape: URL; + + private readonly label: string; + + @NotNull + private readonly contains: List; + + @NotNull + private readonly references: List; + + public constructor(@NotNull id: URL, @NotNull expectedResourceType: URL, label: string, shape: URL, @NotNull references: List, @NotNull contains: List) { + this.id = id; + this.expectedResourceType = expectedResourceType; + this.label = label; + this.shape = shape; + this.references = references; + this.contains = contains; + } + + public validateResource(targetResource: ManageableResource): ValidationResult /* throws ShapeTreeException */ { + return validateResource(targetResource, null); + } + + public validateResource(targetResource: ManageableResource, focusNodeUrls: List): ValidationResult /* throws ShapeTreeException */ { + let bodyGraph: Graph = null; + if (targetResource.getResourceType() != ShapeTreeResourceType.NON_RDF) { + bodyGraph = GraphHelper.readStringIntoGraph(urlToUri(targetResource.getUrl()), targetResource.getBody(), targetResource.getAttributes().firstValue(HttpHeaders.CONTENT_TYPE.getValue()).orElse(null)); + } + return validateResource(targetResource.getName(), targetResource.getResourceType(), bodyGraph, focusNodeUrls); + } + + public validateResource(requestedName: string, resourceType: ShapeTreeResourceType, bodyGraph: Graph, focusNodeUrls: List): ValidationResult /* throws ShapeTreeException */ { + // Check whether the proposed resource is the same type as what is expected by the shape tree + if (!this.expectedResourceType.toString() === resourceType.getValue()) { + return new ValidationResult(false, this, "Resource type " + resourceType + " is invalid. Expected " + this.expectedResourceType); + } + // If a label is specified, check if the proposed name is the same + if (this.label != null && !this.label === requestedName) { + return new ValidationResult(false, this, "Proposed resource name " + requestedName + " is invalid. Expected " + this.label); + } + // If the shape tree specifies a shape to validate, perform shape validation + if (this.shape != null) { + if (focusNodeUrls === null) { + focusNodeUrls = Collections.emptyList(); + } + return this.validateGraph(bodyGraph, focusNodeUrls); + } + // Allow if we fall through to here. Focus node is set to null because we only get here if no shape validation was performed + return new ValidationResult(true, this, this, null); + } + + public validateGraph(graph: Graph, focusNodeUrls: List): ValidationResult /* throws ShapeTreeException */ { + // if (true) return new ValidationResult(true, this, this, focusNodeUrl); // [debug] ShExC parser brings debugger to its knees + if (this.shape === null) { + throw new ShapeTreeException(400, "Attempting to validate a shape for ShapeTree " + this.id + "but it doesn't specify one"); + } + let schema: ShexSchema; + if (SchemaCache.isInitialized() && SchemaCache.containsSchema(this.shape)) { + log.debug("Found cached schema {}", this.shape); + schema = SchemaCache.getSchema(this.shape); + } else { + log.debug("Did not find schema in cache {} will retrieve and parse", this.shape); + let shexShapeContents: DocumentResponse = DocumentLoaderManager.getLoader().loadExternalDocument(this.shape); + if (shexShapeContents === null || shexShapeContents.getBody() === null || shexShapeContents.getBody().isEmpty()) { + throw new ShapeTreeException(400, "Attempting to validate a ShapeTree (" + this.id + ") - Shape at (" + this.shape + ") is not found or is empty"); + } + let shapeBody: string = shexShapeContents.getBody(); + let stream: InputStream = new ByteArrayInputStream(shapeBody.getBytes(StandardCharsets.UTF_8)); + let shexCParser: ShExCParser = new ShExCParser(); + try { + schema = new ShexSchema(GlobalFactory.RDFFactory, shexCParser.getRules(stream), shexCParser.getStart()); + if (SchemaCache.isInitialized()) { + SchemaCache.putSchema(this.shape, schema); + } + } catch (ex: Exception) { + throw new ShapeTreeException(500, "Error parsing ShEx schema - " + ex.getMessage()); + } + } + // Tell ShExJava we want to use Jena as our graph library + let jenaRDF: JenaRDF = new org.apache.commons.rdf.jena.JenaRDF(); + GlobalFactory.RDFFactory = jenaRDF; + let validation: ValidationAlgorithm = new RecursiveValidation(schema, jenaRDF.asGraph(graph)); + let shapeLabel: Label = new Label(GlobalFactory.RDFFactory.createIRI(this.shape.toString())); + if (!focusNodeUrls.isEmpty()) { + // One or more focus nodes were provided for validation + for (let focusNodeUrl: URL : focusNodeUrls) { + // Evaluate each provided focus node + let focusNode: IRI = GlobalFactory.RDFFactory.createIRI(focusNodeUrl.toString()); + log.debug("Validating Shape Label = {}, Focus Node = {}", shapeLabel.toPrettyString(), focusNode.getIRIString()); + validation.validate(focusNode, shapeLabel); + let valid: boolean = validation.getTyping().isConformant(focusNode, shapeLabel); + if (valid) { + return new ValidationResult(valid, this, this, focusNodeUrl); + } + } + // None of the provided focus nodes were valid - this will return the last failure + return new ValidationResult(false, this, "Failed to validate: " + shapeLabel.toPrettyString()); + } else { + // No focus nodes were provided for validation, so all subject nodes will be evaluated + let evaluateNodes: List = GraphUtil.listSubjects(graph, Node.ANY, Node.ANY).toList(); + for (let evaluateNode: Node : evaluateNodes) { + const focusUriString: string = evaluateNode.getURI(); + let node: IRI = GlobalFactory.RDFFactory.createIRI(focusUriString); + validation.validate(node, shapeLabel); + let valid: boolean = validation.getTyping().isConformant(node, shapeLabel); + if (valid) { + const matchingFocusNode: URL; + try { + matchingFocusNode = new URL(focusUriString); + } catch (ex: MalformedURLException) { + throw new ShapeTreeException(500, "Error reporting validation success on malformed URL <" + focusUriString + ">: " + ex.getMessage()); + } + return new ValidationResult(valid, this, this, matchingFocusNode); + } + } + return new ValidationResult(false, this, "Failed to validate: " + shapeLabel.toPrettyString()); + } + } + + public validateContainedResource(containedResource: ManageableResource): ValidationResult /* throws ShapeTreeException */ { + if (this.contains === null || this.contains.isEmpty()) { + // TODO: say it can't be null? + // The contained resource is permitted because this shape tree has no restrictions on what it contains + return new ValidationResult(true, this, this, null); + } + return validateContainedResource(containedResource, Collections.emptyList(), Collections.emptyList()); + } + + public validateContainedResource(containedResource: ManageableResource, targetShapeTreeUrls: List, focusNodeUrls: List): ValidationResult /* throws ShapeTreeException */ { + let containedResourceGraph: Graph = null; + if (containedResource.getResourceType() != ShapeTreeResourceType.NON_RDF) { + containedResourceGraph = GraphHelper.readStringIntoGraph(urlToUri(containedResource.getUrl()), containedResource.getBody(), containedResource.getAttributes().firstValue(HttpHeaders.CONTENT_TYPE.getValue()).orElse(null)); + } + return validateContainedResource(containedResource.getName(), containedResource.getResourceType(), targetShapeTreeUrls, containedResourceGraph, focusNodeUrls); + } + + public validateContainedResource(requestedName: string, resourceType: ShapeTreeResourceType, targetShapeTreeUrls: List, bodyGraph: Graph, focusNodeUrls: List): ValidationResult /* throws ShapeTreeException */ { + if (this.contains === null || this.contains.isEmpty()) { + // The contained resource is permitted because this shape tree has no restrictions on what it contains + return new ValidationResult(true, this, this, null); + } + // If one or more target shape trees have been supplied + if (!targetShapeTreeUrls.isEmpty()) { + // Test each supplied target shape tree + for (let targetShapeTreeUrl: URL : targetShapeTreeUrls) { + // Check if it exists in st:contains + if (this.contains.contains(targetShapeTreeUrl)) { + let targetShapeTree: ShapeTree = ShapeTreeFactory.getShapeTree(targetShapeTreeUrl); + // Evaluate the shape tree against the attributes of the proposed resources + let result: ValidationResult = targetShapeTree.validateResource(requestedName, resourceType, bodyGraph, focusNodeUrls); + if (Boolean.TRUE === result.getValid()) { + // Return a successful validation result, including the matching shape tree + return new ValidationResult(true, this, targetShapeTree, result.getMatchingFocusNode()); + } + } + } + // None of the provided target shape trees matched + return new ValidationResult(false, null, "Failed to validate " + targetShapeTreeUrls); + } else { + // For each shape tree in st:contains + for (let containsShapeTreeUrl: URL : getPrioritizedContains()) { + let containsShapeTree: ShapeTree = ShapeTreeFactory.getShapeTree(containsShapeTreeUrl); + // Continue if the shape tree isn't gettable + if (containsShapeTree === null) { + continue; + } + // Evaluate the shape tree against the attributes of the proposed resources + let result: ValidationResult = containsShapeTree.validateResource(requestedName, resourceType, bodyGraph, focusNodeUrls); + // Continue if the proposed attributes were not a match + if (Boolean.FALSE === result.getValid()) { + continue; + } + // Return the successful validation result + return new ValidationResult(true, this, containsShapeTree, result.getMatchingFocusNode()); + } + } + return new ValidationResult(false, null, "Failed to validate shape tree: " + this.id); + } + + public getReferencedShapeTrees(): Iterator /* throws ShapeTreeException */ { + return getReferencedShapeTrees(RecursionMethods.DEPTH_FIRST); + } + + public getReferencedShapeTrees(recursionMethods: RecursionMethods): Iterator /* throws ShapeTreeException */ { + return getReferencedShapeTreesList(recursionMethods).iterator(); + } + + // Return the list of shape tree contains by priority from most to least strict + public getPrioritizedContains(): List { + let prioritized: List = new ArrayList<>(this.contains); + Collections.sort(prioritized, new ShapeTreeContainsPriority()); + return prioritized; + } + + private getReferencedShapeTreesList(recursionMethods: RecursionMethods): List /* throws ShapeTreeException */ { + if (recursionMethods === RecursionMethods.BREADTH_FIRST) { + return getReferencedShapeTreesListBreadthFirst(); + } else { + let referencedShapeTrees: List = new ArrayList<>(); + return getReferencedShapeTreesListDepthFirst(this.getReferences(), referencedShapeTrees); + } + } + + private getReferencedShapeTreesListBreadthFirst(): List /* throws ShapeTreeException */ { + let referencedShapeTrees: List = new ArrayList<>(); + let queue: Queue = new LinkedList<>(this.getReferences()); + while (!queue.isEmpty()) { + let currentShapeTree: ShapeTreeReference = queue.poll(); + referencedShapeTrees.add(currentShapeTree); + let shapeTree: ShapeTree = ShapeTreeFactory.getShapeTree(currentShapeTree.getReferenceUrl()); + if (shapeTree != null) { + let currentReferencedShapeTrees: List = shapeTree.getReferences(); + if (currentReferencedShapeTrees != null) { + queue.addAll(currentReferencedShapeTrees); + } + } + } + return referencedShapeTrees; + } + + private getReferencedShapeTreesListDepthFirst(currentReferencedShapeTrees: List, referencedShapeTrees: List): List /* throws ShapeTreeException */ { + for (let currentShapeTreeReference: ShapeTreeReference : currentReferencedShapeTrees) { + referencedShapeTrees.add(currentShapeTreeReference); + let currentReferencedShapeTree: ShapeTree = ShapeTreeFactory.getShapeTree(currentShapeTreeReference.getReferenceUrl()); + if (currentReferencedShapeTree != null) { + referencedShapeTrees = getReferencedShapeTreesListDepthFirst(currentReferencedShapeTree.getReferences(), referencedShapeTrees); + } + } + return referencedShapeTrees; + } + + public getId(): URL { + return this.id; + } + + public getExpectedResourceType(): URL { + return this.expectedResourceType; + } + + public getShape(): URL { + return this.shape; + } + + public getLabel(): string { + return this.label; + } + + public getContains(): List { + return this.contains; + } + + public getReferences(): List { + return this.references; + } +} diff --git a/asTypescript/packages/core/src/ShapeTreeAssignment.ts b/asTypescript/packages/core/src/ShapeTreeAssignment.ts new file mode 100644 index 00000000..f16be483 --- /dev/null +++ b/asTypescript/packages/core/src/ShapeTreeAssignment.ts @@ -0,0 +1,131 @@ +// Corresponding shapetrees-java package: com.janeirodigital.shapetrees.core +import { ShapeTreeException } from './exceptions/ShapeTreeException'; +import { RdfVocabulary } from './vocabularies/RdfVocabulary'; +import { ShapeTreeVocabulary } from './vocabularies/ShapeTreeVocabulary'; +import * as Graph from 'org/apache/jena/graph'; +import * as Node from 'org/apache/jena/graph'; +import * as NodeFactory from 'org/apache/jena/graph'; +import * as Triple from 'org/apache/jena/graph'; +import * as MalformedURLException from 'java/net'; +import * as URL from 'java/net'; +import * as Objects from 'java/util'; + +/** + * ShapeTreeAssignment + * + * Shape Trees, §3: Each shape tree assignment identifies a shape tree associated with the managed resource, + * the focus node for shape validation, and the information needed to navigate the physical hierarchy in + * which that managed resource resides. + * https://shapetrees.org/TR/specification/#manager + */ +@EqualsAndHashCode +export class ShapeTreeAssignment { + + // Identifies the shape tree to be associated with the managed resource + private readonly shapeTree: URL; + + // Identifies the resource managed by the shape tree assignment + private readonly managedResource: URL; + + // Identifies the root shape tree assignment + private readonly rootAssignment: URL; + + // Identifies the focus node for shape validation in the managed resource + private readonly focusNode: URL; + + // Identifies the shape to which focusNode must conform + private readonly shape: URL; + + private readonly url: URL; + + public constructor(shapeTree: URL, managedResource: URL, rootAssignment: URL, focusNode: URL, shape: URL, url: URL) throws ShapeTreeException { + try { + this.shapeTree = Objects.requireNonNull(shapeTree, "Must provide an assigned shape tree"); + this.managedResource = Objects.requireNonNull(managedResource, "Must provide a shape tree context"); + this.rootAssignment = Objects.requireNonNull(rootAssignment, "Must provide a root shape tree assignment"); + this.url = Objects.requireNonNull(url, "Must provide a url for shape tree assignment"); + if (shape != null) { + this.shape = shape; + this.focusNode = Objects.requireNonNull(focusNode, "Must provide a focus node for shape validation"); + } else { + this.shape = null; + if (focusNode != null) { + throw new IllegalStateException("Cannot provide a focus node when no shape has been provided"); + } + this.focusNode = null; + } + } catch (ex: NullPointerException | IllegalStateException) { + throw new ShapeTreeException(500, "Failed to initialize shape tree assignment: " + ex.getMessage()); + } + } + + public static getFromGraph(url: URL, managerGraph: Graph): ShapeTreeAssignment /* throws MalformedURLException, ShapeTreeException */ { + let shapeTree: URL = null; + let managedResource: URL = null; + let rootAssignment: URL = null; + let focusNode: URL = null; + let shape: URL = null; + // Look up the ShapeTreeAssignment in the ManagerResource Graph via its URL + let assignmentTriples: Array = managerGraph.find(NodeFactory.createURI(url.toString()), Node.ANY, Node.ANY).toList(); + // A valid assignment must have at least a shape tree, managed resource, and root assignment urls + if (assignmentTriples.size() < 3) { + throw new IllegalStateException("Incomplete shape tree assignment, Only " + assignmentTriples.size() + " attributes found"); + } + // Lookup and assign each triple in the nested ShapeTreeAssignment + for (let assignmentTriple: Triple : assignmentTriples) { + switch(assignmentTriple.getPredicate().getURI()) { + case RdfVocabulary.TYPE: + if (!assignmentTriple.getObject().isURI() || !assignmentTriple.getObject().getURI() === ShapeTreeVocabulary.SHAPETREE_ASSIGNMENT) { + throw new IllegalStateException("Unexpected value: " + assignmentTriple.getPredicate().getURI()); + } + break; + case ShapeTreeVocabulary.ASSIGNS_SHAPE_TREE: + shapeTree = new URL(assignmentTriple.getObject().getURI()); + break; + case ShapeTreeVocabulary.HAS_ROOT_ASSIGNMENT: + rootAssignment = new URL(assignmentTriple.getObject().getURI()); + break; + case ShapeTreeVocabulary.MANAGES_RESOURCE: + managedResource = new URL(assignmentTriple.getObject().getURI()); + break; + case ShapeTreeVocabulary.SHAPE: + shape = new URL(assignmentTriple.getObject().getURI()); + break; + case ShapeTreeVocabulary.FOCUS_NODE: + focusNode = new URL(assignmentTriple.getObject().getURI()); + break; + default: + throw new IllegalStateException("Unexpected value: " + assignmentTriple.getPredicate().getURI()); + } + } + return new ShapeTreeAssignment(shapeTree, managedResource, rootAssignment, focusNode, shape, url); + } + + public isRootAssignment(): boolean { + return this.getUrl() === this.getRootAssignment(); + } + + public getShapeTree(): URL { + return this.shapeTree; + } + + public getManagedResource(): URL { + return this.managedResource; + } + + public getRootAssignment(): URL { + return this.rootAssignment; + } + + public getFocusNode(): URL { + return this.focusNode; + } + + public getShape(): URL { + return this.shape; + } + + public getUrl(): URL { + return this.url; + } +} diff --git a/asTypescript/packages/core/src/ShapeTreeContext.ts b/asTypescript/packages/core/src/ShapeTreeContext.ts new file mode 100644 index 00000000..a588d764 --- /dev/null +++ b/asTypescript/packages/core/src/ShapeTreeContext.ts @@ -0,0 +1,13 @@ +// Corresponding shapetrees-java package: com.janeirodigital.shapetrees.core +export class ShapeTreeContext { + + private authorizationHeaderValue: string; + + public constructor(authorizationHeaderValue: string) { + this.authorizationHeaderValue = authorizationHeaderValue; + } + + public getAuthorizationHeaderValue(): string { + return this.authorizationHeaderValue; + } +} diff --git a/asTypescript/packages/core/src/ShapeTreeFactory.ts b/asTypescript/packages/core/src/ShapeTreeFactory.ts new file mode 100644 index 00000000..fd45a29a --- /dev/null +++ b/asTypescript/packages/core/src/ShapeTreeFactory.ts @@ -0,0 +1,212 @@ +// Corresponding shapetrees-java package: com.janeirodigital.shapetrees.core +import { ShapeTreeException } from './exceptions/ShapeTreeException'; +import { ShapeTreeVocabulary } from './vocabularies/ShapeTreeVocabulary'; +import * as Slf4j from 'lombok/extern/slf4j'; +import * as Node from 'org/apache/jena/graph'; +import * as Node_URI from 'org/apache/jena/graph'; +import * as model from 'org/apache/jena/rdf'; +import * as MalformedURLException from 'java/net'; +import * as URI from 'java/net'; +import * as URL from 'java/net'; +import * as ArrayList from 'java/util'; +import * as HashMap from 'java/util'; +import { urlToUri } from './helpers/GraphHelper/urlToUri'; +import { ShapeTreeReference } from './ShapeTreeReference'; +import { ShapeTree } from './ShapeTree'; +import { ShapeTreeResource } from './ShapeTreeResource'; + +/** + * Provides a factory to look up and initialize ShapeTrees. + * Includes a simple in-memory local cache to avoid repeated fetching of + * remote shape tree resources. + */ +@Slf4j +export class ShapeTreeFactory { + + private constructor() { + } + + private static readonly RDFS_LABEL: string = "http://www.w3.org/2000/01/rdf-schema#label"; + + @Getter + private static readonly localShapeTreeCache: Map = new HashMap<>(); + + /** + * Looks up and parses the shape tree at shapeTreeUrl. + * Shape trees linked via st:contains and st:references are parsed + * recursively. Maintains a cache to avoid parsing the same shape tree + * more than once. + * @param shapeTreeUrl URL of the shape tree to get + * @return Parsed and initialized shape tree + * @throws ShapeTreeException + */ + public static getShapeTree(shapeTreeUrl: URL): ShapeTree /* throws ShapeTreeException */ { + log.debug("Parsing shape tree: {}", shapeTreeUrl); + if (localShapeTreeCache.containsKey(urlToUri(shapeTreeUrl))) { + log.debug("[{}] previously cached -- returning", shapeTreeUrl.toString()); + return localShapeTreeCache.get(urlToUri(shapeTreeUrl)); + } + // Load the entire shape tree resource (which may contain multiple shape trees) + let shapeTreeResource: ShapeTreeResource = ShapeTreeResource.getShapeTreeResource(shapeTreeUrl); + let resourceModel: Model = shapeTreeResource.getModel(); + let shapeTreeNode: Resource = resourceModel.getResource(shapeTreeUrl.toString()); + // Load and set the expected resource type + const expectsType: URL = getUrlValue(resourceModel, shapeTreeNode, ShapeTreeVocabulary.EXPECTS_TYPE, shapeTreeUrl); + if (expectsType === null) + throw new ShapeTreeException(500, "Shape Tree :expectsType not found"); + // Load and set the Shape URL + const shape: URL = getUrlValue(resourceModel, shapeTreeNode, ShapeTreeVocabulary.SHAPE, shapeTreeUrl); + // Load and set Label + const label: string = getStringValue(resourceModel, shapeTreeNode, RDFS_LABEL); + // Load and set contains list + const contains: Array = getContains(resourceModel, shapeTreeNode, shapeTreeUrl); + // Load and set references list + const references: Array = getReferences(resourceModel, shapeTreeNode, shapeTreeUrl); + if (!contains.isEmpty() && !expectsType.toString() === ShapeTreeVocabulary.CONTAINER) { + throw new ShapeTreeException(400, "Only a container can be expected to have st:contains"); + } + let shapeTree: ShapeTree = new ShapeTree(shapeTreeUrl, expectsType, label, shape, references, contains); + localShapeTreeCache.put(urlToUri(shapeTreeUrl), shapeTree); + // Recursively parse contained shape trees + for (let containedUrl: URL : contains) { + getShapeTree(containedUrl); + } + // Recursively parse referenced shape trees + for (let reference: ShapeTreeReference : references) { + getShapeTree(reference.getReferenceUrl()); + } + return shapeTree; + } + + /** + * Get the list of URLs linked via st:contains by the shape tree being parsed. + * @param resourceModel RDF Model representing the shape tree resource + * @param shapeTreeNode RDF Node of the shape tree + * @param shapeTreeUrl URL of the shape tree + * @return List of URLs linked via st:contains + * @throws ShapeTreeException + */ + private static getContains(resourceModel: Model, shapeTreeNode: Resource, shapeTreeUrl: URL): Array /* throws ShapeTreeException */ { + try { + return getURLListValue(resourceModel, shapeTreeNode, ShapeTreeVocabulary.CONTAINS); + } catch (ex: MalformedURLException | ShapeTreeException) { + throw new ShapeTreeException(500, "List <" + shapeTreeUrl + "> contains malformed URL: " + ex.getMessage()); + } + } + + /** + * Get the list of ShapeTreeReferences linked via st:references by the shape tree being parsed. + * @param resourceModel RDF Model representing the shape tree resource + * @param shapeTreeNode RDF Node of the shape tree + * @param shapeTreeUrl URL of the shape tree + * @return List of ShapeTreeReferences linked via st:references + * @throws ShapeTreeException + */ + private static getReferences(resourceModel: Model, shapeTreeNode: Resource, shapeTreeUrl: URL): Array /* throws ShapeTreeException */ { + let references: ArrayList = new ArrayList<>(); + let referencesProperty: Property = resourceModel.createProperty(ShapeTreeVocabulary.REFERENCES); + if (shapeTreeNode.hasProperty(referencesProperty)) { + let referenceStatements: Array = shapeTreeNode.listProperties(referencesProperty).toList(); + for (let referenceStatement: Statement : referenceStatements) { + let referenceResource: Resource = referenceStatement.getObject().asResource(); + const referencedShapeTreeUrlString: string = getStringValue(resourceModel, referenceResource, ShapeTreeVocabulary.REFERENCES_SHAPE_TREE); + const referencedShapeTreeUrl: URL; + let referencedShapeTree: ShapeTreeReference; + try { + referencedShapeTreeUrl = new URL(referencedShapeTreeUrlString); + } catch (ex: MalformedURLException) { + throw new ShapeTreeException(500, "ShapeTree <" + shapeTreeUrl + "> references malformed URL <" + referencedShapeTreeUrlString + ">: " + ex.getMessage()); + } + let viaShapePath: string = getStringValue(resourceModel, referenceResource, ShapeTreeVocabulary.VIA_SHAPE_PATH); + let viaPredicate: URL = getUrlValue(resourceModel, referenceResource, ShapeTreeVocabulary.VIA_PREDICATE, shapeTreeUrl); + referencedShapeTree = new ShapeTreeReference(referencedShapeTreeUrl, viaShapePath, viaPredicate); + references.add(referencedShapeTree); + } + } + return references; + } + + /** + * Validate and get a single URL value linked to a shape tree by the supplied predicate. + * @param model RDF Model representing the shape tree resource + * @param resource RDF Node of the shape tree + * @param predicate Predicate to match + * @param shapeTreeUrl URL of the shape tree + * @return URL value linked via predicate + * @throws ShapeTreeException + */ + private static getUrlValue(model: Model, resource: Resource, predicate: string, shapeTreeUrl: URL): URL /* throws ShapeTreeException */ { + let property: Property = model.createProperty(predicate); + if (resource.hasProperty(property)) { + let statement: Statement = resource.getProperty(property); + const object: RDFNode = statement.getObject(); + if (object.isURIResource()) { + try { + return new URL(object.asResource().getURI()); + } catch (ex: MalformedURLException) { + throw new IllegalStateException("Malformed ShapeTree <" + shapeTreeUrl + ">: Jena URIResource <" + object + "> didn't parse as URL - " + ex.getMessage()); + } + } else { + throw new ShapeTreeException(500, "Malformed ShapeTree <" + shapeTreeUrl + ">: expected " + object + " to be a URL"); + } + } + return null; + } + + /** + * Validate and get a single String value linked to a shape tree by the supplied predicate. + * @param model RDF Model representing the shape tree resource + * @param resource RDF Node of the shape tree + * @param predicate Predicate to match + * @return String value linked via predicate + * @throws ShapeTreeException + */ + private static getStringValue(model: Model, resource: Resource, predicate: string): string /* throws ShapeTreeException */ { + let property: Property = model.createProperty(predicate); + if (resource.hasProperty(property)) { + let statement: Statement = resource.getProperty(property); + if (statement.getObject().isLiteral()) { + return statement.getObject().asLiteral().getString(); + } else if (statement.getObject().isURIResource()) { + return statement.getObject().asResource().getURI(); + } else { + throw new ShapeTreeException(500, "Cannot determine object type when converting from string for: " + predicate); + } + } + return null; + } + + /** + * Validate and get list of URLs linked to a shape tree by the supplied predicate. + * @param model RDF Model representing the shape tree resource + * @param resource RDF Node of the shape tree + * @param predicate Predicate to match + * @return List of URLs linked via predicate + * @throws MalformedURLException + * @throws ShapeTreeException + */ + private static getURLListValue(model: Model, resource: Resource, predicate: string): Array /* throws MalformedURLException, ShapeTreeException */ { + let urls: Array = new ArrayList<>(); + let property: Property = model.createProperty(predicate); + if (resource.hasProperty(property)) { + let propertyStatements: Array = resource.listProperties(property).toList(); + for (let propertyStatement: Statement : propertyStatements) { + let propertyNode: Node = propertyStatement.getObject().asNode(); + if (propertyNode instanceof Node_URI) { + let contentUrl: URL = new URL(propertyNode.getURI()); + urls.add(contentUrl); + } else { + throw new ShapeTreeException(500, "Must provide a valid URI in URI listing"); + } + } + } + return urls; + } + + /** + * Clears the local shape tree cache + */ + public static clearCache(): void { + localShapeTreeCache.clear(); + } +} diff --git a/asTypescript/packages/core/src/ShapeTreeManager.ts b/asTypescript/packages/core/src/ShapeTreeManager.ts new file mode 100644 index 00000000..df3a86af --- /dev/null +++ b/asTypescript/packages/core/src/ShapeTreeManager.ts @@ -0,0 +1,224 @@ +// Corresponding shapetrees-java package: com.janeirodigital.shapetrees.core +import { ShapeTreeException } from './exceptions/ShapeTreeException'; +import { GraphHelper } from './helpers/GraphHelper'; +import { RdfVocabulary } from './vocabularies/RdfVocabulary'; +import { ShapeTreeVocabulary } from './vocabularies/ShapeTreeVocabulary'; +import * as RandomStringUtils from 'org/apache/commons/lang3'; +import * as Graph from 'org/apache/jena/graph'; +import * as Node from 'org/apache/jena/graph'; +import * as NodeFactory from 'org/apache/jena/graph'; +import * as Triple from 'org/apache/jena/graph'; +import * as RDF from 'org/apache/jena/vocabulary'; +import * as MalformedURLException from 'java/net'; +import * as URI from 'java/net'; +import * as URL from 'java/net'; +import * as ArrayList from 'java/util'; +import { urlToUri } from './helpers/GraphHelper/urlToUri'; +import { ShapeTreeAssignment } from './ShapeTreeAssignment'; +import { ShapeTree } from './ShapeTree'; + +/** + * ShapeTreeManager + * + * Shape Trees, §3: A shape tree manager associates a managed resource with one or more shape trees. No more + * than one shape tree manager may be associated with a managed resource. A shape tree manager includes + * one or more shape tree assignments via st:hasAssignment. + * https://shapetrees.org/TR/specification/#manager + */ +export class ShapeTreeManager { + + private readonly id: URL; + + // Each ShapeTreeManager has one or more ShapeTreeAssignments + private readonly assignments: Array = new ArrayList<>(); + + /** + * Constructor for a new ShapeTreeManager + * @param id URL of the ShapeTreeManager resource + */ + public constructor(id: URL) { + this.id = id; + } + + /** + * Get the URL (identifier) of the ShapeTreeManager + * @return URL identifier of the ShapeTreeManager + */ + protected getUrl(): URL { + return this.id; + } + + /** + * Get the ShapeTreeManager as an RDF Graph + * @return Graph of the ShapeTreeManager + * @throws ShapeTreeException + */ + public getGraph(): Graph /* throws ShapeTreeException */ { + let managerGraph: Graph = GraphHelper.getEmptyGraph(); + let managerSubject: string = this.getUrl().toString(); + // <> a st:Manager + managerGraph.add(GraphHelper.newTriple(managerSubject, RDF.type.toString(), GraphHelper.knownUrl(ShapeTreeVocabulary.SHAPETREE_MANAGER))); + // For each assignment create a blank node and populate + for (let assignment: ShapeTreeAssignment : this.assignments) { + // <> st:hasAssignment , + managerGraph.add(GraphHelper.newTriple(managerSubject, ShapeTreeVocabulary.HAS_ASSIGNMENT, assignment.getUrl())); + const subject: URI = urlToUri(assignment.getUrl()); + managerGraph.add(GraphHelper.newTriple(subject, URI.create(ShapeTreeVocabulary.ASSIGNS_SHAPE_TREE), assignment.getShapeTree())); + managerGraph.add(GraphHelper.newTriple(subject, URI.create(ShapeTreeVocabulary.MANAGES_RESOURCE), assignment.getManagedResource())); + managerGraph.add(GraphHelper.newTriple(subject, URI.create(ShapeTreeVocabulary.HAS_ROOT_ASSIGNMENT), assignment.getRootAssignment())); + if (assignment.getShape() != null) { + managerGraph.add(GraphHelper.newTriple(subject, URI.create(ShapeTreeVocabulary.SHAPE), assignment.getShape())); + } + if (assignment.getFocusNode() != null) { + managerGraph.add(GraphHelper.newTriple(subject, URI.create(ShapeTreeVocabulary.FOCUS_NODE), assignment.getFocusNode())); + } + } + return managerGraph; + } + + /** + * Add a {@link com.janeirodigital.shapetrees.core.ShapeTreeAssignment} to the ShapeTreeManager. + * @param assignment Shape tree assignment to add + * @throws ShapeTreeException + */ + public addAssignment(assignment: ShapeTreeAssignment): void /* throws ShapeTreeException */ { + if (assignment === null) { + throw new ShapeTreeException(500, "Must provide a non-null assignment to an initialized List of assignments"); + } + if (!this.assignments.isEmpty()) { + for (let existingAssignment: ShapeTreeAssignment : this.assignments) { + if (existingAssignment === assignment) { + throw new ShapeTreeException(422, "Identical shape tree assignment cannot be added to Shape Tree Manager: " + this.id); + } + } + } + this.assignments.add(assignment); + } + + /** + * Generates or "mints" a URL for a new ShapeTreeAssignment + * @return URL minted for a new shape tree assignment + */ + public mintAssignmentUrl(): URL { + let fragment: string = RandomStringUtils.random(8, true, true); + let assignmentString: string = this.getUrl().toString() + "#" + fragment; + const assignmentUrl: URL; + try { + assignmentUrl = new URL(assignmentString); + } catch (ex: MalformedURLException) { + throw new IllegalStateException("Minted illegal URL <" + assignmentString + "> - " + ex.getMessage()); + } + return assignmentUrl; + } + + /** + * Ensure a proposed URL for a new ShapeTreeAssigment doesn't conflict with + * other assignment URLs already allocated for the ShapeTreeManager + * @param proposedAssignmentUrl URL of the proposed shape tree assignment + * @return Minted URL for a new shape tree assignment + */ + public mintAssignmentUrl(proposedAssignmentUrl: URL): URL { + for (let assignment: ShapeTreeAssignment : this.assignments) { + if (assignment.getUrl() === proposedAssignmentUrl) { + // If we somehow managed to randomly generate a location URL that already exists, generate another + return mintAssignmentUrl(); + } + } + return proposedAssignmentUrl; + } + + public getContainingAssignments(): Array /* throws ShapeTreeException */ { + let containingAssignments: ArrayList = new ArrayList<>(); + for (let assignment: ShapeTreeAssignment : this.assignments) { + let shapeTree: ShapeTree = ShapeTreeFactory.getShapeTree(assignment.getShapeTree()); + if (!shapeTree.getContains().isEmpty()) { + containingAssignments.add(assignment); + } + } + return containingAssignments; + } + + public static getFromGraph(id: URL, managerGraph: Graph): ShapeTreeManager /* throws ShapeTreeException */ { + let manager: ShapeTreeManager = new ShapeTreeManager(id); + // Look up the ShapeTreeManager in the ManagerResource Graph via (any subject node, rdf:type, st:ShapeTreeManager) + let managerTriples: Array = managerGraph.find(Node.ANY, NodeFactory.createURI(RdfVocabulary.TYPE), NodeFactory.createURI(ShapeTreeVocabulary.SHAPETREE_MANAGER)).toList(); + // Shape Trees, §3: No more than one shape tree manager may be associated with a managed resource. + // https://shapetrees.org/TR/specification/#manager + if (managerTriples.size() > 1) { + throw new IllegalStateException("Multiple ShapeTreeManager instances found: " + managerTriples.size()); + } else if (managerTriples.isEmpty()) { + // Given the fact that a manager resource exists, there should never be a case where the manager resource + // exists but no manager is found inside of it. + throw new IllegalStateException("No ShapeTreeManager instances found: " + managerTriples.size()); + } + // Get the URL of the ShapeTreeManager subject node + let managerUrl: string = managerTriples.get(0).getSubject().getURI(); + // Look up ShapeTreeAssignment nodes (manager subject node, st:hasAssignment, any st:hasAssignment nodes). + // There should be one result per nested ShapeTreeAssignment, each identified by a unique url. + // Shape Trees, §3: A shape tree manager includes one or more shape tree assignments via st:hasAssignment + // https://shapetrees.org/TR/specification/#manager + const s: Node = NodeFactory.createURI(managerUrl); + const stAssignment: Node = NodeFactory.createURI(ShapeTreeVocabulary.HAS_ASSIGNMENT); + let assignmentNodes: Array = managerGraph.find(s, stAssignment, Node.ANY).toList(); + // For each st:hasAssignment node, extract a new ShapeTreeAssignment + for (let assignmentNode: Triple : assignmentNodes) { + let assignment: ShapeTreeAssignment = null; + try { + assignment = ShapeTreeAssignment.getFromGraph(new URL(assignmentNode.getObject().getURI()), managerGraph); + } catch (e: MalformedURLException) { + throw new ShapeTreeException(500, "Object of { " + s + " " + stAssignment + " " + assignmentNode.getObject() + " } must be a URL."); + } + manager.assignments.add(assignment); + } + return manager; + } + + public getAssignmentForShapeTree(shapeTreeUrl: URL): ShapeTreeAssignment { + if (this.assignments.isEmpty()) { + return null; + } + for (let assignment: ShapeTreeAssignment : this.assignments) { + if (assignment.getShapeTree() === shapeTreeUrl) { + return assignment; + } + } + return null; + } + + // Given a root assignment, lookup the corresponding assignment in a shape tree manager that has the same root assignment + public getAssignmentForRoot(rootAssignment: ShapeTreeAssignment): ShapeTreeAssignment { + if (this.getAssignments() === null || this.getAssignments().isEmpty()) { + return null; + } + for (let assignment: ShapeTreeAssignment : this.getAssignments()) { + if (rootAssignment.getUrl() === assignment.getRootAssignment()) { + return assignment; + } + } + return null; + } + + public removeAssignment(assignment: ShapeTreeAssignment): void { + if (assignment === null) { + throw new IllegalStateException("Cannot remove a null assignment"); + } + if (this.assignments.isEmpty()) { + throw new IllegalStateException("Cannot remove assignments from empty set"); + } + if (!this.assignments.remove(assignment)) { + throw new IllegalStateException("Cannot remove assignment that does not exist in set"); + } + } + + public removeAssignmentForShapeTree(shapeTreeUrl: URL): void { + removeAssignment(getAssignmentForShapeTree(shapeTreeUrl)); + } + + public getId(): URL { + return this.id; + } + + public getAssignments(): Array { + return this.assignments; + } +} diff --git a/asTypescript/packages/core/src/ShapeTreeManagerDelta.ts b/asTypescript/packages/core/src/ShapeTreeManagerDelta.ts new file mode 100644 index 00000000..67b24b15 --- /dev/null +++ b/asTypescript/packages/core/src/ShapeTreeManagerDelta.ts @@ -0,0 +1,120 @@ +// Corresponding shapetrees-java package: com.janeirodigital.shapetrees.core +import { ShapeTreeException } from './exceptions/ShapeTreeException'; +import * as URI from 'java/net'; +import * as URISyntaxException from 'java/net'; +import * as ArrayList from 'java/util'; +import { ShapeTreeAssignment } from './ShapeTreeAssignment'; +import { ShapeTreeManager } from './ShapeTreeManager'; + +export class ShapeTreeManagerDelta { + + existingManager: ShapeTreeManager; + + updatedManager: ShapeTreeManager; + + updatedAssignments: Array; + + removedAssignments: Array; + + /** + * Compares an updated ShapeTreeManager (updatedManager) with an existing one (existingManager). Neither may + * be null, managers with no assignments are acceptable for the purposes of comparison. + * @param existingManager + * @param updatedManager + * @return ShapeTreeManagerDelta + */ + public static evaluate(existingManager: ShapeTreeManager, updatedManager: ShapeTreeManager): ShapeTreeManagerDelta /* throws ShapeTreeException */ { + if (existingManager === null && updatedManager === null) { + throw new ShapeTreeException(422, "Cannot compare two null managers"); + } + let delta: ShapeTreeManagerDelta = new ShapeTreeManagerDelta(); + delta.existingManager = existingManager; + delta.updatedManager = updatedManager; + delta.updatedAssignments = new ArrayList<>(); + delta.removedAssignments = new ArrayList<>(); + if (updatedManager === null || updatedManager.getAssignments().isEmpty()) { + // All assignments have been removed in the updated manager, so any existing assignments should + // similarly be removed. No need for further comparison. + delta.removedAssignments = existingManager.getAssignments(); + return delta; + } + if (existingManager === null || existingManager.getAssignments().isEmpty()) { + // This existing manager doesn't have any assignments (which means it shouldn't exist) + // Anything in the updated manager is being added as new. No need for further comparison. + delta.updatedAssignments = updatedManager.getAssignments(); + return delta; + } + for (let existingAssignment: ShapeTreeAssignment : existingManager.getAssignments()) { + // Assignments match, and are unchanged, so continue + if (updatedManager.getAssignments().contains(existingAssignment)) { + continue; + } + // Assignments have the same URL but are different, so update + let updatedAssignment: ShapeTreeAssignment = containsSameUrl(existingAssignment, updatedManager.getAssignments()); + if (updatedAssignment != null) { + delta.updatedAssignments.add(updatedAssignment); + continue; + } + // existing assignment isn't in the updated assignment, so remove + delta.removedAssignments.add(existingAssignment); + } + for (let updatedAssignment: ShapeTreeAssignment : updatedManager.getAssignments()) { + // Assignments match, and are unchanged, so continue + if (existingManager.getAssignments().contains(updatedAssignment)) { + continue; + } + // If this was already processed and marked as updated continue + if (delta.updatedAssignments.contains(updatedAssignment)) { + continue; + } + // updated assignment isn't in the existing assignments, so it is new, add it + delta.updatedAssignments.add(updatedAssignment); + } + return delta; + } + + public static containsSameUrl(assignment: ShapeTreeAssignment, targetAssignments: Array): ShapeTreeAssignment /* throws ShapeTreeException */ { + for (let targetAssignment: ShapeTreeAssignment : targetAssignments) { + let assignmentUri: URI; + let targetAssignmentUri: URI; + try { + assignmentUri = assignment.getUrl().toURI(); + targetAssignmentUri = targetAssignment.getUrl().toURI(); + } catch (ex: URISyntaxException) { + throw new ShapeTreeException(500, "Unable to convert assignment URLs for comparison: " + ex.getMessage()); + } + if (assignmentUri === targetAssignmentUri) { + return targetAssignment; + } + } + return null; + } + + public allRemoved(): boolean { + return (!this.isUpdated() && this.removedAssignments.size() === this.existingManager.getAssignments().size()); + } + + public isUpdated(): boolean { + return !this.updatedAssignments.isEmpty(); + } + + public wasReduced(): boolean { + return !this.removedAssignments.isEmpty(); + } + + public getExistingManager(): ShapeTreeManager { + return this.existingManager; + } + + public getUpdatedManager(): ShapeTreeManager { + return this.updatedManager; + } + + public getUpdatedAssignments(): Array { + return this.updatedAssignments; + } + + public getRemovedAssignments(): Array { + return this.removedAssignments; + } +} diff --git a/asTypescript/packages/core/src/ShapeTreeReference.ts b/asTypescript/packages/core/src/ShapeTreeReference.ts new file mode 100644 index 00000000..a78971cd --- /dev/null +++ b/asTypescript/packages/core/src/ShapeTreeReference.ts @@ -0,0 +1,48 @@ +// Corresponding shapetrees-java package: com.janeirodigital.shapetrees.core +import { ShapeTreeException } from './exceptions/ShapeTreeException'; +import * as URL from 'java/net'; +import * as Objects from 'java/util'; + +export class ShapeTreeReference { + + readonly referenceUrl: URL; + + readonly shapePath: string; + + readonly predicate: URL; + + public constructor(referenceUrl: URL, shapePath: string, predicate: URL) throws ShapeTreeException { + this.referenceUrl = Objects.requireNonNull(referenceUrl); + if (shapePath === null && predicate === null) { + throw new ShapeTreeException(500, "Shape tree reference must have either a shape path or a predicate"); + } else if (shapePath != null && predicate != null) { + throw new ShapeTreeException(500, "Shape tree reference cannot have a shape path and a predicate"); + } else if (shapePath != null) { + this.shapePath = shapePath; + this.predicate = null; + } else { + this.predicate = predicate; + this.shapePath = null; + } + } + + public viaShapePath(): boolean { + return shapePath != null; + } + + public viaPredicate(): boolean { + return predicate != null; + } + + public getReferenceUrl(): URL { + return this.referenceUrl; + } + + public getShapePath(): string { + return this.shapePath; + } + + public getPredicate(): URL { + return this.predicate; + } +} diff --git a/asTypescript/packages/core/src/ShapeTreeRequest.ts b/asTypescript/packages/core/src/ShapeTreeRequest.ts new file mode 100644 index 00000000..d90d70b9 --- /dev/null +++ b/asTypescript/packages/core/src/ShapeTreeRequest.ts @@ -0,0 +1,27 @@ +// Corresponding shapetrees-java package: com.janeirodigital.shapetrees.core +import { ShapeTreeResourceType } from './enums/ShapeTreeResourceType'; +import * as URL from 'java/net'; +import { ResourceAttributes } from './ResourceAttributes'; + +export interface ShapeTreeRequest { + + getMethod(): string; + + getUrl(): URL; + + getHeaders(): ResourceAttributes; + + getLinkHeaders(): ResourceAttributes; + + getHeaderValues(header: string): Array; + + getHeaderValue(header: string): string; + + getBody(): string; + + getContentType(): string; + + getResourceType(): ShapeTreeResourceType; + + setResourceType(resourceType: ShapeTreeResourceType): void; +} diff --git a/asTypescript/packages/core/src/ShapeTreeRequestHandler.ts b/asTypescript/packages/core/src/ShapeTreeRequestHandler.ts new file mode 100644 index 00000000..cabc59f2 --- /dev/null +++ b/asTypescript/packages/core/src/ShapeTreeRequestHandler.ts @@ -0,0 +1,437 @@ +// Corresponding shapetrees-java package: com.janeirodigital.shapetrees.core +import { ResourceTypeAssignmentPriority } from './comparators/ResourceTypeAssignmentPriority'; +import { HttpHeaders } from './enums/HttpHeaders'; +import { ShapeTreeException } from './exceptions/ShapeTreeException'; +import { RequestHelper } from './helpers/RequestHelper'; +import * as Slf4j from 'lombok/extern/slf4j'; +import * as Graph from 'org/apache/jena/graph'; +import * as URL from 'java/net'; +import * as util from 'java'; +import { TEXT_TURTLE } from './ManageableInstance/TEXT_TURTLE'; +import { ResourceAccessor } from './ResourceAccessor'; +import { ShapeTreeAssignment } from './ShapeTreeAssignment'; +import { InstanceResource } from './InstanceResource'; +import { ShapeTree } from './ShapeTree'; +import { ValidationResult } from './ValidationResult'; +import { ShapeTreeRequest } from './ShapeTreeRequest'; +import { ResourceAttributes } from './ResourceAttributes'; +import { ManageableInstance } from './ManageableInstance'; +import { ShapeTreeManagerDelta } from './ShapeTreeManagerDelta'; +import { DocumentResponse } from './DocumentResponse'; +import { ShapeTreeContext } from './ShapeTreeContext'; +import { ManageableResource } from './ManageableResource'; +import { ShapeTreeManager } from './ShapeTreeManager'; +import { ManagerResource } from './ManagerResource'; + +@Slf4j +export class ShapeTreeRequestHandler { + + private static readonly DELETE: string = "DELETE"; + + resourceAccessor: ResourceAccessor; + + public constructor(resourceAccessor: ResourceAccessor) { + this.resourceAccessor = resourceAccessor; + } + + public manageShapeTree(manageableInstance: ManageableInstance, shapeTreeRequest: ShapeTreeRequest): DocumentResponse /* throws ShapeTreeException */ { + let validationResponse: Optional; + let updatedRootManager: ShapeTreeManager = RequestHelper.getIncomingShapeTreeManager(shapeTreeRequest, manageableInstance.getManagerResource()); + let existingRootManager: ShapeTreeManager = manageableInstance.getManagerResource().getManager(); + // Determine assignments that have been removed, added, and/or updated + let delta: ShapeTreeManagerDelta = ShapeTreeManagerDelta.evaluate(existingRootManager, updatedRootManager); + // It is invalid for a manager resource to be left with no assignments. + // Shape Trees, §3: A shape tree manager includes one or more shape tree assignments via st:hasAssignment. + if (delta.allRemoved()) { + ensureAllRemovedFromManagerByDelete(shapeTreeRequest); + } + if (delta.wasReduced()) { + // An existing assignment has been removed from the manager for the managed resource. + validationResponse = unplantShapeTree(manageableInstance, manageableInstance.getShapeTreeContext(), delta); + if (validationResponse.isPresent()) { + return validationResponse.get(); + } + } + if (delta.isUpdated()) { + // An existing assignment has been updated, or new assignments have been added + validationResponse = plantShapeTree(manageableInstance, manageableInstance.getShapeTreeContext(), updatedRootManager, delta); + if (validationResponse.isPresent()) { + return validationResponse.get(); + } + } + // TODO: Test: Need a test with reduce and updated delta to make sure we never return success from plant or unplant. + return successfulValidation(); + } + + /** + * Plants a shape tree on an existing resource + * @param manageableInstance + * @param shapeTreeContext + * @param updatedRootManager + * @param delta + * @return DocumentResponse + * @throws ShapeTreeException + */ + public plantShapeTree(manageableInstance: ManageableInstance, shapeTreeContext: ShapeTreeContext, updatedRootManager: ShapeTreeManager, delta: ShapeTreeManagerDelta): Optional /* throws ShapeTreeException */ { + // Cannot directly update assignments that are not root locations + ensureUpdatedAssignmentIsRoot(delta); + // Run recursive assignment for each updated assignment in the root manager + for (let rootAssignment: ShapeTreeAssignment : delta.getUpdatedAssignments()) { + let validationResponse: Optional = assignShapeTreeToResource(manageableInstance, shapeTreeContext, updatedRootManager, rootAssignment, rootAssignment, null); + if (validationResponse.isPresent()) { + return validationResponse; + } + } + return Optional.empty(); + } + + public unplantShapeTree(manageableInstance: ManageableInstance, shapeTreeContext: ShapeTreeContext, delta: ShapeTreeManagerDelta): Optional /* throws ShapeTreeException */ { + // Cannot unplant a non-root location + ensureRemovedAssignmentsAreRoot(delta); + // Run recursive unassignment for each removed assignment in the updated root manager + for (let rootAssignment: ShapeTreeAssignment : delta.getRemovedAssignments()) { + let validationResponse: Optional = unassignShapeTreeFromResource(manageableInstance, shapeTreeContext, rootAssignment); + if (validationResponse.isPresent()) { + return validationResponse; + } + } + return Optional.empty(); + } + + // TODO: #87: do sanity checks on meta of meta, c.f. @see https://github.com/xformativ/shapetrees-java/issues/87 + public createShapeTreeInstance(manageableInstance: ManageableInstance, containerResource: ManageableInstance, shapeTreeRequest: ShapeTreeRequest, proposedName: string): Optional /* throws ShapeTreeException */ { + // Sanity check user-owned resource @@ delete 'cause type checks + ensureInstanceResourceExists(containerResource.getManageableResource(), "Target container for resource creation not found"); + ensureRequestResourceIsContainer(containerResource.getManageableResource(), "Cannot create a shape tree instance in a non-container resource"); + // Prepare the target resource for validation and creation + let targetResourceUrl: URL = RequestHelper.normalizeSolidResourceUrl(containerResource.getManageableResource().getUrl(), proposedName, shapeTreeRequest.getResourceType()); + ensureTargetResourceDoesNotExist(manageableInstance.getShapeTreeContext(), targetResourceUrl, "Cannot create target resource at " + targetResourceUrl + " because it already exists"); + ensureInstanceResourceExists(containerResource.getManagerResource(), "Should not be creating a shape tree instance on an unmanaged target container"); + let containerManager: ShapeTreeManager = containerResource.getManagerResource().getManager(); + ensureShapeTreeManagerExists(containerManager, "Cannot have a shape tree manager resource without a shape tree manager containing at least one shape tree assignment"); + // Get the shape tree associated that specifies what resources can be contained by the target container (st:contains) + let containingAssignments: List = containerManager.getContainingAssignments(); + // If there are no containing shape trees for the target container, request is valid and can be passed through + if (containingAssignments.isEmpty()) { + return Optional.empty(); + } + let targetShapeTrees: List = RequestHelper.getIncomingTargetShapeTrees(shapeTreeRequest, targetResourceUrl); + let incomingFocusNodes: List = RequestHelper.getIncomingFocusNodes(shapeTreeRequest, targetResourceUrl); + let incomingBodyGraph: Graph = RequestHelper.getIncomingBodyGraph(shapeTreeRequest, targetResourceUrl, null); + let validationResults: HashMap = new HashMap<>(); + for (let containingAssignment: ShapeTreeAssignment : containingAssignments) { + let containerShapeTreeUrl: URL = containingAssignment.getShapeTree(); + let containerShapeTree: ShapeTree = ShapeTreeFactory.getShapeTree(containerShapeTreeUrl); + let validationResult: ValidationResult = containerShapeTree.validateContainedResource(proposedName, shapeTreeRequest.getResourceType(), targetShapeTrees, incomingBodyGraph, incomingFocusNodes); + if (Boolean.FALSE === validationResult.isValid()) { + return failValidation(validationResult); + } + validationResults.put(containingAssignment, validationResult); + } + // if any of the provided focus nodes weren't matched validation must fail + let unmatchedNodes: List = getUnmatchedFocusNodes(validationResults.values(), incomingFocusNodes); + if (!unmatchedNodes.isEmpty()) { + return failValidation(new ValidationResult(false, "Failed to match target focus nodes: " + unmatchedNodes)); + } + log.debug("Creating shape tree instance at {}", targetResourceUrl); + let createdInstance: ManageableInstance = this.resourceAccessor.createInstance(manageableInstance.getShapeTreeContext(), shapeTreeRequest.getMethod(), targetResourceUrl, shapeTreeRequest.getHeaders(), shapeTreeRequest.getBody(), shapeTreeRequest.getContentType()); + for (let containingAssignment: ShapeTreeAssignment : containingAssignments) { + let rootShapeTreeAssignment: ShapeTreeAssignment = getRootAssignment(manageableInstance.getShapeTreeContext(), containingAssignment); + ensureAssignmentExists(rootShapeTreeAssignment, "Unable to find root shape tree assignment at " + containingAssignment.getRootAssignment()); + log.debug("Assigning shape tree to created resource: {}", createdInstance.getManagerResource().getUrl()); + // Note: By providing the positive advance validationResult, we let the assignment operation know that validation + // has already been performed with a positive result, and avoid having it perform the validation a second time + let assignResult: Optional = assignShapeTreeToResource(createdInstance, manageableInstance.getShapeTreeContext(), null, rootShapeTreeAssignment, containingAssignment, validationResults.get(containingAssignment)); + if (assignResult.isPresent()) { + return assignResult; + } + } + return Optional.of(successfulValidation()); + } + + public updateShapeTreeInstance(targetResource: ManageableInstance, shapeTreeContext: ShapeTreeContext, shapeTreeRequest: ShapeTreeRequest): Optional /* throws ShapeTreeException */ { + ensureInstanceResourceExists(targetResource.getManageableResource(), "Target resource to update not found"); + ensureInstanceResourceExists(targetResource.getManagerResource(), "Should not be updating an unmanaged resource as a shape tree instance"); + let manager: ShapeTreeManager = targetResource.getManagerResource().getManager(); + ensureShapeTreeManagerExists(manager, "Cannot have a shape tree manager resource without a shape tree manager with at least one shape tree assignment"); + for (let assignment: ShapeTreeAssignment : manager.getAssignments()) { + // Evaluate the update against each ShapeTreeAssignment managing the resource. + // All must pass for the update to validate + let shapeTree: ShapeTree = ShapeTreeFactory.getShapeTree(assignment.getShapeTree()); + let managedResourceUrl: URL = targetResource.getManageableResource().getUrl(); + let validationResult: ValidationResult = shapeTree.validateResource(null, shapeTreeRequest.getResourceType(), RequestHelper.getIncomingBodyGraph(shapeTreeRequest, managedResourceUrl, targetResource.getManageableResource()), RequestHelper.getIncomingFocusNodes(shapeTreeRequest, managedResourceUrl)); + if (Boolean.FALSE === validationResult.isValid()) { + return failValidation(validationResult); + } + } + // No issues with validation, so the request is passed along + return Optional.empty(); + } + + public deleteShapeTreeInstance(): Optional { + // Nothing to validate in a delete request, so the request is passed along + return Optional.empty(); + } + + protected assignShapeTreeToResource(manageableInstance: ManageableInstance, shapeTreeContext: ShapeTreeContext, rootManager: ShapeTreeManager, rootAssignment: ShapeTreeAssignment, parentAssignment: ShapeTreeAssignment, advanceValidationResult: ValidationResult): Optional /* throws ShapeTreeException */ { + let managingShapeTree: ShapeTree = null; + let shapeTreeManager: ShapeTreeManager = null; + let matchingFocusNode: URL = null; + let managingAssignment: ShapeTreeAssignment = null; + let validationResponse: Optional; + ensureValidationResultIsUsableForAssignment(advanceValidationResult, "Invalid advance validation result provided for resource assignment"); + if (advanceValidationResult != null) { + managingShapeTree = advanceValidationResult.getMatchingShapeTree(); + } + if (advanceValidationResult != null) { + matchingFocusNode = advanceValidationResult.getMatchingFocusNode(); + } + if (atRootOfPlantHierarchy(rootAssignment, manageableInstance.getManageableResource())) { + // If we are at the root of the plant hierarchy we don't need to validate the managed resource against + // a shape tree managing a parent container. We only need to validate the managed resource against + // the shape tree that is being planted at the root to ensure it conforms. + managingShapeTree = ShapeTreeFactory.getShapeTree(rootAssignment.getShapeTree()); + if (advanceValidationResult === null) { + // If this validation wasn't performed in advance + let validationResult: ValidationResult = managingShapeTree.validateResource(manageableInstance.getManageableResource()); + if (Boolean.FALSE === validationResult.isValid()) { + return failValidation(validationResult); + } + matchingFocusNode = validationResult.getMatchingFocusNode(); + } + } else { + // Not at the root of the plant hierarchy. Validate proposed resource against the shape tree + // managing the parent container, then extract the matching shape tree and focus node on success + if (advanceValidationResult === null) { + // If this validation wasn't performed in advance + let parentShapeTree: ShapeTree = ShapeTreeFactory.getShapeTree(parentAssignment.getShapeTree()); + let validationResult: ValidationResult = parentShapeTree.validateContainedResource(manageableInstance.getManageableResource()); + if (Boolean.FALSE === validationResult.isValid()) { + return failValidation(validationResult); + } + managingShapeTree = validationResult.getMatchingShapeTree(); + matchingFocusNode = validationResult.getMatchingFocusNode(); + } + } + shapeTreeManager = getManagerForAssignment(manageableInstance, rootManager, rootAssignment); + managingAssignment = getAssignment(manageableInstance.getManageableResource(), shapeTreeManager, rootAssignment, managingShapeTree, matchingFocusNode); + // If the primary resource is a container, and its shape tree specifies its contents with st:contains + // Recursively traverse the hierarchy and perform shape tree assignment + if (manageableInstance.getManageableResource().isContainer() && !managingShapeTree.getContains().isEmpty()) { + // If the container is not empty, perform a recursive, depth first validation and assignment for each + // contained resource by recursively calling this method (assignShapeTreeToResource) + // TODO - Provide a configurable maximum limit on contained resources for a recursive plant, generate ShapeTreeException + let containedResources: List = this.resourceAccessor.getContainedInstances(shapeTreeContext, manageableInstance.getManageableResource().getUrl()); + if (!containedResources.isEmpty()) { + // Evaluate containers, then resources + Collections.sort(containedResources, new ResourceTypeAssignmentPriority()); + for (let containedResource: ManageableInstance : containedResources) { + validationResponse = assignShapeTreeToResource(containedResource, shapeTreeContext, null, rootAssignment, managingAssignment, null); + if (validationResponse.isPresent()) { + return validationResponse; + } + } + } + } + if (manageableInstance.getManagerResource().isExists()) { + // update manager resource + this.resourceAccessor.updateResource(shapeTreeContext, "PUT", manageableInstance.getManagerResource(), shapeTreeManager.getGraph().toString()); + } else { + // create manager resource + let headers: ResourceAttributes = new ResourceAttributes(); + headers.setAll(HttpHeaders.CONTENT_TYPE.getValue(), Collections.singletonList(TEXT_TURTLE)); + this.resourceAccessor.createResource(shapeTreeContext, "POST", manageableInstance.getManagerResource().getUrl(), headers, shapeTreeManager.getGraph().toString(), TEXT_TURTLE); + } + return Optional.empty(); + } + + protected unassignShapeTreeFromResource(manageableInstance: ManageableInstance, shapeTreeContext: ShapeTreeContext, rootAssignment: ShapeTreeAssignment): Optional /* throws ShapeTreeException */ { + ensureInstanceResourceExists(manageableInstance.getManageableResource(), "Cannot remove assignment from non-existent managed resource"); + ensureInstanceResourceExists(manageableInstance.getManagerResource(), "Cannot remove assignment from non-existent manager resource"); + let shapeTreeManager: ShapeTreeManager = manageableInstance.getManagerResource().getManager(); + let assignmentToRemove: ShapeTreeAssignment = shapeTreeManager.getAssignmentForRoot(rootAssignment); + let assignedShapeTree: ShapeTree = ShapeTreeFactory.getShapeTree(assignmentToRemove.getShapeTree()); + let validationResponse: Optional; + // If the managed resource is a container, and its shape tree specifies its contents with st:contains + // Recursively traverse the hierarchy and perform shape tree unassignment + if (manageableInstance.getManageableResource().isContainer() && !assignedShapeTree.getContains().isEmpty()) { + // TODO - Should there also be a configurable maximum limit on unplanting? + let containedResources: List = this.resourceAccessor.getContainedInstances(shapeTreeContext, manageableInstance.getManageableResource().getUrl()); + // If the container is not empty + if (!containedResources.isEmpty()) { + // Sort contained resources so that containers are evaluated first, then resources + Collections.sort(containedResources, new ResourceTypeAssignmentPriority()); + // Perform a depth first unassignment for each contained resource + for (let containedResource: ManageableInstance : containedResources) { + // Recursively call this function on the contained resource + validationResponse = unassignShapeTreeFromResource(containedResource, shapeTreeContext, rootAssignment); + if (validationResponse.isPresent()) { + return validationResponse; + } + } + } + } + shapeTreeManager.removeAssignment(assignmentToRemove); + deleteOrUpdateManagerResource(shapeTreeContext, manageableInstance.getManagerResource(), shapeTreeManager); + return Optional.empty(); + } + + private deleteOrUpdateManagerResource(shapeTreeContext: ShapeTreeContext, managerResource: ManagerResource, shapeTreeManager: ShapeTreeManager): void /* throws ShapeTreeException */ { + if (shapeTreeManager.getAssignments().isEmpty()) { + let response: DocumentResponse = this.resourceAccessor.deleteResource(shapeTreeContext, managerResource); + ensureDeleteIsSuccessful(response); + } else { + // Update the existing manager resource for the managed resource + this.resourceAccessor.updateResource(shapeTreeContext, "PUT", managerResource, shapeTreeManager.getGraph().toString()); + } + } + + private getManagerForAssignment(manageableInstance: ManageableInstance, rootManager: ShapeTreeManager, rootAssignment: ShapeTreeAssignment): ShapeTreeManager /* throws ShapeTreeException */ { + let shapeTreeManager: ShapeTreeManager = null; + let managerResourceUrl: URL = manageableInstance.getManagerResource().getUrl(); + // When at the top of the plant hierarchy, use the root manager from the initial plant request body + if (atRootOfPlantHierarchy(rootAssignment, manageableInstance.getManageableResource())) { + return rootManager; + } + if (!manageableInstance.getManagerResource().isExists()) { + // If the existing manager resource doesn't exist make a new shape tree manager + shapeTreeManager = new ShapeTreeManager(managerResourceUrl); + } else { + // Get the existing shape tree manager from the manager resource graph + // TODO - this was seemingly incorrect before it was adjusted. Needs to be debugged and confirmed as working properly now + let managerGraph: Graph = manageableInstance.getManagerResource().getGraph(managerResourceUrl); + shapeTreeManager = ShapeTreeManager.getFromGraph(managerResourceUrl, managerGraph); + } + return shapeTreeManager; + } + + private getAssignment(manageableResource: ManageableResource, shapeTreeManager: ShapeTreeManager, rootAssignment: ShapeTreeAssignment, managingShapeTree: ShapeTree, matchingFocusNode: URL): ShapeTreeAssignment /* throws ShapeTreeException */ { + if (atRootOfPlantHierarchy(rootAssignment, manageableResource)) { + return rootAssignment; + } + // Mint a new assignment URL, since it wouldn't have been passed in the initial request body + let assignmentUrl: URL = shapeTreeManager.mintAssignmentUrl(); + // Build the managed resource assignment + let matchingNode: URL = matchingFocusNode === null ? null : matchingFocusNode; + let managedResourceAssignment: ShapeTreeAssignment = new ShapeTreeAssignment(managingShapeTree.getId(), manageableResource.getUrl(), rootAssignment.getUrl(), matchingNode, managingShapeTree.getShape(), assignmentUrl); + // Add the shape tree assignment to the shape tree managed for the managed resource + shapeTreeManager.addAssignment(managedResourceAssignment); + return managedResourceAssignment; + } + + private atRootOfPlantHierarchy(rootAssignment: ShapeTreeAssignment, manageableResource: ManageableResource): boolean { + return rootAssignment.getManagedResource() === manageableResource.getUrl(); + } + + // Return a root shape tree manager associated with a given shape tree assignment + private getRootManager(shapeTreeContext: ShapeTreeContext, assignment: ShapeTreeAssignment): ShapeTreeManager /* throws ShapeTreeException */ { + let rootAssignmentUrl: URL = assignment.getRootAssignment(); + let instance: ManageableInstance = this.resourceAccessor.getInstance(shapeTreeContext, rootAssignmentUrl); + return instance.getManagerResource().getManager(); + } + + // Return a root shape tree manager associated with a given shape tree assignment + private getRootAssignment(shapeTreeContext: ShapeTreeContext, assignment: ShapeTreeAssignment): ShapeTreeAssignment /* throws ShapeTreeException */ { + let rootManager: ShapeTreeManager = getRootManager(shapeTreeContext, assignment); + for (let rootAssignment: ShapeTreeAssignment : rootManager.getAssignments()) { + if (rootAssignment.getUrl() != null && rootAssignment.getUrl() === assignment.getRootAssignment()) { + return rootAssignment; + } + } + return null; + } + + private getUnmatchedFocusNodes(validationResults: Collection, focusNodes: List): List { + let unmatchedNodes: List = new ArrayList<>(); + for (let focusNode: URL : focusNodes) { + // Determine if each target focus node was matched + let matched: boolean = false; + for (let validationResult: ValidationResult : validationResults) { + if (validationResult.getMatchingShapeTree().getShape() != null) { + if (validationResult.getMatchingFocusNode() === focusNode) { + matched = true; + } + } + } + if (!matched) { + unmatchedNodes.add(focusNode); + } + } + return unmatchedNodes; + } + + private ensureValidationResultIsUsableForAssignment(validationResult: ValidationResult, message: string): void /* throws ShapeTreeException */ { + // Null is a usable state of the validation result in the context of assignment + if (validationResult != null && (validationResult.getValid() === null || validationResult.getMatchingShapeTree() === null || validationResult.getValidatingShapeTree() === null)) { + throw new ShapeTreeException(400, message); + } + } + + private ensureInstanceResourceExists(instanceResource: InstanceResource, message: string): void /* throws ShapeTreeException */ { + if (instanceResource === null || !instanceResource.isExists()) { + throw new ShapeTreeException(404, message); + } + } + + private ensureRequestResourceIsContainer(shapeTreeResource: ManageableResource, message: string): void /* throws ShapeTreeException */ { + if (!shapeTreeResource.isContainer()) { + throw new ShapeTreeException(400, message); + } + } + + private ensureTargetResourceDoesNotExist(shapeTreeContext: ShapeTreeContext, targetResourceUrl: URL, message: string): void /* throws ShapeTreeException */ { + let targetInstance: ManageableInstance = this.resourceAccessor.getInstance(shapeTreeContext, targetResourceUrl); + if (targetInstance.wasRequestForManager() || targetInstance.getManageableResource().isExists()) { + throw new ShapeTreeException(409, message); + } + } + + private ensureShapeTreeManagerExists(manager: ShapeTreeManager, message: string): void /* throws ShapeTreeException */ { + if (manager === null || manager.getAssignments() === null || manager.getAssignments().isEmpty()) { + throw new ShapeTreeException(400, message); + } + } + + private ensureAssignmentExists(assignment: ShapeTreeAssignment, message: string): void /* throws ShapeTreeException */ { + if (assignment === null) { + throw new ShapeTreeException(400, message); + } + } + + private ensureAllRemovedFromManagerByDelete(shapeTreeRequest: ShapeTreeRequest): void /* throws ShapeTreeException */ { + if (!shapeTreeRequest.getMethod() === DELETE) { + throw new ShapeTreeException(500, "Removal of all ShapeTreeAssignments from a ShapeTreeManager MUST use HTTP DELETE"); + } + } + + private ensureRemovedAssignmentsAreRoot(delta: ShapeTreeManagerDelta): void /* throws ShapeTreeException */ { + for (let assignment: ShapeTreeAssignment : delta.getRemovedAssignments()) { + if (!assignment.isRootAssignment()) { + throw new ShapeTreeException(500, "Cannot remove non-root assignment: " + assignment.getUrl().toString() + ". Must unplant root assignment at: " + assignment.getRootAssignment().toString()); + } + } + } + + private ensureUpdatedAssignmentIsRoot(delta: ShapeTreeManagerDelta): void /* throws ShapeTreeException */ { + for (let updatedAssignment: ShapeTreeAssignment : delta.getUpdatedAssignments()) { + if (!updatedAssignment.isRootAssignment()) { + throw new ShapeTreeException(500, "Cannot update non-root assignment: " + updatedAssignment.getUrl().toString() + ". Must update root assignment at: " + updatedAssignment.getRootAssignment().toString()); + } + } + } + + private ensureDeleteIsSuccessful(response: DocumentResponse): void /* throws ShapeTreeException */ { + let successCodes: List = Arrays.asList(202, 204, 200); + if (!successCodes.contains(response.getStatusCode())) { + throw new ShapeTreeException(500, "Failed to delete manager resource. Received " + response.getStatusCode() + ": " + response.getBody()); + } + } + + private successfulValidation(): DocumentResponse { + return new DocumentResponse(new ResourceAttributes(), "OK", 201); + } + + private failValidation(validationResult: ValidationResult): Optional { + return Optional.of(new DocumentResponse(new ResourceAttributes(), validationResult.getMessage(), 422)); + } +} diff --git a/asTypescript/packages/core/src/ShapeTreeResource.ts b/asTypescript/packages/core/src/ShapeTreeResource.ts new file mode 100644 index 00000000..abce84a6 --- /dev/null +++ b/asTypescript/packages/core/src/ShapeTreeResource.ts @@ -0,0 +1,90 @@ +// Corresponding shapetrees-java package: com.janeirodigital.shapetrees.core +import { DocumentLoaderManager } from './contentloaders/DocumentLoaderManager'; +import { ShapeTreeException } from './exceptions/ShapeTreeException'; +import { GraphHelper } from './helpers/GraphHelper'; +import * as Slf4j from 'lombok/extern/slf4j'; +import * as Model from 'org/apache/jena/rdf/model'; +import * as URI from 'java/net'; +import * as URL from 'java/net'; +import * as HashMap from 'java/util'; +import { removeUrlFragment } from './helpers/GraphHelper/removeUrlFragment'; +import { urlToUri } from './helpers/GraphHelper/urlToUri'; +import { DocumentResponse } from './DocumentResponse'; + +/** + * Represents a resource that contains one or more shape tree definitions. Provides + * a factory to lookup, initialize, and cache them. + */ +@Slf4j +export class ShapeTreeResource { + + readonly url: URL; + + readonly body: string; + + readonly contentType: string; + + readonly model: Model; + + @Getter + private static readonly localResourceCache: Map = new HashMap<>(); + + /** + * Looks up and caches the shape tree resource at resourceUrl. Will used cached + * verion if it exists. Throws exceptions if the resource doesn't exist, or isn't a valid + * RDF document. + * @param resourceUrl URL of shape tree resource + * @return Shape tree resource at provided resourceUrl + * @throws ShapeTreeException + */ + public static getShapeTreeResource(resourceUrl: URL): ShapeTreeResource /* throws ShapeTreeException */ { + resourceUrl = removeUrlFragment(resourceUrl); + if (localResourceCache.containsKey(urlToUri(resourceUrl))) { + log.debug("[{}] previously cached -- returning", resourceUrl); + return localResourceCache.get(urlToUri(resourceUrl)); + } + let externalDocument: DocumentResponse = DocumentLoaderManager.getLoader().loadExternalDocument(resourceUrl); + if (!externalDocument.isExists()) { + throw new ShapeTreeException(500, "Cannot load shape shape tree resource at " + resourceUrl); + } + let model: Model = GraphHelper.readStringIntoModel(urlToUri(resourceUrl), externalDocument.getBody(), externalDocument.getContentType().orElse("text/turtle")); + let resource: ShapeTreeResource = new ShapeTreeResource(resourceUrl, externalDocument.getBody(), externalDocument.getContentType().orElse("text/turtle"), model); + localResourceCache.put(urlToUri(resourceUrl), resource); + return resource; + } + + /** + * Clears the local shape tree resource cache + */ + public static clearCache(): void { + localResourceCache.clear(); + } + + public constructor(url: URL, body: string, contentType: string, model: Model, localResourceCache: Map) { + this.url = url; + this.body = body; + this.contentType = contentType; + this.model = model; + this.localResourceCache = localResourceCache; + } + + public getUrl(): URL { + return this.url; + } + + public getBody(): string { + return this.body; + } + + public getContentType(): string { + return this.contentType; + } + + public getModel(): Model { + return this.model; + } + + public getLocalResourceCache(): Map { + return this.localResourceCache; + } +} diff --git a/asTypescript/packages/core/src/UnmanagedResource.ts b/asTypescript/packages/core/src/UnmanagedResource.ts new file mode 100644 index 00000000..7c25b8a9 --- /dev/null +++ b/asTypescript/packages/core/src/UnmanagedResource.ts @@ -0,0 +1,22 @@ +// Corresponding shapetrees-java package: com.janeirodigital.shapetrees.core +import * as URL from 'java/net'; +import * as Optional from 'java/util'; +import { ManageableResource } from './ManageableResource'; + +/** + * An UnmanagedResource indicates that a given ManageableResource + * is not managed by a shape tree. This means that there is not an + * associated ManagerResource that exists. + */ +export class UnmanagedResource extends ManageableResource { + + /** + * Construct an UnmanagedResource based on a provided ManageableResource + * manageable and managerUrl + * @param manageable ManageableResource to construct the UnmanagedResource from + * @param managerUrl URL of the associated shape tree manager resource + */ + public constructor(manageable: ManageableResource, managerUrl: Optional) { + super(manageable.getUrl(), manageable.getResourceType(), manageable.getAttributes(), manageable.getBody(), manageable.getName(), manageable.isExists(), managerUrl, manageable.isContainer()); + } +} diff --git a/asTypescript/packages/core/src/ValidationResult.ts b/asTypescript/packages/core/src/ValidationResult.ts new file mode 100644 index 00000000..14530fd7 --- /dev/null +++ b/asTypescript/packages/core/src/ValidationResult.ts @@ -0,0 +1,101 @@ +// Corresponding shapetrees-java package: com.janeirodigital.shapetrees.core +import * as URL from 'java/net'; +import { ShapeTreeAssignment } from './ShapeTreeAssignment'; +import { ShapeTree } from './ShapeTree'; + +export class ValidationResult { + + private valid: Boolean; + + private validatingShapeTree: ShapeTree; + + private matchingShapeTree: ShapeTree; + + private managingAssignment: ShapeTreeAssignment; + + private matchingFocusNode: URL; + + private message: string; + + public isValid(): Boolean { + return (this.valid != null && this.valid); + } + + public constructor(valid: Boolean, message: string) { + this.valid = valid; + this.message = message; + this.validatingShapeTree = null; + this.matchingShapeTree = null; + this.managingAssignment = null; + this.matchingFocusNode = null; + } + + public constructor(valid: Boolean, validatingShapeTree: ShapeTree) { + this.valid = valid; + this.message = null; + this.validatingShapeTree = validatingShapeTree; + this.matchingShapeTree = null; + this.managingAssignment = null; + this.matchingFocusNode = null; + } + + public constructor(valid: Boolean, validatingShapeTree: ShapeTree, message: string) { + this.valid = valid; + this.message = message; + this.validatingShapeTree = validatingShapeTree; + this.matchingShapeTree = null; + this.managingAssignment = null; + this.matchingFocusNode = null; + } + + public constructor(valid: Boolean, validatingShapeTree: ShapeTree, matchingFocusNode: URL) { + this.valid = valid; + this.message = null; + this.validatingShapeTree = validatingShapeTree; + this.matchingShapeTree = null; + this.managingAssignment = null; + this.matchingFocusNode = matchingFocusNode; + } + + public constructor(valid: Boolean, validatingShapeTree: ShapeTree, matchingShapeTree: ShapeTree, matchingFocusNode: URL) { + this.valid = valid; + this.message = null; + this.validatingShapeTree = validatingShapeTree; + this.matchingShapeTree = matchingShapeTree; + this.managingAssignment = null; + this.matchingFocusNode = matchingFocusNode; + } + + public constructor(valid: Boolean, validatingShapeTree: ShapeTree, matchingShapeTree: ShapeTree, managingAssignment: ShapeTreeAssignment, matchingFocusNode: URL, message: string) { + this.valid = valid; + this.validatingShapeTree = validatingShapeTree; + this.matchingShapeTree = matchingShapeTree; + this.managingAssignment = managingAssignment; + this.matchingFocusNode = matchingFocusNode; + this.message = message; + } + + public getValid(): Boolean { + return this.valid; + } + + public getValidatingShapeTree(): ShapeTree { + return this.validatingShapeTree; + } + + public getMatchingShapeTree(): ShapeTree { + return this.matchingShapeTree; + } + + public getManagingAssignment(): ShapeTreeAssignment { + return this.managingAssignment; + } + + public getMatchingFocusNode(): URL { + return this.matchingFocusNode; + } + + public getMessage(): string { + return this.message; + } +} diff --git a/asTypescript/packages/core/src/comparators/ResourceTypeAssignmentPriority.ts b/asTypescript/packages/core/src/comparators/ResourceTypeAssignmentPriority.ts new file mode 100644 index 00000000..f8bd1adc --- /dev/null +++ b/asTypescript/packages/core/src/comparators/ResourceTypeAssignmentPriority.ts @@ -0,0 +1,15 @@ +// Corresponding shapetrees-java package: com.janeirodigital.shapetrees.core.comparators +import { ManageableInstance } from '../ManageableInstance'; +import * as Comparator from 'java/util'; + +export class ResourceTypeAssignmentPriority implements Comparator, Serializable { + + // Used for sorting by shape tree resource type with the following order + // 1. Containers + // 2. Resources + // 3. Non-RDF Resources + // @SneakyThrows + public compare(a: ManageableInstance, b: ManageableInstance): number { + return a.getManageableResource().getResourceType().compareTo(b.getManageableResource().getResourceType()); + } +} diff --git a/asTypescript/packages/core/src/comparators/ShapeTreeContainsPriority.ts b/asTypescript/packages/core/src/comparators/ShapeTreeContainsPriority.ts new file mode 100644 index 00000000..0af8f332 --- /dev/null +++ b/asTypescript/packages/core/src/comparators/ShapeTreeContainsPriority.ts @@ -0,0 +1,32 @@ +// Corresponding shapetrees-java package: com.janeirodigital.shapetrees.core.comparators +import { ShapeTree } from '../ShapeTree'; +import { ShapeTreeFactory } from '../ShapeTreeFactory'; +import * as URL from 'java/net'; +import * as Comparator from 'java/util'; + +export class ShapeTreeContainsPriority implements Comparator, Serializable { + + // Used for sorting shape trees in st:contains by most to least strict + // @SneakyThrows + override public compare(stUrl1: URL, stUrl2: URL): number { + let st1: ShapeTree = ShapeTreeFactory.getShapeTree(stUrl1); + let st2: ShapeTree = ShapeTreeFactory.getShapeTree(stUrl2); + let st1Priority: number = 0; + let st2Priority: number = 0; + if (st1.getShape() != null) { + st1Priority += 2; + } + if (st1.getLabel() != null) { + st1Priority++; + } + // st:expectsType is required so it doesn't affect score priority + if (st2.getShape() != null) { + st2Priority += 2; + } + if (st2.getLabel() != null) { + st2Priority++; + } + // Reversed to ensure ordering goes from most strict to least + return Integer.compare(st2Priority, st1Priority); + } +} diff --git a/asTypescript/packages/core/src/contentloaders/DocumentLoaderManager.ts b/asTypescript/packages/core/src/contentloaders/DocumentLoaderManager.ts new file mode 100644 index 00000000..e8ca18be --- /dev/null +++ b/asTypescript/packages/core/src/contentloaders/DocumentLoaderManager.ts @@ -0,0 +1,31 @@ +// Corresponding shapetrees-java package: com.janeirodigital.shapetrees.core.contentloaders +import { ShapeTreeException } from '../exceptions/ShapeTreeException'; +import { ExternalDocumentLoader } from './ExternalDocumentLoader'; + +/** + * Utility class which allows an external document loader to be set by calling code, and then + * utilized by other code that doesn't require special knowledge of specific document + * loader implementations. + */ +export abstract class DocumentLoaderManager { + + @Setter(onMethod_ = { @Synchronized }) + private static loader: ExternalDocumentLoader; + + // Private constructor to offset an implicit public constructor on a utility class + private constructor() { + } + + /** + * Return an ExternalDocumentLoader that was previously set and stored statically + * @return A valid ExternalDocumentLoader that was previously set + * @throws ShapeTreeException + */ + // @Synchronized + public static getLoader(): ExternalDocumentLoader /* throws ShapeTreeException */ { + if (loader === null) { + throw new ShapeTreeException(500, "Must provide a valid ExternalDocumentLoader"); + } + return DocumentLoaderManager.loader; + } +} diff --git a/asTypescript/packages/core/src/contentloaders/ExternalDocumentLoader.ts b/asTypescript/packages/core/src/contentloaders/ExternalDocumentLoader.ts new file mode 100644 index 00000000..44ad252f --- /dev/null +++ b/asTypescript/packages/core/src/contentloaders/ExternalDocumentLoader.ts @@ -0,0 +1,20 @@ +// Corresponding shapetrees-java package: com.janeirodigital.shapetrees.core.contentloaders +import { DocumentResponse } from '../DocumentResponse'; +import { ShapeTreeException } from '../exceptions/ShapeTreeException'; +import * as URL from 'java/net'; + +/** + * Interface defining how a remote document can be loaded and its contents extracted. + * Implementations can add capabilities like caching, retrieving resources from alternate + * locations, etc. + */ +export interface ExternalDocumentLoader { + + /** + * Describes the retrieval of a remote document + * @param resourceUrl URL of resource to be retrieved + * @return DocumentResponse representation which contains body and content type + * @throws ShapeTreeException ShapeTreeException + */ + loadExternalDocument(resourceUrl: URL): DocumentResponse /* throws ShapeTreeException */; +} diff --git a/asTypescript/packages/core/src/contentloaders/HttpExternalDocumentLoader.ts b/asTypescript/packages/core/src/contentloaders/HttpExternalDocumentLoader.ts new file mode 100644 index 00000000..ec8e3a60 --- /dev/null +++ b/asTypescript/packages/core/src/contentloaders/HttpExternalDocumentLoader.ts @@ -0,0 +1,38 @@ +// Corresponding shapetrees-java package: com.janeirodigital.shapetrees.core.contentloaders +import { DocumentResponse } from '../DocumentResponse'; +import { ResourceAttributes } from '../ResourceAttributes'; +import { ShapeTreeException } from '../exceptions/ShapeTreeException'; +import * as URISyntaxException from 'java/net'; +import * as URL from 'java/net'; +import * as HttpClient from 'java/net/http'; +import * as HttpRequest from 'java/net/http'; +import * as HttpResponse from 'java/net/http'; +import { ExternalDocumentLoader } from './ExternalDocumentLoader'; + +/** + * Simple HTTP implementation of ExternalDocumentLoader provided as an example + * as well as for its utility in unit tests. + */ +export class HttpExternalDocumentLoader implements ExternalDocumentLoader { + + private readonly httpClient: HttpClient = HttpClient.newBuilder().followRedirects(HttpClient.Redirect.NEVER).build(); + + override public loadExternalDocument(resourceUrl: URL): DocumentResponse /* throws ShapeTreeException */ { + try { + let request: HttpRequest = HttpRequest.newBuilder().GET().uri(resourceUrl.toURI()).build(); + let response: HttpResponse = this.httpClient.send(request, HttpResponse.BodyHandlers.ofString()); + if (response.statusCode() != 200) { + throw new IOException("Failed to load contents of document: " + resourceUrl); + } + let attributes: ResourceAttributes = new ResourceAttributes(response.headers().map()); + return new DocumentResponse(attributes, response.body(), response.statusCode()); + } catch (ex: IOException) { + throw new ShapeTreeException(500, "Error retrieving <" + resourceUrl + ">: " + ex.getMessage()); + } catch (ex: InterruptedException) { + Thread.currentThread().interrupt(); + throw new ShapeTreeException(500, "Error retrieving <" + resourceUrl + ">: " + ex.getMessage()); + } catch (ex: URISyntaxException) { + throw new ShapeTreeException(500, "Malformed URL <" + resourceUrl + ">: " + ex.getMessage()); + } + } +} diff --git a/asTypescript/packages/core/src/enums/HttpHeaders.ts b/asTypescript/packages/core/src/enums/HttpHeaders.ts new file mode 100644 index 00000000..48020a1a --- /dev/null +++ b/asTypescript/packages/core/src/enums/HttpHeaders.ts @@ -0,0 +1,22 @@ +// Corresponding shapetrees-java package: com.janeirodigital.shapetrees.core.enums +public enum HttpHeaders { + + ACCEPT("Accept"), + AUTHORIZATION("Authorization"), + CONTENT_TYPE("Content-Type"), + LINK("Link"), + LOCATION("Location"), + SLUG("Slug"), + INTEROP_ORIGINATOR("InteropOrigin"), + INTEROP_WEBID("InteropWebID"); + + public getValue(): string { + return this.value; + } + + private readonly value: string; + + constructor(value: string) { + this.value = value; + } +} diff --git a/asTypescript/packages/core/src/enums/LinkRelations.ts b/asTypescript/packages/core/src/enums/LinkRelations.ts new file mode 100644 index 00000000..b5f6f06f --- /dev/null +++ b/asTypescript/packages/core/src/enums/LinkRelations.ts @@ -0,0 +1,15 @@ +// Corresponding shapetrees-java package: com.janeirodigital.shapetrees.core.enums +public enum LinkRelations { + + DESCRIBED_BY("describedby"), FOCUS_NODE("http://www.w3.org/ns/shapetrees#FocusNode"), MANAGED_BY("http://www.w3.org/ns/shapetrees#managedBy"), MANAGES("http://www.w3.org/ns/shapetrees#manages"), TARGET_SHAPETREE("http://www.w3.org/ns/shapetrees#TargetShapeTree"), TYPE("type"), ACL("acl"); + + private readonly value: string; + + public getValue(): string { + return this.value; + } + + constructor(value: string) { + this.value = value; + } +} diff --git a/asTypescript/packages/core/src/enums/RecursionMethods.ts b/asTypescript/packages/core/src/enums/RecursionMethods.ts new file mode 100644 index 00000000..5106a741 --- /dev/null +++ b/asTypescript/packages/core/src/enums/RecursionMethods.ts @@ -0,0 +1,5 @@ +// Corresponding shapetrees-java package: com.janeirodigital.shapetrees.core.enums +public enum RecursionMethods { + + DEPTH_FIRST, BREADTH_FIRST +} diff --git a/asTypescript/packages/core/src/enums/ShapeTreeResourceType.ts b/asTypescript/packages/core/src/enums/ShapeTreeResourceType.ts new file mode 100644 index 00000000..7a31c03d --- /dev/null +++ b/asTypescript/packages/core/src/enums/ShapeTreeResourceType.ts @@ -0,0 +1,17 @@ +// Corresponding shapetrees-java package: com.janeirodigital.shapetrees.core.enums +import { ShapeTreeVocabulary } from '../vocabularies/ShapeTreeVocabulary'; + +public enum ShapeTreeResourceType { + + CONTAINER(ShapeTreeVocabulary.CONTAINER), RESOURCE(ShapeTreeVocabulary.RESOURCE), NON_RDF(ShapeTreeVocabulary.NON_RDF_RESOURCE); + + private readonly value: string; + + public getValue(): string { + return this.value; + } + + constructor(value: string) { + this.value = value; + } +} diff --git a/asTypescript/packages/core/src/exceptions/ShapeTreeException.ts b/asTypescript/packages/core/src/exceptions/ShapeTreeException.ts new file mode 100644 index 00000000..46195435 --- /dev/null +++ b/asTypescript/packages/core/src/exceptions/ShapeTreeException.ts @@ -0,0 +1,21 @@ +// Corresponding shapetrees-java package: com.janeirodigital.shapetrees.core.exceptions +export class ShapeTreeException extends Exception { + + private readonly statusCode: number; + + private readonly message: string; + + public constructor(statusCode: number, message: string) { + super; + this.statusCode = statusCode; + this.message = message; + } + + public getStatusCode(): number { + return this.statusCode; + } + + public getMessage(): string { + return this.message; + } +} diff --git a/asTypescript/packages/core/src/helpers/GraphHelper.ts b/asTypescript/packages/core/src/helpers/GraphHelper.ts new file mode 100644 index 00000000..e65a3fcf --- /dev/null +++ b/asTypescript/packages/core/src/helpers/GraphHelper.ts @@ -0,0 +1,192 @@ +// Corresponding shapetrees-java package: com.janeirodigital.shapetrees.core.helpers +import { ShapeTreeException } from '../exceptions/ShapeTreeException'; +import * as Slf4j from 'lombok/extern/slf4j'; +import * as XSDDatatype from 'org/apache/jena/datatypes/xsd'; +import * as graph from 'org/apache/jena'; +import * as Model from 'org/apache/jena/rdf/model'; +import * as ModelFactory from 'org/apache/jena/rdf/model'; +import * as Lang from 'org/apache/jena/riot'; +import * as RDFDataMgr from 'org/apache/jena/riot'; +import * as RiotException from 'org/apache/jena/riot'; +import { Writable } from 'stream'; +import * as MalformedURLException from 'java/net'; +import * as URI from 'java/net'; +import * as URISyntaxException from 'java/net'; +import * as URL from 'java/net'; +import * as OffsetDateTime from 'java/time'; + +/** + * Assorted helper methods related to RDF Graphs + */ +@Slf4j +export class GraphHelper { + + private constructor() { + } + + /** + * Determine the Jena language (graph serialization type) based on a content type string + * @param contentType Content type string + * @return Serialization language + */ + public static getLangForContentType(contentType: string): Lang { + // !! Optional + if (contentType === null) { + return Lang.TURTLE; + } + switch(contentType) { + case "application/ld+json": + return Lang.JSONLD; + case "application/rdf+xml": + return Lang.RDFXML; + case "application/n-triples": + return Lang.NTRIPLES; + default: + return Lang.TURTLE; + } + } + + /** + * Writes a graph into a turtle serialization + * @param graph Graph to serialize + * @return String in TTL serialization + */ + public static writeGraphToTurtleString(graph: Graph): string { + if (graph === null) + return null; + if (graph.isClosed()) + return null; + let sw: Writable = new Writable(); + RDFDataMgr.write(sw, graph, Lang.TURTLE); + graph.close(); + return sw.toString(); + } + + /** + * Deserializes a string into a Model + * @param baseURI Base URI to use for statements + * @param rawContent String of RDF + * @param contentType Content type of content + * @return Deserialized model + * @throws ShapeTreeException ShapeTreeException + */ + public static readStringIntoModel(baseURI: URI, rawContent: string, contentType: string): Model /* throws ShapeTreeException */ { + try { + let model: Model = ModelFactory.createDefaultModel(); + let reader: StringReader = new StringReader(rawContent); + RDFDataMgr.read(model.getGraph(), reader, baseURI.toString(), GraphHelper.getLangForContentType(contentType)); + return model; + } catch (rex: RiotException) { + throw new ShapeTreeException(422, "Error processing input - " + rex.getMessage()); + } + } + + /** + * Deserializes a string into a Graph + * @param baseURI Base URI to use for statements + * @param rawContent String of RDF + * @param contentType Content type of content + * @return Deserialized graph + * @throws ShapeTreeException ShapeTreeException + */ + public static readStringIntoGraph(baseURI: URI, rawContent: string, contentType: string): Graph /* throws ShapeTreeException */ { + return readStringIntoModel(baseURI, rawContent, contentType).getGraph(); + } + + /** + * Creates an empty Graph with initialized prefixes + * @return Graph Empty Graph + */ + public static getEmptyGraph(): Graph { + let model: Model = ModelFactory.createDefaultModel(); + model.setNsPrefix("rdfs", "http://www.w3.org/2000/01/rdf-schema#"); + model.setNsPrefix("xsd", "http://www.w3.org/2001/XMLSchema#"); + model.setNsPrefix("st", "http://www.w3.org/ns/shapetrees#"); + return model.getGraph(); + } + + /** + * Create a new triple statement with URIs + * @param subject Subject to include + * @param predicate Predicate to include + * @param object Object to include + * @return + */ + public static newTriple(subject: URI, predicate: URI, object: Object): Triple /* throws ShapeTreeException */ { + if (subject === null || predicate === null || object === null) { + throw new ShapeTreeException(500, "Cannot provide null values as input to triple construction"); + } + return newTriple(subject.toString(), predicate.toString(), object); + } + + /** + * Create a new triple statement with strings + * @param subject Subject to include + * @param predicate Predicate to include + * @param object Object to include + * @return + */ + public static newTriple(subject: string, predicate: string, object: Object): Triple /* throws ShapeTreeException */ { + if (subject === null || predicate === null || object === null) { + throw new ShapeTreeException(500, "Cannot provide null values as input to triple construction"); + } + let objectNode: Node = null; + if (object.getClass() === URI.class) { + // TODO: needed? + objectNode = NodeFactory.createURI(object.toString()); + } else if (object.getClass() === URL.class) { + objectNode = NodeFactory.createURI(object.toString()); + } else if (object.getClass() === String.class) { + objectNode = NodeFactory.createLiteral(object.toString()); + } else if (object.getClass() === OffsetDateTime.class) { + objectNode = NodeFactory.createLiteralByValue(object, XSDDatatype.XSDdateTime); + } else if (object.getClass() === Boolean.class) { + objectNode = NodeFactory.createLiteralByValue(object, XSDDatatype.XSDboolean); + } else if (object.getClass() === Node_Blank.class) { + objectNode = (Node) object; + } + if (objectNode === null) { + throw new ShapeTreeException(500, "Unsupported object value in triple construction: " + object.getClass()); + } + return new Triple(NodeFactory.createURI(subject), NodeFactory.createURI(predicate), objectNode); + } + + /** + * Wrap conversion from URL to URI which should never fail on a well-formed URL. + * @param url covert this URL to a URI + * @return IRI java native object for a URI (useful for Jena graph operations) + */ + public static urlToUri(url: URL): URI { + try { + return url.toURI(); + } catch (ex: URISyntaxException) { + throw new IllegalStateException("can't convert URL <" + url + "> to IRI: " + ex); + } + } + + /** + * Remove a fragment from a URL. Returns the same URL if there is no fragment + * @param url to remove fragment from + * @return URL without fragment + */ + public static removeUrlFragment(url: URL): URL { + let uri: URI = urlToUri(url); + if (uri.getFragment() === null) { + return url; + } + try { + let noFragment: URI = new URI(uri.getScheme(), uri.getSchemeSpecificPart(), null); + return noFragment.toURL(); + } catch (ex: MalformedURLException | URISyntaxException) { + throw new IllegalStateException("Unable to remove fragment from URL: " + ex.getMessage()); + } + } + + public static knownUrl(urlString: string): URL { + try { + return new URL(urlString); + } catch (ex: MalformedURLException) { + throw new IllegalStateException("Expected known URL <" + urlString + "> to parse as valid URL - " + ex.toString()); + } + } +} diff --git a/asTypescript/packages/core/src/helpers/RequestHelper.ts b/asTypescript/packages/core/src/helpers/RequestHelper.ts new file mode 100644 index 00000000..26cc725f --- /dev/null +++ b/asTypescript/packages/core/src/helpers/RequestHelper.ts @@ -0,0 +1,222 @@ +// Corresponding shapetrees-java package: com.janeirodigital.shapetrees.core.helpers +import * as core from 'com/janeirodigital/shapetrees'; +import { HttpHeaders } from '../enums/HttpHeaders'; +import { LinkRelations } from '../enums/LinkRelations'; +import { ShapeTreeResourceType } from '../enums/ShapeTreeResourceType'; +import { ShapeTreeException } from '../exceptions/ShapeTreeException'; +import { LdpVocabulary } from '../vocabularies/LdpVocabulary'; +import * as Slf4j from 'lombok/extern/slf4j'; +import * as Graph from 'org/apache/jena/graph'; +import * as ModelFactory from 'org/apache/jena/rdf/model'; +import * as UpdateAction from 'org/apache/jena/update'; +import * as UpdateFactory from 'org/apache/jena/update'; +import * as UpdateRequest from 'org/apache/jena/update'; +import * as MalformedURLException from 'java/net'; +import * as URL from 'java/net'; +import * as ArrayList from 'java/util'; +import * as Set from 'java/util'; +import { urlToUri } from './GraphHelper/urlToUri'; + +@Slf4j +export class RequestHelper { + + private static readonly PUT: string = "PUT"; + + private static readonly PATCH: string = "PATCH"; + + private static readonly DELETE: string = "DELETE"; + + private static readonly supportedRDFContentTypes: Set = Set.of("text/turtle", "application/rdf+xml", "application/n-triples", "application/ld+json"); + + private static readonly supportedSPARQLContentTypes: Set = Set.of("application/sparql-update"); + + private constructor() { + } + + /** + * Builds a ShapeTreeContext from the incoming request. Specifically it retrieves + * the incoming Authorization header and stashes that value for use on any additional requests made during + * validation. + * @param shapeTreeRequest Incoming request + * @return ShapeTreeContext object populated with authentication details, if present + */ + public static buildContextFromRequest(shapeTreeRequest: ShapeTreeRequest): ShapeTreeContext { + return new ShapeTreeContext(shapeTreeRequest.getHeaderValue(HttpHeaders.AUTHORIZATION.getValue())); + } + + /** + * This determines the type of resource being processed. + * + * Initial test is based on the incoming request headers, specifically the Content-Type header. + * If the content type is not one of the accepted RDF types, it will be treated as a NON-RDF source. + * + * Then the determination becomes whether or not the resource is a container. + * + * If it is a PATCH or PUT and the URL provided already exists, then the existing resource's Link header(s) + * are used to determine if it is a container or not. + * + * If it is a POST or if the resource does not already exist, the incoming request Link header(s) are relied + * upon. + * + * @param shapeTreeRequest The current incoming request + * @param existingResource The resource located at the incoming request's URL + * @return ShapeTreeResourceType aligning to current request + * @throws ShapeTreeException ShapeTreeException throw, specifically if Content-Type is not included on request + */ + public static determineResourceType(shapeTreeRequest: ShapeTreeRequest, existingResource: ManageableInstance): ShapeTreeResourceType /* throws ShapeTreeException */ { + let isNonRdf: boolean; + if (!shapeTreeRequest.getMethod() === DELETE) { + let incomingRequestContentType: string = shapeTreeRequest.getContentType(); + // Ensure a content-type is present + if (incomingRequestContentType === null) { + throw new ShapeTreeException(400, "Content-Type is required"); + } + isNonRdf = determineIsNonRdfSource(incomingRequestContentType); + } else { + isNonRdf = false; + } + if (isNonRdf) { + return ShapeTreeResourceType.NON_RDF; + } + let isContainer: boolean = false; + let resourceAlreadyExists: boolean = existingResource.getManageableResource().isExists(); + if ((shapeTreeRequest.getMethod() === PUT || shapeTreeRequest.getMethod() === PATCH) && resourceAlreadyExists) { + isContainer = existingResource.getManageableResource().isContainer(); + } else if (shapeTreeRequest.getLinkHeaders() != null) { + isContainer = getIsContainerFromRequest(shapeTreeRequest); + } + return isContainer ? ShapeTreeResourceType.CONTAINER : ShapeTreeResourceType.RESOURCE; + } + + public static getIncomingFocusNodes(shapeTreeRequest: ShapeTreeRequest, baseUrl: URL): Array /* throws ShapeTreeException */ { + const focusNodeStrings: Array = shapeTreeRequest.getLinkHeaders().allValues(LinkRelations.FOCUS_NODE.getValue()); + const focusNodeUrls: Array = new ArrayList<>(); + if (!focusNodeStrings.isEmpty()) { + for (let focusNodeUrlString: string : focusNodeStrings) { + try { + const focusNodeUrl: URL = new URL(baseUrl, focusNodeUrlString); + focusNodeUrls.add(focusNodeUrl); + } catch (e: MalformedURLException) { + throw new ShapeTreeException(500, "Malformed focus node when resolving <" + focusNodeUrlString + "> against <" + baseUrl + ">"); + } + } + } + return focusNodeUrls; + } + + /** + * Gets target shape tree / hint from request header + * @param shapeTreeRequest Request + * @return URL value of target shape tree + * @throws ShapeTreeException ShapeTreeException + */ + public static getIncomingTargetShapeTrees(shapeTreeRequest: ShapeTreeRequest, baseUrl: URL): Array /* throws ShapeTreeException */ { + const targetShapeTreeStrings: Array = shapeTreeRequest.getLinkHeaders().allValues(LinkRelations.TARGET_SHAPETREE.getValue()); + const targetShapeTreeUrls: Array = new ArrayList<>(); + if (!targetShapeTreeStrings.isEmpty()) { + for (let targetShapeTreeUrlString: string : targetShapeTreeStrings) { + try { + const targetShapeTreeUrl: URL = new URL(targetShapeTreeUrlString); + targetShapeTreeUrls.add(targetShapeTreeUrl); + } catch (e: MalformedURLException) { + throw new ShapeTreeException(500, "Malformed focus node when resolving <" + targetShapeTreeUrlString + "> against <" + baseUrl + ">"); + } + } + } + return targetShapeTreeUrls; + } + + public static getIncomingShapeTreeManager(shapeTreeRequest: ShapeTreeRequest, managerResource: ManagerResource): ShapeTreeManager /* throws ShapeTreeException */ { + let incomingBodyGraph: Graph = RequestHelper.getIncomingBodyGraph(shapeTreeRequest, RequestHelper.normalizeSolidResourceUrl(shapeTreeRequest.getUrl(), null, ShapeTreeResourceType.RESOURCE), managerResource); + if (incomingBodyGraph === null) { + return null; + } + return ShapeTreeManager.getFromGraph(shapeTreeRequest.getUrl(), incomingBodyGraph); + } + + /** + * Normalizes the BaseURL to use for a request based on the incoming request. + * @param url URL of request + * @param requestedName Requested name of resource (provided on created resources via POST) + * @param resourceType Description of resource (Container, NonRDF, Resource) + * @return BaseURL to use for RDF Graphs + * @throws ShapeTreeException ShapeTreeException + */ + public static normalizeSolidResourceUrl(url: URL, requestedName: string, resourceType: ShapeTreeResourceType): URL /* throws ShapeTreeException */ { + let urlString: string = url.toString(); + if (requestedName != null) { + urlString += requestedName; + } + if (resourceType === ShapeTreeResourceType.CONTAINER && !urlString.endsWith("/")) { + urlString += "/"; + } + try { + return new URL(urlString); + } catch (ex: MalformedURLException) { + throw new ShapeTreeException(500, "normalized to malformed URL <" + urlString + "> - " + ex.getMessage()); + } + } + + /** + * Loads body of request into graph + * @param shapeTreeRequest Request + * @param baseUrl BaseURL to use for graph + * @param targetResource + * @return Graph representation of request body + * @throws ShapeTreeException ShapeTreeException + */ + public static getIncomingBodyGraph(shapeTreeRequest: ShapeTreeRequest, baseUrl: URL, targetResource: InstanceResource): Graph /* throws ShapeTreeException */ { + log.debug("Reading request body into graph with baseUrl {}", baseUrl); + if ((shapeTreeRequest.getResourceType() === ShapeTreeResourceType.NON_RDF && !shapeTreeRequest.getContentType().equalsIgnoreCase("application/sparql-update")) || shapeTreeRequest.getBody() === null || shapeTreeRequest.getBody().length() === 0) { + return null; + } + let targetResourceGraph: Graph = null; + if (shapeTreeRequest.getMethod() === PATCH) { + // In the event of a SPARQL PATCH, we get the SPARQL query and evaluate it, passing the + // resultant graph back to the caller + if (targetResource != null) { + targetResourceGraph = targetResource.getGraph(baseUrl); + } + if (targetResourceGraph === null) { + // if the target resource doesn't exist or has no content + log.debug("Existing target resource graph to patch does not exist. Creating an empty graph."); + targetResourceGraph = ModelFactory.createDefaultModel().getGraph(); + } + // Perform a SPARQL update locally to ensure that resulting graph validates against ShapeTree + let updateRequest: UpdateRequest = UpdateFactory.create(shapeTreeRequest.getBody(), baseUrl.toString()); + UpdateAction.execute(updateRequest, targetResourceGraph); + if (targetResourceGraph === null) { + throw new ShapeTreeException(400, "No graph after update"); + } + } else { + targetResourceGraph = GraphHelper.readStringIntoGraph(urlToUri(baseUrl), shapeTreeRequest.getBody(), shapeTreeRequest.getContentType()); + } + return targetResourceGraph; + } + + /** + * Determines whether a content type is a supported RDF type + * @param incomingRequestContentType Content type to test + * @return Boolean indicating whether it is RDF or not + */ + private static determineIsNonRdfSource(incomingRequestContentType: string): boolean { + return (!supportedRDFContentTypes.contains(incomingRequestContentType.toLowerCase()) && !supportedSPARQLContentTypes.contains(incomingRequestContentType.toLowerCase())); + } + + /** + * Determines if a resource should be treated as a container based on its request Link headers + * @param shapeTreeRequest Request + * @return Is the resource a container? + */ + private static getIsContainerFromRequest(shapeTreeRequest: ShapeTreeRequest): Boolean { + // First try to determine based on link headers + if (shapeTreeRequest.getLinkHeaders() != null) { + const typeLinks: Array = shapeTreeRequest.getLinkHeaders().allValues(LinkRelations.TYPE.getValue()); + if (!typeLinks.isEmpty()) { + return (typeLinks.contains(LdpVocabulary.CONTAINER) || typeLinks.contains(LdpVocabulary.BASIC_CONTAINER)); + } + } + // As a secondary attempt, use slash path semantics + return shapeTreeRequest.getUrl().getPath().endsWith("/"); + } +} diff --git a/asTypescript/packages/core/src/methodhandlers/AbstractValidatingMethodHandler.ts b/asTypescript/packages/core/src/methodhandlers/AbstractValidatingMethodHandler.ts new file mode 100644 index 00000000..526ea47a --- /dev/null +++ b/asTypescript/packages/core/src/methodhandlers/AbstractValidatingMethodHandler.ts @@ -0,0 +1,22 @@ +// Corresponding shapetrees-java package: com.janeirodigital.shapetrees.core.methodhandlers +import { ResourceAccessor } from '../ResourceAccessor'; +import { ShapeTreeRequestHandler } from '../ShapeTreeRequestHandler'; +import * as Slf4j from 'lombok/extern/slf4j'; + +/** + * Abstract class providing reusable functionality to different method handlers + */ +@Slf4j +export abstract class AbstractValidatingMethodHandler { + + private static readonly DELETE: string = "DELETE"; + + protected readonly resourceAccessor: ResourceAccessor; + + protected readonly requestHandler: ShapeTreeRequestHandler; + + protected constructor(resourceAccessor: ResourceAccessor) { + this.resourceAccessor = resourceAccessor; + this.requestHandler = new ShapeTreeRequestHandler(resourceAccessor); + } +} diff --git a/asTypescript/packages/core/src/methodhandlers/ValidatingDeleteMethodHandler.ts b/asTypescript/packages/core/src/methodhandlers/ValidatingDeleteMethodHandler.ts new file mode 100644 index 00000000..8e2a8a79 --- /dev/null +++ b/asTypescript/packages/core/src/methodhandlers/ValidatingDeleteMethodHandler.ts @@ -0,0 +1,31 @@ +// Corresponding shapetrees-java package: com.janeirodigital.shapetrees.core.methodhandlers +import { DocumentResponse } from '../DocumentResponse'; +import { ResourceAccessor } from '../ResourceAccessor'; +import { ManageableInstance } from '../ManageableInstance'; +import { ShapeTreeRequest } from '../ShapeTreeRequest'; +import { ShapeTreeException } from '../exceptions/ShapeTreeException'; +import { RequestHelper } from '../helpers/RequestHelper'; +import { ShapeTreeContext } from '../ShapeTreeContext'; +import * as Optional from 'java/util'; +import { AbstractValidatingMethodHandler } from './AbstractValidatingMethodHandler'; +import { ValidatingMethodHandler } from './ValidatingMethodHandler'; + +export class ValidatingDeleteMethodHandler extends AbstractValidatingMethodHandler implements ValidatingMethodHandler { + + public constructor(resourceAccessor: ResourceAccessor) { + super(resourceAccessor); + } + + override public validateRequest(shapeTreeRequest: ShapeTreeRequest): Optional /* throws ShapeTreeException */ { + let shapeTreeContext: ShapeTreeContext = RequestHelper.buildContextFromRequest(shapeTreeRequest); + let targetInstance: ManageableInstance = this.resourceAccessor.getInstance(shapeTreeContext, shapeTreeRequest.getUrl()); + if (targetInstance.wasRequestForManager() && targetInstance.getManagerResource().isExists()) { + // If the DELETE request is for an existing shapetree manager resource, + // it must be evaluated to determine if unplanting is necessary + return Optional.of(this.requestHandler.manageShapeTree(targetInstance, shapeTreeRequest)); + } + // Reaching this point means validation was not necessary + // Pass the request along with no validation + return Optional.empty(); + } +} diff --git a/asTypescript/packages/core/src/methodhandlers/ValidatingMethodHandler.ts b/asTypescript/packages/core/src/methodhandlers/ValidatingMethodHandler.ts new file mode 100644 index 00000000..0cd0350b --- /dev/null +++ b/asTypescript/packages/core/src/methodhandlers/ValidatingMethodHandler.ts @@ -0,0 +1,10 @@ +// Corresponding shapetrees-java package: com.janeirodigital.shapetrees.core.methodhandlers +import { DocumentResponse } from '../DocumentResponse'; +import { ShapeTreeException } from '../exceptions/ShapeTreeException'; +import { ShapeTreeRequest } from '../ShapeTreeRequest'; +import * as Optional from 'java/util'; + +export interface ValidatingMethodHandler { + + validateRequest(shapeTreeRequest: ShapeTreeRequest): Optional /* throws ShapeTreeException */; +} diff --git a/asTypescript/packages/core/src/methodhandlers/ValidatingPatchMethodHandler.ts b/asTypescript/packages/core/src/methodhandlers/ValidatingPatchMethodHandler.ts new file mode 100644 index 00000000..5a7975d7 --- /dev/null +++ b/asTypescript/packages/core/src/methodhandlers/ValidatingPatchMethodHandler.ts @@ -0,0 +1,50 @@ +// Corresponding shapetrees-java package: com.janeirodigital.shapetrees.core.methodhandlers +import * as core from 'com/janeirodigital/shapetrees'; +import { ShapeTreeException } from '../exceptions/ShapeTreeException'; +import { RequestHelper } from '../helpers/RequestHelper'; +import { ShapeTreeContext } from '../ShapeTreeContext'; +import * as Slf4j from 'lombok/extern/slf4j'; +import * as Optional from 'java/util'; +import { AbstractValidatingMethodHandler } from './AbstractValidatingMethodHandler'; +import { ValidatingMethodHandler } from './ValidatingMethodHandler'; + +@Slf4j +export class ValidatingPatchMethodHandler extends AbstractValidatingMethodHandler implements ValidatingMethodHandler { + + public constructor(resourceAccessor: ResourceAccessor) { + super(resourceAccessor); + } + + override public validateRequest(shapeTreeRequest: ShapeTreeRequest): Optional /* throws ShapeTreeException */ { + if (shapeTreeRequest.getContentType() === null || !shapeTreeRequest.getContentType().equalsIgnoreCase("application/sparql-update")) { + log.error("Received a patch without a content type of application/sparql-update"); + throw new ShapeTreeException(415, "PATCH verb expects a content type of application/sparql-update"); + } + let shapeTreeContext: ShapeTreeContext = RequestHelper.buildContextFromRequest(shapeTreeRequest); + let targetInstance: ManageableInstance = this.resourceAccessor.getInstance(shapeTreeContext, shapeTreeRequest.getUrl()); + if (targetInstance.wasRequestForManager()) { + // Target resource is for shape tree manager, manage shape trees to plant and/or unplant + return Optional.of(this.requestHandler.manageShapeTree(targetInstance, shapeTreeRequest)); + } else { + let targetResource: ManageableResource = targetInstance.getManageableResource(); + shapeTreeRequest.setResourceType(RequestHelper.determineResourceType(shapeTreeRequest, targetInstance)); + if (targetResource.isExists()) { + // The target resource already exists + if (targetInstance.isManaged()) { + // If it is managed by a shape tree the update must be validated + return this.requestHandler.updateShapeTreeInstance(targetInstance, shapeTreeContext, shapeTreeRequest); + } + } else { + // The target resource doesn't exist + let parentInstance: ManageableInstance = this.resourceAccessor.getInstance(shapeTreeContext, targetResource.getParentContainerUrl()); + if (parentInstance.isManaged()) { + // If the parent container is managed by a shape tree, the resource to create must be validated + return this.requestHandler.createShapeTreeInstance(targetInstance, parentInstance, shapeTreeRequest, targetResource.getName()); + } + } + } + // Reaching this point means validation was not necessary + // Pass the request along with no validation + return Optional.empty(); + } +} diff --git a/asTypescript/packages/core/src/methodhandlers/ValidatingPostMethodHandler.ts b/asTypescript/packages/core/src/methodhandlers/ValidatingPostMethodHandler.ts new file mode 100644 index 00000000..0dd9cec9 --- /dev/null +++ b/asTypescript/packages/core/src/methodhandlers/ValidatingPostMethodHandler.ts @@ -0,0 +1,36 @@ +// Corresponding shapetrees-java package: com.janeirodigital.shapetrees.core.methodhandlers +import * as core from 'com/janeirodigital/shapetrees'; +import { HttpHeaders } from '../enums/HttpHeaders'; +import { ShapeTreeException } from '../exceptions/ShapeTreeException'; +import { RequestHelper } from '../helpers/RequestHelper'; +import { ShapeTreeContext } from '../ShapeTreeContext'; +import * as Slf4j from 'lombok/extern/slf4j'; +import * as Optional from 'java/util'; +import * as UUID from 'java/util'; +import { AbstractValidatingMethodHandler } from './AbstractValidatingMethodHandler'; +import { ValidatingMethodHandler } from './ValidatingMethodHandler'; + +@Slf4j +export class ValidatingPostMethodHandler extends AbstractValidatingMethodHandler implements ValidatingMethodHandler { + + public constructor(resourceAccessor: ResourceAccessor) { + super(resourceAccessor); + } + + override public validateRequest(shapeTreeRequest: ShapeTreeRequest): Optional /* throws ShapeTreeException */ { + let shapeTreeContext: ShapeTreeContext = RequestHelper.buildContextFromRequest(shapeTreeRequest); + // Look up the target container for the POST. Error if it doesn't exist, or is a manager resource + let targetContainer: ManageableInstance = this.resourceAccessor.getInstance(shapeTreeContext, shapeTreeRequest.getUrl()); + // Get resource name from the slug or default to UUID + let proposedName: string = shapeTreeRequest.getHeaders().firstValue(HttpHeaders.SLUG.getValue()).orElse(UUID.randomUUID().toString()); + // If the parent container is managed by a shape tree, the proposed resource being posted must be + // validated against the parent tree. + if (targetContainer.isManaged()) { + shapeTreeRequest.setResourceType(RequestHelper.determineResourceType(shapeTreeRequest, targetContainer)); + return this.requestHandler.createShapeTreeInstance(targetContainer, targetContainer, shapeTreeRequest, proposedName); + } + // Reaching this point means validation was not necessary + // Pass the request along with no validation + return Optional.empty(); + } +} diff --git a/asTypescript/packages/core/src/methodhandlers/ValidatingPutMethodHandler.ts b/asTypescript/packages/core/src/methodhandlers/ValidatingPutMethodHandler.ts new file mode 100644 index 00000000..dcd876ab --- /dev/null +++ b/asTypescript/packages/core/src/methodhandlers/ValidatingPutMethodHandler.ts @@ -0,0 +1,44 @@ +// Corresponding shapetrees-java package: com.janeirodigital.shapetrees.core.methodhandlers +import * as core from 'com/janeirodigital/shapetrees'; +import { ShapeTreeException } from '../exceptions/ShapeTreeException'; +import { RequestHelper } from '../helpers/RequestHelper'; +import { ShapeTreeContext } from '../ShapeTreeContext'; +import * as Optional from 'java/util'; +import { AbstractValidatingMethodHandler } from './AbstractValidatingMethodHandler'; +import { ValidatingMethodHandler } from './ValidatingMethodHandler'; + +export class ValidatingPutMethodHandler extends AbstractValidatingMethodHandler implements ValidatingMethodHandler { + + public constructor(resourceAccessor: ResourceAccessor) { + super(resourceAccessor); + } + + override public validateRequest(shapeTreeRequest: ShapeTreeRequest): Optional /* throws ShapeTreeException */ { + let shapeTreeContext: ShapeTreeContext = RequestHelper.buildContextFromRequest(shapeTreeRequest); + let targetInstance: ManageableInstance = this.resourceAccessor.getInstance(shapeTreeContext, shapeTreeRequest.getUrl()); + if (targetInstance.wasRequestForManager()) { + // Target resource is for shape tree manager, manage shape trees to plant and/or unplant + return Optional.of(this.requestHandler.manageShapeTree(targetInstance, shapeTreeRequest)); + } else { + let targetResource: ManageableResource = targetInstance.getManageableResource(); + shapeTreeRequest.setResourceType(RequestHelper.determineResourceType(shapeTreeRequest, targetInstance)); + if (targetResource.isExists()) { + // The target resource already exists + if (targetInstance.isManaged()) { + // If it is managed by a shape tree the update must be validated + return this.requestHandler.updateShapeTreeInstance(targetInstance, shapeTreeContext, shapeTreeRequest); + } + } else { + // The target resource doesn't exist + let parentInstance: ManageableInstance = this.resourceAccessor.getInstance(shapeTreeContext, targetResource.getParentContainerUrl()); + if (parentInstance.isManaged()) { + // If the parent container is managed by a shape tree, the resource to create must be validated + return this.requestHandler.createShapeTreeInstance(targetInstance, parentInstance, shapeTreeRequest, targetResource.getName()); + } + } + } + // Reaching this point means validation was not necessary + // Pass the request along with no validation + return Optional.empty(); + } +} diff --git a/asTypescript/packages/core/src/vocabularies/LdpVocabulary.ts b/asTypescript/packages/core/src/vocabularies/LdpVocabulary.ts new file mode 100644 index 00000000..6401c005 --- /dev/null +++ b/asTypescript/packages/core/src/vocabularies/LdpVocabulary.ts @@ -0,0 +1,12 @@ +// Corresponding shapetrees-java package: com.janeirodigital.shapetrees.core.vocabularies +export const class LdpVocabulary { + + private constructor() { + } + + public static readonly CONTAINER: string = Namespaces.LDP + "Container"; + + public static readonly BASIC_CONTAINER: string = Namespaces.LDP + "BasicContainer"; + + public static readonly CONTAINS: string = Namespaces.LDP + "contains"; +} diff --git a/asTypescript/packages/core/src/vocabularies/Namespaces.ts b/asTypescript/packages/core/src/vocabularies/Namespaces.ts new file mode 100644 index 00000000..5d847435 --- /dev/null +++ b/asTypescript/packages/core/src/vocabularies/Namespaces.ts @@ -0,0 +1,12 @@ +// Corresponding shapetrees-java package: com.janeirodigital.shapetrees.core.vocabularies +export const class Namespaces { + + private constructor() { + } + + public static readonly SHAPETREE: string = "http://www.w3.org/ns/shapetrees#"; + + public static readonly LDP: string = "http://www.w3.org/ns/ldp#"; + + public static readonly RDF: string = "http://www.w3.org/1999/02/22-rdf-syntax-ns#"; +} diff --git a/asTypescript/packages/core/src/vocabularies/RdfVocabulary.ts b/asTypescript/packages/core/src/vocabularies/RdfVocabulary.ts new file mode 100644 index 00000000..d5f41453 --- /dev/null +++ b/asTypescript/packages/core/src/vocabularies/RdfVocabulary.ts @@ -0,0 +1,9 @@ +// Corresponding shapetrees-java package: com.janeirodigital.shapetrees.core.vocabularies +export const class RdfVocabulary { + + private constructor() { + } + + // rdf:type + public static readonly TYPE: string = Namespaces.RDF + "type"; +} diff --git a/asTypescript/packages/core/src/vocabularies/ShapeTreeVocabulary.ts b/asTypescript/packages/core/src/vocabularies/ShapeTreeVocabulary.ts new file mode 100644 index 00000000..7d26a7c8 --- /dev/null +++ b/asTypescript/packages/core/src/vocabularies/ShapeTreeVocabulary.ts @@ -0,0 +1,40 @@ +// Corresponding shapetrees-java package: com.janeirodigital.shapetrees.core.vocabularies +export const class ShapeTreeVocabulary { + + private constructor() { + } + + public static readonly HAS_ROOT_ASSIGNMENT: string = Namespaces.SHAPETREE + "hasRootAssignment"; + + public static readonly MANAGES_RESOURCE: string = Namespaces.SHAPETREE + "manages"; + + public static readonly ASSIGNS_SHAPE_TREE: string = Namespaces.SHAPETREE + "assigns"; + + public static readonly REFERENCES_SHAPE_TREE: string = Namespaces.SHAPETREE + "referencesShapeTree"; + + public static readonly SHAPETREE_MANAGER: string = Namespaces.SHAPETREE + "Manager"; + + public static readonly SHAPETREE_ASSIGNMENT: string = Namespaces.SHAPETREE + "Assignment"; + + public static readonly EXPECTS_TYPE: string = Namespaces.SHAPETREE + "expectsType"; + + public static readonly REFERENCES: string = Namespaces.SHAPETREE + "references"; + + public static readonly VIA_SHAPE_PATH: string = Namespaces.SHAPETREE + "viaShapePath"; + + public static readonly VIA_PREDICATE: string = Namespaces.SHAPETREE + "viaPredicate"; + + public static readonly CONTAINS: string = Namespaces.SHAPETREE + "contains"; + + public static readonly HAS_ASSIGNMENT: string = Namespaces.SHAPETREE + "hasAssignment"; + + public static readonly SHAPE: string = Namespaces.SHAPETREE + "shape"; + + public static readonly FOCUS_NODE: string = Namespaces.SHAPETREE + "focusNode"; + + public static readonly CONTAINER: string = Namespaces.SHAPETREE + "Container"; + + public static readonly RESOURCE: string = Namespaces.SHAPETREE + "Resource"; + + public static readonly NON_RDF_RESOURCE: string = Namespaces.SHAPETREE + "NonRDFResource"; +} diff --git a/asTypescript/packages/javahttp/src/JavaHttpClient.ts b/asTypescript/packages/javahttp/src/JavaHttpClient.ts new file mode 100644 index 00000000..5978d8b5 --- /dev/null +++ b/asTypescript/packages/javahttp/src/JavaHttpClient.ts @@ -0,0 +1,147 @@ +// Corresponding shapetrees-java package: com.janeirodigital.shapetrees.javahttp +import { HttpClient } from '@shapetrees/ttpClient'; +import { HttpRequest } from '@shapetrees/ttpRequest'; +import { DocumentResponse } from '@shapetrees/ocumentResponse'; +import { ResourceAttributes } from '@shapetrees/esourceAttributes'; +import { ShapeTreeException } from '@shapetrees/xceptions/ShapeTreeException'; +import * as Slf4j from 'lombok/extern/slf4j'; +import * as ssl from 'javax/net'; +import * as URISyntaxException from 'java/net'; +import * as KeyManagementException from 'java/security'; +import * as NoSuchAlgorithmException from 'java/security'; +import * as CertificateException from 'java/security/cert'; +import * as X509Certificate from 'java/security/cert'; +import * as Objects from 'java/util'; +import { JavaHttpValidatingShapeTreeInterceptor } from './JavaHttpValidatingShapeTreeInterceptor'; + +/** + * java.net.http implementation of HttpClient + */ +@Slf4j +export class JavaHttpClient implements HttpClient { + + private readonly httpClient: java.net.http.HttpClient; + + private validatingWrapper: JavaHttpValidatingShapeTreeInterceptor; + + /** + * Execute an HTTP request to create a DocumentResponse object + * Implements `HttpClient` interface + * @param request an HTTP request with appropriate headers for ShapeTree interactions + * @return new DocumentResponse with response headers and contents + * @throws ShapeTreeException + */ + override public fetchShapeTreeResponse(request: HttpRequest): DocumentResponse /* throws ShapeTreeException */ { + let response: java.net.http.HttpResponse = fetch(request); + let body: string = null; + try { + body = Objects.requireNonNull(response.body()).toString(); + } catch (ex: NullPointerException) { + log.error("Exception retrieving body string"); + } + return new DocumentResponse(new ResourceAttributes(response.headers().map()), body, response.statusCode()); + } + + /** + * Construct an JavaHttpClient with switches to enable or disable SSL and ShapeTree validation + * @param useSslValidation + * @param useShapeTreeValidation + * @throws NoSuchAlgorithmException potentially thrown while disabling SSL validation + * @throws KeyManagementException potentially thrown while disabling SSL validation + */ + protected constructor(useSslValidation: boolean, useShapeTreeValidation: boolean) throws NoSuchAlgorithmException, KeyManagementException { + let clientBuilder: java.net.http.HttpClient.Builder = java.net.http.HttpClient.newBuilder(); + this.validatingWrapper = null; + if (Boolean.TRUE === useShapeTreeValidation) { + this.validatingWrapper = new JavaHttpValidatingShapeTreeInterceptor(); + } + if (Boolean.FALSE === useSslValidation) { + let trustAllCerts: TrustManager[] = new TrustManager[] { new X509TrustManager() { + + public getAcceptedIssuers(): java.security.cert.X509Certificate[] { + return null; + } + + override public checkClientTrusted(arg0: X509Certificate[], arg1: string): void /* throws CertificateException */ { + } + + override public checkServerTrusted(arg0: X509Certificate[], arg1: string): void /* throws CertificateException */ { + } + } }; + let sc: SSLContext = null; + try { + sc = SSLContext.getInstance("TLSv1.2"); + } catch (e: NoSuchAlgorithmException) { + e.printStackTrace(); + } + try { + sc.init(null, trustAllCerts, new java.security.SecureRandom()); + } catch (e: KeyManagementException) { + e.printStackTrace(); + } + HttpsURLConnection.setDefaultSSLSocketFactory(sc.getSocketFactory()); + // Create all-trusting host name verifier + let validHosts: HostnameVerifier = new HostnameVerifier() { + + override public verify(arg0: string, arg1: SSLSession): boolean { + return true; + } + }; + // All hosts will be valid + HttpsURLConnection.setDefaultHostnameVerifier(validHosts); + } + this.httpClient = clientBuilder.build(); + } + + /** + * Internal function to execute HTTP request and return java.net.http response + * @param request + * @return + * @throws ShapeTreeException + */ + private fetch(request: HttpRequest): java.net.http.HttpResponse /* throws ShapeTreeException */ { + if (request.body === null) + request.body = ""; + try { + let requestBuilder: java.net.http.HttpRequest.Builder = java.net.http.HttpRequest.newBuilder(); + requestBuilder.uri(request.resourceURL.toURI()); + if (request.headers != null) { + let headerList: string[] = request.headers.toList("connection", "content-length", "date", "expect", "from", "host", "upgrade", "via", "warning"); + if (headerList.length > 0) { + requestBuilder.headers(headerList); + } + } + switch(request.method) { + case HttpClient.GET: + case HttpClient.DELETE: + requestBuilder.method(request.method, java.net.http.HttpRequest.BodyPublishers.noBody()); + break; + case HttpClient.PUT: + case HttpClient.POST: + case HttpClient.PATCH: + requestBuilder.method(request.method, java.net.http.HttpRequest.BodyPublishers.ofString(request.body)); + requestBuilder.header("Content-Type", request.contentType); + break; + default: + throw new ShapeTreeException(500, "Unsupported HTTP method for resource creation"); + } + let nativeRequest: java.net.http.HttpRequest = requestBuilder.build(); + if (this.validatingWrapper === null) { + return JavaHttpClient.check(this.httpClient.send(nativeRequest, java.net.http.HttpResponse.BodyHandlers.ofString())); + } else { + return this.validatingWrapper.validatingWrap(nativeRequest, this.httpClient, request.body, request.contentType); + } + } catch (ex: IOException | InterruptedException) { + throw new ShapeTreeException(500, ex.getMessage()); + } catch (ex: URISyntaxException) { + throw new ShapeTreeException(500, "Malformed URL <" + request.resourceURL + ">: " + ex.getMessage()); + } + } + + protected static check(resp: java.net.http.HttpResponse): java.net.http.HttpResponse { + if (resp.statusCode() > 599) { + throw new Error("invalid HTTP response: " + resp + (resp.body() === null ? "" : "\n" + resp.body())); + } + return resp; + } +} diff --git a/asTypescript/packages/javahttp/src/JavaHttpClientFactory.ts b/asTypescript/packages/javahttp/src/JavaHttpClientFactory.ts new file mode 100644 index 00000000..13a237e9 --- /dev/null +++ b/asTypescript/packages/javahttp/src/JavaHttpClientFactory.ts @@ -0,0 +1,61 @@ +// Corresponding shapetrees-java package: com.janeirodigital.shapetrees.javahttp +import { HttpClientFactory } from '@shapetrees/ttpClientFactory'; +import { HttpRequest } from '@shapetrees/ttpRequest'; +import { DocumentResponse } from '@shapetrees/ocumentResponse'; +import { ExternalDocumentLoader } from '@shapetrees/ontentloaders/ExternalDocumentLoader'; +import { ShapeTreeException } from '@shapetrees/xceptions/ShapeTreeException'; +import * as URL from 'java/net'; +import { JavaHttpClient } from './JavaHttpClient'; + +/** + * The ShapeTree library uses a generic interface (`HttpClient`) to execute HTTP queries on the POD and for external documents. + * The JavaHttpClient uses the java.net.http library to implement `HttpClient`. + * This factory generates variations of java.net.http those clients depending on the need for SSL validation and ShapeTree validation. + */ +export class JavaHttpClientFactory implements HttpClientFactory, ExternalDocumentLoader { + + useSslValidation: boolean; + + /** + * Construct a factory for JavaHttpClients + * + * @param useSslValidation + */ + constructor(useSslValidation: boolean) { + this.useSslValidation = useSslValidation; + } + + /** + * Create a new java.net.http HttpClient. + * This fulfils the HttpClientFactory interface, so this factory can be use in + * HttpClientFactoryManager.setFactory(new JavaHttpClientFactory(...)); + * + * @param useShapeTreeValidation + * @return a new or existing java.net.http HttpClient + * @throws ShapeTreeException if the JavaHttpClient constructor threw one + */ + public get(useShapeTreeValidation: boolean): JavaHttpClient /* throws ShapeTreeException */ { + try { + return new JavaHttpClient(this.useSslValidation, useShapeTreeValidation); + } catch (ex: Exception) { + throw new ShapeTreeException(500, ex.getMessage()); + } + } + + /** + * Load a non-POD document + * This fulfils the ExternalDocumentLoader interface, so this factory can be use in + * DocumentLoaderManager.setLoader(new JavaHttpClientFactory(...)); + * + * @param resourceUrl URL of resource to be retrieved + * @return a DocumentResponse with the results of a successful GET + * @throws ShapeTreeException if the GET was not successful + */ + override public loadExternalDocument(resourceUrl: URL): DocumentResponse /* throws ShapeTreeException */ { + let response: DocumentResponse = this.get(false).fetchShapeTreeResponse(new HttpRequest("GET", resourceUrl, null, null, null)); + if (response.getStatusCode() != 200) { + throw new ShapeTreeException(500, "Failed to load contents of document: " + resourceUrl); + } + return response; + } +} diff --git a/asTypescript/packages/javahttp/src/JavaHttpValidatingShapeTreeInterceptor.ts b/asTypescript/packages/javahttp/src/JavaHttpValidatingShapeTreeInterceptor.ts new file mode 100644 index 00000000..cc66a5d6 --- /dev/null +++ b/asTypescript/packages/javahttp/src/JavaHttpValidatingShapeTreeInterceptor.ts @@ -0,0 +1,201 @@ +// Corresponding shapetrees-java package: com.janeirodigital.shapetrees.javahttp +import { HttpResourceAccessor } from '@shapetrees/ttpResourceAccessor'; +import * as core from 'com/janeirodigital/shapetrees'; +import { HttpHeaders } from '@shapetrees/nums/HttpHeaders'; +import { ShapeTreeResourceType } from '@shapetrees/nums/ShapeTreeResourceType'; +import { ShapeTreeException } from '@shapetrees/xceptions/ShapeTreeException'; +import * as methodhandlers from '@shapetrees/ethodhandlers'; +import * as Slf4j from 'lombok/extern/slf4j'; +import * as NotNull from 'org/jetbrains/annotations'; +import * as SSLSession from 'javax/net/ssl'; +import * as MalformedURLException from 'java/net'; +import * as URI from 'java/net'; +import * as URL from 'java/net'; +import * as HttpResponse from 'java/net/http'; +import * as util from 'java'; + +/** + * Wrapper used for client-side validation + */ +@Slf4j +export class JavaHttpValidatingShapeTreeInterceptor { + + private static readonly POST: string = "POST"; + + private static readonly PUT: string = "PUT"; + + private static readonly PATCH: string = "PATCH"; + + private static readonly DELETE: string = "DELETE"; + + // @NotNull + public validatingWrap(clientRequest: java.net.http.HttpRequest, httpClient: java.net.http.HttpClient, body: string, contentType: string): java.net.http.HttpResponse /* throws IOException, InterruptedException */ { + let shapeTreeRequest: ShapeTreeRequest = new JavaHttpShapeTreeRequest(clientRequest, body, contentType); + let resourceAccessor: ResourceAccessor = new HttpResourceAccessor(); + // Get the handler + let handler: ValidatingMethodHandler = getHandler(shapeTreeRequest.getMethod(), resourceAccessor); + if (handler != null) { + try { + let shapeTreeResponse: Optional = handler.validateRequest(shapeTreeRequest); + if (!shapeTreeResponse.isPresent()) { + return JavaHttpClient.check(httpClient.send(clientRequest, java.net.http.HttpResponse.BodyHandlers.ofString())); + } else { + return createResponse(clientRequest, shapeTreeResponse.get()); + } + } catch (ex: ShapeTreeException) { + log.error("Error processing shape tree request: ", ex); + return createErrorResponse(ex, clientRequest); + } catch (ex: Exception) { + log.error("Error processing shape tree request: ", ex); + return createErrorResponse(new ShapeTreeException(500, ex.getMessage()), clientRequest); + } + } else { + log.warn("No handler for method [{}] - passing through request", shapeTreeRequest.getMethod()); + return JavaHttpClient.check(httpClient.send(clientRequest, java.net.http.HttpResponse.BodyHandlers.ofString())); + } + } + + private getHandler(requestMethod: string, resourceAccessor: ResourceAccessor): ValidatingMethodHandler { + switch(requestMethod) { + case POST: + return new ValidatingPostMethodHandler(resourceAccessor); + case PUT: + return new ValidatingPutMethodHandler(resourceAccessor); + case PATCH: + return new ValidatingPatchMethodHandler(resourceAccessor); + case DELETE: + return new ValidatingDeleteMethodHandler(resourceAccessor); + default: + return null; + } + } + + private createErrorResponse(exception: ShapeTreeException, nativeRequest: java.net.http.HttpRequest): java.net.http.HttpResponse { + return new MyHttpResponse(exception.getStatusCode(), nativeRequest, java.net.http.HttpHeaders.of(Collections.emptyMap(), (a, v) -> true), exception.getMessage()); + } + + // @SneakyThrows + private createResponse(nativeRequest: java.net.http.HttpRequest, response: DocumentResponse): java.net.http.HttpResponse { + let headers: java.net.http.HttpHeaders = java.net.http.HttpHeaders.of(response.getResourceAttributes().toMultimap(), (a, v) -> true); + return new MyHttpResponse(response.getStatusCode(), nativeRequest, headers, response.getBody()); + } + + private class JavaHttpShapeTreeRequest implements ShapeTreeRequest { + + private readonly request: java.net.http.HttpRequest; + + private resourceType: ShapeTreeResourceType; + + private readonly body: string; + + private readonly contentType: string; + + private readonly headers: ResourceAttributes; + + public constructor(request: java.net.http.HttpRequest, body: string, contentType: string) { + this.request = request; + this.body = body; + this.contentType = contentType; + let tm: TreeMap> = new TreeMap<>(String.CASE_INSENSITIVE_ORDER); + let headerMap: Map> = this.request.headers().map(); + for (let entry: Map.Entry> : headerMap.entrySet()) { + tm.put(entry.getKey(), entry.getValue()); + } + this.headers = new ResourceAttributes(tm); + } + + override public getMethod(): string { + return this.request.method(); + } + + override public getUrl(): URL { + try { + return this.request.uri().toURL(); + } catch (ex: MalformedURLException) { + throw new IllegalStateException("request has a malformed URL <" + request.uri() + ">: " + ex.getMessage()); + } + } + + override public getHeaders(): ResourceAttributes { + return this.headers; + } + + override public getLinkHeaders(): ResourceAttributes { + return ResourceAttributes.parseLinkHeaders(this.getHeaderValues(HttpHeaders.LINK.getValue())); + } + + override public getHeaderValues(header: string): List { + return this.request.headers().allValues(header); + } + + override public getHeaderValue(header: string): string { + return this.request.headers().firstValue(header).orElse(null); + } + + override public getContentType(): string { + return this.getHeaders().firstValue(HttpHeaders.CONTENT_TYPE.getValue()).orElse(null); + } + + override public getResourceType(): ShapeTreeResourceType { + return this.resourceType; + } + + override public setResourceType(resourceType: ShapeTreeResourceType): void { + this.resourceType = resourceType; + } + + override public getBody(): string { + return this.body; + } + } + + private class MyHttpResponse implements java.net.http.HttpResponse { + + private statusCode: number; + + private request: java.net.http.HttpRequest; + + private headers: java.net.http.HttpHeaders; + + private body: string; + + override public statusCode(): number { + return this.statusCode; + } + + override public request(): java.net.http.HttpRequest { + return this.request; + } + + override public previousResponse(): Optional> { + return Optional.empty(); + } + + override public headers(): java.net.http.HttpHeaders { + return this.headers; + } + + override public body(): string { + return this.body; + } + + override public sslSession(): Optional { + return Optional.empty(); + } + + override public uri(): URI { + return null; + } + + override public version(): java.net.http.HttpClient.Version { + return null; + } + + public constructor(statusCode: number, request: java.net.http.HttpRequest, headers: java.net.http.HttpHeaders, body: string) { + this.statusCode = statusCode; + this.request = request; + this.headers = headers; + this.body = body; + } + } +} From 281e3d6d9421b10633a0fb2385030856187e9c17 Mon Sep 17 00:00:00 2001 From: Eric Prud'hommeaux Date: Wed, 24 Nov 2021 18:42:22 +0100 Subject: [PATCH 02/15] ~ expand import X.* --- .../client/http/HttpResourceAccessor.java | 21 ++++++++++++++++--- .../client/http/HttpShapeTreeClient.java | 10 ++++++++- .../shapetrees/core/ResourceAttributes.java | 7 ++++++- .../shapetrees/core/ShapeTree.java | 7 ++++++- .../shapetrees/core/ShapeTreeFactory.java | 6 +++++- .../core/ShapeTreeRequestHandler.java | 8 ++++++- .../shapetrees/core/helpers/GraphHelper.java | 6 +++++- .../core/helpers/RequestHelper.java | 7 ++++++- .../ValidatingPatchMethodHandler.java | 8 +++++-- .../ValidatingPostMethodHandler.java | 9 +++++--- .../ValidatingPutMethodHandler.java | 8 +++++-- .../shapetrees/javahttp/JavaHttpClient.java | 11 +++++++++- ...avaHttpValidatingShapeTreeInterceptor.java | 17 ++++++++++++--- 13 files changed, 104 insertions(+), 21 deletions(-) diff --git a/shapetrees-java-client-http/src/main/java/com/janeirodigital/shapetrees/client/http/HttpResourceAccessor.java b/shapetrees-java-client-http/src/main/java/com/janeirodigital/shapetrees/client/http/HttpResourceAccessor.java index 7b13b3c2..6dd65df0 100644 --- a/shapetrees-java-client-http/src/main/java/com/janeirodigital/shapetrees/client/http/HttpResourceAccessor.java +++ b/shapetrees-java-client-http/src/main/java/com/janeirodigital/shapetrees/client/http/HttpResourceAccessor.java @@ -1,11 +1,22 @@ package com.janeirodigital.shapetrees.client.http; -import com.janeirodigital.shapetrees.core.*; +import com.janeirodigital.shapetrees.core.ShapeTreeManager; +import com.janeirodigital.shapetrees.core.ShapeTreeContext; +import com.janeirodigital.shapetrees.core.ManageableInstance; +import com.janeirodigital.shapetrees.core.ManageableResource; +import com.janeirodigital.shapetrees.core.DocumentResponse; +import com.janeirodigital.shapetrees.core.ResourceAttributes; +import com.janeirodigital.shapetrees.core.InstanceResource; +import com.janeirodigital.shapetrees.core.ResourceAccessor; +import com.janeirodigital.shapetrees.core.ManagerResource; +import com.janeirodigital.shapetrees.core.MissingManageableResource; +import com.janeirodigital.shapetrees.core.MissingManagerResource; +import com.janeirodigital.shapetrees.core.UnmanagedResource; +import com.janeirodigital.shapetrees.core.ManagedResource; import com.janeirodigital.shapetrees.core.enums.HttpHeaders; import com.janeirodigital.shapetrees.core.enums.LinkRelations; import com.janeirodigital.shapetrees.core.enums.ShapeTreeResourceType; import com.janeirodigital.shapetrees.core.exceptions.ShapeTreeException; -import com.janeirodigital.shapetrees.core.ShapeTreeContext; import com.janeirodigital.shapetrees.core.vocabularies.LdpVocabulary; import lombok.NoArgsConstructor; import lombok.extern.slf4j.Slf4j; @@ -16,7 +27,11 @@ import java.net.MalformedURLException; import java.net.URL; -import java.util.*; +import java.util.Set; +import java.util.Optional; +import java.util.Collections; +import java.util.List; +import java.util.ArrayList; import static com.janeirodigital.shapetrees.core.helpers.GraphHelper.readStringIntoGraph; import static com.janeirodigital.shapetrees.core.helpers.GraphHelper.urlToUri; diff --git a/shapetrees-java-client-http/src/main/java/com/janeirodigital/shapetrees/client/http/HttpShapeTreeClient.java b/shapetrees-java-client-http/src/main/java/com/janeirodigital/shapetrees/client/http/HttpShapeTreeClient.java index f5ffcb5d..a54d8365 100644 --- a/shapetrees-java-client-http/src/main/java/com/janeirodigital/shapetrees/client/http/HttpShapeTreeClient.java +++ b/shapetrees-java-client-http/src/main/java/com/janeirodigital/shapetrees/client/http/HttpShapeTreeClient.java @@ -1,7 +1,15 @@ package com.janeirodigital.shapetrees.client.http; import com.janeirodigital.shapetrees.client.core.ShapeTreeClient; -import com.janeirodigital.shapetrees.core.*; +import com.janeirodigital.shapetrees.core.ShapeTreeManager; +import com.janeirodigital.shapetrees.core.ShapeTreeContext; +import com.janeirodigital.shapetrees.core.ManageableInstance; +import com.janeirodigital.shapetrees.core.ManageableResource; +import com.janeirodigital.shapetrees.core.DocumentResponse; +import com.janeirodigital.shapetrees.core.ShapeTree; +import com.janeirodigital.shapetrees.core.ShapeTreeFactory; +import com.janeirodigital.shapetrees.core.ShapeTreeAssignment; +import com.janeirodigital.shapetrees.core.ResourceAttributes; import com.janeirodigital.shapetrees.core.enums.HttpHeaders; import com.janeirodigital.shapetrees.core.enums.LinkRelations; import com.janeirodigital.shapetrees.core.exceptions.ShapeTreeException; diff --git a/shapetrees-java-core/src/main/java/com/janeirodigital/shapetrees/core/ResourceAttributes.java b/shapetrees-java-core/src/main/java/com/janeirodigital/shapetrees/core/ResourceAttributes.java index a208b6dc..6f7c5bd2 100644 --- a/shapetrees-java-core/src/main/java/com/janeirodigital/shapetrees/core/ResourceAttributes.java +++ b/shapetrees-java-core/src/main/java/com/janeirodigital/shapetrees/core/ResourceAttributes.java @@ -2,7 +2,12 @@ import com.janeirodigital.shapetrees.core.exceptions.ShapeTreeException; import lombok.extern.slf4j.Slf4j; -import java.util.*; +import java.util.TreeMap; +import java.util.Optional; +import java.util.Map; +import java.util.List; +import java.util.ArrayList; +import java.util.Arrays; import java.util.regex.Matcher; import java.util.regex.Pattern; diff --git a/shapetrees-java-core/src/main/java/com/janeirodigital/shapetrees/core/ShapeTree.java b/shapetrees-java-core/src/main/java/com/janeirodigital/shapetrees/core/ShapeTree.java index 88f669eb..0cbf244c 100644 --- a/shapetrees-java-core/src/main/java/com/janeirodigital/shapetrees/core/ShapeTree.java +++ b/shapetrees-java-core/src/main/java/com/janeirodigital/shapetrees/core/ShapeTree.java @@ -27,7 +27,12 @@ import java.net.MalformedURLException; import java.net.URL; import java.nio.charset.StandardCharsets; -import java.util.*; +import java.util.Iterator; +import java.util.Collections; +import java.util.List; +import java.util.ArrayList; +import java.util.Queue; +import java.util.LinkedList; import static com.janeirodigital.shapetrees.core.helpers.GraphHelper.urlToUri; diff --git a/shapetrees-java-core/src/main/java/com/janeirodigital/shapetrees/core/ShapeTreeFactory.java b/shapetrees-java-core/src/main/java/com/janeirodigital/shapetrees/core/ShapeTreeFactory.java index 0bacc6c0..0503440e 100644 --- a/shapetrees-java-core/src/main/java/com/janeirodigital/shapetrees/core/ShapeTreeFactory.java +++ b/shapetrees-java-core/src/main/java/com/janeirodigital/shapetrees/core/ShapeTreeFactory.java @@ -6,7 +6,11 @@ import lombok.extern.slf4j.Slf4j; import org.apache.jena.graph.Node; import org.apache.jena.graph.Node_URI; -import org.apache.jena.rdf.model.*; +import org.apache.jena.rdf.model.Model; +import org.apache.jena.rdf.model.Resource; +import org.apache.jena.rdf.model.Statement; +import org.apache.jena.rdf.model.Property; +import org.apache.jena.rdf.model.RDFNode; import java.net.MalformedURLException; import java.net.URI; diff --git a/shapetrees-java-core/src/main/java/com/janeirodigital/shapetrees/core/ShapeTreeRequestHandler.java b/shapetrees-java-core/src/main/java/com/janeirodigital/shapetrees/core/ShapeTreeRequestHandler.java index dc1dd9bb..192ba8a4 100644 --- a/shapetrees-java-core/src/main/java/com/janeirodigital/shapetrees/core/ShapeTreeRequestHandler.java +++ b/shapetrees-java-core/src/main/java/com/janeirodigital/shapetrees/core/ShapeTreeRequestHandler.java @@ -8,7 +8,13 @@ import org.apache.jena.graph.Graph; import java.net.URL; -import java.util.*; +import java.util.HashMap; +import java.util.Optional; +import java.util.Collections; +import java.util.List; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Arrays; import static com.janeirodigital.shapetrees.core.ManageableInstance.TEXT_TURTLE; diff --git a/shapetrees-java-core/src/main/java/com/janeirodigital/shapetrees/core/helpers/GraphHelper.java b/shapetrees-java-core/src/main/java/com/janeirodigital/shapetrees/core/helpers/GraphHelper.java index 1e721475..ae0d4676 100644 --- a/shapetrees-java-core/src/main/java/com/janeirodigital/shapetrees/core/helpers/GraphHelper.java +++ b/shapetrees-java-core/src/main/java/com/janeirodigital/shapetrees/core/helpers/GraphHelper.java @@ -3,7 +3,11 @@ import com.janeirodigital.shapetrees.core.exceptions.ShapeTreeException; import lombok.extern.slf4j.Slf4j; import org.apache.jena.datatypes.xsd.XSDDatatype; -import org.apache.jena.graph.*; +import org.apache.jena.graph.Graph; +import org.apache.jena.graph.Triple; +import org.apache.jena.graph.NodeFactory; +import org.apache.jena.graph.Node; +import org.apache.jena.graph.Node_Blank; import org.apache.jena.rdf.model.Model; import org.apache.jena.rdf.model.ModelFactory; import org.apache.jena.riot.Lang; diff --git a/shapetrees-java-core/src/main/java/com/janeirodigital/shapetrees/core/helpers/RequestHelper.java b/shapetrees-java-core/src/main/java/com/janeirodigital/shapetrees/core/helpers/RequestHelper.java index 2c2152b6..db0248b1 100644 --- a/shapetrees-java-core/src/main/java/com/janeirodigital/shapetrees/core/helpers/RequestHelper.java +++ b/shapetrees-java-core/src/main/java/com/janeirodigital/shapetrees/core/helpers/RequestHelper.java @@ -1,6 +1,11 @@ package com.janeirodigital.shapetrees.core.helpers; -import com.janeirodigital.shapetrees.core.*; +import com.janeirodigital.shapetrees.core.ShapeTreeManager; +import com.janeirodigital.shapetrees.core.ShapeTreeContext; +import com.janeirodigital.shapetrees.core.ManageableInstance; +import com.janeirodigital.shapetrees.core.InstanceResource; +import com.janeirodigital.shapetrees.core.ManagerResource; +import com.janeirodigital.shapetrees.core.ShapeTreeRequest; import com.janeirodigital.shapetrees.core.enums.HttpHeaders; import com.janeirodigital.shapetrees.core.enums.LinkRelations; import com.janeirodigital.shapetrees.core.enums.ShapeTreeResourceType; diff --git a/shapetrees-java-core/src/main/java/com/janeirodigital/shapetrees/core/methodhandlers/ValidatingPatchMethodHandler.java b/shapetrees-java-core/src/main/java/com/janeirodigital/shapetrees/core/methodhandlers/ValidatingPatchMethodHandler.java index 86415124..fddb6b10 100644 --- a/shapetrees-java-core/src/main/java/com/janeirodigital/shapetrees/core/methodhandlers/ValidatingPatchMethodHandler.java +++ b/shapetrees-java-core/src/main/java/com/janeirodigital/shapetrees/core/methodhandlers/ValidatingPatchMethodHandler.java @@ -1,9 +1,13 @@ package com.janeirodigital.shapetrees.core.methodhandlers; -import com.janeirodigital.shapetrees.core.*; +import com.janeirodigital.shapetrees.core.ShapeTreeRequest; +import com.janeirodigital.shapetrees.core.ShapeTreeContext; +import com.janeirodigital.shapetrees.core.ManageableInstance; +import com.janeirodigital.shapetrees.core.DocumentResponse; +import com.janeirodigital.shapetrees.core.ResourceAccessor; import com.janeirodigital.shapetrees.core.exceptions.ShapeTreeException; import com.janeirodigital.shapetrees.core.helpers.RequestHelper; -import com.janeirodigital.shapetrees.core.ShapeTreeContext; +import com.janeirodigital.shapetrees.core.ManageableResource; import lombok.extern.slf4j.Slf4j; import java.util.Optional; diff --git a/shapetrees-java-core/src/main/java/com/janeirodigital/shapetrees/core/methodhandlers/ValidatingPostMethodHandler.java b/shapetrees-java-core/src/main/java/com/janeirodigital/shapetrees/core/methodhandlers/ValidatingPostMethodHandler.java index 36680f18..96fff667 100644 --- a/shapetrees-java-core/src/main/java/com/janeirodigital/shapetrees/core/methodhandlers/ValidatingPostMethodHandler.java +++ b/shapetrees-java-core/src/main/java/com/janeirodigital/shapetrees/core/methodhandlers/ValidatingPostMethodHandler.java @@ -1,10 +1,13 @@ package com.janeirodigital.shapetrees.core.methodhandlers; -import com.janeirodigital.shapetrees.core.*; -import com.janeirodigital.shapetrees.core.enums.HttpHeaders; +import com.janeirodigital.shapetrees.core.ShapeTreeRequest; +import com.janeirodigital.shapetrees.core.ShapeTreeContext; +import com.janeirodigital.shapetrees.core.ManageableInstance; +import com.janeirodigital.shapetrees.core.DocumentResponse; +import com.janeirodigital.shapetrees.core.ResourceAccessor; import com.janeirodigital.shapetrees.core.exceptions.ShapeTreeException; import com.janeirodigital.shapetrees.core.helpers.RequestHelper; -import com.janeirodigital.shapetrees.core.ShapeTreeContext; +import com.janeirodigital.shapetrees.core.enums.HttpHeaders; import lombok.extern.slf4j.Slf4j; import java.util.Optional; diff --git a/shapetrees-java-core/src/main/java/com/janeirodigital/shapetrees/core/methodhandlers/ValidatingPutMethodHandler.java b/shapetrees-java-core/src/main/java/com/janeirodigital/shapetrees/core/methodhandlers/ValidatingPutMethodHandler.java index 199bb96a..2790b345 100644 --- a/shapetrees-java-core/src/main/java/com/janeirodigital/shapetrees/core/methodhandlers/ValidatingPutMethodHandler.java +++ b/shapetrees-java-core/src/main/java/com/janeirodigital/shapetrees/core/methodhandlers/ValidatingPutMethodHandler.java @@ -1,9 +1,13 @@ package com.janeirodigital.shapetrees.core.methodhandlers; -import com.janeirodigital.shapetrees.core.*; +import com.janeirodigital.shapetrees.core.ShapeTreeRequest; +import com.janeirodigital.shapetrees.core.ShapeTreeContext; +import com.janeirodigital.shapetrees.core.ManageableInstance; +import com.janeirodigital.shapetrees.core.ManageableResource; +import com.janeirodigital.shapetrees.core.DocumentResponse; +import com.janeirodigital.shapetrees.core.ResourceAccessor; import com.janeirodigital.shapetrees.core.exceptions.ShapeTreeException; import com.janeirodigital.shapetrees.core.helpers.RequestHelper; -import com.janeirodigital.shapetrees.core.ShapeTreeContext; import java.util.Optional; diff --git a/shapetrees-java-javahttp/src/main/java/com/janeirodigital/shapetrees/javahttp/JavaHttpClient.java b/shapetrees-java-javahttp/src/main/java/com/janeirodigital/shapetrees/javahttp/JavaHttpClient.java index 3008d8cb..a031c63b 100644 --- a/shapetrees-java-javahttp/src/main/java/com/janeirodigital/shapetrees/javahttp/JavaHttpClient.java +++ b/shapetrees-java-javahttp/src/main/java/com/janeirodigital/shapetrees/javahttp/JavaHttpClient.java @@ -7,7 +7,16 @@ import com.janeirodigital.shapetrees.core.exceptions.ShapeTreeException; import lombok.extern.slf4j.Slf4j; -import javax.net.ssl.*; +import javax.net.ssl.TrustManager; +import javax.net.ssl.X509TrustManager; +import javax.net.ssl.SSLContext; +import javax.net.ssl.HttpsURLConnection; +import javax.net.ssl.HostnameVerifier; +import javax.net.ssl.SSLSession; +import javax.net.ssl.X509TrustManager; +import javax.net.ssl.X509TrustManager; +import javax.net.ssl.X509TrustManager; +import javax.net.ssl.X509TrustManager; import java.io.IOException; import java.net.URISyntaxException; import java.security.KeyManagementException; diff --git a/shapetrees-java-javahttp/src/main/java/com/janeirodigital/shapetrees/javahttp/JavaHttpValidatingShapeTreeInterceptor.java b/shapetrees-java-javahttp/src/main/java/com/janeirodigital/shapetrees/javahttp/JavaHttpValidatingShapeTreeInterceptor.java index a807c4dc..ef0da83a 100644 --- a/shapetrees-java-javahttp/src/main/java/com/janeirodigital/shapetrees/javahttp/JavaHttpValidatingShapeTreeInterceptor.java +++ b/shapetrees-java-javahttp/src/main/java/com/janeirodigital/shapetrees/javahttp/JavaHttpValidatingShapeTreeInterceptor.java @@ -1,11 +1,18 @@ package com.janeirodigital.shapetrees.javahttp; import com.janeirodigital.shapetrees.client.http.HttpResourceAccessor; -import com.janeirodigital.shapetrees.core.*; +import com.janeirodigital.shapetrees.core.DocumentResponse; +import com.janeirodigital.shapetrees.core.ResourceAttributes; +import com.janeirodigital.shapetrees.core.ResourceAccessor; +import com.janeirodigital.shapetrees.core.ShapeTreeRequest; import com.janeirodigital.shapetrees.core.enums.HttpHeaders; import com.janeirodigital.shapetrees.core.enums.ShapeTreeResourceType; import com.janeirodigital.shapetrees.core.exceptions.ShapeTreeException; -import com.janeirodigital.shapetrees.core.methodhandlers.*; +import com.janeirodigital.shapetrees.core.methodhandlers.ValidatingMethodHandler; +import com.janeirodigital.shapetrees.core.methodhandlers.ValidatingDeleteMethodHandler; +import com.janeirodigital.shapetrees.core.methodhandlers.ValidatingPutMethodHandler; +import com.janeirodigital.shapetrees.core.methodhandlers.ValidatingPatchMethodHandler; +import com.janeirodigital.shapetrees.core.methodhandlers.ValidatingPostMethodHandler; import lombok.AllArgsConstructor; import lombok.SneakyThrows; import lombok.extern.slf4j.Slf4j; @@ -17,7 +24,11 @@ import java.net.URI; import java.net.URL; import java.net.http.HttpResponse; -import java.util.*; +import java.util.Map; +import java.util.Optional; +import java.util.Collections; +import java.util.List; +import java.util.TreeMap; /** * Wrapper used for client-side validation From b31aef987a0ac8657fd3a14fcd9a9d2fc6a3efc3 Mon Sep 17 00:00:00 2001 From: Eric Prud'hommeaux Date: Wed, 24 Nov 2021 18:43:58 +0100 Subject: [PATCH 03/15] ~ fix off-by-one error in javatots --- .../client-core/src/ShapeTreeClient.ts | 8 ++-- .../packages/client-http/src/HttpClient.ts | 4 +- .../client-http/src/HttpClientFactory.ts | 2 +- .../src/HttpClientFactoryManager.ts | 2 +- .../packages/client-http/src/HttpRequest.ts | 2 +- .../client-http/src/HttpResourceAccessor.ts | 48 ++++++++++++------- .../client-http/src/HttpShapeTreeClient.ts | 18 +++++-- .../packages/core/src/ResourceAttributes.ts | 29 ++++++----- asTypescript/packages/core/src/ShapeTree.ts | 44 +++++++++-------- .../packages/core/src/ShapeTreeFactory.ts | 6 ++- .../core/src/ShapeTreeRequestHandler.ts | 25 ++++++---- .../packages/core/src/helpers/GraphHelper.ts | 6 ++- .../core/src/helpers/RequestHelper.ts | 7 ++- .../ValidatingPatchMethodHandler.ts | 8 +++- .../ValidatingPostMethodHandler.ts | 9 ++-- .../ValidatingPutMethodHandler.ts | 8 +++- .../packages/javahttp/src/JavaHttpClient.ts | 21 +++++--- .../javahttp/src/JavaHttpClientFactory.ts | 10 ++-- .../JavaHttpValidatingShapeTreeInterceptor.ts | 31 +++++++----- 19 files changed, 182 insertions(+), 106 deletions(-) diff --git a/asTypescript/packages/client-core/src/ShapeTreeClient.ts b/asTypescript/packages/client-core/src/ShapeTreeClient.ts index f4b4ca20..5021c009 100644 --- a/asTypescript/packages/client-core/src/ShapeTreeClient.ts +++ b/asTypescript/packages/client-core/src/ShapeTreeClient.ts @@ -1,8 +1,8 @@ // Corresponding shapetrees-java package: com.janeirodigital.shapetrees.client.core -import { DocumentResponse } from '@shapetrees/ocumentResponse'; -import { ShapeTreeContext } from '@shapetrees/hapeTreeContext'; -import { ShapeTreeManager } from '@shapetrees/hapeTreeManager'; -import { ShapeTreeException } from '@shapetrees/xceptions/ShapeTreeException'; +import { DocumentResponse } from '@shapetrees/DocumentResponse'; +import { ShapeTreeContext } from '@shapetrees/ShapeTreeContext'; +import { ShapeTreeManager } from '@shapetrees/ShapeTreeManager'; +import { ShapeTreeException } from '@shapetrees/exceptions/ShapeTreeException'; import * as URL from 'java/net'; import * as Optional from 'java/util'; diff --git a/asTypescript/packages/client-http/src/HttpClient.ts b/asTypescript/packages/client-http/src/HttpClient.ts index 1967322e..4cd7b4eb 100644 --- a/asTypescript/packages/client-http/src/HttpClient.ts +++ b/asTypescript/packages/client-http/src/HttpClient.ts @@ -1,6 +1,6 @@ // Corresponding shapetrees-java package: com.janeirodigital.shapetrees.client.http -import { DocumentResponse } from '@shapetrees/ocumentResponse'; -import { ShapeTreeException } from '@shapetrees/xceptions/ShapeTreeException'; +import { DocumentResponse } from '@shapetrees/DocumentResponse'; +import { ShapeTreeException } from '@shapetrees/exceptions/ShapeTreeException'; import { HttpRequest } from './HttpRequest'; /** diff --git a/asTypescript/packages/client-http/src/HttpClientFactory.ts b/asTypescript/packages/client-http/src/HttpClientFactory.ts index 9fa2a0d8..a36ead2f 100644 --- a/asTypescript/packages/client-http/src/HttpClientFactory.ts +++ b/asTypescript/packages/client-http/src/HttpClientFactory.ts @@ -1,5 +1,5 @@ // Corresponding shapetrees-java package: com.janeirodigital.shapetrees.client.http -import { ShapeTreeException } from '@shapetrees/xceptions/ShapeTreeException'; +import { ShapeTreeException } from '@shapetrees/exceptions/ShapeTreeException'; import { HttpClient } from './HttpClient'; /** diff --git a/asTypescript/packages/client-http/src/HttpClientFactoryManager.ts b/asTypescript/packages/client-http/src/HttpClientFactoryManager.ts index 86343cf9..2393572e 100644 --- a/asTypescript/packages/client-http/src/HttpClientFactoryManager.ts +++ b/asTypescript/packages/client-http/src/HttpClientFactoryManager.ts @@ -1,5 +1,5 @@ // Corresponding shapetrees-java package: com.janeirodigital.shapetrees.client.http -import { ShapeTreeException } from '@shapetrees/xceptions/ShapeTreeException'; +import { ShapeTreeException } from '@shapetrees/exceptions/ShapeTreeException'; import { HttpClientFactory } from './HttpClientFactory'; export abstract class HttpClientFactoryManager { diff --git a/asTypescript/packages/client-http/src/HttpRequest.ts b/asTypescript/packages/client-http/src/HttpRequest.ts index 0193e2f4..157ecf79 100644 --- a/asTypescript/packages/client-http/src/HttpRequest.ts +++ b/asTypescript/packages/client-http/src/HttpRequest.ts @@ -1,5 +1,5 @@ // Corresponding shapetrees-java package: com.janeirodigital.shapetrees.client.http -import { ResourceAttributes } from '@shapetrees/esourceAttributes'; +import { ResourceAttributes } from '@shapetrees/ResourceAttributes'; import * as URL from 'java/net'; export class HttpRequest { diff --git a/asTypescript/packages/client-http/src/HttpResourceAccessor.ts b/asTypescript/packages/client-http/src/HttpResourceAccessor.ts index d7d1e199..9edb13a1 100644 --- a/asTypescript/packages/client-http/src/HttpResourceAccessor.ts +++ b/asTypescript/packages/client-http/src/HttpResourceAccessor.ts @@ -1,11 +1,22 @@ // Corresponding shapetrees-java package: com.janeirodigital.shapetrees.client.http -import * as core from 'com/janeirodigital/shapetrees'; -import { HttpHeaders } from '@shapetrees/nums/HttpHeaders'; -import { LinkRelations } from '@shapetrees/nums/LinkRelations'; -import { ShapeTreeResourceType } from '@shapetrees/nums/ShapeTreeResourceType'; -import { ShapeTreeException } from '@shapetrees/xceptions/ShapeTreeException'; -import { ShapeTreeContext } from '@shapetrees/hapeTreeContext'; -import { LdpVocabulary } from '@shapetrees/ocabularies/LdpVocabulary'; +import { ShapeTreeManager } from '@shapetrees/ShapeTreeManager'; +import { ShapeTreeContext } from '@shapetrees/ShapeTreeContext'; +import { ManageableInstance } from '@shapetrees/ManageableInstance'; +import { ManageableResource } from '@shapetrees/ManageableResource'; +import { DocumentResponse } from '@shapetrees/DocumentResponse'; +import { ResourceAttributes } from '@shapetrees/ResourceAttributes'; +import { InstanceResource } from '@shapetrees/InstanceResource'; +import { ResourceAccessor } from '@shapetrees/ResourceAccessor'; +import { ManagerResource } from '@shapetrees/ManagerResource'; +import { MissingManageableResource } from '@shapetrees/MissingManageableResource'; +import { MissingManagerResource } from '@shapetrees/MissingManagerResource'; +import { UnmanagedResource } from '@shapetrees/UnmanagedResource'; +import { ManagedResource } from '@shapetrees/ManagedResource'; +import { HttpHeaders } from '@shapetrees/enums/HttpHeaders'; +import { LinkRelations } from '@shapetrees/enums/LinkRelations'; +import { ShapeTreeResourceType } from '@shapetrees/enums/ShapeTreeResourceType'; +import { ShapeTreeException } from '@shapetrees/exceptions/ShapeTreeException'; +import { LdpVocabulary } from '@shapetrees/vocabularies/LdpVocabulary'; import * as Slf4j from 'lombok/extern/slf4j'; import * as Graph from 'org/apache/jena/graph'; import * as Node from 'org/apache/jena/graph'; @@ -13,9 +24,12 @@ import * as NodeFactory from 'org/apache/jena/graph'; import * as Triple from 'org/apache/jena/graph'; import * as MalformedURLException from 'java/net'; import * as URL from 'java/net'; -import * as util from 'java'; -import { readStringIntoGraph } from '@shapetrees/elpers/GraphHelper/readStringIntoGraph'; -import { urlToUri } from '@shapetrees/elpers/GraphHelper/urlToUri'; +import * as Set from 'java/util'; +import * as Optional from 'java/util'; +import * as Collections from 'java/util'; +import * as ArrayList from 'java/util'; +import { readStringIntoGraph } from '@shapetrees/helpers/GraphHelper/readStringIntoGraph'; +import { urlToUri } from '@shapetrees/helpers/GraphHelper/urlToUri'; import { HttpRequest } from './HttpRequest'; import { HttpClient } from './HttpClient'; @@ -287,7 +301,7 @@ export class HttpResourceAccessor implements ResourceAccessor { log.error("Could not retrieve the body string from response for " + url); } // Parse Link headers from response and populate ResourceAttributes - const linkHeaders: List = attributes.allValues(HttpHeaders.LINK.getValue()); + const linkHeaders: Array = attributes.allValues(HttpHeaders.LINK.getValue()); let parsedLinkHeaders: ResourceAttributes = // !! linkHeaders.isEmpty() ? new ResourceAttributes() : ResourceAttributes.parseLinkHeaders(linkHeaders); // Determine if the resource is a shape tree manager based on the response @@ -317,7 +331,7 @@ export class HttpResourceAccessor implements ResourceAccessor { * @return List of {@link ManageableInstance}s from the target container * @throws ShapeTreeException */ - override public getContainedInstances(context: ShapeTreeContext, containerUrl: URL): List /* throws ShapeTreeException */ { + override public getContainedInstances(context: ShapeTreeContext, containerUrl: URL): Array /* throws ShapeTreeException */ { try { let resource: InstanceResource = this.getResource(context, containerUrl); if (!(resource instanceof ManageableResource)) { @@ -331,7 +345,7 @@ export class HttpResourceAccessor implements ResourceAccessor { if (containerGraph.isEmpty()) { return Collections.emptyList(); } - let containerTriples: List = containerGraph.find(NodeFactory.createURI(containerUrl.toString()), NodeFactory.createURI(LdpVocabulary.CONTAINS), Node.ANY).toList(); + let containerTriples: Array = containerGraph.find(NodeFactory.createURI(containerUrl.toString()), NodeFactory.createURI(LdpVocabulary.CONTAINS), Node.ANY).toList(); if (containerTriples.isEmpty()) { return Collections.emptyList(); } @@ -390,12 +404,12 @@ export class HttpResourceAccessor implements ResourceAccessor { * @return True if headers indicating a container are found */ private isContainerFromHeaders(headers: ResourceAttributes, url: URL): boolean { - let linkHeaders: List = headers.allValues(HttpHeaders.LINK.getValue()); + let linkHeaders: Array = headers.allValues(HttpHeaders.LINK.getValue()); if (linkHeaders.isEmpty()) { return url.getPath().endsWith("/"); } let parsedLinkHeaders: ResourceAttributes = ResourceAttributes.parseLinkHeaders(linkHeaders); - let typeLinks: List = parsedLinkHeaders.allValues(LinkRelations.TYPE.getValue()); + let typeLinks: Array = parsedLinkHeaders.allValues(LinkRelations.TYPE.getValue()); if (!typeLinks.isEmpty()) { return typeLinks.contains(LdpVocabulary.CONTAINER) || typeLinks.contains(LdpVocabulary.BASIC_CONTAINER); } @@ -408,12 +422,12 @@ export class HttpResourceAccessor implements ResourceAccessor { * @return Type of resource */ private getResourceTypeFromHeaders(headers: ResourceAttributes): ShapeTreeResourceType { - let linkHeaders: List = headers.allValues(HttpHeaders.LINK.getValue()); + let linkHeaders: Array = headers.allValues(HttpHeaders.LINK.getValue()); if (linkHeaders === null) { return null; } let parsedLinkHeaders: ResourceAttributes = ResourceAttributes.parseLinkHeaders(linkHeaders); - let typeLinks: List = parsedLinkHeaders.allValues(LinkRelations.TYPE.getValue()); + let typeLinks: Array = parsedLinkHeaders.allValues(LinkRelations.TYPE.getValue()); if (typeLinks != null && (typeLinks.contains(LdpVocabulary.CONTAINER) || typeLinks.contains(LdpVocabulary.BASIC_CONTAINER))) { return ShapeTreeResourceType.CONTAINER; } diff --git a/asTypescript/packages/client-http/src/HttpShapeTreeClient.ts b/asTypescript/packages/client-http/src/HttpShapeTreeClient.ts index 6e5be1e4..598dcb91 100644 --- a/asTypescript/packages/client-http/src/HttpShapeTreeClient.ts +++ b/asTypescript/packages/client-http/src/HttpShapeTreeClient.ts @@ -1,9 +1,17 @@ // Corresponding shapetrees-java package: com.janeirodigital.shapetrees.client.http -import { ShapeTreeClient } from '@shapetrees/hapeTreeClient'; -import * as core from 'com/janeirodigital/shapetrees'; -import { HttpHeaders } from '@shapetrees/nums/HttpHeaders'; -import { LinkRelations } from '@shapetrees/nums/LinkRelations'; -import { ShapeTreeException } from '@shapetrees/xceptions/ShapeTreeException'; +import { ShapeTreeClient } from '@shapetrees/ShapeTreeClient'; +import { ShapeTreeManager } from '@shapetrees/ShapeTreeManager'; +import { ShapeTreeContext } from '@shapetrees/ShapeTreeContext'; +import { ManageableInstance } from '@shapetrees/ManageableInstance'; +import { ManageableResource } from '@shapetrees/ManageableResource'; +import { DocumentResponse } from '@shapetrees/DocumentResponse'; +import { ShapeTree } from '@shapetrees/ShapeTree'; +import { ShapeTreeFactory } from '@shapetrees/ShapeTreeFactory'; +import { ShapeTreeAssignment } from '@shapetrees/ShapeTreeAssignment'; +import { ResourceAttributes } from '@shapetrees/ResourceAttributes'; +import { HttpHeaders } from '@shapetrees/enums/HttpHeaders'; +import { LinkRelations } from '@shapetrees/enums/LinkRelations'; +import { ShapeTreeException } from '@shapetrees/exceptions/ShapeTreeException'; import * as Slf4j from 'lombok/extern/slf4j'; import * as Lang from 'org/apache/jena/riot'; import * as RDFDataMgr from 'org/apache/jena/riot'; diff --git a/asTypescript/packages/core/src/ResourceAttributes.ts b/asTypescript/packages/core/src/ResourceAttributes.ts index 1d1a3957..f601ad40 100644 --- a/asTypescript/packages/core/src/ResourceAttributes.ts +++ b/asTypescript/packages/core/src/ResourceAttributes.ts @@ -1,7 +1,10 @@ // Corresponding shapetrees-java package: com.janeirodigital.shapetrees.core import { ShapeTreeException } from './exceptions/ShapeTreeException'; import * as Slf4j from 'lombok/extern/slf4j'; -import * as util from 'java'; +import * as TreeMap from 'java/util'; +import * as Optional from 'java/util'; +import * as ArrayList from 'java/util'; +import * as Arrays from 'java/util'; import * as Matcher from 'java/util/regex'; import * as Pattern from 'java/util/regex'; import * as requireNonNull from 'java/util/Objects'; @@ -14,7 +17,7 @@ import * as requireNonNull from 'java/util/Objects'; @Slf4j export class ResourceAttributes { - myMapOfLists: Map>; + myMapOfLists: Map>; /** * construct a case-insensitive ResourceAttributes container @@ -37,14 +40,14 @@ export class ResourceAttributes { * Construct ResourceAttributes with passed map, which may be case-sensitive. * @param newMap replacement for myMapOfLists */ - public constructor(newMap: Map>) { + public constructor(newMap: Map>) { this.myMapOfLists = newMap; } // copy constructor private copy(): ResourceAttributes { let ret: ResourceAttributes = new ResourceAttributes(); - for (let entry: Map.Entry> : this.myMapOfLists.entrySet()) { + for (let entry: Map.Entry> : this.myMapOfLists.entrySet()) { ret.myMapOfLists.put(entry.getKey(), new ArrayList<>(entry.getValue())); } return ret; @@ -56,7 +59,7 @@ export class ResourceAttributes { * @param headerValues Header values for Link headers * @return subset of this matching the pattern */ - public static parseLinkHeaders(headerValues: List): ResourceAttributes { + public static parseLinkHeaders(headerValues: Array): ResourceAttributes { let linkHeaderMap: ResourceAttributes = new ResourceAttributes(); for (let headerValue: string : headerValues) { let matcher: Matcher = LINK_HEADER_PATTERN.matcher(headerValue); @@ -99,7 +102,7 @@ export class ResourceAttributes { return; } if (this.myMapOfLists.containsKey(attr)) { - let existingValues: List = this.myMapOfLists.get(attr); + let existingValues: Array = this.myMapOfLists.get(attr); let alreadySet: boolean = existingValues.stream().anyMatch(s -> s === value); if (!alreadySet) { existingValues.add(value); @@ -119,14 +122,14 @@ export class ResourceAttributes { * @param attr attribute (header) name to set * @param values String values to assign to attr */ - public setAll(attr: string, values: List): void { + public setAll(attr: string, values: Array): void { this.myMapOfLists.put(attr, values); } /** * Returns a map of attributes to lists of values */ - public toMultimap(): Map> { + public toMultimap(): Map> { return this.myMapOfLists; } @@ -136,8 +139,8 @@ export class ResourceAttributes { * (This is useful for HttpRequest.Builder().) */ public toList(...exclusions: string): string[] { - let ret: List = new ArrayList<>(); - for (let entry: Map.Entry> : this.myMapOfLists.entrySet()) { + let ret: Array = new ArrayList<>(); + for (let entry: Map.Entry> : this.myMapOfLists.entrySet()) { let attr: string = entry.getKey(); if (!Arrays.stream(exclusions).anyMatch(s -> s === attr)) { for (let value: string : entry.getValue()) { @@ -170,9 +173,9 @@ export class ResourceAttributes { * @param name the header name * @return a List of headers string values */ - public allValues(name: string): List { + public allValues(name: string): Array { requireNonNull(name); - let values: List = toMultimap().get(name); + let values: Array = toMultimap().get(name); // Making unmodifiable list out of empty in order to make a list which // throws UOE unconditionally return values != null ? values : List.of(); @@ -180,7 +183,7 @@ export class ResourceAttributes { public toString(): string { let sb: StringBuilder = new StringBuilder(); - for (let entry: Map.Entry> : this.myMapOfLists.entrySet()) { + for (let entry: Map.Entry> : this.myMapOfLists.entrySet()) { for (let value: string : entry.getValue()) { if (sb.length() != 0) { sb.append(","); diff --git a/asTypescript/packages/core/src/ShapeTree.ts b/asTypescript/packages/core/src/ShapeTree.ts index cf96b4c1..773a75e4 100644 --- a/asTypescript/packages/core/src/ShapeTree.ts +++ b/asTypescript/packages/core/src/ShapeTree.ts @@ -22,7 +22,11 @@ import * as NotNull from 'org/jetbrains/annotations'; import * as MalformedURLException from 'java/net'; import * as URL from 'java/net'; import * as StandardCharsets from 'java/nio/charset'; -import * as util from 'java'; +import * as Iterator from 'java/util'; +import * as Collections from 'java/util'; +import * as ArrayList from 'java/util'; +import * as Queue from 'java/util'; +import * as LinkedList from 'java/util'; import { urlToUri } from './helpers/GraphHelper/urlToUri'; import { ShapeTreeReference } from './ShapeTreeReference'; import { DocumentResponse } from './DocumentResponse'; @@ -43,12 +47,12 @@ export class ShapeTree { private readonly label: string; @NotNull - private readonly contains: List; + private readonly contains: Array; @NotNull - private readonly references: List; + private readonly references: Array; - public constructor(@NotNull id: URL, @NotNull expectedResourceType: URL, label: string, shape: URL, @NotNull references: List, @NotNull contains: List) { + public constructor(@NotNull id: URL, @NotNull expectedResourceType: URL, label: string, shape: URL, @NotNull references: Array, @NotNull contains: Array) { this.id = id; this.expectedResourceType = expectedResourceType; this.label = label; @@ -61,7 +65,7 @@ export class ShapeTree { return validateResource(targetResource, null); } - public validateResource(targetResource: ManageableResource, focusNodeUrls: List): ValidationResult /* throws ShapeTreeException */ { + public validateResource(targetResource: ManageableResource, focusNodeUrls: Array): ValidationResult /* throws ShapeTreeException */ { let bodyGraph: Graph = null; if (targetResource.getResourceType() != ShapeTreeResourceType.NON_RDF) { bodyGraph = GraphHelper.readStringIntoGraph(urlToUri(targetResource.getUrl()), targetResource.getBody(), targetResource.getAttributes().firstValue(HttpHeaders.CONTENT_TYPE.getValue()).orElse(null)); @@ -69,7 +73,7 @@ export class ShapeTree { return validateResource(targetResource.getName(), targetResource.getResourceType(), bodyGraph, focusNodeUrls); } - public validateResource(requestedName: string, resourceType: ShapeTreeResourceType, bodyGraph: Graph, focusNodeUrls: List): ValidationResult /* throws ShapeTreeException */ { + public validateResource(requestedName: string, resourceType: ShapeTreeResourceType, bodyGraph: Graph, focusNodeUrls: Array): ValidationResult /* throws ShapeTreeException */ { // Check whether the proposed resource is the same type as what is expected by the shape tree if (!this.expectedResourceType.toString() === resourceType.getValue()) { return new ValidationResult(false, this, "Resource type " + resourceType + " is invalid. Expected " + this.expectedResourceType); @@ -89,7 +93,7 @@ export class ShapeTree { return new ValidationResult(true, this, this, null); } - public validateGraph(graph: Graph, focusNodeUrls: List): ValidationResult /* throws ShapeTreeException */ { + public validateGraph(graph: Graph, focusNodeUrls: Array): ValidationResult /* throws ShapeTreeException */ { // if (true) return new ValidationResult(true, this, this, focusNodeUrl); // [debug] ShExC parser brings debugger to its knees if (this.shape === null) { throw new ShapeTreeException(400, "Attempting to validate a shape for ShapeTree " + this.id + "but it doesn't specify one"); @@ -137,7 +141,7 @@ export class ShapeTree { return new ValidationResult(false, this, "Failed to validate: " + shapeLabel.toPrettyString()); } else { // No focus nodes were provided for validation, so all subject nodes will be evaluated - let evaluateNodes: List = GraphUtil.listSubjects(graph, Node.ANY, Node.ANY).toList(); + let evaluateNodes: Array = GraphUtil.listSubjects(graph, Node.ANY, Node.ANY).toList(); for (let evaluateNode: Node : evaluateNodes) { const focusUriString: string = evaluateNode.getURI(); let node: IRI = GlobalFactory.RDFFactory.createIRI(focusUriString); @@ -166,7 +170,7 @@ export class ShapeTree { return validateContainedResource(containedResource, Collections.emptyList(), Collections.emptyList()); } - public validateContainedResource(containedResource: ManageableResource, targetShapeTreeUrls: List, focusNodeUrls: List): ValidationResult /* throws ShapeTreeException */ { + public validateContainedResource(containedResource: ManageableResource, targetShapeTreeUrls: Array, focusNodeUrls: Array): ValidationResult /* throws ShapeTreeException */ { let containedResourceGraph: Graph = null; if (containedResource.getResourceType() != ShapeTreeResourceType.NON_RDF) { containedResourceGraph = GraphHelper.readStringIntoGraph(urlToUri(containedResource.getUrl()), containedResource.getBody(), containedResource.getAttributes().firstValue(HttpHeaders.CONTENT_TYPE.getValue()).orElse(null)); @@ -174,7 +178,7 @@ export class ShapeTree { return validateContainedResource(containedResource.getName(), containedResource.getResourceType(), targetShapeTreeUrls, containedResourceGraph, focusNodeUrls); } - public validateContainedResource(requestedName: string, resourceType: ShapeTreeResourceType, targetShapeTreeUrls: List, bodyGraph: Graph, focusNodeUrls: List): ValidationResult /* throws ShapeTreeException */ { + public validateContainedResource(requestedName: string, resourceType: ShapeTreeResourceType, targetShapeTreeUrls: Array, bodyGraph: Graph, focusNodeUrls: Array): ValidationResult /* throws ShapeTreeException */ { if (this.contains === null || this.contains.isEmpty()) { // The contained resource is permitted because this shape tree has no restrictions on what it contains return new ValidationResult(true, this, this, null); @@ -226,30 +230,30 @@ export class ShapeTree { } // Return the list of shape tree contains by priority from most to least strict - public getPrioritizedContains(): List { - let prioritized: List = new ArrayList<>(this.contains); + public getPrioritizedContains(): Array { + let prioritized: Array = new ArrayList<>(this.contains); Collections.sort(prioritized, new ShapeTreeContainsPriority()); return prioritized; } - private getReferencedShapeTreesList(recursionMethods: RecursionMethods): List /* throws ShapeTreeException */ { + private getReferencedShapeTreesList(recursionMethods: RecursionMethods): Array /* throws ShapeTreeException */ { if (recursionMethods === RecursionMethods.BREADTH_FIRST) { return getReferencedShapeTreesListBreadthFirst(); } else { - let referencedShapeTrees: List = new ArrayList<>(); + let referencedShapeTrees: Array = new ArrayList<>(); return getReferencedShapeTreesListDepthFirst(this.getReferences(), referencedShapeTrees); } } - private getReferencedShapeTreesListBreadthFirst(): List /* throws ShapeTreeException */ { - let referencedShapeTrees: List = new ArrayList<>(); + private getReferencedShapeTreesListBreadthFirst(): Array /* throws ShapeTreeException */ { + let referencedShapeTrees: Array = new ArrayList<>(); let queue: Queue = new LinkedList<>(this.getReferences()); while (!queue.isEmpty()) { let currentShapeTree: ShapeTreeReference = queue.poll(); referencedShapeTrees.add(currentShapeTree); let shapeTree: ShapeTree = ShapeTreeFactory.getShapeTree(currentShapeTree.getReferenceUrl()); if (shapeTree != null) { - let currentReferencedShapeTrees: List = shapeTree.getReferences(); + let currentReferencedShapeTrees: Array = shapeTree.getReferences(); if (currentReferencedShapeTrees != null) { queue.addAll(currentReferencedShapeTrees); } @@ -258,7 +262,7 @@ export class ShapeTree { return referencedShapeTrees; } - private getReferencedShapeTreesListDepthFirst(currentReferencedShapeTrees: List, referencedShapeTrees: List): List /* throws ShapeTreeException */ { + private getReferencedShapeTreesListDepthFirst(currentReferencedShapeTrees: Array, referencedShapeTrees: Array): Array /* throws ShapeTreeException */ { for (let currentShapeTreeReference: ShapeTreeReference : currentReferencedShapeTrees) { referencedShapeTrees.add(currentShapeTreeReference); let currentReferencedShapeTree: ShapeTree = ShapeTreeFactory.getShapeTree(currentShapeTreeReference.getReferenceUrl()); @@ -285,11 +289,11 @@ export class ShapeTree { return this.label; } - public getContains(): List { + public getContains(): Array { return this.contains; } - public getReferences(): List { + public getReferences(): Array { return this.references; } } diff --git a/asTypescript/packages/core/src/ShapeTreeFactory.ts b/asTypescript/packages/core/src/ShapeTreeFactory.ts index fd45a29a..9b077414 100644 --- a/asTypescript/packages/core/src/ShapeTreeFactory.ts +++ b/asTypescript/packages/core/src/ShapeTreeFactory.ts @@ -4,7 +4,11 @@ import { ShapeTreeVocabulary } from './vocabularies/ShapeTreeVocabulary'; import * as Slf4j from 'lombok/extern/slf4j'; import * as Node from 'org/apache/jena/graph'; import * as Node_URI from 'org/apache/jena/graph'; -import * as model from 'org/apache/jena/rdf'; +import * as Model from 'org/apache/jena/rdf/model'; +import * as Resource from 'org/apache/jena/rdf/model'; +import * as Statement from 'org/apache/jena/rdf/model'; +import * as Property from 'org/apache/jena/rdf/model'; +import * as RDFNode from 'org/apache/jena/rdf/model'; import * as MalformedURLException from 'java/net'; import * as URI from 'java/net'; import * as URL from 'java/net'; diff --git a/asTypescript/packages/core/src/ShapeTreeRequestHandler.ts b/asTypescript/packages/core/src/ShapeTreeRequestHandler.ts index cabc59f2..8b8e4024 100644 --- a/asTypescript/packages/core/src/ShapeTreeRequestHandler.ts +++ b/asTypescript/packages/core/src/ShapeTreeRequestHandler.ts @@ -6,7 +6,12 @@ import { RequestHelper } from './helpers/RequestHelper'; import * as Slf4j from 'lombok/extern/slf4j'; import * as Graph from 'org/apache/jena/graph'; import * as URL from 'java/net'; -import * as util from 'java'; +import * as HashMap from 'java/util'; +import * as Optional from 'java/util'; +import * as Collections from 'java/util'; +import * as ArrayList from 'java/util'; +import * as Collection from 'java/util'; +import * as Arrays from 'java/util'; import { TEXT_TURTLE } from './ManageableInstance/TEXT_TURTLE'; import { ResourceAccessor } from './ResourceAccessor'; import { ShapeTreeAssignment } from './ShapeTreeAssignment'; @@ -110,13 +115,13 @@ export class ShapeTreeRequestHandler { let containerManager: ShapeTreeManager = containerResource.getManagerResource().getManager(); ensureShapeTreeManagerExists(containerManager, "Cannot have a shape tree manager resource without a shape tree manager containing at least one shape tree assignment"); // Get the shape tree associated that specifies what resources can be contained by the target container (st:contains) - let containingAssignments: List = containerManager.getContainingAssignments(); + let containingAssignments: Array = containerManager.getContainingAssignments(); // If there are no containing shape trees for the target container, request is valid and can be passed through if (containingAssignments.isEmpty()) { return Optional.empty(); } - let targetShapeTrees: List = RequestHelper.getIncomingTargetShapeTrees(shapeTreeRequest, targetResourceUrl); - let incomingFocusNodes: List = RequestHelper.getIncomingFocusNodes(shapeTreeRequest, targetResourceUrl); + let targetShapeTrees: Array = RequestHelper.getIncomingTargetShapeTrees(shapeTreeRequest, targetResourceUrl); + let incomingFocusNodes: Array = RequestHelper.getIncomingFocusNodes(shapeTreeRequest, targetResourceUrl); let incomingBodyGraph: Graph = RequestHelper.getIncomingBodyGraph(shapeTreeRequest, targetResourceUrl, null); let validationResults: HashMap = new HashMap<>(); for (let containingAssignment: ShapeTreeAssignment : containingAssignments) { @@ -129,7 +134,7 @@ export class ShapeTreeRequestHandler { validationResults.put(containingAssignment, validationResult); } // if any of the provided focus nodes weren't matched validation must fail - let unmatchedNodes: List = getUnmatchedFocusNodes(validationResults.values(), incomingFocusNodes); + let unmatchedNodes: Array = getUnmatchedFocusNodes(validationResults.values(), incomingFocusNodes); if (!unmatchedNodes.isEmpty()) { return failValidation(new ValidationResult(false, "Failed to match target focus nodes: " + unmatchedNodes)); } @@ -221,7 +226,7 @@ export class ShapeTreeRequestHandler { // If the container is not empty, perform a recursive, depth first validation and assignment for each // contained resource by recursively calling this method (assignShapeTreeToResource) // TODO - Provide a configurable maximum limit on contained resources for a recursive plant, generate ShapeTreeException - let containedResources: List = this.resourceAccessor.getContainedInstances(shapeTreeContext, manageableInstance.getManageableResource().getUrl()); + let containedResources: Array = this.resourceAccessor.getContainedInstances(shapeTreeContext, manageableInstance.getManageableResource().getUrl()); if (!containedResources.isEmpty()) { // Evaluate containers, then resources Collections.sort(containedResources, new ResourceTypeAssignmentPriority()); @@ -256,7 +261,7 @@ export class ShapeTreeRequestHandler { // Recursively traverse the hierarchy and perform shape tree unassignment if (manageableInstance.getManageableResource().isContainer() && !assignedShapeTree.getContains().isEmpty()) { // TODO - Should there also be a configurable maximum limit on unplanting? - let containedResources: List = this.resourceAccessor.getContainedInstances(shapeTreeContext, manageableInstance.getManageableResource().getUrl()); + let containedResources: Array = this.resourceAccessor.getContainedInstances(shapeTreeContext, manageableInstance.getManageableResource().getUrl()); // If the container is not empty if (!containedResources.isEmpty()) { // Sort contained resources so that containers are evaluated first, then resources @@ -341,8 +346,8 @@ export class ShapeTreeRequestHandler { return null; } - private getUnmatchedFocusNodes(validationResults: Collection, focusNodes: List): List { - let unmatchedNodes: List = new ArrayList<>(); + private getUnmatchedFocusNodes(validationResults: Collection, focusNodes: Array): Array { + let unmatchedNodes: Array = new ArrayList<>(); for (let focusNode: URL : focusNodes) { // Determine if each target focus node was matched let matched: boolean = false; @@ -421,7 +426,7 @@ export class ShapeTreeRequestHandler { } private ensureDeleteIsSuccessful(response: DocumentResponse): void /* throws ShapeTreeException */ { - let successCodes: List = Arrays.asList(202, 204, 200); + let successCodes: Array = Arrays.asList(202, 204, 200); if (!successCodes.contains(response.getStatusCode())) { throw new ShapeTreeException(500, "Failed to delete manager resource. Received " + response.getStatusCode() + ": " + response.getBody()); } diff --git a/asTypescript/packages/core/src/helpers/GraphHelper.ts b/asTypescript/packages/core/src/helpers/GraphHelper.ts index e65a3fcf..93519cc1 100644 --- a/asTypescript/packages/core/src/helpers/GraphHelper.ts +++ b/asTypescript/packages/core/src/helpers/GraphHelper.ts @@ -2,7 +2,11 @@ import { ShapeTreeException } from '../exceptions/ShapeTreeException'; import * as Slf4j from 'lombok/extern/slf4j'; import * as XSDDatatype from 'org/apache/jena/datatypes/xsd'; -import * as graph from 'org/apache/jena'; +import * as Graph from 'org/apache/jena/graph'; +import * as Triple from 'org/apache/jena/graph'; +import * as NodeFactory from 'org/apache/jena/graph'; +import * as Node from 'org/apache/jena/graph'; +import * as Node_Blank from 'org/apache/jena/graph'; import * as Model from 'org/apache/jena/rdf/model'; import * as ModelFactory from 'org/apache/jena/rdf/model'; import * as Lang from 'org/apache/jena/riot'; diff --git a/asTypescript/packages/core/src/helpers/RequestHelper.ts b/asTypescript/packages/core/src/helpers/RequestHelper.ts index 26cc725f..cc54f17c 100644 --- a/asTypescript/packages/core/src/helpers/RequestHelper.ts +++ b/asTypescript/packages/core/src/helpers/RequestHelper.ts @@ -1,5 +1,10 @@ // Corresponding shapetrees-java package: com.janeirodigital.shapetrees.core.helpers -import * as core from 'com/janeirodigital/shapetrees'; +import { ShapeTreeManager } from '../ShapeTreeManager'; +import { ShapeTreeContext } from '../ShapeTreeContext'; +import { ManageableInstance } from '../ManageableInstance'; +import { InstanceResource } from '../InstanceResource'; +import { ManagerResource } from '../ManagerResource'; +import { ShapeTreeRequest } from '../ShapeTreeRequest'; import { HttpHeaders } from '../enums/HttpHeaders'; import { LinkRelations } from '../enums/LinkRelations'; import { ShapeTreeResourceType } from '../enums/ShapeTreeResourceType'; diff --git a/asTypescript/packages/core/src/methodhandlers/ValidatingPatchMethodHandler.ts b/asTypescript/packages/core/src/methodhandlers/ValidatingPatchMethodHandler.ts index 5a7975d7..f9715d11 100644 --- a/asTypescript/packages/core/src/methodhandlers/ValidatingPatchMethodHandler.ts +++ b/asTypescript/packages/core/src/methodhandlers/ValidatingPatchMethodHandler.ts @@ -1,8 +1,12 @@ // Corresponding shapetrees-java package: com.janeirodigital.shapetrees.core.methodhandlers -import * as core from 'com/janeirodigital/shapetrees'; +import { ShapeTreeRequest } from '../ShapeTreeRequest'; +import { ShapeTreeContext } from '../ShapeTreeContext'; +import { ManageableInstance } from '../ManageableInstance'; +import { DocumentResponse } from '../DocumentResponse'; +import { ResourceAccessor } from '../ResourceAccessor'; import { ShapeTreeException } from '../exceptions/ShapeTreeException'; import { RequestHelper } from '../helpers/RequestHelper'; -import { ShapeTreeContext } from '../ShapeTreeContext'; +import { ManageableResource } from '../ManageableResource'; import * as Slf4j from 'lombok/extern/slf4j'; import * as Optional from 'java/util'; import { AbstractValidatingMethodHandler } from './AbstractValidatingMethodHandler'; diff --git a/asTypescript/packages/core/src/methodhandlers/ValidatingPostMethodHandler.ts b/asTypescript/packages/core/src/methodhandlers/ValidatingPostMethodHandler.ts index 0dd9cec9..5f362876 100644 --- a/asTypescript/packages/core/src/methodhandlers/ValidatingPostMethodHandler.ts +++ b/asTypescript/packages/core/src/methodhandlers/ValidatingPostMethodHandler.ts @@ -1,9 +1,12 @@ // Corresponding shapetrees-java package: com.janeirodigital.shapetrees.core.methodhandlers -import * as core from 'com/janeirodigital/shapetrees'; -import { HttpHeaders } from '../enums/HttpHeaders'; +import { ShapeTreeRequest } from '../ShapeTreeRequest'; +import { ShapeTreeContext } from '../ShapeTreeContext'; +import { ManageableInstance } from '../ManageableInstance'; +import { DocumentResponse } from '../DocumentResponse'; +import { ResourceAccessor } from '../ResourceAccessor'; import { ShapeTreeException } from '../exceptions/ShapeTreeException'; import { RequestHelper } from '../helpers/RequestHelper'; -import { ShapeTreeContext } from '../ShapeTreeContext'; +import { HttpHeaders } from '../enums/HttpHeaders'; import * as Slf4j from 'lombok/extern/slf4j'; import * as Optional from 'java/util'; import * as UUID from 'java/util'; diff --git a/asTypescript/packages/core/src/methodhandlers/ValidatingPutMethodHandler.ts b/asTypescript/packages/core/src/methodhandlers/ValidatingPutMethodHandler.ts index dcd876ab..2cea3d9f 100644 --- a/asTypescript/packages/core/src/methodhandlers/ValidatingPutMethodHandler.ts +++ b/asTypescript/packages/core/src/methodhandlers/ValidatingPutMethodHandler.ts @@ -1,8 +1,12 @@ // Corresponding shapetrees-java package: com.janeirodigital.shapetrees.core.methodhandlers -import * as core from 'com/janeirodigital/shapetrees'; +import { ShapeTreeRequest } from '../ShapeTreeRequest'; +import { ShapeTreeContext } from '../ShapeTreeContext'; +import { ManageableInstance } from '../ManageableInstance'; +import { ManageableResource } from '../ManageableResource'; +import { DocumentResponse } from '../DocumentResponse'; +import { ResourceAccessor } from '../ResourceAccessor'; import { ShapeTreeException } from '../exceptions/ShapeTreeException'; import { RequestHelper } from '../helpers/RequestHelper'; -import { ShapeTreeContext } from '../ShapeTreeContext'; import * as Optional from 'java/util'; import { AbstractValidatingMethodHandler } from './AbstractValidatingMethodHandler'; import { ValidatingMethodHandler } from './ValidatingMethodHandler'; diff --git a/asTypescript/packages/javahttp/src/JavaHttpClient.ts b/asTypescript/packages/javahttp/src/JavaHttpClient.ts index 5978d8b5..cbaf1315 100644 --- a/asTypescript/packages/javahttp/src/JavaHttpClient.ts +++ b/asTypescript/packages/javahttp/src/JavaHttpClient.ts @@ -1,11 +1,20 @@ // Corresponding shapetrees-java package: com.janeirodigital.shapetrees.javahttp -import { HttpClient } from '@shapetrees/ttpClient'; -import { HttpRequest } from '@shapetrees/ttpRequest'; -import { DocumentResponse } from '@shapetrees/ocumentResponse'; -import { ResourceAttributes } from '@shapetrees/esourceAttributes'; -import { ShapeTreeException } from '@shapetrees/xceptions/ShapeTreeException'; +import { HttpClient } from '@shapetrees/HttpClient'; +import { HttpRequest } from '@shapetrees/HttpRequest'; +import { DocumentResponse } from '@shapetrees/DocumentResponse'; +import { ResourceAttributes } from '@shapetrees/ResourceAttributes'; +import { ShapeTreeException } from '@shapetrees/exceptions/ShapeTreeException'; import * as Slf4j from 'lombok/extern/slf4j'; -import * as ssl from 'javax/net'; +import * as TrustManager from 'javax/net/ssl'; +import * as X509TrustManager from 'javax/net/ssl'; +import * as SSLContext from 'javax/net/ssl'; +import * as HttpsURLConnection from 'javax/net/ssl'; +import * as HostnameVerifier from 'javax/net/ssl'; +import * as SSLSession from 'javax/net/ssl'; +import * as X509TrustManager from 'javax/net/ssl'; +import * as X509TrustManager from 'javax/net/ssl'; +import * as X509TrustManager from 'javax/net/ssl'; +import * as X509TrustManager from 'javax/net/ssl'; import * as URISyntaxException from 'java/net'; import * as KeyManagementException from 'java/security'; import * as NoSuchAlgorithmException from 'java/security'; diff --git a/asTypescript/packages/javahttp/src/JavaHttpClientFactory.ts b/asTypescript/packages/javahttp/src/JavaHttpClientFactory.ts index 13a237e9..fb0480c9 100644 --- a/asTypescript/packages/javahttp/src/JavaHttpClientFactory.ts +++ b/asTypescript/packages/javahttp/src/JavaHttpClientFactory.ts @@ -1,9 +1,9 @@ // Corresponding shapetrees-java package: com.janeirodigital.shapetrees.javahttp -import { HttpClientFactory } from '@shapetrees/ttpClientFactory'; -import { HttpRequest } from '@shapetrees/ttpRequest'; -import { DocumentResponse } from '@shapetrees/ocumentResponse'; -import { ExternalDocumentLoader } from '@shapetrees/ontentloaders/ExternalDocumentLoader'; -import { ShapeTreeException } from '@shapetrees/xceptions/ShapeTreeException'; +import { HttpClientFactory } from '@shapetrees/HttpClientFactory'; +import { HttpRequest } from '@shapetrees/HttpRequest'; +import { DocumentResponse } from '@shapetrees/DocumentResponse'; +import { ExternalDocumentLoader } from '@shapetrees/contentloaders/ExternalDocumentLoader'; +import { ShapeTreeException } from '@shapetrees/exceptions/ShapeTreeException'; import * as URL from 'java/net'; import { JavaHttpClient } from './JavaHttpClient'; diff --git a/asTypescript/packages/javahttp/src/JavaHttpValidatingShapeTreeInterceptor.ts b/asTypescript/packages/javahttp/src/JavaHttpValidatingShapeTreeInterceptor.ts index cc66a5d6..e660da99 100644 --- a/asTypescript/packages/javahttp/src/JavaHttpValidatingShapeTreeInterceptor.ts +++ b/asTypescript/packages/javahttp/src/JavaHttpValidatingShapeTreeInterceptor.ts @@ -1,10 +1,17 @@ // Corresponding shapetrees-java package: com.janeirodigital.shapetrees.javahttp -import { HttpResourceAccessor } from '@shapetrees/ttpResourceAccessor'; -import * as core from 'com/janeirodigital/shapetrees'; -import { HttpHeaders } from '@shapetrees/nums/HttpHeaders'; -import { ShapeTreeResourceType } from '@shapetrees/nums/ShapeTreeResourceType'; -import { ShapeTreeException } from '@shapetrees/xceptions/ShapeTreeException'; -import * as methodhandlers from '@shapetrees/ethodhandlers'; +import { HttpResourceAccessor } from '@shapetrees/HttpResourceAccessor'; +import { DocumentResponse } from '@shapetrees/DocumentResponse'; +import { ResourceAttributes } from '@shapetrees/ResourceAttributes'; +import { ResourceAccessor } from '@shapetrees/ResourceAccessor'; +import { ShapeTreeRequest } from '@shapetrees/ShapeTreeRequest'; +import { HttpHeaders } from '@shapetrees/enums/HttpHeaders'; +import { ShapeTreeResourceType } from '@shapetrees/enums/ShapeTreeResourceType'; +import { ShapeTreeException } from '@shapetrees/exceptions/ShapeTreeException'; +import { ValidatingMethodHandler } from '@shapetrees/methodhandlers/ValidatingMethodHandler'; +import { ValidatingDeleteMethodHandler } from '@shapetrees/methodhandlers/ValidatingDeleteMethodHandler'; +import { ValidatingPutMethodHandler } from '@shapetrees/methodhandlers/ValidatingPutMethodHandler'; +import { ValidatingPatchMethodHandler } from '@shapetrees/methodhandlers/ValidatingPatchMethodHandler'; +import { ValidatingPostMethodHandler } from '@shapetrees/methodhandlers/ValidatingPostMethodHandler'; import * as Slf4j from 'lombok/extern/slf4j'; import * as NotNull from 'org/jetbrains/annotations'; import * as SSLSession from 'javax/net/ssl'; @@ -12,7 +19,9 @@ import * as MalformedURLException from 'java/net'; import * as URI from 'java/net'; import * as URL from 'java/net'; import * as HttpResponse from 'java/net/http'; -import * as util from 'java'; +import * as Optional from 'java/util'; +import * as Collections from 'java/util'; +import * as TreeMap from 'java/util'; /** * Wrapper used for client-side validation @@ -96,9 +105,9 @@ export class JavaHttpValidatingShapeTreeInterceptor { this.request = request; this.body = body; this.contentType = contentType; - let tm: TreeMap> = new TreeMap<>(String.CASE_INSENSITIVE_ORDER); - let headerMap: Map> = this.request.headers().map(); - for (let entry: Map.Entry> : headerMap.entrySet()) { + let tm: TreeMap> = new TreeMap<>(String.CASE_INSENSITIVE_ORDER); + let headerMap: Map> = this.request.headers().map(); + for (let entry: Map.Entry> : headerMap.entrySet()) { tm.put(entry.getKey(), entry.getValue()); } this.headers = new ResourceAttributes(tm); @@ -124,7 +133,7 @@ export class JavaHttpValidatingShapeTreeInterceptor { return ResourceAttributes.parseLinkHeaders(this.getHeaderValues(HttpHeaders.LINK.getValue())); } - override public getHeaderValues(header: string): List { + override public getHeaderValues(header: string): Array { return this.request.headers().allValues(header); } From 519c0c6ee7ad919b261f6cddb37d09d778314b35 Mon Sep 17 00:00:00 2001 From: Eric Prud'hommeaux Date: Wed, 24 Nov 2021 19:45:13 +0100 Subject: [PATCH 04/15] + map java.util.Optional to X | null --- .../client-core/src/ShapeTreeClient.ts | 8 ++--- .../packages/client-http/src/HttpRequest.ts | 1 - .../client-http/src/HttpResourceAccessor.ts | 12 ++++---- .../client-http/src/HttpShapeTreeClient.ts | 10 +++---- .../packages/core/src/DocumentResponse.ts | 3 +- .../packages/core/src/InstanceResource.ts | 1 - .../packages/core/src/ManageableInstance.ts | 1 - .../packages/core/src/ManageableResource.ts | 8 ++--- .../packages/core/src/ManagedResource.ts | 4 +-- .../packages/core/src/ManagerResource.ts | 1 - .../core/src/MissingManageableResource.ts | 4 +-- .../core/src/MissingManagerResource.ts | 1 - .../packages/core/src/ResourceAccessor.ts | 1 - .../packages/core/src/ResourceAttributes.ts | 3 +- asTypescript/packages/core/src/SchemaCache.ts | 1 - asTypescript/packages/core/src/ShapeTree.ts | 1 - .../packages/core/src/ShapeTreeAssignment.ts | 1 - .../packages/core/src/ShapeTreeFactory.ts | 1 - .../packages/core/src/ShapeTreeManager.ts | 1 - .../packages/core/src/ShapeTreeReference.ts | 1 - .../packages/core/src/ShapeTreeRequest.ts | 1 - .../core/src/ShapeTreeRequestHandler.ts | 30 +++++++++---------- .../packages/core/src/ShapeTreeResource.ts | 1 - .../packages/core/src/UnmanagedResource.ts | 4 +-- .../packages/core/src/ValidationResult.ts | 19 ++++++------ .../comparators/ShapeTreeContainsPriority.ts | 1 - .../contentloaders/ExternalDocumentLoader.ts | 1 - .../HttpExternalDocumentLoader.ts | 1 - .../packages/core/src/helpers/GraphHelper.ts | 1 - .../core/src/helpers/RequestHelper.ts | 3 +- .../ValidatingDeleteMethodHandler.ts | 3 +- .../methodhandlers/ValidatingMethodHandler.ts | 3 +- .../ValidatingPatchMethodHandler.ts | 3 +- .../ValidatingPostMethodHandler.ts | 3 +- .../ValidatingPutMethodHandler.ts | 3 +- .../javahttp/src/JavaHttpClientFactory.ts | 1 - .../JavaHttpValidatingShapeTreeInterceptor.ts | 8 ++--- 37 files changed, 52 insertions(+), 98 deletions(-) diff --git a/asTypescript/packages/client-core/src/ShapeTreeClient.ts b/asTypescript/packages/client-core/src/ShapeTreeClient.ts index 5021c009..960498a8 100644 --- a/asTypescript/packages/client-core/src/ShapeTreeClient.ts +++ b/asTypescript/packages/client-core/src/ShapeTreeClient.ts @@ -3,8 +3,6 @@ import { DocumentResponse } from '@shapetrees/DocumentResponse'; import { ShapeTreeContext } from '@shapetrees/ShapeTreeContext'; import { ShapeTreeManager } from '@shapetrees/ShapeTreeManager'; import { ShapeTreeException } from '@shapetrees/exceptions/ShapeTreeException'; -import * as URL from 'java/net'; -import * as Optional from 'java/util'; /** * This interface defines a proposed API to be used for any client-side implementations of @@ -23,7 +21,7 @@ export interface ShapeTreeClient { * @return A ShapeTreeManager associated with targetResource * @throws ShapeTreeException ShapeTreeException */ - discoverShapeTree(context: ShapeTreeContext, targetResource: URL): Optional /* throws ShapeTreeException */; + discoverShapeTree(context: ShapeTreeContext, targetResource: URL): ShapeTreeManager | null /* throws ShapeTreeException */; /** * Shape Trees, §4.2: This operation marks an existing resource as being managed by one or more shape trees, @@ -74,7 +72,7 @@ export interface ShapeTreeClient { * @return DocumentResponse containing status and response headers/attributes * @throws ShapeTreeException ShapeTreeException */ - postManagedInstance(context: ShapeTreeContext, parentContainer: URL, focusNodes: Array, targetShapeTrees: Array, proposedName: string, isContainer: Boolean, bodyString: string, contentType: string): DocumentResponse /* throws ShapeTreeException */; + postManagedInstance(context: ShapeTreeContext, parentContainer: URL, focusNodes: Array, targetShapeTrees: Array, proposedName: string, isContainer: boolean, bodyString: string, contentType: string): DocumentResponse /* throws ShapeTreeException */; /** * Creates a resource via HTTP PUT that has been validated against the provided target shape tree @@ -88,7 +86,7 @@ export interface ShapeTreeClient { * @return DocumentResponse containing status and response header / attributes * @throws ShapeTreeException */ - putManagedInstance(context: ShapeTreeContext, targetResource: URL, focusNodes: Array, targetShapeTrees: Array, isContainer: Boolean, bodyString: string, contentType: string): DocumentResponse /* throws ShapeTreeException */; + putManagedInstance(context: ShapeTreeContext, targetResource: URL, focusNodes: Array, targetShapeTrees: Array, isContainer: boolean, bodyString: string, contentType: string): DocumentResponse /* throws ShapeTreeException */; /** * Updates a resource via HTTP PUT that has been validated against an associated shape tree diff --git a/asTypescript/packages/client-http/src/HttpRequest.ts b/asTypescript/packages/client-http/src/HttpRequest.ts index 157ecf79..d02d8e11 100644 --- a/asTypescript/packages/client-http/src/HttpRequest.ts +++ b/asTypescript/packages/client-http/src/HttpRequest.ts @@ -1,6 +1,5 @@ // Corresponding shapetrees-java package: com.janeirodigital.shapetrees.client.http import { ResourceAttributes } from '@shapetrees/ResourceAttributes'; -import * as URL from 'java/net'; export class HttpRequest { diff --git a/asTypescript/packages/client-http/src/HttpResourceAccessor.ts b/asTypescript/packages/client-http/src/HttpResourceAccessor.ts index 9edb13a1..ecd505af 100644 --- a/asTypescript/packages/client-http/src/HttpResourceAccessor.ts +++ b/asTypescript/packages/client-http/src/HttpResourceAccessor.ts @@ -23,9 +23,7 @@ import * as Node from 'org/apache/jena/graph'; import * as NodeFactory from 'org/apache/jena/graph'; import * as Triple from 'org/apache/jena/graph'; import * as MalformedURLException from 'java/net'; -import * as URL from 'java/net'; import * as Set from 'java/util'; -import * as Optional from 'java/util'; import * as Collections from 'java/util'; import * as ArrayList from 'java/util'; import { readStringIntoGraph } from '@shapetrees/helpers/GraphHelper/readStringIntoGraph'; @@ -280,7 +278,7 @@ export class HttpResourceAccessor implements ResourceAccessor { */ private generateResource(url: URL, response: DocumentResponse): InstanceResource /* throws ShapeTreeException */ { // If a resource was created, ensure the URL returned in the Location header is valid - let location: Optional = response.getResourceAttributes().firstValue(HttpHeaders.LOCATION.getValue()); + let location: string | null = response.getResourceAttributes().firstValue(HttpHeaders.LOCATION.getValue()); if (location.isPresent()) { try { url = new URL(location.get()); @@ -315,7 +313,7 @@ export class HttpResourceAccessor implements ResourceAccessor { } } else { // Look for presence of st:managedBy in link headers from response and get the target manager URL - const managerUrl: Optional = calculateManagerUrl(url, parsedLinkHeaders); + const managerUrl: URL | null = calculateManagerUrl(url, parsedLinkHeaders); if (exists) { return new ManageableResource(url, resourceType, attributes, body, name, true, managerUrl, container); } else { @@ -447,8 +445,8 @@ export class HttpResourceAccessor implements ResourceAccessor { * @return * @throws ShapeTreeException */ - private calculateManagerUrl(url: URL, parsedLinkHeaders: ResourceAttributes): Optional /* throws ShapeTreeException */ { - const optManagerString: Optional = parsedLinkHeaders.firstValue(LinkRelations.MANAGED_BY.getValue()); + private calculateManagerUrl(url: URL, parsedLinkHeaders: ResourceAttributes): URL | null /* throws ShapeTreeException */ { + const optManagerString: string | null = parsedLinkHeaders.firstValue(LinkRelations.MANAGED_BY.getValue()); if (optManagerString.isEmpty()) { log.info("The resource {} does not contain a link header of {}", url, LinkRelations.MANAGED_BY.getValue()); return Optional.empty(); @@ -473,7 +471,7 @@ export class HttpResourceAccessor implements ResourceAccessor { private calculateManagedUrl(managerUrl: URL, parsedLinkHeaders: ResourceAttributes): URL /* throws ShapeTreeException */ { let managedUrlString: string; let managedResourceUrl: URL; - const optManagedString: Optional = parsedLinkHeaders.firstValue(LinkRelations.MANAGES.getValue()); + const optManagedString: string | null = parsedLinkHeaders.firstValue(LinkRelations.MANAGES.getValue()); if (!optManagedString.isEmpty()) { managedUrlString = optManagedString.get(); } else { diff --git a/asTypescript/packages/client-http/src/HttpShapeTreeClient.ts b/asTypescript/packages/client-http/src/HttpShapeTreeClient.ts index 598dcb91..9533c0aa 100644 --- a/asTypescript/packages/client-http/src/HttpShapeTreeClient.ts +++ b/asTypescript/packages/client-http/src/HttpShapeTreeClient.ts @@ -16,8 +16,6 @@ import * as Slf4j from 'lombok/extern/slf4j'; import * as Lang from 'org/apache/jena/riot'; import * as RDFDataMgr from 'org/apache/jena/riot'; import { Writable } from 'stream'; -import * as URL from 'java/net'; -import * as Optional from 'java/util'; import { HttpRequest } from './HttpRequest'; import { HttpResourceAccessor } from './HttpResourceAccessor'; import { HttpClient } from './HttpClient'; @@ -48,7 +46,7 @@ export class HttpShapeTreeClient implements ShapeTreeClient { * @return * @throws ShapeTreeException */ - override public discoverShapeTree(context: ShapeTreeContext, targetResource: URL): Optional /* throws ShapeTreeException */ { + override public discoverShapeTree(context: ShapeTreeContext, targetResource: URL): ShapeTreeManager | null /* throws ShapeTreeException */ { if (targetResource === null) { throw new ShapeTreeException(500, "Must provide a value target resource for discovery"); } @@ -127,7 +125,7 @@ export class HttpShapeTreeClient implements ShapeTreeClient { return fetcher.fetchShapeTreeResponse(new HttpRequest("PUT", managerResourceUrl, headers, sw.toString(), "text/turtle")); } - override public postManagedInstance(context: ShapeTreeContext, parentContainer: URL, focusNodes: Array, targetShapeTrees: Array, proposedResourceName: string, isContainer: Boolean, bodyString: string, contentType: string): DocumentResponse /* throws ShapeTreeException */ { + override public postManagedInstance(context: ShapeTreeContext, parentContainer: URL, focusNodes: Array, targetShapeTrees: Array, proposedResourceName: string, isContainer: boolean, bodyString: string, contentType: string): DocumentResponse /* throws ShapeTreeException */ { if (context === null || parentContainer === null) { throw new ShapeTreeException(500, "Must provide a valid context and parent container to post shape tree instance"); } @@ -141,7 +139,7 @@ export class HttpShapeTreeClient implements ShapeTreeClient { } // Create via HTTP PUT - override public putManagedInstance(context: ShapeTreeContext, resourceUrl: URL, focusNodes: Array, targetShapeTrees: Array, isContainer: Boolean, bodyString: string, contentType: string): DocumentResponse /* throws ShapeTreeException */ { + override public putManagedInstance(context: ShapeTreeContext, resourceUrl: URL, focusNodes: Array, targetShapeTrees: Array, isContainer: boolean, bodyString: string, contentType: string): DocumentResponse /* throws ShapeTreeException */ { if (context === null || resourceUrl === null) { throw new ShapeTreeException(500, "Must provide a valid context and target resource to create shape tree instance via PUT"); } @@ -227,7 +225,7 @@ export class HttpShapeTreeClient implements ShapeTreeClient { null, body, contentType)); } - private getCommonHeaders(context: ShapeTreeContext, focusNodes: Array, targetShapeTrees: Array, isContainer: Boolean, proposedResourceName: string, contentType: string): ResourceAttributes { + private getCommonHeaders(context: ShapeTreeContext, focusNodes: Array, targetShapeTrees: Array, isContainer: boolean, proposedResourceName: string, contentType: string): ResourceAttributes { let ret: ResourceAttributes = new ResourceAttributes(); if (context.getAuthorizationHeaderValue() != null) { ret.maybeSet(HttpHeaders.AUTHORIZATION.getValue(), context.getAuthorizationHeaderValue()); diff --git a/asTypescript/packages/core/src/DocumentResponse.ts b/asTypescript/packages/core/src/DocumentResponse.ts index b9a4f44f..470af99d 100644 --- a/asTypescript/packages/core/src/DocumentResponse.ts +++ b/asTypescript/packages/core/src/DocumentResponse.ts @@ -1,6 +1,5 @@ // Corresponding shapetrees-java package: com.janeirodigital.shapetrees.core import { HttpHeaders } from './enums/HttpHeaders'; -import * as Optional from 'java/util'; import { ResourceAttributes } from './ResourceAttributes'; export class DocumentResponse { @@ -11,7 +10,7 @@ export class DocumentResponse { private readonly statusCode: number; - public getContentType(): Optional { + public getContentType(): string | null { return this.resourceAttributes.firstValue(HttpHeaders.CONTENT_TYPE.getValue()); } diff --git a/asTypescript/packages/core/src/InstanceResource.ts b/asTypescript/packages/core/src/InstanceResource.ts index 496f9809..483ab386 100644 --- a/asTypescript/packages/core/src/InstanceResource.ts +++ b/asTypescript/packages/core/src/InstanceResource.ts @@ -5,7 +5,6 @@ import { ShapeTreeException } from './exceptions/ShapeTreeException'; import { GraphHelper } from './helpers/GraphHelper'; import * as Graph from 'org/apache/jena/graph'; import * as URI from 'java/net'; -import * as URL from 'java/net'; import { urlToUri } from './helpers/GraphHelper/urlToUri'; import { ResourceAttributes } from './ResourceAttributes'; diff --git a/asTypescript/packages/core/src/ManageableInstance.ts b/asTypescript/packages/core/src/ManageableInstance.ts index fdee8694..923c372f 100644 --- a/asTypescript/packages/core/src/ManageableInstance.ts +++ b/asTypescript/packages/core/src/ManageableInstance.ts @@ -1,6 +1,5 @@ // Corresponding shapetrees-java package: com.janeirodigital.shapetrees.core import * as Slf4j from 'lombok/extern/slf4j'; -import * as URL from 'java/net'; import * as Objects from 'java/util'; import { ResourceAccessor } from './ResourceAccessor'; import { ShapeTreeContext } from './ShapeTreeContext'; diff --git a/asTypescript/packages/core/src/ManageableResource.ts b/asTypescript/packages/core/src/ManageableResource.ts index 6d96847e..1d74443d 100644 --- a/asTypescript/packages/core/src/ManageableResource.ts +++ b/asTypescript/packages/core/src/ManageableResource.ts @@ -2,8 +2,6 @@ import { ShapeTreeResourceType } from './enums/ShapeTreeResourceType'; import { ShapeTreeException } from './exceptions/ShapeTreeException'; import * as MalformedURLException from 'java/net'; -import * as URL from 'java/net'; -import * as Optional from 'java/util'; import { InstanceResource } from './InstanceResource'; import { ResourceAttributes } from './ResourceAttributes'; @@ -16,7 +14,7 @@ import { ResourceAttributes } from './ResourceAttributes'; */ export class ManageableResource extends InstanceResource { - private readonly managerResourceUrl: Optional; + private readonly managerResourceUrl: URL | null; private readonly isContainer: boolean; @@ -31,7 +29,7 @@ export class ManageableResource extends InstanceResource { * @param managerResourceUrl URL of the shape tree manager resource * @param isContainer Whether the resource is a container */ - public constructor(url: URL, resourceType: ShapeTreeResourceType, attributes: ResourceAttributes, body: string, name: string, exists: boolean, managerResourceUrl: Optional, isContainer: boolean) { + public constructor(url: URL, resourceType: ShapeTreeResourceType, attributes: ResourceAttributes, body: string, name: string, exists: boolean, managerResourceUrl: URL | null, isContainer: boolean) { super(url, resourceType, attributes, body, name, exists); this.managerResourceUrl = managerResourceUrl; this.isContainer = isContainer; @@ -51,7 +49,7 @@ export class ManageableResource extends InstanceResource { } } - public getManagerResourceUrl(): Optional { + public getManagerResourceUrl(): URL | null { return this.managerResourceUrl; } diff --git a/asTypescript/packages/core/src/ManagedResource.ts b/asTypescript/packages/core/src/ManagedResource.ts index 441e79ee..b2ecea0c 100644 --- a/asTypescript/packages/core/src/ManagedResource.ts +++ b/asTypescript/packages/core/src/ManagedResource.ts @@ -1,6 +1,4 @@ // Corresponding shapetrees-java package: com.janeirodigital.shapetrees.core -import * as URL from 'java/net'; -import * as Optional from 'java/util'; import { ManageableResource } from './ManageableResource'; /** @@ -16,7 +14,7 @@ export class ManagedResource extends ManageableResource { * @param manageable ManageableResource to construct the ManagedResource from * @param managerUrl URL of the associated shape tree manager resource */ - public constructor(manageable: ManageableResource, managerUrl: Optional) { + public constructor(manageable: ManageableResource, managerUrl: URL | null) { super(manageable.getUrl(), manageable.getResourceType(), manageable.getAttributes(), manageable.getBody(), manageable.getName(), manageable.isExists(), managerUrl, manageable.isContainer()); } } diff --git a/asTypescript/packages/core/src/ManagerResource.ts b/asTypescript/packages/core/src/ManagerResource.ts index c51cd263..0e1bfe90 100644 --- a/asTypescript/packages/core/src/ManagerResource.ts +++ b/asTypescript/packages/core/src/ManagerResource.ts @@ -2,7 +2,6 @@ import { ShapeTreeResourceType } from './enums/ShapeTreeResourceType'; import { ShapeTreeException } from './exceptions/ShapeTreeException'; import * as Graph from 'org/apache/jena/graph'; -import * as URL from 'java/net'; import { InstanceResource } from './InstanceResource'; import { ShapeTreeManager } from './ShapeTreeManager'; import { ResourceAttributes } from './ResourceAttributes'; diff --git a/asTypescript/packages/core/src/MissingManageableResource.ts b/asTypescript/packages/core/src/MissingManageableResource.ts index bc9413ad..721a5a5f 100644 --- a/asTypescript/packages/core/src/MissingManageableResource.ts +++ b/asTypescript/packages/core/src/MissingManageableResource.ts @@ -1,7 +1,5 @@ // Corresponding shapetrees-java package: com.janeirodigital.shapetrees.core import { ShapeTreeResourceType } from './enums/ShapeTreeResourceType'; -import * as URL from 'java/net'; -import * as Optional from 'java/util'; import { ManageableResource } from './ManageableResource'; import { ResourceAttributes } from './ResourceAttributes'; @@ -21,7 +19,7 @@ export class MissingManageableResource extends ManageableResource { * @param managerResourceUrl URL of the shape tree manager resource * @param isContainer Whether the resource is a container */ - public constructor(url: URL, resourceType: ShapeTreeResourceType, attributes: ResourceAttributes, body: string, name: string, managerResourceUrl: Optional, isContainer: boolean) { + public constructor(url: URL, resourceType: ShapeTreeResourceType, attributes: ResourceAttributes, body: string, name: string, managerResourceUrl: URL | null, isContainer: boolean) { super(url, resourceType, attributes, body, name, false, managerResourceUrl, isContainer); } } diff --git a/asTypescript/packages/core/src/MissingManagerResource.ts b/asTypescript/packages/core/src/MissingManagerResource.ts index 946f000b..b19209e9 100644 --- a/asTypescript/packages/core/src/MissingManagerResource.ts +++ b/asTypescript/packages/core/src/MissingManagerResource.ts @@ -1,6 +1,5 @@ // Corresponding shapetrees-java package: com.janeirodigital.shapetrees.core import { ShapeTreeResourceType } from './enums/ShapeTreeResourceType'; -import * as URL from 'java/net'; import { MissingManageableResource } from './MissingManageableResource'; import { ManagerResource } from './ManagerResource'; import { ResourceAttributes } from './ResourceAttributes'; diff --git a/asTypescript/packages/core/src/ResourceAccessor.ts b/asTypescript/packages/core/src/ResourceAccessor.ts index 0b1977a2..38977f0e 100644 --- a/asTypescript/packages/core/src/ResourceAccessor.ts +++ b/asTypescript/packages/core/src/ResourceAccessor.ts @@ -1,6 +1,5 @@ // Corresponding shapetrees-java package: com.janeirodigital.shapetrees.core import { ShapeTreeException } from './exceptions/ShapeTreeException'; -import * as URL from 'java/net'; import { ManageableInstance } from './ManageableInstance'; import { InstanceResource } from './InstanceResource'; import { ShapeTreeContext } from './ShapeTreeContext'; diff --git a/asTypescript/packages/core/src/ResourceAttributes.ts b/asTypescript/packages/core/src/ResourceAttributes.ts index f601ad40..95a584f9 100644 --- a/asTypescript/packages/core/src/ResourceAttributes.ts +++ b/asTypescript/packages/core/src/ResourceAttributes.ts @@ -2,7 +2,6 @@ import { ShapeTreeException } from './exceptions/ShapeTreeException'; import * as Slf4j from 'lombok/extern/slf4j'; import * as TreeMap from 'java/util'; -import * as Optional from 'java/util'; import * as ArrayList from 'java/util'; import * as Arrays from 'java/util'; import * as Matcher from 'java/util/regex'; @@ -161,7 +160,7 @@ export class ResourceAttributes { * @return an {@code Optional} containing the first named header * string value, if present */ - public firstValue(name: string): Optional { + public firstValue(name: string): string | null { return allValues(name).stream().findFirst(); } diff --git a/asTypescript/packages/core/src/SchemaCache.ts b/asTypescript/packages/core/src/SchemaCache.ts index 462458c5..0b431863 100644 --- a/asTypescript/packages/core/src/SchemaCache.ts +++ b/asTypescript/packages/core/src/SchemaCache.ts @@ -2,7 +2,6 @@ import { ShapeTreeException } from './exceptions/ShapeTreeException'; import * as ShexSchema from 'fr/inria/lille/shexjava/schema'; import * as Slf4j from 'lombok/extern/slf4j'; -import * as URL from 'java/net'; import * as HashMap from 'java/util'; /** diff --git a/asTypescript/packages/core/src/ShapeTree.ts b/asTypescript/packages/core/src/ShapeTree.ts index 773a75e4..8f124756 100644 --- a/asTypescript/packages/core/src/ShapeTree.ts +++ b/asTypescript/packages/core/src/ShapeTree.ts @@ -20,7 +20,6 @@ import * as GraphUtil from 'org/apache/jena/graph'; import * as Node from 'org/apache/jena/graph'; import * as NotNull from 'org/jetbrains/annotations'; import * as MalformedURLException from 'java/net'; -import * as URL from 'java/net'; import * as StandardCharsets from 'java/nio/charset'; import * as Iterator from 'java/util'; import * as Collections from 'java/util'; diff --git a/asTypescript/packages/core/src/ShapeTreeAssignment.ts b/asTypescript/packages/core/src/ShapeTreeAssignment.ts index f16be483..dd554b1b 100644 --- a/asTypescript/packages/core/src/ShapeTreeAssignment.ts +++ b/asTypescript/packages/core/src/ShapeTreeAssignment.ts @@ -7,7 +7,6 @@ import * as Node from 'org/apache/jena/graph'; import * as NodeFactory from 'org/apache/jena/graph'; import * as Triple from 'org/apache/jena/graph'; import * as MalformedURLException from 'java/net'; -import * as URL from 'java/net'; import * as Objects from 'java/util'; /** diff --git a/asTypescript/packages/core/src/ShapeTreeFactory.ts b/asTypescript/packages/core/src/ShapeTreeFactory.ts index 9b077414..3998d854 100644 --- a/asTypescript/packages/core/src/ShapeTreeFactory.ts +++ b/asTypescript/packages/core/src/ShapeTreeFactory.ts @@ -11,7 +11,6 @@ import * as Property from 'org/apache/jena/rdf/model'; import * as RDFNode from 'org/apache/jena/rdf/model'; import * as MalformedURLException from 'java/net'; import * as URI from 'java/net'; -import * as URL from 'java/net'; import * as ArrayList from 'java/util'; import * as HashMap from 'java/util'; import { urlToUri } from './helpers/GraphHelper/urlToUri'; diff --git a/asTypescript/packages/core/src/ShapeTreeManager.ts b/asTypescript/packages/core/src/ShapeTreeManager.ts index df3a86af..78df39a6 100644 --- a/asTypescript/packages/core/src/ShapeTreeManager.ts +++ b/asTypescript/packages/core/src/ShapeTreeManager.ts @@ -11,7 +11,6 @@ import * as Triple from 'org/apache/jena/graph'; import * as RDF from 'org/apache/jena/vocabulary'; import * as MalformedURLException from 'java/net'; import * as URI from 'java/net'; -import * as URL from 'java/net'; import * as ArrayList from 'java/util'; import { urlToUri } from './helpers/GraphHelper/urlToUri'; import { ShapeTreeAssignment } from './ShapeTreeAssignment'; diff --git a/asTypescript/packages/core/src/ShapeTreeReference.ts b/asTypescript/packages/core/src/ShapeTreeReference.ts index a78971cd..e20c062c 100644 --- a/asTypescript/packages/core/src/ShapeTreeReference.ts +++ b/asTypescript/packages/core/src/ShapeTreeReference.ts @@ -1,6 +1,5 @@ // Corresponding shapetrees-java package: com.janeirodigital.shapetrees.core import { ShapeTreeException } from './exceptions/ShapeTreeException'; -import * as URL from 'java/net'; import * as Objects from 'java/util'; export class ShapeTreeReference { diff --git a/asTypescript/packages/core/src/ShapeTreeRequest.ts b/asTypescript/packages/core/src/ShapeTreeRequest.ts index d90d70b9..5475ae30 100644 --- a/asTypescript/packages/core/src/ShapeTreeRequest.ts +++ b/asTypescript/packages/core/src/ShapeTreeRequest.ts @@ -1,6 +1,5 @@ // Corresponding shapetrees-java package: com.janeirodigital.shapetrees.core import { ShapeTreeResourceType } from './enums/ShapeTreeResourceType'; -import * as URL from 'java/net'; import { ResourceAttributes } from './ResourceAttributes'; export interface ShapeTreeRequest { diff --git a/asTypescript/packages/core/src/ShapeTreeRequestHandler.ts b/asTypescript/packages/core/src/ShapeTreeRequestHandler.ts index 8b8e4024..438ea8b0 100644 --- a/asTypescript/packages/core/src/ShapeTreeRequestHandler.ts +++ b/asTypescript/packages/core/src/ShapeTreeRequestHandler.ts @@ -5,9 +5,7 @@ import { ShapeTreeException } from './exceptions/ShapeTreeException'; import { RequestHelper } from './helpers/RequestHelper'; import * as Slf4j from 'lombok/extern/slf4j'; import * as Graph from 'org/apache/jena/graph'; -import * as URL from 'java/net'; import * as HashMap from 'java/util'; -import * as Optional from 'java/util'; import * as Collections from 'java/util'; import * as ArrayList from 'java/util'; import * as Collection from 'java/util'; @@ -40,7 +38,7 @@ export class ShapeTreeRequestHandler { } public manageShapeTree(manageableInstance: ManageableInstance, shapeTreeRequest: ShapeTreeRequest): DocumentResponse /* throws ShapeTreeException */ { - let validationResponse: Optional; + let validationResponse: DocumentResponse | null; let updatedRootManager: ShapeTreeManager = RequestHelper.getIncomingShapeTreeManager(shapeTreeRequest, manageableInstance.getManagerResource()); let existingRootManager: ShapeTreeManager = manageableInstance.getManagerResource().getManager(); // Determine assignments that have been removed, added, and/or updated @@ -77,12 +75,12 @@ export class ShapeTreeRequestHandler { * @return DocumentResponse * @throws ShapeTreeException */ - public plantShapeTree(manageableInstance: ManageableInstance, shapeTreeContext: ShapeTreeContext, updatedRootManager: ShapeTreeManager, delta: ShapeTreeManagerDelta): Optional /* throws ShapeTreeException */ { + public plantShapeTree(manageableInstance: ManageableInstance, shapeTreeContext: ShapeTreeContext, updatedRootManager: ShapeTreeManager, delta: ShapeTreeManagerDelta): DocumentResponse | null /* throws ShapeTreeException */ { // Cannot directly update assignments that are not root locations ensureUpdatedAssignmentIsRoot(delta); // Run recursive assignment for each updated assignment in the root manager for (let rootAssignment: ShapeTreeAssignment : delta.getUpdatedAssignments()) { - let validationResponse: Optional = assignShapeTreeToResource(manageableInstance, shapeTreeContext, updatedRootManager, rootAssignment, rootAssignment, null); + let validationResponse: DocumentResponse | null = assignShapeTreeToResource(manageableInstance, shapeTreeContext, updatedRootManager, rootAssignment, rootAssignment, null); if (validationResponse.isPresent()) { return validationResponse; } @@ -90,12 +88,12 @@ export class ShapeTreeRequestHandler { return Optional.empty(); } - public unplantShapeTree(manageableInstance: ManageableInstance, shapeTreeContext: ShapeTreeContext, delta: ShapeTreeManagerDelta): Optional /* throws ShapeTreeException */ { + public unplantShapeTree(manageableInstance: ManageableInstance, shapeTreeContext: ShapeTreeContext, delta: ShapeTreeManagerDelta): DocumentResponse | null /* throws ShapeTreeException */ { // Cannot unplant a non-root location ensureRemovedAssignmentsAreRoot(delta); // Run recursive unassignment for each removed assignment in the updated root manager for (let rootAssignment: ShapeTreeAssignment : delta.getRemovedAssignments()) { - let validationResponse: Optional = unassignShapeTreeFromResource(manageableInstance, shapeTreeContext, rootAssignment); + let validationResponse: DocumentResponse | null = unassignShapeTreeFromResource(manageableInstance, shapeTreeContext, rootAssignment); if (validationResponse.isPresent()) { return validationResponse; } @@ -104,7 +102,7 @@ export class ShapeTreeRequestHandler { } // TODO: #87: do sanity checks on meta of meta, c.f. @see https://github.com/xformativ/shapetrees-java/issues/87 - public createShapeTreeInstance(manageableInstance: ManageableInstance, containerResource: ManageableInstance, shapeTreeRequest: ShapeTreeRequest, proposedName: string): Optional /* throws ShapeTreeException */ { + public createShapeTreeInstance(manageableInstance: ManageableInstance, containerResource: ManageableInstance, shapeTreeRequest: ShapeTreeRequest, proposedName: string): DocumentResponse | null /* throws ShapeTreeException */ { // Sanity check user-owned resource @@ delete 'cause type checks ensureInstanceResourceExists(containerResource.getManageableResource(), "Target container for resource creation not found"); ensureRequestResourceIsContainer(containerResource.getManageableResource(), "Cannot create a shape tree instance in a non-container resource"); @@ -146,7 +144,7 @@ export class ShapeTreeRequestHandler { log.debug("Assigning shape tree to created resource: {}", createdInstance.getManagerResource().getUrl()); // Note: By providing the positive advance validationResult, we let the assignment operation know that validation // has already been performed with a positive result, and avoid having it perform the validation a second time - let assignResult: Optional = assignShapeTreeToResource(createdInstance, manageableInstance.getShapeTreeContext(), null, rootShapeTreeAssignment, containingAssignment, validationResults.get(containingAssignment)); + let assignResult: DocumentResponse | null = assignShapeTreeToResource(createdInstance, manageableInstance.getShapeTreeContext(), null, rootShapeTreeAssignment, containingAssignment, validationResults.get(containingAssignment)); if (assignResult.isPresent()) { return assignResult; } @@ -154,7 +152,7 @@ export class ShapeTreeRequestHandler { return Optional.of(successfulValidation()); } - public updateShapeTreeInstance(targetResource: ManageableInstance, shapeTreeContext: ShapeTreeContext, shapeTreeRequest: ShapeTreeRequest): Optional /* throws ShapeTreeException */ { + public updateShapeTreeInstance(targetResource: ManageableInstance, shapeTreeContext: ShapeTreeContext, shapeTreeRequest: ShapeTreeRequest): DocumentResponse | null /* throws ShapeTreeException */ { ensureInstanceResourceExists(targetResource.getManageableResource(), "Target resource to update not found"); ensureInstanceResourceExists(targetResource.getManagerResource(), "Should not be updating an unmanaged resource as a shape tree instance"); let manager: ShapeTreeManager = targetResource.getManagerResource().getManager(); @@ -173,17 +171,17 @@ export class ShapeTreeRequestHandler { return Optional.empty(); } - public deleteShapeTreeInstance(): Optional { + public deleteShapeTreeInstance(): DocumentResponse | null { // Nothing to validate in a delete request, so the request is passed along return Optional.empty(); } - protected assignShapeTreeToResource(manageableInstance: ManageableInstance, shapeTreeContext: ShapeTreeContext, rootManager: ShapeTreeManager, rootAssignment: ShapeTreeAssignment, parentAssignment: ShapeTreeAssignment, advanceValidationResult: ValidationResult): Optional /* throws ShapeTreeException */ { + protected assignShapeTreeToResource(manageableInstance: ManageableInstance, shapeTreeContext: ShapeTreeContext, rootManager: ShapeTreeManager, rootAssignment: ShapeTreeAssignment, parentAssignment: ShapeTreeAssignment, advanceValidationResult: ValidationResult): DocumentResponse | null /* throws ShapeTreeException */ { let managingShapeTree: ShapeTree = null; let shapeTreeManager: ShapeTreeManager = null; let matchingFocusNode: URL = null; let managingAssignment: ShapeTreeAssignment = null; - let validationResponse: Optional; + let validationResponse: DocumentResponse | null; ensureValidationResultIsUsableForAssignment(advanceValidationResult, "Invalid advance validation result provided for resource assignment"); if (advanceValidationResult != null) { managingShapeTree = advanceValidationResult.getMatchingShapeTree(); @@ -250,13 +248,13 @@ export class ShapeTreeRequestHandler { return Optional.empty(); } - protected unassignShapeTreeFromResource(manageableInstance: ManageableInstance, shapeTreeContext: ShapeTreeContext, rootAssignment: ShapeTreeAssignment): Optional /* throws ShapeTreeException */ { + protected unassignShapeTreeFromResource(manageableInstance: ManageableInstance, shapeTreeContext: ShapeTreeContext, rootAssignment: ShapeTreeAssignment): DocumentResponse | null /* throws ShapeTreeException */ { ensureInstanceResourceExists(manageableInstance.getManageableResource(), "Cannot remove assignment from non-existent managed resource"); ensureInstanceResourceExists(manageableInstance.getManagerResource(), "Cannot remove assignment from non-existent manager resource"); let shapeTreeManager: ShapeTreeManager = manageableInstance.getManagerResource().getManager(); let assignmentToRemove: ShapeTreeAssignment = shapeTreeManager.getAssignmentForRoot(rootAssignment); let assignedShapeTree: ShapeTree = ShapeTreeFactory.getShapeTree(assignmentToRemove.getShapeTree()); - let validationResponse: Optional; + let validationResponse: DocumentResponse | null; // If the managed resource is a container, and its shape tree specifies its contents with st:contains // Recursively traverse the hierarchy and perform shape tree unassignment if (manageableInstance.getManageableResource().isContainer() && !assignedShapeTree.getContains().isEmpty()) { @@ -436,7 +434,7 @@ export class ShapeTreeRequestHandler { return new DocumentResponse(new ResourceAttributes(), "OK", 201); } - private failValidation(validationResult: ValidationResult): Optional { + private failValidation(validationResult: ValidationResult): DocumentResponse | null { return Optional.of(new DocumentResponse(new ResourceAttributes(), validationResult.getMessage(), 422)); } } diff --git a/asTypescript/packages/core/src/ShapeTreeResource.ts b/asTypescript/packages/core/src/ShapeTreeResource.ts index abce84a6..2b137853 100644 --- a/asTypescript/packages/core/src/ShapeTreeResource.ts +++ b/asTypescript/packages/core/src/ShapeTreeResource.ts @@ -5,7 +5,6 @@ import { GraphHelper } from './helpers/GraphHelper'; import * as Slf4j from 'lombok/extern/slf4j'; import * as Model from 'org/apache/jena/rdf/model'; import * as URI from 'java/net'; -import * as URL from 'java/net'; import * as HashMap from 'java/util'; import { removeUrlFragment } from './helpers/GraphHelper/removeUrlFragment'; import { urlToUri } from './helpers/GraphHelper/urlToUri'; diff --git a/asTypescript/packages/core/src/UnmanagedResource.ts b/asTypescript/packages/core/src/UnmanagedResource.ts index 7c25b8a9..8cab63e1 100644 --- a/asTypescript/packages/core/src/UnmanagedResource.ts +++ b/asTypescript/packages/core/src/UnmanagedResource.ts @@ -1,6 +1,4 @@ // Corresponding shapetrees-java package: com.janeirodigital.shapetrees.core -import * as URL from 'java/net'; -import * as Optional from 'java/util'; import { ManageableResource } from './ManageableResource'; /** @@ -16,7 +14,7 @@ export class UnmanagedResource extends ManageableResource { * @param manageable ManageableResource to construct the UnmanagedResource from * @param managerUrl URL of the associated shape tree manager resource */ - public constructor(manageable: ManageableResource, managerUrl: Optional) { + public constructor(manageable: ManageableResource, managerUrl: URL | null) { super(manageable.getUrl(), manageable.getResourceType(), manageable.getAttributes(), manageable.getBody(), manageable.getName(), manageable.isExists(), managerUrl, manageable.isContainer()); } } diff --git a/asTypescript/packages/core/src/ValidationResult.ts b/asTypescript/packages/core/src/ValidationResult.ts index 14530fd7..e85e38ee 100644 --- a/asTypescript/packages/core/src/ValidationResult.ts +++ b/asTypescript/packages/core/src/ValidationResult.ts @@ -1,11 +1,10 @@ // Corresponding shapetrees-java package: com.janeirodigital.shapetrees.core -import * as URL from 'java/net'; import { ShapeTreeAssignment } from './ShapeTreeAssignment'; import { ShapeTree } from './ShapeTree'; export class ValidationResult { - private valid: Boolean; + private valid: boolean; private validatingShapeTree: ShapeTree; @@ -17,11 +16,11 @@ export class ValidationResult { private message: string; - public isValid(): Boolean { + public isValid(): boolean { return (this.valid != null && this.valid); } - public constructor(valid: Boolean, message: string) { + public constructor(valid: boolean, message: string) { this.valid = valid; this.message = message; this.validatingShapeTree = null; @@ -30,7 +29,7 @@ export class ValidationResult { this.matchingFocusNode = null; } - public constructor(valid: Boolean, validatingShapeTree: ShapeTree) { + public constructor(valid: boolean, validatingShapeTree: ShapeTree) { this.valid = valid; this.message = null; this.validatingShapeTree = validatingShapeTree; @@ -39,7 +38,7 @@ export class ValidationResult { this.matchingFocusNode = null; } - public constructor(valid: Boolean, validatingShapeTree: ShapeTree, message: string) { + public constructor(valid: boolean, validatingShapeTree: ShapeTree, message: string) { this.valid = valid; this.message = message; this.validatingShapeTree = validatingShapeTree; @@ -48,7 +47,7 @@ export class ValidationResult { this.matchingFocusNode = null; } - public constructor(valid: Boolean, validatingShapeTree: ShapeTree, matchingFocusNode: URL) { + public constructor(valid: boolean, validatingShapeTree: ShapeTree, matchingFocusNode: URL) { this.valid = valid; this.message = null; this.validatingShapeTree = validatingShapeTree; @@ -57,7 +56,7 @@ export class ValidationResult { this.matchingFocusNode = matchingFocusNode; } - public constructor(valid: Boolean, validatingShapeTree: ShapeTree, matchingShapeTree: ShapeTree, matchingFocusNode: URL) { + public constructor(valid: boolean, validatingShapeTree: ShapeTree, matchingShapeTree: ShapeTree, matchingFocusNode: URL) { this.valid = valid; this.message = null; this.validatingShapeTree = validatingShapeTree; @@ -66,7 +65,7 @@ export class ValidationResult { this.matchingFocusNode = matchingFocusNode; } - public constructor(valid: Boolean, validatingShapeTree: ShapeTree, matchingShapeTree: ShapeTree, managingAssignment: ShapeTreeAssignment, matchingFocusNode: URL, message: string) { + public constructor(valid: boolean, validatingShapeTree: ShapeTree, matchingShapeTree: ShapeTree, managingAssignment: ShapeTreeAssignment, matchingFocusNode: URL, message: string) { this.valid = valid; this.validatingShapeTree = validatingShapeTree; this.matchingShapeTree = matchingShapeTree; @@ -75,7 +74,7 @@ export class ValidationResult { this.message = message; } - public getValid(): Boolean { + public getValid(): boolean { return this.valid; } diff --git a/asTypescript/packages/core/src/comparators/ShapeTreeContainsPriority.ts b/asTypescript/packages/core/src/comparators/ShapeTreeContainsPriority.ts index 0af8f332..d0c93cb9 100644 --- a/asTypescript/packages/core/src/comparators/ShapeTreeContainsPriority.ts +++ b/asTypescript/packages/core/src/comparators/ShapeTreeContainsPriority.ts @@ -1,7 +1,6 @@ // Corresponding shapetrees-java package: com.janeirodigital.shapetrees.core.comparators import { ShapeTree } from '../ShapeTree'; import { ShapeTreeFactory } from '../ShapeTreeFactory'; -import * as URL from 'java/net'; import * as Comparator from 'java/util'; export class ShapeTreeContainsPriority implements Comparator, Serializable { diff --git a/asTypescript/packages/core/src/contentloaders/ExternalDocumentLoader.ts b/asTypescript/packages/core/src/contentloaders/ExternalDocumentLoader.ts index 44ad252f..2a977120 100644 --- a/asTypescript/packages/core/src/contentloaders/ExternalDocumentLoader.ts +++ b/asTypescript/packages/core/src/contentloaders/ExternalDocumentLoader.ts @@ -1,7 +1,6 @@ // Corresponding shapetrees-java package: com.janeirodigital.shapetrees.core.contentloaders import { DocumentResponse } from '../DocumentResponse'; import { ShapeTreeException } from '../exceptions/ShapeTreeException'; -import * as URL from 'java/net'; /** * Interface defining how a remote document can be loaded and its contents extracted. diff --git a/asTypescript/packages/core/src/contentloaders/HttpExternalDocumentLoader.ts b/asTypescript/packages/core/src/contentloaders/HttpExternalDocumentLoader.ts index ec8e3a60..daecfa7b 100644 --- a/asTypescript/packages/core/src/contentloaders/HttpExternalDocumentLoader.ts +++ b/asTypescript/packages/core/src/contentloaders/HttpExternalDocumentLoader.ts @@ -3,7 +3,6 @@ import { DocumentResponse } from '../DocumentResponse'; import { ResourceAttributes } from '../ResourceAttributes'; import { ShapeTreeException } from '../exceptions/ShapeTreeException'; import * as URISyntaxException from 'java/net'; -import * as URL from 'java/net'; import * as HttpClient from 'java/net/http'; import * as HttpRequest from 'java/net/http'; import * as HttpResponse from 'java/net/http'; diff --git a/asTypescript/packages/core/src/helpers/GraphHelper.ts b/asTypescript/packages/core/src/helpers/GraphHelper.ts index 93519cc1..155754cd 100644 --- a/asTypescript/packages/core/src/helpers/GraphHelper.ts +++ b/asTypescript/packages/core/src/helpers/GraphHelper.ts @@ -16,7 +16,6 @@ import { Writable } from 'stream'; import * as MalformedURLException from 'java/net'; import * as URI from 'java/net'; import * as URISyntaxException from 'java/net'; -import * as URL from 'java/net'; import * as OffsetDateTime from 'java/time'; /** diff --git a/asTypescript/packages/core/src/helpers/RequestHelper.ts b/asTypescript/packages/core/src/helpers/RequestHelper.ts index cc54f17c..3cb2447f 100644 --- a/asTypescript/packages/core/src/helpers/RequestHelper.ts +++ b/asTypescript/packages/core/src/helpers/RequestHelper.ts @@ -17,7 +17,6 @@ import * as UpdateAction from 'org/apache/jena/update'; import * as UpdateFactory from 'org/apache/jena/update'; import * as UpdateRequest from 'org/apache/jena/update'; import * as MalformedURLException from 'java/net'; -import * as URL from 'java/net'; import * as ArrayList from 'java/util'; import * as Set from 'java/util'; import { urlToUri } from './GraphHelper/urlToUri'; @@ -213,7 +212,7 @@ export class RequestHelper { * @param shapeTreeRequest Request * @return Is the resource a container? */ - private static getIsContainerFromRequest(shapeTreeRequest: ShapeTreeRequest): Boolean { + private static getIsContainerFromRequest(shapeTreeRequest: ShapeTreeRequest): boolean { // First try to determine based on link headers if (shapeTreeRequest.getLinkHeaders() != null) { const typeLinks: Array = shapeTreeRequest.getLinkHeaders().allValues(LinkRelations.TYPE.getValue()); diff --git a/asTypescript/packages/core/src/methodhandlers/ValidatingDeleteMethodHandler.ts b/asTypescript/packages/core/src/methodhandlers/ValidatingDeleteMethodHandler.ts index 8e2a8a79..47e154b5 100644 --- a/asTypescript/packages/core/src/methodhandlers/ValidatingDeleteMethodHandler.ts +++ b/asTypescript/packages/core/src/methodhandlers/ValidatingDeleteMethodHandler.ts @@ -6,7 +6,6 @@ import { ShapeTreeRequest } from '../ShapeTreeRequest'; import { ShapeTreeException } from '../exceptions/ShapeTreeException'; import { RequestHelper } from '../helpers/RequestHelper'; import { ShapeTreeContext } from '../ShapeTreeContext'; -import * as Optional from 'java/util'; import { AbstractValidatingMethodHandler } from './AbstractValidatingMethodHandler'; import { ValidatingMethodHandler } from './ValidatingMethodHandler'; @@ -16,7 +15,7 @@ export class ValidatingDeleteMethodHandler extends AbstractValidatingMethodHandl super(resourceAccessor); } - override public validateRequest(shapeTreeRequest: ShapeTreeRequest): Optional /* throws ShapeTreeException */ { + override public validateRequest(shapeTreeRequest: ShapeTreeRequest): DocumentResponse | null /* throws ShapeTreeException */ { let shapeTreeContext: ShapeTreeContext = RequestHelper.buildContextFromRequest(shapeTreeRequest); let targetInstance: ManageableInstance = this.resourceAccessor.getInstance(shapeTreeContext, shapeTreeRequest.getUrl()); if (targetInstance.wasRequestForManager() && targetInstance.getManagerResource().isExists()) { diff --git a/asTypescript/packages/core/src/methodhandlers/ValidatingMethodHandler.ts b/asTypescript/packages/core/src/methodhandlers/ValidatingMethodHandler.ts index 0cd0350b..ae0dbdaf 100644 --- a/asTypescript/packages/core/src/methodhandlers/ValidatingMethodHandler.ts +++ b/asTypescript/packages/core/src/methodhandlers/ValidatingMethodHandler.ts @@ -2,9 +2,8 @@ import { DocumentResponse } from '../DocumentResponse'; import { ShapeTreeException } from '../exceptions/ShapeTreeException'; import { ShapeTreeRequest } from '../ShapeTreeRequest'; -import * as Optional from 'java/util'; export interface ValidatingMethodHandler { - validateRequest(shapeTreeRequest: ShapeTreeRequest): Optional /* throws ShapeTreeException */; + validateRequest(shapeTreeRequest: ShapeTreeRequest): DocumentResponse | null /* throws ShapeTreeException */; } diff --git a/asTypescript/packages/core/src/methodhandlers/ValidatingPatchMethodHandler.ts b/asTypescript/packages/core/src/methodhandlers/ValidatingPatchMethodHandler.ts index f9715d11..3be81330 100644 --- a/asTypescript/packages/core/src/methodhandlers/ValidatingPatchMethodHandler.ts +++ b/asTypescript/packages/core/src/methodhandlers/ValidatingPatchMethodHandler.ts @@ -8,7 +8,6 @@ import { ShapeTreeException } from '../exceptions/ShapeTreeException'; import { RequestHelper } from '../helpers/RequestHelper'; import { ManageableResource } from '../ManageableResource'; import * as Slf4j from 'lombok/extern/slf4j'; -import * as Optional from 'java/util'; import { AbstractValidatingMethodHandler } from './AbstractValidatingMethodHandler'; import { ValidatingMethodHandler } from './ValidatingMethodHandler'; @@ -19,7 +18,7 @@ export class ValidatingPatchMethodHandler extends AbstractValidatingMethodHandle super(resourceAccessor); } - override public validateRequest(shapeTreeRequest: ShapeTreeRequest): Optional /* throws ShapeTreeException */ { + override public validateRequest(shapeTreeRequest: ShapeTreeRequest): DocumentResponse | null /* throws ShapeTreeException */ { if (shapeTreeRequest.getContentType() === null || !shapeTreeRequest.getContentType().equalsIgnoreCase("application/sparql-update")) { log.error("Received a patch without a content type of application/sparql-update"); throw new ShapeTreeException(415, "PATCH verb expects a content type of application/sparql-update"); diff --git a/asTypescript/packages/core/src/methodhandlers/ValidatingPostMethodHandler.ts b/asTypescript/packages/core/src/methodhandlers/ValidatingPostMethodHandler.ts index 5f362876..319e3ccb 100644 --- a/asTypescript/packages/core/src/methodhandlers/ValidatingPostMethodHandler.ts +++ b/asTypescript/packages/core/src/methodhandlers/ValidatingPostMethodHandler.ts @@ -8,7 +8,6 @@ import { ShapeTreeException } from '../exceptions/ShapeTreeException'; import { RequestHelper } from '../helpers/RequestHelper'; import { HttpHeaders } from '../enums/HttpHeaders'; import * as Slf4j from 'lombok/extern/slf4j'; -import * as Optional from 'java/util'; import * as UUID from 'java/util'; import { AbstractValidatingMethodHandler } from './AbstractValidatingMethodHandler'; import { ValidatingMethodHandler } from './ValidatingMethodHandler'; @@ -20,7 +19,7 @@ export class ValidatingPostMethodHandler extends AbstractValidatingMethodHandler super(resourceAccessor); } - override public validateRequest(shapeTreeRequest: ShapeTreeRequest): Optional /* throws ShapeTreeException */ { + override public validateRequest(shapeTreeRequest: ShapeTreeRequest): DocumentResponse | null /* throws ShapeTreeException */ { let shapeTreeContext: ShapeTreeContext = RequestHelper.buildContextFromRequest(shapeTreeRequest); // Look up the target container for the POST. Error if it doesn't exist, or is a manager resource let targetContainer: ManageableInstance = this.resourceAccessor.getInstance(shapeTreeContext, shapeTreeRequest.getUrl()); diff --git a/asTypescript/packages/core/src/methodhandlers/ValidatingPutMethodHandler.ts b/asTypescript/packages/core/src/methodhandlers/ValidatingPutMethodHandler.ts index 2cea3d9f..f96627ac 100644 --- a/asTypescript/packages/core/src/methodhandlers/ValidatingPutMethodHandler.ts +++ b/asTypescript/packages/core/src/methodhandlers/ValidatingPutMethodHandler.ts @@ -7,7 +7,6 @@ import { DocumentResponse } from '../DocumentResponse'; import { ResourceAccessor } from '../ResourceAccessor'; import { ShapeTreeException } from '../exceptions/ShapeTreeException'; import { RequestHelper } from '../helpers/RequestHelper'; -import * as Optional from 'java/util'; import { AbstractValidatingMethodHandler } from './AbstractValidatingMethodHandler'; import { ValidatingMethodHandler } from './ValidatingMethodHandler'; @@ -17,7 +16,7 @@ export class ValidatingPutMethodHandler extends AbstractValidatingMethodHandler super(resourceAccessor); } - override public validateRequest(shapeTreeRequest: ShapeTreeRequest): Optional /* throws ShapeTreeException */ { + override public validateRequest(shapeTreeRequest: ShapeTreeRequest): DocumentResponse | null /* throws ShapeTreeException */ { let shapeTreeContext: ShapeTreeContext = RequestHelper.buildContextFromRequest(shapeTreeRequest); let targetInstance: ManageableInstance = this.resourceAccessor.getInstance(shapeTreeContext, shapeTreeRequest.getUrl()); if (targetInstance.wasRequestForManager()) { diff --git a/asTypescript/packages/javahttp/src/JavaHttpClientFactory.ts b/asTypescript/packages/javahttp/src/JavaHttpClientFactory.ts index fb0480c9..b462bec6 100644 --- a/asTypescript/packages/javahttp/src/JavaHttpClientFactory.ts +++ b/asTypescript/packages/javahttp/src/JavaHttpClientFactory.ts @@ -4,7 +4,6 @@ import { HttpRequest } from '@shapetrees/HttpRequest'; import { DocumentResponse } from '@shapetrees/DocumentResponse'; import { ExternalDocumentLoader } from '@shapetrees/contentloaders/ExternalDocumentLoader'; import { ShapeTreeException } from '@shapetrees/exceptions/ShapeTreeException'; -import * as URL from 'java/net'; import { JavaHttpClient } from './JavaHttpClient'; /** diff --git a/asTypescript/packages/javahttp/src/JavaHttpValidatingShapeTreeInterceptor.ts b/asTypescript/packages/javahttp/src/JavaHttpValidatingShapeTreeInterceptor.ts index e660da99..3d1cf6ab 100644 --- a/asTypescript/packages/javahttp/src/JavaHttpValidatingShapeTreeInterceptor.ts +++ b/asTypescript/packages/javahttp/src/JavaHttpValidatingShapeTreeInterceptor.ts @@ -17,9 +17,7 @@ import * as NotNull from 'org/jetbrains/annotations'; import * as SSLSession from 'javax/net/ssl'; import * as MalformedURLException from 'java/net'; import * as URI from 'java/net'; -import * as URL from 'java/net'; import * as HttpResponse from 'java/net/http'; -import * as Optional from 'java/util'; import * as Collections from 'java/util'; import * as TreeMap from 'java/util'; @@ -45,7 +43,7 @@ export class JavaHttpValidatingShapeTreeInterceptor { let handler: ValidatingMethodHandler = getHandler(shapeTreeRequest.getMethod(), resourceAccessor); if (handler != null) { try { - let shapeTreeResponse: Optional = handler.validateRequest(shapeTreeRequest); + let shapeTreeResponse: DocumentResponse | null = handler.validateRequest(shapeTreeRequest); if (!shapeTreeResponse.isPresent()) { return JavaHttpClient.check(httpClient.send(clientRequest, java.net.http.HttpResponse.BodyHandlers.ofString())); } else { @@ -176,7 +174,7 @@ export class JavaHttpValidatingShapeTreeInterceptor { return this.request; } - override public previousResponse(): Optional> { + override public previousResponse(): HttpResponse | null { return Optional.empty(); } @@ -188,7 +186,7 @@ export class JavaHttpValidatingShapeTreeInterceptor { return this.body; } - override public sslSession(): Optional { + override public sslSession(): SSLSession | null { return Optional.empty(); } From 676aa55e3c4116b698ac9de356c22107d718dc20 Mon Sep 17 00:00:00 2001 From: Eric Prud'hommeaux Date: Wed, 24 Nov 2021 22:34:47 +0100 Subject: [PATCH 05/15] ~ clean up extra imports --- .../janeirodigital/shapetrees/javahttp/JavaHttpClient.java | 4 ---- 1 file changed, 4 deletions(-) diff --git a/shapetrees-java-javahttp/src/main/java/com/janeirodigital/shapetrees/javahttp/JavaHttpClient.java b/shapetrees-java-javahttp/src/main/java/com/janeirodigital/shapetrees/javahttp/JavaHttpClient.java index a031c63b..74c29978 100644 --- a/shapetrees-java-javahttp/src/main/java/com/janeirodigital/shapetrees/javahttp/JavaHttpClient.java +++ b/shapetrees-java-javahttp/src/main/java/com/janeirodigital/shapetrees/javahttp/JavaHttpClient.java @@ -13,10 +13,6 @@ import javax.net.ssl.HttpsURLConnection; import javax.net.ssl.HostnameVerifier; import javax.net.ssl.SSLSession; -import javax.net.ssl.X509TrustManager; -import javax.net.ssl.X509TrustManager; -import javax.net.ssl.X509TrustManager; -import javax.net.ssl.X509TrustManager; import java.io.IOException; import java.net.URISyntaxException; import java.security.KeyManagementException; From 5752669cc1d1cb25aa8e43d83f8cd97cbf9e6bf8 Mon Sep 17 00:00:00 2001 From: Eric Prud'hommeaux Date: Wed, 24 Nov 2021 23:36:41 +0100 Subject: [PATCH 06/15] ~ strip out lombok.extern.slf4j.Slf4j --- .../packages/client-http/src/HttpResourceAccessor.ts | 2 -- .../packages/client-http/src/HttpShapeTreeClient.ts | 2 -- asTypescript/packages/core/src/ManageableInstance.ts | 2 -- asTypescript/packages/core/src/ResourceAttributes.ts | 2 -- asTypescript/packages/core/src/SchemaCache.ts | 2 -- asTypescript/packages/core/src/ShapeTree.ts | 2 -- asTypescript/packages/core/src/ShapeTreeFactory.ts | 2 -- asTypescript/packages/core/src/ShapeTreeRequestHandler.ts | 2 -- asTypescript/packages/core/src/ShapeTreeResource.ts | 2 -- asTypescript/packages/core/src/helpers/GraphHelper.ts | 2 -- asTypescript/packages/core/src/helpers/RequestHelper.ts | 2 -- .../src/methodhandlers/AbstractValidatingMethodHandler.ts | 2 -- .../core/src/methodhandlers/ValidatingPatchMethodHandler.ts | 2 -- .../core/src/methodhandlers/ValidatingPostMethodHandler.ts | 2 -- asTypescript/packages/javahttp/src/JavaHttpClient.ts | 6 ------ .../javahttp/src/JavaHttpValidatingShapeTreeInterceptor.ts | 2 -- 16 files changed, 36 deletions(-) diff --git a/asTypescript/packages/client-http/src/HttpResourceAccessor.ts b/asTypescript/packages/client-http/src/HttpResourceAccessor.ts index ecd505af..9e0a975c 100644 --- a/asTypescript/packages/client-http/src/HttpResourceAccessor.ts +++ b/asTypescript/packages/client-http/src/HttpResourceAccessor.ts @@ -17,7 +17,6 @@ import { LinkRelations } from '@shapetrees/enums/LinkRelations'; import { ShapeTreeResourceType } from '@shapetrees/enums/ShapeTreeResourceType'; import { ShapeTreeException } from '@shapetrees/exceptions/ShapeTreeException'; import { LdpVocabulary } from '@shapetrees/vocabularies/LdpVocabulary'; -import * as Slf4j from 'lombok/extern/slf4j'; import * as Graph from 'org/apache/jena/graph'; import * as Node from 'org/apache/jena/graph'; import * as NodeFactory from 'org/apache/jena/graph'; @@ -40,7 +39,6 @@ import { HttpClient } from './HttpClient'; *

Given the fact that resources are accessed via HTTP, some inferences must be made on * resource state based on responses to HTTP requests.

*/ -@Slf4j export class HttpResourceAccessor implements ResourceAccessor { private static readonly supportedRDFContentTypes: Set = Set.of("text/turtle", "application/rdf+xml", "application/n-triples", "application/ld+json"); diff --git a/asTypescript/packages/client-http/src/HttpShapeTreeClient.ts b/asTypescript/packages/client-http/src/HttpShapeTreeClient.ts index 9533c0aa..270c7899 100644 --- a/asTypescript/packages/client-http/src/HttpShapeTreeClient.ts +++ b/asTypescript/packages/client-http/src/HttpShapeTreeClient.ts @@ -12,7 +12,6 @@ import { ResourceAttributes } from '@shapetrees/ResourceAttributes'; import { HttpHeaders } from '@shapetrees/enums/HttpHeaders'; import { LinkRelations } from '@shapetrees/enums/LinkRelations'; import { ShapeTreeException } from '@shapetrees/exceptions/ShapeTreeException'; -import * as Slf4j from 'lombok/extern/slf4j'; import * as Lang from 'org/apache/jena/riot'; import * as RDFDataMgr from 'org/apache/jena/riot'; import { Writable } from 'stream'; @@ -20,7 +19,6 @@ import { HttpRequest } from './HttpRequest'; import { HttpResourceAccessor } from './HttpResourceAccessor'; import { HttpClient } from './HttpClient'; -@Slf4j export class HttpShapeTreeClient implements ShapeTreeClient { private useClientShapeTreeValidation: boolean = true; diff --git a/asTypescript/packages/core/src/ManageableInstance.ts b/asTypescript/packages/core/src/ManageableInstance.ts index 923c372f..60b6fd29 100644 --- a/asTypescript/packages/core/src/ManageableInstance.ts +++ b/asTypescript/packages/core/src/ManageableInstance.ts @@ -1,5 +1,4 @@ // Corresponding shapetrees-java package: com.janeirodigital.shapetrees.core -import * as Slf4j from 'lombok/extern/slf4j'; import * as Objects from 'java/util'; import { ResourceAccessor } from './ResourceAccessor'; import { ShapeTreeContext } from './ShapeTreeContext'; @@ -25,7 +24,6 @@ import { MissingManagerResource } from './MissingManagerResource'; * through a ResourceAccessor. Once constructed, the ManageableInstance * is immutable. */ -@Slf4j export class ManageableInstance { public static readonly TEXT_TURTLE: string = "text/turtle"; diff --git a/asTypescript/packages/core/src/ResourceAttributes.ts b/asTypescript/packages/core/src/ResourceAttributes.ts index 95a584f9..e21a9b8f 100644 --- a/asTypescript/packages/core/src/ResourceAttributes.ts +++ b/asTypescript/packages/core/src/ResourceAttributes.ts @@ -1,6 +1,5 @@ // Corresponding shapetrees-java package: com.janeirodigital.shapetrees.core import { ShapeTreeException } from './exceptions/ShapeTreeException'; -import * as Slf4j from 'lombok/extern/slf4j'; import * as TreeMap from 'java/util'; import * as ArrayList from 'java/util'; import * as Arrays from 'java/util'; @@ -13,7 +12,6 @@ import * as requireNonNull from 'java/util/Objects'; * shapetrees-java libraries. The only behavior that's at all HTTP-specific is the * parseLinkHeaders factory which includes logic for HTTP Link headers. */ -@Slf4j export class ResourceAttributes { myMapOfLists: Map>; diff --git a/asTypescript/packages/core/src/SchemaCache.ts b/asTypescript/packages/core/src/SchemaCache.ts index 0b431863..a6af8dc5 100644 --- a/asTypescript/packages/core/src/SchemaCache.ts +++ b/asTypescript/packages/core/src/SchemaCache.ts @@ -1,13 +1,11 @@ // Corresponding shapetrees-java package: com.janeirodigital.shapetrees.core import { ShapeTreeException } from './exceptions/ShapeTreeException'; import * as ShexSchema from 'fr/inria/lille/shexjava/schema'; -import * as Slf4j from 'lombok/extern/slf4j'; import * as HashMap from 'java/util'; /** * Optional, static cache for pre-compiled ShEx schemas */ -@Slf4j export class SchemaCache { private constructor() { diff --git a/asTypescript/packages/core/src/ShapeTree.ts b/asTypescript/packages/core/src/ShapeTree.ts index 8f124756..e3e99baa 100644 --- a/asTypescript/packages/core/src/ShapeTree.ts +++ b/asTypescript/packages/core/src/ShapeTree.ts @@ -12,7 +12,6 @@ import * as ShexSchema from 'fr/inria/lille/shexjava/schema'; import * as ShExCParser from 'fr/inria/lille/shexjava/schema/parsing'; import * as RecursiveValidation from 'fr/inria/lille/shexjava/validation'; import * as ValidationAlgorithm from 'fr/inria/lille/shexjava/validation'; -import * as Slf4j from 'lombok/extern/slf4j'; import * as IRI from 'org/apache/commons/rdf/api'; import * as JenaRDF from 'org/apache/commons/rdf/jena'; import * as Graph from 'org/apache/jena/graph'; @@ -32,7 +31,6 @@ import { DocumentResponse } from './DocumentResponse'; import { ManageableResource } from './ManageableResource'; import { ValidationResult } from './ValidationResult'; -@Slf4j export class ShapeTree { @NotNull diff --git a/asTypescript/packages/core/src/ShapeTreeFactory.ts b/asTypescript/packages/core/src/ShapeTreeFactory.ts index 3998d854..6ef1b989 100644 --- a/asTypescript/packages/core/src/ShapeTreeFactory.ts +++ b/asTypescript/packages/core/src/ShapeTreeFactory.ts @@ -1,7 +1,6 @@ // Corresponding shapetrees-java package: com.janeirodigital.shapetrees.core import { ShapeTreeException } from './exceptions/ShapeTreeException'; import { ShapeTreeVocabulary } from './vocabularies/ShapeTreeVocabulary'; -import * as Slf4j from 'lombok/extern/slf4j'; import * as Node from 'org/apache/jena/graph'; import * as Node_URI from 'org/apache/jena/graph'; import * as Model from 'org/apache/jena/rdf/model'; @@ -23,7 +22,6 @@ import { ShapeTreeResource } from './ShapeTreeResource'; * Includes a simple in-memory local cache to avoid repeated fetching of * remote shape tree resources. */ -@Slf4j export class ShapeTreeFactory { private constructor() { diff --git a/asTypescript/packages/core/src/ShapeTreeRequestHandler.ts b/asTypescript/packages/core/src/ShapeTreeRequestHandler.ts index 438ea8b0..4372213d 100644 --- a/asTypescript/packages/core/src/ShapeTreeRequestHandler.ts +++ b/asTypescript/packages/core/src/ShapeTreeRequestHandler.ts @@ -3,7 +3,6 @@ import { ResourceTypeAssignmentPriority } from './comparators/ResourceTypeAssign import { HttpHeaders } from './enums/HttpHeaders'; import { ShapeTreeException } from './exceptions/ShapeTreeException'; import { RequestHelper } from './helpers/RequestHelper'; -import * as Slf4j from 'lombok/extern/slf4j'; import * as Graph from 'org/apache/jena/graph'; import * as HashMap from 'java/util'; import * as Collections from 'java/util'; @@ -26,7 +25,6 @@ import { ManageableResource } from './ManageableResource'; import { ShapeTreeManager } from './ShapeTreeManager'; import { ManagerResource } from './ManagerResource'; -@Slf4j export class ShapeTreeRequestHandler { private static readonly DELETE: string = "DELETE"; diff --git a/asTypescript/packages/core/src/ShapeTreeResource.ts b/asTypescript/packages/core/src/ShapeTreeResource.ts index 2b137853..63488f73 100644 --- a/asTypescript/packages/core/src/ShapeTreeResource.ts +++ b/asTypescript/packages/core/src/ShapeTreeResource.ts @@ -2,7 +2,6 @@ import { DocumentLoaderManager } from './contentloaders/DocumentLoaderManager'; import { ShapeTreeException } from './exceptions/ShapeTreeException'; import { GraphHelper } from './helpers/GraphHelper'; -import * as Slf4j from 'lombok/extern/slf4j'; import * as Model from 'org/apache/jena/rdf/model'; import * as URI from 'java/net'; import * as HashMap from 'java/util'; @@ -14,7 +13,6 @@ import { DocumentResponse } from './DocumentResponse'; * Represents a resource that contains one or more shape tree definitions. Provides * a factory to lookup, initialize, and cache them. */ -@Slf4j export class ShapeTreeResource { readonly url: URL; diff --git a/asTypescript/packages/core/src/helpers/GraphHelper.ts b/asTypescript/packages/core/src/helpers/GraphHelper.ts index 155754cd..4a35c18f 100644 --- a/asTypescript/packages/core/src/helpers/GraphHelper.ts +++ b/asTypescript/packages/core/src/helpers/GraphHelper.ts @@ -1,6 +1,5 @@ // Corresponding shapetrees-java package: com.janeirodigital.shapetrees.core.helpers import { ShapeTreeException } from '../exceptions/ShapeTreeException'; -import * as Slf4j from 'lombok/extern/slf4j'; import * as XSDDatatype from 'org/apache/jena/datatypes/xsd'; import * as Graph from 'org/apache/jena/graph'; import * as Triple from 'org/apache/jena/graph'; @@ -21,7 +20,6 @@ import * as OffsetDateTime from 'java/time'; /** * Assorted helper methods related to RDF Graphs */ -@Slf4j export class GraphHelper { private constructor() { diff --git a/asTypescript/packages/core/src/helpers/RequestHelper.ts b/asTypescript/packages/core/src/helpers/RequestHelper.ts index 3cb2447f..66e39e82 100644 --- a/asTypescript/packages/core/src/helpers/RequestHelper.ts +++ b/asTypescript/packages/core/src/helpers/RequestHelper.ts @@ -10,7 +10,6 @@ import { LinkRelations } from '../enums/LinkRelations'; import { ShapeTreeResourceType } from '../enums/ShapeTreeResourceType'; import { ShapeTreeException } from '../exceptions/ShapeTreeException'; import { LdpVocabulary } from '../vocabularies/LdpVocabulary'; -import * as Slf4j from 'lombok/extern/slf4j'; import * as Graph from 'org/apache/jena/graph'; import * as ModelFactory from 'org/apache/jena/rdf/model'; import * as UpdateAction from 'org/apache/jena/update'; @@ -21,7 +20,6 @@ import * as ArrayList from 'java/util'; import * as Set from 'java/util'; import { urlToUri } from './GraphHelper/urlToUri'; -@Slf4j export class RequestHelper { private static readonly PUT: string = "PUT"; diff --git a/asTypescript/packages/core/src/methodhandlers/AbstractValidatingMethodHandler.ts b/asTypescript/packages/core/src/methodhandlers/AbstractValidatingMethodHandler.ts index 526ea47a..b4964c51 100644 --- a/asTypescript/packages/core/src/methodhandlers/AbstractValidatingMethodHandler.ts +++ b/asTypescript/packages/core/src/methodhandlers/AbstractValidatingMethodHandler.ts @@ -1,12 +1,10 @@ // Corresponding shapetrees-java package: com.janeirodigital.shapetrees.core.methodhandlers import { ResourceAccessor } from '../ResourceAccessor'; import { ShapeTreeRequestHandler } from '../ShapeTreeRequestHandler'; -import * as Slf4j from 'lombok/extern/slf4j'; /** * Abstract class providing reusable functionality to different method handlers */ -@Slf4j export abstract class AbstractValidatingMethodHandler { private static readonly DELETE: string = "DELETE"; diff --git a/asTypescript/packages/core/src/methodhandlers/ValidatingPatchMethodHandler.ts b/asTypescript/packages/core/src/methodhandlers/ValidatingPatchMethodHandler.ts index 3be81330..7a0725db 100644 --- a/asTypescript/packages/core/src/methodhandlers/ValidatingPatchMethodHandler.ts +++ b/asTypescript/packages/core/src/methodhandlers/ValidatingPatchMethodHandler.ts @@ -7,11 +7,9 @@ import { ResourceAccessor } from '../ResourceAccessor'; import { ShapeTreeException } from '../exceptions/ShapeTreeException'; import { RequestHelper } from '../helpers/RequestHelper'; import { ManageableResource } from '../ManageableResource'; -import * as Slf4j from 'lombok/extern/slf4j'; import { AbstractValidatingMethodHandler } from './AbstractValidatingMethodHandler'; import { ValidatingMethodHandler } from './ValidatingMethodHandler'; -@Slf4j export class ValidatingPatchMethodHandler extends AbstractValidatingMethodHandler implements ValidatingMethodHandler { public constructor(resourceAccessor: ResourceAccessor) { diff --git a/asTypescript/packages/core/src/methodhandlers/ValidatingPostMethodHandler.ts b/asTypescript/packages/core/src/methodhandlers/ValidatingPostMethodHandler.ts index 319e3ccb..2ca8a1f6 100644 --- a/asTypescript/packages/core/src/methodhandlers/ValidatingPostMethodHandler.ts +++ b/asTypescript/packages/core/src/methodhandlers/ValidatingPostMethodHandler.ts @@ -7,12 +7,10 @@ import { ResourceAccessor } from '../ResourceAccessor'; import { ShapeTreeException } from '../exceptions/ShapeTreeException'; import { RequestHelper } from '../helpers/RequestHelper'; import { HttpHeaders } from '../enums/HttpHeaders'; -import * as Slf4j from 'lombok/extern/slf4j'; import * as UUID from 'java/util'; import { AbstractValidatingMethodHandler } from './AbstractValidatingMethodHandler'; import { ValidatingMethodHandler } from './ValidatingMethodHandler'; -@Slf4j export class ValidatingPostMethodHandler extends AbstractValidatingMethodHandler implements ValidatingMethodHandler { public constructor(resourceAccessor: ResourceAccessor) { diff --git a/asTypescript/packages/javahttp/src/JavaHttpClient.ts b/asTypescript/packages/javahttp/src/JavaHttpClient.ts index cbaf1315..325d9c91 100644 --- a/asTypescript/packages/javahttp/src/JavaHttpClient.ts +++ b/asTypescript/packages/javahttp/src/JavaHttpClient.ts @@ -4,17 +4,12 @@ import { HttpRequest } from '@shapetrees/HttpRequest'; import { DocumentResponse } from '@shapetrees/DocumentResponse'; import { ResourceAttributes } from '@shapetrees/ResourceAttributes'; import { ShapeTreeException } from '@shapetrees/exceptions/ShapeTreeException'; -import * as Slf4j from 'lombok/extern/slf4j'; import * as TrustManager from 'javax/net/ssl'; import * as X509TrustManager from 'javax/net/ssl'; import * as SSLContext from 'javax/net/ssl'; import * as HttpsURLConnection from 'javax/net/ssl'; import * as HostnameVerifier from 'javax/net/ssl'; import * as SSLSession from 'javax/net/ssl'; -import * as X509TrustManager from 'javax/net/ssl'; -import * as X509TrustManager from 'javax/net/ssl'; -import * as X509TrustManager from 'javax/net/ssl'; -import * as X509TrustManager from 'javax/net/ssl'; import * as URISyntaxException from 'java/net'; import * as KeyManagementException from 'java/security'; import * as NoSuchAlgorithmException from 'java/security'; @@ -26,7 +21,6 @@ import { JavaHttpValidatingShapeTreeInterceptor } from './JavaHttpValidatingShap /** * java.net.http implementation of HttpClient */ -@Slf4j export class JavaHttpClient implements HttpClient { private readonly httpClient: java.net.http.HttpClient; diff --git a/asTypescript/packages/javahttp/src/JavaHttpValidatingShapeTreeInterceptor.ts b/asTypescript/packages/javahttp/src/JavaHttpValidatingShapeTreeInterceptor.ts index 3d1cf6ab..233d240f 100644 --- a/asTypescript/packages/javahttp/src/JavaHttpValidatingShapeTreeInterceptor.ts +++ b/asTypescript/packages/javahttp/src/JavaHttpValidatingShapeTreeInterceptor.ts @@ -12,7 +12,6 @@ import { ValidatingDeleteMethodHandler } from '@shapetrees/methodhandlers/Valida import { ValidatingPutMethodHandler } from '@shapetrees/methodhandlers/ValidatingPutMethodHandler'; import { ValidatingPatchMethodHandler } from '@shapetrees/methodhandlers/ValidatingPatchMethodHandler'; import { ValidatingPostMethodHandler } from '@shapetrees/methodhandlers/ValidatingPostMethodHandler'; -import * as Slf4j from 'lombok/extern/slf4j'; import * as NotNull from 'org/jetbrains/annotations'; import * as SSLSession from 'javax/net/ssl'; import * as MalformedURLException from 'java/net'; @@ -24,7 +23,6 @@ import * as TreeMap from 'java/util'; /** * Wrapper used for client-side validation */ -@Slf4j export class JavaHttpValidatingShapeTreeInterceptor { private static readonly POST: string = "POST"; From c2c035a47468210ca53b221b3247f2de6f99726a Mon Sep 17 00:00:00 2001 From: Eric Prud'hommeaux Date: Wed, 24 Nov 2021 23:43:51 +0100 Subject: [PATCH 07/15] ~ ArrayList -> Array, HashMap -> Map --- .../packages/client-http/src/HttpResourceAccessor.ts | 3 +-- asTypescript/packages/core/src/ResourceAttributes.ts | 9 ++++----- asTypescript/packages/core/src/SchemaCache.ts | 3 +-- asTypescript/packages/core/src/ShapeTree.ts | 7 +++---- asTypescript/packages/core/src/ShapeTreeFactory.ts | 8 +++----- asTypescript/packages/core/src/ShapeTreeManager.ts | 5 ++--- asTypescript/packages/core/src/ShapeTreeManagerDelta.ts | 5 ++--- .../packages/core/src/ShapeTreeRequestHandler.ts | 6 ++---- asTypescript/packages/core/src/ShapeTreeResource.ts | 3 +-- asTypescript/packages/core/src/helpers/RequestHelper.ts | 5 ++--- 10 files changed, 21 insertions(+), 33 deletions(-) diff --git a/asTypescript/packages/client-http/src/HttpResourceAccessor.ts b/asTypescript/packages/client-http/src/HttpResourceAccessor.ts index 9e0a975c..55b6c042 100644 --- a/asTypescript/packages/client-http/src/HttpResourceAccessor.ts +++ b/asTypescript/packages/client-http/src/HttpResourceAccessor.ts @@ -24,7 +24,6 @@ import * as Triple from 'org/apache/jena/graph'; import * as MalformedURLException from 'java/net'; import * as Set from 'java/util'; import * as Collections from 'java/util'; -import * as ArrayList from 'java/util'; import { readStringIntoGraph } from '@shapetrees/helpers/GraphHelper/readStringIntoGraph'; import { urlToUri } from '@shapetrees/helpers/GraphHelper/urlToUri'; import { HttpRequest } from './HttpRequest'; @@ -345,7 +344,7 @@ export class HttpResourceAccessor implements ResourceAccessor { if (containerTriples.isEmpty()) { return Collections.emptyList(); } - let containedInstances: ArrayList = new ArrayList<>(); + let containedInstances: Array = new Array<>(); for (let containerTriple: Triple : containerTriples) { let containedInstance: ManageableInstance = this.getInstance(context, new URL(containerTriple.getObject().getURI())); containedInstances.add(containedInstance); diff --git a/asTypescript/packages/core/src/ResourceAttributes.ts b/asTypescript/packages/core/src/ResourceAttributes.ts index e21a9b8f..94b31039 100644 --- a/asTypescript/packages/core/src/ResourceAttributes.ts +++ b/asTypescript/packages/core/src/ResourceAttributes.ts @@ -1,7 +1,6 @@ // Corresponding shapetrees-java package: com.janeirodigital.shapetrees.core import { ShapeTreeException } from './exceptions/ShapeTreeException'; import * as TreeMap from 'java/util'; -import * as ArrayList from 'java/util'; import * as Arrays from 'java/util'; import * as Matcher from 'java/util/regex'; import * as Pattern from 'java/util/regex'; @@ -45,7 +44,7 @@ export class ResourceAttributes { private copy(): ResourceAttributes { let ret: ResourceAttributes = new ResourceAttributes(); for (let entry: Map.Entry> : this.myMapOfLists.entrySet()) { - ret.myMapOfLists.put(entry.getKey(), new ArrayList<>(entry.getValue())); + ret.myMapOfLists.put(entry.getKey(), new Array<>(entry.getValue())); } return ret; } @@ -64,7 +63,7 @@ export class ResourceAttributes { if (matcher.matches()) { let uri: string = matcher.group(1); let rel: string = matcher.group(2); - linkHeaderMap.myMapOfLists.computeIfAbsent(rel, k -> new ArrayList<>()); + linkHeaderMap.myMapOfLists.computeIfAbsent(rel, k -> new Array<>()); linkHeaderMap.myMapOfLists.get(rel).add(uri); } else { log.warn("Unable to parse link header: [{}]", headerValue); @@ -108,7 +107,7 @@ export class ResourceAttributes { throw new Exception(attr + ": " + value + " already set."); }*/ } else { - let list: ArrayList = new ArrayList(); + let list: Array = new Array(); list.add(value); this.myMapOfLists.put(attr, list); } @@ -136,7 +135,7 @@ export class ResourceAttributes { * (This is useful for HttpRequest.Builder().) */ public toList(...exclusions: string): string[] { - let ret: Array = new ArrayList<>(); + let ret: Array = new Array<>(); for (let entry: Map.Entry> : this.myMapOfLists.entrySet()) { let attr: string = entry.getKey(); if (!Arrays.stream(exclusions).anyMatch(s -> s === attr)) { diff --git a/asTypescript/packages/core/src/SchemaCache.ts b/asTypescript/packages/core/src/SchemaCache.ts index a6af8dc5..6b985c57 100644 --- a/asTypescript/packages/core/src/SchemaCache.ts +++ b/asTypescript/packages/core/src/SchemaCache.ts @@ -1,7 +1,6 @@ // Corresponding shapetrees-java package: com.janeirodigital.shapetrees.core import { ShapeTreeException } from './exceptions/ShapeTreeException'; import * as ShexSchema from 'fr/inria/lille/shexjava/schema'; -import * as HashMap from 'java/util'; /** * Optional, static cache for pre-compiled ShEx schemas @@ -16,7 +15,7 @@ export class SchemaCache { private static cache: Map = null; public static initializeCache(): void { - cache = new HashMap<>(); + cache = new Map<>(); } public static initializeCache(existingCache: Map): void { diff --git a/asTypescript/packages/core/src/ShapeTree.ts b/asTypescript/packages/core/src/ShapeTree.ts index e3e99baa..bbbe1369 100644 --- a/asTypescript/packages/core/src/ShapeTree.ts +++ b/asTypescript/packages/core/src/ShapeTree.ts @@ -22,7 +22,6 @@ import * as MalformedURLException from 'java/net'; import * as StandardCharsets from 'java/nio/charset'; import * as Iterator from 'java/util'; import * as Collections from 'java/util'; -import * as ArrayList from 'java/util'; import * as Queue from 'java/util'; import * as LinkedList from 'java/util'; import { urlToUri } from './helpers/GraphHelper/urlToUri'; @@ -228,7 +227,7 @@ export class ShapeTree { // Return the list of shape tree contains by priority from most to least strict public getPrioritizedContains(): Array { - let prioritized: Array = new ArrayList<>(this.contains); + let prioritized: Array = new Array<>(this.contains); Collections.sort(prioritized, new ShapeTreeContainsPriority()); return prioritized; } @@ -237,13 +236,13 @@ export class ShapeTree { if (recursionMethods === RecursionMethods.BREADTH_FIRST) { return getReferencedShapeTreesListBreadthFirst(); } else { - let referencedShapeTrees: Array = new ArrayList<>(); + let referencedShapeTrees: Array = new Array<>(); return getReferencedShapeTreesListDepthFirst(this.getReferences(), referencedShapeTrees); } } private getReferencedShapeTreesListBreadthFirst(): Array /* throws ShapeTreeException */ { - let referencedShapeTrees: Array = new ArrayList<>(); + let referencedShapeTrees: Array = new Array<>(); let queue: Queue = new LinkedList<>(this.getReferences()); while (!queue.isEmpty()) { let currentShapeTree: ShapeTreeReference = queue.poll(); diff --git a/asTypescript/packages/core/src/ShapeTreeFactory.ts b/asTypescript/packages/core/src/ShapeTreeFactory.ts index 6ef1b989..548311b6 100644 --- a/asTypescript/packages/core/src/ShapeTreeFactory.ts +++ b/asTypescript/packages/core/src/ShapeTreeFactory.ts @@ -10,8 +10,6 @@ import * as Property from 'org/apache/jena/rdf/model'; import * as RDFNode from 'org/apache/jena/rdf/model'; import * as MalformedURLException from 'java/net'; import * as URI from 'java/net'; -import * as ArrayList from 'java/util'; -import * as HashMap from 'java/util'; import { urlToUri } from './helpers/GraphHelper/urlToUri'; import { ShapeTreeReference } from './ShapeTreeReference'; import { ShapeTree } from './ShapeTree'; @@ -30,7 +28,7 @@ export class ShapeTreeFactory { private static readonly RDFS_LABEL: string = "http://www.w3.org/2000/01/rdf-schema#label"; @Getter - private static readonly localShapeTreeCache: Map = new HashMap<>(); + private static readonly localShapeTreeCache: Map = new Map<>(); /** * Looks up and parses the shape tree at shapeTreeUrl. @@ -104,7 +102,7 @@ export class ShapeTreeFactory { * @throws ShapeTreeException */ private static getReferences(resourceModel: Model, shapeTreeNode: Resource, shapeTreeUrl: URL): Array /* throws ShapeTreeException */ { - let references: ArrayList = new ArrayList<>(); + let references: Array = new Array<>(); let referencesProperty: Property = resourceModel.createProperty(ShapeTreeVocabulary.REFERENCES); if (shapeTreeNode.hasProperty(referencesProperty)) { let referenceStatements: Array = shapeTreeNode.listProperties(referencesProperty).toList(); @@ -187,7 +185,7 @@ export class ShapeTreeFactory { * @throws ShapeTreeException */ private static getURLListValue(model: Model, resource: Resource, predicate: string): Array /* throws MalformedURLException, ShapeTreeException */ { - let urls: Array = new ArrayList<>(); + let urls: Array = new Array<>(); let property: Property = model.createProperty(predicate); if (resource.hasProperty(property)) { let propertyStatements: Array = resource.listProperties(property).toList(); diff --git a/asTypescript/packages/core/src/ShapeTreeManager.ts b/asTypescript/packages/core/src/ShapeTreeManager.ts index 78df39a6..f186bba5 100644 --- a/asTypescript/packages/core/src/ShapeTreeManager.ts +++ b/asTypescript/packages/core/src/ShapeTreeManager.ts @@ -11,7 +11,6 @@ import * as Triple from 'org/apache/jena/graph'; import * as RDF from 'org/apache/jena/vocabulary'; import * as MalformedURLException from 'java/net'; import * as URI from 'java/net'; -import * as ArrayList from 'java/util'; import { urlToUri } from './helpers/GraphHelper/urlToUri'; import { ShapeTreeAssignment } from './ShapeTreeAssignment'; import { ShapeTree } from './ShapeTree'; @@ -29,7 +28,7 @@ export class ShapeTreeManager { private readonly id: URL; // Each ShapeTreeManager has one or more ShapeTreeAssignments - private readonly assignments: Array = new ArrayList<>(); + private readonly assignments: Array = new Array<>(); /** * Constructor for a new ShapeTreeManager @@ -127,7 +126,7 @@ export class ShapeTreeManager { } public getContainingAssignments(): Array /* throws ShapeTreeException */ { - let containingAssignments: ArrayList = new ArrayList<>(); + let containingAssignments: Array = new Array<>(); for (let assignment: ShapeTreeAssignment : this.assignments) { let shapeTree: ShapeTree = ShapeTreeFactory.getShapeTree(assignment.getShapeTree()); if (!shapeTree.getContains().isEmpty()) { diff --git a/asTypescript/packages/core/src/ShapeTreeManagerDelta.ts b/asTypescript/packages/core/src/ShapeTreeManagerDelta.ts index 67b24b15..27615147 100644 --- a/asTypescript/packages/core/src/ShapeTreeManagerDelta.ts +++ b/asTypescript/packages/core/src/ShapeTreeManagerDelta.ts @@ -2,7 +2,6 @@ import { ShapeTreeException } from './exceptions/ShapeTreeException'; import * as URI from 'java/net'; import * as URISyntaxException from 'java/net'; -import * as ArrayList from 'java/util'; import { ShapeTreeAssignment } from './ShapeTreeAssignment'; import { ShapeTreeManager } from './ShapeTreeManager'; @@ -30,8 +29,8 @@ export class ShapeTreeManagerDelta { let delta: ShapeTreeManagerDelta = new ShapeTreeManagerDelta(); delta.existingManager = existingManager; delta.updatedManager = updatedManager; - delta.updatedAssignments = new ArrayList<>(); - delta.removedAssignments = new ArrayList<>(); + delta.updatedAssignments = new Array<>(); + delta.removedAssignments = new Array<>(); if (updatedManager === null || updatedManager.getAssignments().isEmpty()) { // All assignments have been removed in the updated manager, so any existing assignments should // similarly be removed. No need for further comparison. diff --git a/asTypescript/packages/core/src/ShapeTreeRequestHandler.ts b/asTypescript/packages/core/src/ShapeTreeRequestHandler.ts index 4372213d..a4fcc9c9 100644 --- a/asTypescript/packages/core/src/ShapeTreeRequestHandler.ts +++ b/asTypescript/packages/core/src/ShapeTreeRequestHandler.ts @@ -4,9 +4,7 @@ import { HttpHeaders } from './enums/HttpHeaders'; import { ShapeTreeException } from './exceptions/ShapeTreeException'; import { RequestHelper } from './helpers/RequestHelper'; import * as Graph from 'org/apache/jena/graph'; -import * as HashMap from 'java/util'; import * as Collections from 'java/util'; -import * as ArrayList from 'java/util'; import * as Collection from 'java/util'; import * as Arrays from 'java/util'; import { TEXT_TURTLE } from './ManageableInstance/TEXT_TURTLE'; @@ -119,7 +117,7 @@ export class ShapeTreeRequestHandler { let targetShapeTrees: Array = RequestHelper.getIncomingTargetShapeTrees(shapeTreeRequest, targetResourceUrl); let incomingFocusNodes: Array = RequestHelper.getIncomingFocusNodes(shapeTreeRequest, targetResourceUrl); let incomingBodyGraph: Graph = RequestHelper.getIncomingBodyGraph(shapeTreeRequest, targetResourceUrl, null); - let validationResults: HashMap = new HashMap<>(); + let validationResults: Map = new Map<>(); for (let containingAssignment: ShapeTreeAssignment : containingAssignments) { let containerShapeTreeUrl: URL = containingAssignment.getShapeTree(); let containerShapeTree: ShapeTree = ShapeTreeFactory.getShapeTree(containerShapeTreeUrl); @@ -343,7 +341,7 @@ export class ShapeTreeRequestHandler { } private getUnmatchedFocusNodes(validationResults: Collection, focusNodes: Array): Array { - let unmatchedNodes: Array = new ArrayList<>(); + let unmatchedNodes: Array = new Array<>(); for (let focusNode: URL : focusNodes) { // Determine if each target focus node was matched let matched: boolean = false; diff --git a/asTypescript/packages/core/src/ShapeTreeResource.ts b/asTypescript/packages/core/src/ShapeTreeResource.ts index 63488f73..fe346bb7 100644 --- a/asTypescript/packages/core/src/ShapeTreeResource.ts +++ b/asTypescript/packages/core/src/ShapeTreeResource.ts @@ -4,7 +4,6 @@ import { ShapeTreeException } from './exceptions/ShapeTreeException'; import { GraphHelper } from './helpers/GraphHelper'; import * as Model from 'org/apache/jena/rdf/model'; import * as URI from 'java/net'; -import * as HashMap from 'java/util'; import { removeUrlFragment } from './helpers/GraphHelper/removeUrlFragment'; import { urlToUri } from './helpers/GraphHelper/urlToUri'; import { DocumentResponse } from './DocumentResponse'; @@ -24,7 +23,7 @@ export class ShapeTreeResource { readonly model: Model; @Getter - private static readonly localResourceCache: Map = new HashMap<>(); + private static readonly localResourceCache: Map = new Map<>(); /** * Looks up and caches the shape tree resource at resourceUrl. Will used cached diff --git a/asTypescript/packages/core/src/helpers/RequestHelper.ts b/asTypescript/packages/core/src/helpers/RequestHelper.ts index 66e39e82..87ba9c82 100644 --- a/asTypescript/packages/core/src/helpers/RequestHelper.ts +++ b/asTypescript/packages/core/src/helpers/RequestHelper.ts @@ -16,7 +16,6 @@ import * as UpdateAction from 'org/apache/jena/update'; import * as UpdateFactory from 'org/apache/jena/update'; import * as UpdateRequest from 'org/apache/jena/update'; import * as MalformedURLException from 'java/net'; -import * as ArrayList from 'java/util'; import * as Set from 'java/util'; import { urlToUri } from './GraphHelper/urlToUri'; @@ -92,7 +91,7 @@ export class RequestHelper { public static getIncomingFocusNodes(shapeTreeRequest: ShapeTreeRequest, baseUrl: URL): Array /* throws ShapeTreeException */ { const focusNodeStrings: Array = shapeTreeRequest.getLinkHeaders().allValues(LinkRelations.FOCUS_NODE.getValue()); - const focusNodeUrls: Array = new ArrayList<>(); + const focusNodeUrls: Array = new Array<>(); if (!focusNodeStrings.isEmpty()) { for (let focusNodeUrlString: string : focusNodeStrings) { try { @@ -114,7 +113,7 @@ export class RequestHelper { */ public static getIncomingTargetShapeTrees(shapeTreeRequest: ShapeTreeRequest, baseUrl: URL): Array /* throws ShapeTreeException */ { const targetShapeTreeStrings: Array = shapeTreeRequest.getLinkHeaders().allValues(LinkRelations.TARGET_SHAPETREE.getValue()); - const targetShapeTreeUrls: Array = new ArrayList<>(); + const targetShapeTreeUrls: Array = new Array<>(); if (!targetShapeTreeStrings.isEmpty()) { for (let targetShapeTreeUrlString: string : targetShapeTreeStrings) { try { From de1cc05c53f9233f910b98a6878c74fe680c0102 Mon Sep 17 00:00:00 2001 From: Eric Prud'hommeaux Date: Thu, 25 Nov 2021 00:04:32 +0100 Subject: [PATCH 08/15] + TS printer for ForEachStmt --- .../client-http/src/HttpResourceAccessor.ts | 2 +- .../client-http/src/HttpShapeTreeClient.ts | 4 ++-- .../packages/core/src/ResourceAttributes.ts | 12 +++++----- asTypescript/packages/core/src/ShapeTree.ts | 10 ++++---- .../packages/core/src/ShapeTreeAssignment.ts | 2 +- .../packages/core/src/ShapeTreeFactory.ts | 8 +++---- .../packages/core/src/ShapeTreeManager.ts | 14 +++++------ .../core/src/ShapeTreeManagerDelta.ts | 6 ++--- .../core/src/ShapeTreeRequestHandler.ts | 24 +++++++++---------- .../core/src/helpers/RequestHelper.ts | 4 ++-- .../JavaHttpValidatingShapeTreeInterceptor.ts | 2 +- 11 files changed, 44 insertions(+), 44 deletions(-) diff --git a/asTypescript/packages/client-http/src/HttpResourceAccessor.ts b/asTypescript/packages/client-http/src/HttpResourceAccessor.ts index 55b6c042..dfb9f4c6 100644 --- a/asTypescript/packages/client-http/src/HttpResourceAccessor.ts +++ b/asTypescript/packages/client-http/src/HttpResourceAccessor.ts @@ -345,7 +345,7 @@ export class HttpResourceAccessor implements ResourceAccessor { return Collections.emptyList(); } let containedInstances: Array = new Array<>(); - for (let containerTriple: Triple : containerTriples) { + for (const containerTriple of containerTriples) { let containedInstance: ManageableInstance = this.getInstance(context, new URL(containerTriple.getObject().getURI())); containedInstances.add(containedInstance); } diff --git a/asTypescript/packages/client-http/src/HttpShapeTreeClient.ts b/asTypescript/packages/client-http/src/HttpShapeTreeClient.ts index 270c7899..ced8a4c7 100644 --- a/asTypescript/packages/client-http/src/HttpShapeTreeClient.ts +++ b/asTypescript/packages/client-http/src/HttpShapeTreeClient.ts @@ -233,12 +233,12 @@ export class HttpShapeTreeClient implements ShapeTreeClient { ret.maybeSet(HttpHeaders.LINK.getValue(), "<" + resourceTypeUrl + ">; rel=\"type\""); } if (focusNodes != null && !focusNodes.isEmpty()) { - for (let focusNode: URL : focusNodes) { + for (const focusNode of focusNodes) { ret.maybeSet(HttpHeaders.LINK.getValue(), "<" + focusNode + ">; rel=\"" + LinkRelations.FOCUS_NODE.getValue() + "\""); } } if (targetShapeTrees != null && !targetShapeTrees.isEmpty()) { - for (let targetShapeTree: URL : targetShapeTrees) { + for (const targetShapeTree of targetShapeTrees) { ret.maybeSet(HttpHeaders.LINK.getValue(), "<" + targetShapeTree + ">; rel=\"" + LinkRelations.TARGET_SHAPETREE.getValue() + "\""); } } diff --git a/asTypescript/packages/core/src/ResourceAttributes.ts b/asTypescript/packages/core/src/ResourceAttributes.ts index 94b31039..df2e213c 100644 --- a/asTypescript/packages/core/src/ResourceAttributes.ts +++ b/asTypescript/packages/core/src/ResourceAttributes.ts @@ -43,7 +43,7 @@ export class ResourceAttributes { // copy constructor private copy(): ResourceAttributes { let ret: ResourceAttributes = new ResourceAttributes(); - for (let entry: Map.Entry> : this.myMapOfLists.entrySet()) { + for (const entry of this.myMapOfLists.entrySet()) { ret.myMapOfLists.put(entry.getKey(), new Array<>(entry.getValue())); } return ret; @@ -57,7 +57,7 @@ export class ResourceAttributes { */ public static parseLinkHeaders(headerValues: Array): ResourceAttributes { let linkHeaderMap: ResourceAttributes = new ResourceAttributes(); - for (let headerValue: string : headerValues) { + for (const headerValue of headerValues) { let matcher: Matcher = LINK_HEADER_PATTERN.matcher(headerValue); // if (matcher.matches() && matcher.groupCount() >= 2) { if (matcher.matches()) { @@ -136,10 +136,10 @@ export class ResourceAttributes { */ public toList(...exclusions: string): string[] { let ret: Array = new Array<>(); - for (let entry: Map.Entry> : this.myMapOfLists.entrySet()) { + for (const entry of this.myMapOfLists.entrySet()) { let attr: string = entry.getKey(); if (!Arrays.stream(exclusions).anyMatch(s -> s === attr)) { - for (let value: string : entry.getValue()) { + for (const value of entry.getValue()) { ret.add(attr); ret.add(value); } @@ -179,8 +179,8 @@ export class ResourceAttributes { public toString(): string { let sb: StringBuilder = new StringBuilder(); - for (let entry: Map.Entry> : this.myMapOfLists.entrySet()) { - for (let value: string : entry.getValue()) { + for (const entry of this.myMapOfLists.entrySet()) { + for (const value of entry.getValue()) { if (sb.length() != 0) { sb.append(","); } diff --git a/asTypescript/packages/core/src/ShapeTree.ts b/asTypescript/packages/core/src/ShapeTree.ts index bbbe1369..5c46f7a7 100644 --- a/asTypescript/packages/core/src/ShapeTree.ts +++ b/asTypescript/packages/core/src/ShapeTree.ts @@ -123,7 +123,7 @@ export class ShapeTree { let shapeLabel: Label = new Label(GlobalFactory.RDFFactory.createIRI(this.shape.toString())); if (!focusNodeUrls.isEmpty()) { // One or more focus nodes were provided for validation - for (let focusNodeUrl: URL : focusNodeUrls) { + for (const focusNodeUrl of focusNodeUrls) { // Evaluate each provided focus node let focusNode: IRI = GlobalFactory.RDFFactory.createIRI(focusNodeUrl.toString()); log.debug("Validating Shape Label = {}, Focus Node = {}", shapeLabel.toPrettyString(), focusNode.getIRIString()); @@ -138,7 +138,7 @@ export class ShapeTree { } else { // No focus nodes were provided for validation, so all subject nodes will be evaluated let evaluateNodes: Array = GraphUtil.listSubjects(graph, Node.ANY, Node.ANY).toList(); - for (let evaluateNode: Node : evaluateNodes) { + for (const evaluateNode of evaluateNodes) { const focusUriString: string = evaluateNode.getURI(); let node: IRI = GlobalFactory.RDFFactory.createIRI(focusUriString); validation.validate(node, shapeLabel); @@ -182,7 +182,7 @@ export class ShapeTree { // If one or more target shape trees have been supplied if (!targetShapeTreeUrls.isEmpty()) { // Test each supplied target shape tree - for (let targetShapeTreeUrl: URL : targetShapeTreeUrls) { + for (const targetShapeTreeUrl of targetShapeTreeUrls) { // Check if it exists in st:contains if (this.contains.contains(targetShapeTreeUrl)) { let targetShapeTree: ShapeTree = ShapeTreeFactory.getShapeTree(targetShapeTreeUrl); @@ -198,7 +198,7 @@ export class ShapeTree { return new ValidationResult(false, null, "Failed to validate " + targetShapeTreeUrls); } else { // For each shape tree in st:contains - for (let containsShapeTreeUrl: URL : getPrioritizedContains()) { + for (const containsShapeTreeUrl of getPrioritizedContains()) { let containsShapeTree: ShapeTree = ShapeTreeFactory.getShapeTree(containsShapeTreeUrl); // Continue if the shape tree isn't gettable if (containsShapeTree === null) { @@ -259,7 +259,7 @@ export class ShapeTree { } private getReferencedShapeTreesListDepthFirst(currentReferencedShapeTrees: Array, referencedShapeTrees: Array): Array /* throws ShapeTreeException */ { - for (let currentShapeTreeReference: ShapeTreeReference : currentReferencedShapeTrees) { + for (const currentShapeTreeReference of currentReferencedShapeTrees) { referencedShapeTrees.add(currentShapeTreeReference); let currentReferencedShapeTree: ShapeTree = ShapeTreeFactory.getShapeTree(currentShapeTreeReference.getReferenceUrl()); if (currentReferencedShapeTree != null) { diff --git a/asTypescript/packages/core/src/ShapeTreeAssignment.ts b/asTypescript/packages/core/src/ShapeTreeAssignment.ts index dd554b1b..3f029db0 100644 --- a/asTypescript/packages/core/src/ShapeTreeAssignment.ts +++ b/asTypescript/packages/core/src/ShapeTreeAssignment.ts @@ -71,7 +71,7 @@ export class ShapeTreeAssignment { throw new IllegalStateException("Incomplete shape tree assignment, Only " + assignmentTriples.size() + " attributes found"); } // Lookup and assign each triple in the nested ShapeTreeAssignment - for (let assignmentTriple: Triple : assignmentTriples) { + for (const assignmentTriple of assignmentTriples) { switch(assignmentTriple.getPredicate().getURI()) { case RdfVocabulary.TYPE: if (!assignmentTriple.getObject().isURI() || !assignmentTriple.getObject().getURI() === ShapeTreeVocabulary.SHAPETREE_ASSIGNMENT) { diff --git a/asTypescript/packages/core/src/ShapeTreeFactory.ts b/asTypescript/packages/core/src/ShapeTreeFactory.ts index 548311b6..c5e654d3 100644 --- a/asTypescript/packages/core/src/ShapeTreeFactory.ts +++ b/asTypescript/packages/core/src/ShapeTreeFactory.ts @@ -67,11 +67,11 @@ export class ShapeTreeFactory { let shapeTree: ShapeTree = new ShapeTree(shapeTreeUrl, expectsType, label, shape, references, contains); localShapeTreeCache.put(urlToUri(shapeTreeUrl), shapeTree); // Recursively parse contained shape trees - for (let containedUrl: URL : contains) { + for (const containedUrl of contains) { getShapeTree(containedUrl); } // Recursively parse referenced shape trees - for (let reference: ShapeTreeReference : references) { + for (const reference of references) { getShapeTree(reference.getReferenceUrl()); } return shapeTree; @@ -106,7 +106,7 @@ export class ShapeTreeFactory { let referencesProperty: Property = resourceModel.createProperty(ShapeTreeVocabulary.REFERENCES); if (shapeTreeNode.hasProperty(referencesProperty)) { let referenceStatements: Array = shapeTreeNode.listProperties(referencesProperty).toList(); - for (let referenceStatement: Statement : referenceStatements) { + for (const referenceStatement of referenceStatements) { let referenceResource: Resource = referenceStatement.getObject().asResource(); const referencedShapeTreeUrlString: string = getStringValue(resourceModel, referenceResource, ShapeTreeVocabulary.REFERENCES_SHAPE_TREE); const referencedShapeTreeUrl: URL; @@ -189,7 +189,7 @@ export class ShapeTreeFactory { let property: Property = model.createProperty(predicate); if (resource.hasProperty(property)) { let propertyStatements: Array = resource.listProperties(property).toList(); - for (let propertyStatement: Statement : propertyStatements) { + for (const propertyStatement of propertyStatements) { let propertyNode: Node = propertyStatement.getObject().asNode(); if (propertyNode instanceof Node_URI) { let contentUrl: URL = new URL(propertyNode.getURI()); diff --git a/asTypescript/packages/core/src/ShapeTreeManager.ts b/asTypescript/packages/core/src/ShapeTreeManager.ts index f186bba5..9841127c 100644 --- a/asTypescript/packages/core/src/ShapeTreeManager.ts +++ b/asTypescript/packages/core/src/ShapeTreeManager.ts @@ -57,7 +57,7 @@ export class ShapeTreeManager { // <> a st:Manager managerGraph.add(GraphHelper.newTriple(managerSubject, RDF.type.toString(), GraphHelper.knownUrl(ShapeTreeVocabulary.SHAPETREE_MANAGER))); // For each assignment create a blank node and populate - for (let assignment: ShapeTreeAssignment : this.assignments) { + for (const assignment of this.assignments) { // <> st:hasAssignment , managerGraph.add(GraphHelper.newTriple(managerSubject, ShapeTreeVocabulary.HAS_ASSIGNMENT, assignment.getUrl())); const subject: URI = urlToUri(assignment.getUrl()); @@ -84,7 +84,7 @@ export class ShapeTreeManager { throw new ShapeTreeException(500, "Must provide a non-null assignment to an initialized List of assignments"); } if (!this.assignments.isEmpty()) { - for (let existingAssignment: ShapeTreeAssignment : this.assignments) { + for (const existingAssignment of this.assignments) { if (existingAssignment === assignment) { throw new ShapeTreeException(422, "Identical shape tree assignment cannot be added to Shape Tree Manager: " + this.id); } @@ -116,7 +116,7 @@ export class ShapeTreeManager { * @return Minted URL for a new shape tree assignment */ public mintAssignmentUrl(proposedAssignmentUrl: URL): URL { - for (let assignment: ShapeTreeAssignment : this.assignments) { + for (const assignment of this.assignments) { if (assignment.getUrl() === proposedAssignmentUrl) { // If we somehow managed to randomly generate a location URL that already exists, generate another return mintAssignmentUrl(); @@ -127,7 +127,7 @@ export class ShapeTreeManager { public getContainingAssignments(): Array /* throws ShapeTreeException */ { let containingAssignments: Array = new Array<>(); - for (let assignment: ShapeTreeAssignment : this.assignments) { + for (const assignment of this.assignments) { let shapeTree: ShapeTree = ShapeTreeFactory.getShapeTree(assignment.getShapeTree()); if (!shapeTree.getContains().isEmpty()) { containingAssignments.add(assignment); @@ -159,7 +159,7 @@ export class ShapeTreeManager { const stAssignment: Node = NodeFactory.createURI(ShapeTreeVocabulary.HAS_ASSIGNMENT); let assignmentNodes: Array = managerGraph.find(s, stAssignment, Node.ANY).toList(); // For each st:hasAssignment node, extract a new ShapeTreeAssignment - for (let assignmentNode: Triple : assignmentNodes) { + for (const assignmentNode of assignmentNodes) { let assignment: ShapeTreeAssignment = null; try { assignment = ShapeTreeAssignment.getFromGraph(new URL(assignmentNode.getObject().getURI()), managerGraph); @@ -175,7 +175,7 @@ export class ShapeTreeManager { if (this.assignments.isEmpty()) { return null; } - for (let assignment: ShapeTreeAssignment : this.assignments) { + for (const assignment of this.assignments) { if (assignment.getShapeTree() === shapeTreeUrl) { return assignment; } @@ -188,7 +188,7 @@ export class ShapeTreeManager { if (this.getAssignments() === null || this.getAssignments().isEmpty()) { return null; } - for (let assignment: ShapeTreeAssignment : this.getAssignments()) { + for (const assignment of this.getAssignments()) { if (rootAssignment.getUrl() === assignment.getRootAssignment()) { return assignment; } diff --git a/asTypescript/packages/core/src/ShapeTreeManagerDelta.ts b/asTypescript/packages/core/src/ShapeTreeManagerDelta.ts index 27615147..74537ef3 100644 --- a/asTypescript/packages/core/src/ShapeTreeManagerDelta.ts +++ b/asTypescript/packages/core/src/ShapeTreeManagerDelta.ts @@ -43,7 +43,7 @@ export class ShapeTreeManagerDelta { delta.updatedAssignments = updatedManager.getAssignments(); return delta; } - for (let existingAssignment: ShapeTreeAssignment : existingManager.getAssignments()) { + for (const existingAssignment of existingManager.getAssignments()) { // Assignments match, and are unchanged, so continue if (updatedManager.getAssignments().contains(existingAssignment)) { continue; @@ -57,7 +57,7 @@ export class ShapeTreeManagerDelta { // existing assignment isn't in the updated assignment, so remove delta.removedAssignments.add(existingAssignment); } - for (let updatedAssignment: ShapeTreeAssignment : updatedManager.getAssignments()) { + for (const updatedAssignment of updatedManager.getAssignments()) { // Assignments match, and are unchanged, so continue if (existingManager.getAssignments().contains(updatedAssignment)) { continue; @@ -73,7 +73,7 @@ export class ShapeTreeManagerDelta { } public static containsSameUrl(assignment: ShapeTreeAssignment, targetAssignments: Array): ShapeTreeAssignment /* throws ShapeTreeException */ { - for (let targetAssignment: ShapeTreeAssignment : targetAssignments) { + for (const targetAssignment of targetAssignments) { let assignmentUri: URI; let targetAssignmentUri: URI; try { diff --git a/asTypescript/packages/core/src/ShapeTreeRequestHandler.ts b/asTypescript/packages/core/src/ShapeTreeRequestHandler.ts index a4fcc9c9..bfb62b39 100644 --- a/asTypescript/packages/core/src/ShapeTreeRequestHandler.ts +++ b/asTypescript/packages/core/src/ShapeTreeRequestHandler.ts @@ -75,7 +75,7 @@ export class ShapeTreeRequestHandler { // Cannot directly update assignments that are not root locations ensureUpdatedAssignmentIsRoot(delta); // Run recursive assignment for each updated assignment in the root manager - for (let rootAssignment: ShapeTreeAssignment : delta.getUpdatedAssignments()) { + for (const rootAssignment of delta.getUpdatedAssignments()) { let validationResponse: DocumentResponse | null = assignShapeTreeToResource(manageableInstance, shapeTreeContext, updatedRootManager, rootAssignment, rootAssignment, null); if (validationResponse.isPresent()) { return validationResponse; @@ -88,7 +88,7 @@ export class ShapeTreeRequestHandler { // Cannot unplant a non-root location ensureRemovedAssignmentsAreRoot(delta); // Run recursive unassignment for each removed assignment in the updated root manager - for (let rootAssignment: ShapeTreeAssignment : delta.getRemovedAssignments()) { + for (const rootAssignment of delta.getRemovedAssignments()) { let validationResponse: DocumentResponse | null = unassignShapeTreeFromResource(manageableInstance, shapeTreeContext, rootAssignment); if (validationResponse.isPresent()) { return validationResponse; @@ -118,7 +118,7 @@ export class ShapeTreeRequestHandler { let incomingFocusNodes: Array = RequestHelper.getIncomingFocusNodes(shapeTreeRequest, targetResourceUrl); let incomingBodyGraph: Graph = RequestHelper.getIncomingBodyGraph(shapeTreeRequest, targetResourceUrl, null); let validationResults: Map = new Map<>(); - for (let containingAssignment: ShapeTreeAssignment : containingAssignments) { + for (const containingAssignment of containingAssignments) { let containerShapeTreeUrl: URL = containingAssignment.getShapeTree(); let containerShapeTree: ShapeTree = ShapeTreeFactory.getShapeTree(containerShapeTreeUrl); let validationResult: ValidationResult = containerShapeTree.validateContainedResource(proposedName, shapeTreeRequest.getResourceType(), targetShapeTrees, incomingBodyGraph, incomingFocusNodes); @@ -134,7 +134,7 @@ export class ShapeTreeRequestHandler { } log.debug("Creating shape tree instance at {}", targetResourceUrl); let createdInstance: ManageableInstance = this.resourceAccessor.createInstance(manageableInstance.getShapeTreeContext(), shapeTreeRequest.getMethod(), targetResourceUrl, shapeTreeRequest.getHeaders(), shapeTreeRequest.getBody(), shapeTreeRequest.getContentType()); - for (let containingAssignment: ShapeTreeAssignment : containingAssignments) { + for (const containingAssignment of containingAssignments) { let rootShapeTreeAssignment: ShapeTreeAssignment = getRootAssignment(manageableInstance.getShapeTreeContext(), containingAssignment); ensureAssignmentExists(rootShapeTreeAssignment, "Unable to find root shape tree assignment at " + containingAssignment.getRootAssignment()); log.debug("Assigning shape tree to created resource: {}", createdInstance.getManagerResource().getUrl()); @@ -153,7 +153,7 @@ export class ShapeTreeRequestHandler { ensureInstanceResourceExists(targetResource.getManagerResource(), "Should not be updating an unmanaged resource as a shape tree instance"); let manager: ShapeTreeManager = targetResource.getManagerResource().getManager(); ensureShapeTreeManagerExists(manager, "Cannot have a shape tree manager resource without a shape tree manager with at least one shape tree assignment"); - for (let assignment: ShapeTreeAssignment : manager.getAssignments()) { + for (const assignment of manager.getAssignments()) { // Evaluate the update against each ShapeTreeAssignment managing the resource. // All must pass for the update to validate let shapeTree: ShapeTree = ShapeTreeFactory.getShapeTree(assignment.getShapeTree()); @@ -224,7 +224,7 @@ export class ShapeTreeRequestHandler { if (!containedResources.isEmpty()) { // Evaluate containers, then resources Collections.sort(containedResources, new ResourceTypeAssignmentPriority()); - for (let containedResource: ManageableInstance : containedResources) { + for (const containedResource of containedResources) { validationResponse = assignShapeTreeToResource(containedResource, shapeTreeContext, null, rootAssignment, managingAssignment, null); if (validationResponse.isPresent()) { return validationResponse; @@ -261,7 +261,7 @@ export class ShapeTreeRequestHandler { // Sort contained resources so that containers are evaluated first, then resources Collections.sort(containedResources, new ResourceTypeAssignmentPriority()); // Perform a depth first unassignment for each contained resource - for (let containedResource: ManageableInstance : containedResources) { + for (const containedResource of containedResources) { // Recursively call this function on the contained resource validationResponse = unassignShapeTreeFromResource(containedResource, shapeTreeContext, rootAssignment); if (validationResponse.isPresent()) { @@ -332,7 +332,7 @@ export class ShapeTreeRequestHandler { // Return a root shape tree manager associated with a given shape tree assignment private getRootAssignment(shapeTreeContext: ShapeTreeContext, assignment: ShapeTreeAssignment): ShapeTreeAssignment /* throws ShapeTreeException */ { let rootManager: ShapeTreeManager = getRootManager(shapeTreeContext, assignment); - for (let rootAssignment: ShapeTreeAssignment : rootManager.getAssignments()) { + for (const rootAssignment of rootManager.getAssignments()) { if (rootAssignment.getUrl() != null && rootAssignment.getUrl() === assignment.getRootAssignment()) { return rootAssignment; } @@ -342,10 +342,10 @@ export class ShapeTreeRequestHandler { private getUnmatchedFocusNodes(validationResults: Collection, focusNodes: Array): Array { let unmatchedNodes: Array = new Array<>(); - for (let focusNode: URL : focusNodes) { + for (const focusNode of focusNodes) { // Determine if each target focus node was matched let matched: boolean = false; - for (let validationResult: ValidationResult : validationResults) { + for (const validationResult of validationResults) { if (validationResult.getMatchingShapeTree().getShape() != null) { if (validationResult.getMatchingFocusNode() === focusNode) { matched = true; @@ -404,7 +404,7 @@ export class ShapeTreeRequestHandler { } private ensureRemovedAssignmentsAreRoot(delta: ShapeTreeManagerDelta): void /* throws ShapeTreeException */ { - for (let assignment: ShapeTreeAssignment : delta.getRemovedAssignments()) { + for (const assignment of delta.getRemovedAssignments()) { if (!assignment.isRootAssignment()) { throw new ShapeTreeException(500, "Cannot remove non-root assignment: " + assignment.getUrl().toString() + ". Must unplant root assignment at: " + assignment.getRootAssignment().toString()); } @@ -412,7 +412,7 @@ export class ShapeTreeRequestHandler { } private ensureUpdatedAssignmentIsRoot(delta: ShapeTreeManagerDelta): void /* throws ShapeTreeException */ { - for (let updatedAssignment: ShapeTreeAssignment : delta.getUpdatedAssignments()) { + for (const updatedAssignment of delta.getUpdatedAssignments()) { if (!updatedAssignment.isRootAssignment()) { throw new ShapeTreeException(500, "Cannot update non-root assignment: " + updatedAssignment.getUrl().toString() + ". Must update root assignment at: " + updatedAssignment.getRootAssignment().toString()); } diff --git a/asTypescript/packages/core/src/helpers/RequestHelper.ts b/asTypescript/packages/core/src/helpers/RequestHelper.ts index 87ba9c82..90ab0316 100644 --- a/asTypescript/packages/core/src/helpers/RequestHelper.ts +++ b/asTypescript/packages/core/src/helpers/RequestHelper.ts @@ -93,7 +93,7 @@ export class RequestHelper { const focusNodeStrings: Array = shapeTreeRequest.getLinkHeaders().allValues(LinkRelations.FOCUS_NODE.getValue()); const focusNodeUrls: Array = new Array<>(); if (!focusNodeStrings.isEmpty()) { - for (let focusNodeUrlString: string : focusNodeStrings) { + for (const focusNodeUrlString of focusNodeStrings) { try { const focusNodeUrl: URL = new URL(baseUrl, focusNodeUrlString); focusNodeUrls.add(focusNodeUrl); @@ -115,7 +115,7 @@ export class RequestHelper { const targetShapeTreeStrings: Array = shapeTreeRequest.getLinkHeaders().allValues(LinkRelations.TARGET_SHAPETREE.getValue()); const targetShapeTreeUrls: Array = new Array<>(); if (!targetShapeTreeStrings.isEmpty()) { - for (let targetShapeTreeUrlString: string : targetShapeTreeStrings) { + for (const targetShapeTreeUrlString of targetShapeTreeStrings) { try { const targetShapeTreeUrl: URL = new URL(targetShapeTreeUrlString); targetShapeTreeUrls.add(targetShapeTreeUrl); diff --git a/asTypescript/packages/javahttp/src/JavaHttpValidatingShapeTreeInterceptor.ts b/asTypescript/packages/javahttp/src/JavaHttpValidatingShapeTreeInterceptor.ts index 233d240f..e2b52a5d 100644 --- a/asTypescript/packages/javahttp/src/JavaHttpValidatingShapeTreeInterceptor.ts +++ b/asTypescript/packages/javahttp/src/JavaHttpValidatingShapeTreeInterceptor.ts @@ -103,7 +103,7 @@ export class JavaHttpValidatingShapeTreeInterceptor { this.contentType = contentType; let tm: TreeMap> = new TreeMap<>(String.CASE_INSENSITIVE_ORDER); let headerMap: Map> = this.request.headers().map(); - for (let entry: Map.Entry> : headerMap.entrySet()) { + for (const entry of headerMap.entrySet()) { tm.put(entry.getKey(), entry.getValue()); } this.headers = new ResourceAttributes(tm); From 58ab9c56674c2967b264ab8b3b18336f06c10d66 Mon Sep 17 00:00:00 2001 From: Eric Prud'hommeaux Date: Tue, 7 Dec 2021 23:43:02 +0100 Subject: [PATCH 09/15] ~ javatots rewrite catch --- .../client-http/src/HttpResourceAccessor.ts | 28 ++++++++------- .../packages/core/src/ManageableResource.ts | 7 ++-- .../packages/core/src/ResourceAttributes.ts | 2 +- asTypescript/packages/core/src/ShapeTree.ts | 14 ++++---- .../packages/core/src/ShapeTreeAssignment.ts | 9 ++--- .../packages/core/src/ShapeTreeFactory.ts | 21 +++++++----- .../packages/core/src/ShapeTreeManager.ts | 14 ++++---- .../core/src/ShapeTreeManagerDelta.ts | 7 ++-- .../packages/core/src/ShapeTreeReference.ts | 2 +- .../HttpExternalDocumentLoader.ts | 17 +++++----- .../packages/core/src/helpers/GraphHelper.ts | 28 ++++++++------- .../core/src/helpers/RequestHelper.ts | 21 +++++++----- .../packages/javahttp/src/JavaHttpClient.ts | 34 +++++++++++-------- .../javahttp/src/JavaHttpClientFactory.ts | 7 ++-- .../JavaHttpValidatingShapeTreeInterceptor.ts | 22 ++++++------ 15 files changed, 131 insertions(+), 102 deletions(-) diff --git a/asTypescript/packages/client-http/src/HttpResourceAccessor.ts b/asTypescript/packages/client-http/src/HttpResourceAccessor.ts index dfb9f4c6..6135bec9 100644 --- a/asTypescript/packages/client-http/src/HttpResourceAccessor.ts +++ b/asTypescript/packages/client-http/src/HttpResourceAccessor.ts @@ -279,9 +279,10 @@ export class HttpResourceAccessor implements ResourceAccessor { if (location.isPresent()) { try { url = new URL(location.get()); - } catch (e: MalformedURLException) { - throw new ShapeTreeException(500, "Retrieving <" + url + "> yielded a Location header \"" + location.get() + "\" which doesn't parse as a URL: " + e.getMessage()); - } + } catch (ex) { + if (ex instanceof MalformedURLException) { + throw new ShapeTreeException(500, "Retrieving <" + url + "> yielded a Location header \"" + location.get() + "\" which doesn't parse as a URL: " + e.getMessage()); + } } // Determine whether the resource exists based on the response. Even if the resource // doesn't exist, additional context and processing is done to provide the appropriate @@ -350,9 +351,10 @@ export class HttpResourceAccessor implements ResourceAccessor { containedInstances.add(containedInstance); } return containedInstances; - } catch (ex: Exception) { - throw new ShapeTreeException(500, ex.getMessage()); - } + } catch (ex) { + if (ex instanceof Exception) { + throw new ShapeTreeException(500, ex.getMessage()); + } } /** @@ -451,9 +453,10 @@ export class HttpResourceAccessor implements ResourceAccessor { let managerUrlString: string = optManagerString.get(); try { return Optional.of(new URL(url, managerUrlString)); - } catch (e: MalformedURLException) { - throw new ShapeTreeException(500, "Malformed relative URL <" + managerUrlString + "> (resolved from <" + url + ">)"); - } + } catch (ex) { + if (ex instanceof MalformedURLException) { + throw new ShapeTreeException(500, "Malformed relative URL <" + managerUrlString + "> (resolved from <" + url + ">)"); + } } /** @@ -479,9 +482,10 @@ export class HttpResourceAccessor implements ResourceAccessor { } try { managedResourceUrl = new URL(managerUrl, managedUrlString); - } catch (e: MalformedURLException) { - throw new ShapeTreeException(500, "Can't calculate managed resource for shape tree manager <" + managerUrl + ">"); - } + } catch (ex) { + if (ex instanceof MalformedURLException) { + throw new ShapeTreeException(500, "Can't calculate managed resource for shape tree manager <" + managerUrl + ">"); + } return managedResourceUrl; } diff --git a/asTypescript/packages/core/src/ManageableResource.ts b/asTypescript/packages/core/src/ManageableResource.ts index 1d74443d..d87aba13 100644 --- a/asTypescript/packages/core/src/ManageableResource.ts +++ b/asTypescript/packages/core/src/ManageableResource.ts @@ -44,9 +44,10 @@ export class ManageableResource extends InstanceResource { const rel: string = this.isContainer() ? ".." : "."; try { return new URL(this.getUrl(), rel); - } catch (e: MalformedURLException) { - throw new ShapeTreeException(500, "Malformed focus node when resolving <" + rel + "> against <" + this.getUrl() + ">"); - } + } catch (ex) { + if (ex instanceof MalformedURLException) { + throw new ShapeTreeException(500, "Malformed focus node when resolving <" + rel + "> against <" + this.getUrl() + ">"); + } } public getManagerResourceUrl(): URL | null { diff --git a/asTypescript/packages/core/src/ResourceAttributes.ts b/asTypescript/packages/core/src/ResourceAttributes.ts index df2e213c..7f6c85c1 100644 --- a/asTypescript/packages/core/src/ResourceAttributes.ts +++ b/asTypescript/packages/core/src/ResourceAttributes.ts @@ -27,7 +27,7 @@ export class ResourceAttributes { * @param attr attribute (header) name to set * @param value String value to assign to attr */ - public constructor(attr: string, value: string) throws ShapeTreeException { + public constructor(attr: string, value: string) /* throws ShapeTreeException */ { this.myMapOfLists = new TreeMap<>(String.CASE_INSENSITIVE_ORDER); this.maybeSet(attr, value); } diff --git a/asTypescript/packages/core/src/ShapeTree.ts b/asTypescript/packages/core/src/ShapeTree.ts index 5c46f7a7..8d8ddc1e 100644 --- a/asTypescript/packages/core/src/ShapeTree.ts +++ b/asTypescript/packages/core/src/ShapeTree.ts @@ -112,9 +112,10 @@ export class ShapeTree { if (SchemaCache.isInitialized()) { SchemaCache.putSchema(this.shape, schema); } - } catch (ex: Exception) { - throw new ShapeTreeException(500, "Error parsing ShEx schema - " + ex.getMessage()); - } + } catch (ex) { + if (ex instanceof Exception) { + throw new ShapeTreeException(500, "Error parsing ShEx schema - " + ex.getMessage()); + } } // Tell ShExJava we want to use Jena as our graph library let jenaRDF: JenaRDF = new org.apache.commons.rdf.jena.JenaRDF(); @@ -147,9 +148,10 @@ export class ShapeTree { const matchingFocusNode: URL; try { matchingFocusNode = new URL(focusUriString); - } catch (ex: MalformedURLException) { - throw new ShapeTreeException(500, "Error reporting validation success on malformed URL <" + focusUriString + ">: " + ex.getMessage()); - } + } catch (ex) { + if (ex instanceof MalformedURLException) { + throw new ShapeTreeException(500, "Error reporting validation success on malformed URL <" + focusUriString + ">: " + ex.getMessage()); + } return new ValidationResult(valid, this, this, matchingFocusNode); } } diff --git a/asTypescript/packages/core/src/ShapeTreeAssignment.ts b/asTypescript/packages/core/src/ShapeTreeAssignment.ts index 3f029db0..f7b47f23 100644 --- a/asTypescript/packages/core/src/ShapeTreeAssignment.ts +++ b/asTypescript/packages/core/src/ShapeTreeAssignment.ts @@ -37,7 +37,7 @@ export class ShapeTreeAssignment { private readonly url: URL; - public constructor(shapeTree: URL, managedResource: URL, rootAssignment: URL, focusNode: URL, shape: URL, url: URL) throws ShapeTreeException { + public constructor(shapeTree: URL, managedResource: URL, rootAssignment: URL, focusNode: URL, shape: URL, url: URL) /* throws ShapeTreeException */ { try { this.shapeTree = Objects.requireNonNull(shapeTree, "Must provide an assigned shape tree"); this.managedResource = Objects.requireNonNull(managedResource, "Must provide a shape tree context"); @@ -53,9 +53,10 @@ export class ShapeTreeAssignment { } this.focusNode = null; } - } catch (ex: NullPointerException | IllegalStateException) { - throw new ShapeTreeException(500, "Failed to initialize shape tree assignment: " + ex.getMessage()); - } + } catch (ex) { + if (ex instanceof NullPointerException || ex instanceof IllegalStateException) { + throw new ShapeTreeException(500, "Failed to initialize shape tree assignment: " + ex.getMessage()); + } } public static getFromGraph(url: URL, managerGraph: Graph): ShapeTreeAssignment /* throws MalformedURLException, ShapeTreeException */ { diff --git a/asTypescript/packages/core/src/ShapeTreeFactory.ts b/asTypescript/packages/core/src/ShapeTreeFactory.ts index c5e654d3..a74fe58a 100644 --- a/asTypescript/packages/core/src/ShapeTreeFactory.ts +++ b/asTypescript/packages/core/src/ShapeTreeFactory.ts @@ -88,9 +88,10 @@ export class ShapeTreeFactory { private static getContains(resourceModel: Model, shapeTreeNode: Resource, shapeTreeUrl: URL): Array /* throws ShapeTreeException */ { try { return getURLListValue(resourceModel, shapeTreeNode, ShapeTreeVocabulary.CONTAINS); - } catch (ex: MalformedURLException | ShapeTreeException) { - throw new ShapeTreeException(500, "List <" + shapeTreeUrl + "> contains malformed URL: " + ex.getMessage()); - } + } catch (ex) { + if (ex instanceof MalformedURLException || ex instanceof ShapeTreeException) { + throw new ShapeTreeException(500, "List <" + shapeTreeUrl + "> contains malformed URL: " + ex.getMessage()); + } } /** @@ -113,9 +114,10 @@ export class ShapeTreeFactory { let referencedShapeTree: ShapeTreeReference; try { referencedShapeTreeUrl = new URL(referencedShapeTreeUrlString); - } catch (ex: MalformedURLException) { - throw new ShapeTreeException(500, "ShapeTree <" + shapeTreeUrl + "> references malformed URL <" + referencedShapeTreeUrlString + ">: " + ex.getMessage()); - } + } catch (ex) { + if (ex instanceof MalformedURLException) { + throw new ShapeTreeException(500, "ShapeTree <" + shapeTreeUrl + "> references malformed URL <" + referencedShapeTreeUrlString + ">: " + ex.getMessage()); + } let viaShapePath: string = getStringValue(resourceModel, referenceResource, ShapeTreeVocabulary.VIA_SHAPE_PATH); let viaPredicate: URL = getUrlValue(resourceModel, referenceResource, ShapeTreeVocabulary.VIA_PREDICATE, shapeTreeUrl); referencedShapeTree = new ShapeTreeReference(referencedShapeTreeUrl, viaShapePath, viaPredicate); @@ -142,9 +144,10 @@ export class ShapeTreeFactory { if (object.isURIResource()) { try { return new URL(object.asResource().getURI()); - } catch (ex: MalformedURLException) { - throw new IllegalStateException("Malformed ShapeTree <" + shapeTreeUrl + ">: Jena URIResource <" + object + "> didn't parse as URL - " + ex.getMessage()); - } + } catch (ex) { + if (ex instanceof MalformedURLException) { + throw new IllegalStateException("Malformed ShapeTree <" + shapeTreeUrl + ">: Jena URIResource <" + object + "> didn't parse as URL - " + ex.getMessage()); + } } else { throw new ShapeTreeException(500, "Malformed ShapeTree <" + shapeTreeUrl + ">: expected " + object + " to be a URL"); } diff --git a/asTypescript/packages/core/src/ShapeTreeManager.ts b/asTypescript/packages/core/src/ShapeTreeManager.ts index 9841127c..3d9e64f0 100644 --- a/asTypescript/packages/core/src/ShapeTreeManager.ts +++ b/asTypescript/packages/core/src/ShapeTreeManager.ts @@ -103,9 +103,10 @@ export class ShapeTreeManager { const assignmentUrl: URL; try { assignmentUrl = new URL(assignmentString); - } catch (ex: MalformedURLException) { - throw new IllegalStateException("Minted illegal URL <" + assignmentString + "> - " + ex.getMessage()); - } + } catch (ex) { + if (ex instanceof MalformedURLException) { + throw new IllegalStateException("Minted illegal URL <" + assignmentString + "> - " + ex.getMessage()); + } return assignmentUrl; } @@ -163,9 +164,10 @@ export class ShapeTreeManager { let assignment: ShapeTreeAssignment = null; try { assignment = ShapeTreeAssignment.getFromGraph(new URL(assignmentNode.getObject().getURI()), managerGraph); - } catch (e: MalformedURLException) { - throw new ShapeTreeException(500, "Object of { " + s + " " + stAssignment + " " + assignmentNode.getObject() + " } must be a URL."); - } + } catch (ex) { + if (ex instanceof MalformedURLException) { + throw new ShapeTreeException(500, "Object of { " + s + " " + stAssignment + " " + assignmentNode.getObject() + " } must be a URL."); + } manager.assignments.add(assignment); } return manager; diff --git a/asTypescript/packages/core/src/ShapeTreeManagerDelta.ts b/asTypescript/packages/core/src/ShapeTreeManagerDelta.ts index 74537ef3..3cc7643f 100644 --- a/asTypescript/packages/core/src/ShapeTreeManagerDelta.ts +++ b/asTypescript/packages/core/src/ShapeTreeManagerDelta.ts @@ -79,9 +79,10 @@ export class ShapeTreeManagerDelta { try { assignmentUri = assignment.getUrl().toURI(); targetAssignmentUri = targetAssignment.getUrl().toURI(); - } catch (ex: URISyntaxException) { - throw new ShapeTreeException(500, "Unable to convert assignment URLs for comparison: " + ex.getMessage()); - } + } catch (ex) { + if (ex instanceof URISyntaxException) { + throw new ShapeTreeException(500, "Unable to convert assignment URLs for comparison: " + ex.getMessage()); + } if (assignmentUri === targetAssignmentUri) { return targetAssignment; } diff --git a/asTypescript/packages/core/src/ShapeTreeReference.ts b/asTypescript/packages/core/src/ShapeTreeReference.ts index e20c062c..2cb562c4 100644 --- a/asTypescript/packages/core/src/ShapeTreeReference.ts +++ b/asTypescript/packages/core/src/ShapeTreeReference.ts @@ -10,7 +10,7 @@ export class ShapeTreeReference { readonly predicate: URL; - public constructor(referenceUrl: URL, shapePath: string, predicate: URL) throws ShapeTreeException { + public constructor(referenceUrl: URL, shapePath: string, predicate: URL) /* throws ShapeTreeException */ { this.referenceUrl = Objects.requireNonNull(referenceUrl); if (shapePath === null && predicate === null) { throw new ShapeTreeException(500, "Shape tree reference must have either a shape path or a predicate"); diff --git a/asTypescript/packages/core/src/contentloaders/HttpExternalDocumentLoader.ts b/asTypescript/packages/core/src/contentloaders/HttpExternalDocumentLoader.ts index daecfa7b..e0175d42 100644 --- a/asTypescript/packages/core/src/contentloaders/HttpExternalDocumentLoader.ts +++ b/asTypescript/packages/core/src/contentloaders/HttpExternalDocumentLoader.ts @@ -25,13 +25,14 @@ export class HttpExternalDocumentLoader implements ExternalDocumentLoader { } let attributes: ResourceAttributes = new ResourceAttributes(response.headers().map()); return new DocumentResponse(attributes, response.body(), response.statusCode()); - } catch (ex: IOException) { - throw new ShapeTreeException(500, "Error retrieving <" + resourceUrl + ">: " + ex.getMessage()); - } catch (ex: InterruptedException) { - Thread.currentThread().interrupt(); - throw new ShapeTreeException(500, "Error retrieving <" + resourceUrl + ">: " + ex.getMessage()); - } catch (ex: URISyntaxException) { - throw new ShapeTreeException(500, "Malformed URL <" + resourceUrl + ">: " + ex.getMessage()); - } + } catch (ex) { + if (ex instanceof IOException) { + throw new ShapeTreeException(500, "Error retrieving <" + resourceUrl + ">: " + ex.getMessage()); + } else if (ex instanceof InterruptedException) { + Thread.currentThread().interrupt(); + throw new ShapeTreeException(500, "Error retrieving <" + resourceUrl + ">: " + ex.getMessage()); + } else if (ex instanceof URISyntaxException) { + throw new ShapeTreeException(500, "Malformed URL <" + resourceUrl + ">: " + ex.getMessage()); + } } } diff --git a/asTypescript/packages/core/src/helpers/GraphHelper.ts b/asTypescript/packages/core/src/helpers/GraphHelper.ts index 4a35c18f..e7c0c07c 100644 --- a/asTypescript/packages/core/src/helpers/GraphHelper.ts +++ b/asTypescript/packages/core/src/helpers/GraphHelper.ts @@ -77,9 +77,10 @@ export class GraphHelper { let reader: StringReader = new StringReader(rawContent); RDFDataMgr.read(model.getGraph(), reader, baseURI.toString(), GraphHelper.getLangForContentType(contentType)); return model; - } catch (rex: RiotException) { - throw new ShapeTreeException(422, "Error processing input - " + rex.getMessage()); - } + } catch (ex) { + if (ex instanceof RiotException) { + throw new ShapeTreeException(422, "Error processing input - " + rex.getMessage()); + } } /** @@ -160,9 +161,10 @@ export class GraphHelper { public static urlToUri(url: URL): URI { try { return url.toURI(); - } catch (ex: URISyntaxException) { - throw new IllegalStateException("can't convert URL <" + url + "> to IRI: " + ex); - } + } catch (ex) { + if (ex instanceof URISyntaxException) { + throw new IllegalStateException("can't convert URL <" + url + "> to IRI: " + ex); + } } /** @@ -178,16 +180,18 @@ export class GraphHelper { try { let noFragment: URI = new URI(uri.getScheme(), uri.getSchemeSpecificPart(), null); return noFragment.toURL(); - } catch (ex: MalformedURLException | URISyntaxException) { - throw new IllegalStateException("Unable to remove fragment from URL: " + ex.getMessage()); - } + } catch (ex) { + if (ex instanceof MalformedURLException || ex instanceof URISyntaxException) { + throw new IllegalStateException("Unable to remove fragment from URL: " + ex.getMessage()); + } } public static knownUrl(urlString: string): URL { try { return new URL(urlString); - } catch (ex: MalformedURLException) { - throw new IllegalStateException("Expected known URL <" + urlString + "> to parse as valid URL - " + ex.toString()); - } + } catch (ex) { + if (ex instanceof MalformedURLException) { + throw new IllegalStateException("Expected known URL <" + urlString + "> to parse as valid URL - " + ex.toString()); + } } } diff --git a/asTypescript/packages/core/src/helpers/RequestHelper.ts b/asTypescript/packages/core/src/helpers/RequestHelper.ts index 90ab0316..884612f0 100644 --- a/asTypescript/packages/core/src/helpers/RequestHelper.ts +++ b/asTypescript/packages/core/src/helpers/RequestHelper.ts @@ -97,9 +97,10 @@ export class RequestHelper { try { const focusNodeUrl: URL = new URL(baseUrl, focusNodeUrlString); focusNodeUrls.add(focusNodeUrl); - } catch (e: MalformedURLException) { - throw new ShapeTreeException(500, "Malformed focus node when resolving <" + focusNodeUrlString + "> against <" + baseUrl + ">"); - } + } catch (ex) { + if (ex instanceof MalformedURLException) { + throw new ShapeTreeException(500, "Malformed focus node when resolving <" + focusNodeUrlString + "> against <" + baseUrl + ">"); + } } } return focusNodeUrls; @@ -119,9 +120,10 @@ export class RequestHelper { try { const targetShapeTreeUrl: URL = new URL(targetShapeTreeUrlString); targetShapeTreeUrls.add(targetShapeTreeUrl); - } catch (e: MalformedURLException) { - throw new ShapeTreeException(500, "Malformed focus node when resolving <" + targetShapeTreeUrlString + "> against <" + baseUrl + ">"); - } + } catch (ex) { + if (ex instanceof MalformedURLException) { + throw new ShapeTreeException(500, "Malformed focus node when resolving <" + targetShapeTreeUrlString + "> against <" + baseUrl + ">"); + } } } return targetShapeTreeUrls; @@ -153,9 +155,10 @@ export class RequestHelper { } try { return new URL(urlString); - } catch (ex: MalformedURLException) { - throw new ShapeTreeException(500, "normalized to malformed URL <" + urlString + "> - " + ex.getMessage()); - } + } catch (ex) { + if (ex instanceof MalformedURLException) { + throw new ShapeTreeException(500, "normalized to malformed URL <" + urlString + "> - " + ex.getMessage()); + } } /** diff --git a/asTypescript/packages/javahttp/src/JavaHttpClient.ts b/asTypescript/packages/javahttp/src/JavaHttpClient.ts index 325d9c91..8bffef1e 100644 --- a/asTypescript/packages/javahttp/src/JavaHttpClient.ts +++ b/asTypescript/packages/javahttp/src/JavaHttpClient.ts @@ -39,9 +39,10 @@ export class JavaHttpClient implements HttpClient { let body: string = null; try { body = Objects.requireNonNull(response.body()).toString(); - } catch (ex: NullPointerException) { - log.error("Exception retrieving body string"); - } + } catch (ex) { + if (ex instanceof NullPointerException) { + log.error("Exception retrieving body string"); + } return new DocumentResponse(new ResourceAttributes(response.headers().map()), body, response.statusCode()); } @@ -52,7 +53,7 @@ export class JavaHttpClient implements HttpClient { * @throws NoSuchAlgorithmException potentially thrown while disabling SSL validation * @throws KeyManagementException potentially thrown while disabling SSL validation */ - protected constructor(useSslValidation: boolean, useShapeTreeValidation: boolean) throws NoSuchAlgorithmException, KeyManagementException { + protected constructor(useSslValidation: boolean, useShapeTreeValidation: boolean) /* throws NoSuchAlgorithmException, KeyManagementException */ { let clientBuilder: java.net.http.HttpClient.Builder = java.net.http.HttpClient.newBuilder(); this.validatingWrapper = null; if (Boolean.TRUE === useShapeTreeValidation) { @@ -74,14 +75,16 @@ export class JavaHttpClient implements HttpClient { let sc: SSLContext = null; try { sc = SSLContext.getInstance("TLSv1.2"); - } catch (e: NoSuchAlgorithmException) { - e.printStackTrace(); - } + } catch (ex) { + if (ex instanceof NoSuchAlgorithmException) { + e.printStackTrace(); + } try { sc.init(null, trustAllCerts, new java.security.SecureRandom()); - } catch (e: KeyManagementException) { - e.printStackTrace(); - } + } catch (ex) { + if (ex instanceof KeyManagementException) { + e.printStackTrace(); + } HttpsURLConnection.setDefaultSSLSocketFactory(sc.getSocketFactory()); // Create all-trusting host name verifier let validHosts: HostnameVerifier = new HostnameVerifier() { @@ -134,11 +137,12 @@ export class JavaHttpClient implements HttpClient { } else { return this.validatingWrapper.validatingWrap(nativeRequest, this.httpClient, request.body, request.contentType); } - } catch (ex: IOException | InterruptedException) { - throw new ShapeTreeException(500, ex.getMessage()); - } catch (ex: URISyntaxException) { - throw new ShapeTreeException(500, "Malformed URL <" + request.resourceURL + ">: " + ex.getMessage()); - } + } catch (ex) { + if (ex instanceof IOException || ex instanceof InterruptedException) { + throw new ShapeTreeException(500, ex.getMessage()); + } else if (ex instanceof URISyntaxException) { + throw new ShapeTreeException(500, "Malformed URL <" + request.resourceURL + ">: " + ex.getMessage()); + } } protected static check(resp: java.net.http.HttpResponse): java.net.http.HttpResponse { diff --git a/asTypescript/packages/javahttp/src/JavaHttpClientFactory.ts b/asTypescript/packages/javahttp/src/JavaHttpClientFactory.ts index b462bec6..755b4977 100644 --- a/asTypescript/packages/javahttp/src/JavaHttpClientFactory.ts +++ b/asTypescript/packages/javahttp/src/JavaHttpClientFactory.ts @@ -36,9 +36,10 @@ export class JavaHttpClientFactory implements HttpClientFactory, ExternalDocumen public get(useShapeTreeValidation: boolean): JavaHttpClient /* throws ShapeTreeException */ { try { return new JavaHttpClient(this.useSslValidation, useShapeTreeValidation); - } catch (ex: Exception) { - throw new ShapeTreeException(500, ex.getMessage()); - } + } catch (ex) { + if (ex instanceof Exception) { + throw new ShapeTreeException(500, ex.getMessage()); + } } /** diff --git a/asTypescript/packages/javahttp/src/JavaHttpValidatingShapeTreeInterceptor.ts b/asTypescript/packages/javahttp/src/JavaHttpValidatingShapeTreeInterceptor.ts index e2b52a5d..74c361f8 100644 --- a/asTypescript/packages/javahttp/src/JavaHttpValidatingShapeTreeInterceptor.ts +++ b/asTypescript/packages/javahttp/src/JavaHttpValidatingShapeTreeInterceptor.ts @@ -47,13 +47,14 @@ export class JavaHttpValidatingShapeTreeInterceptor { } else { return createResponse(clientRequest, shapeTreeResponse.get()); } - } catch (ex: ShapeTreeException) { - log.error("Error processing shape tree request: ", ex); - return createErrorResponse(ex, clientRequest); - } catch (ex: Exception) { - log.error("Error processing shape tree request: ", ex); - return createErrorResponse(new ShapeTreeException(500, ex.getMessage()), clientRequest); - } + } catch (ex) { + if (ex instanceof ShapeTreeException) { + log.error("Error processing shape tree request: ", ex); + return createErrorResponse(ex, clientRequest); + } else if (ex instanceof Exception) { + log.error("Error processing shape tree request: ", ex); + return createErrorResponse(new ShapeTreeException(500, ex.getMessage()), clientRequest); + } } else { log.warn("No handler for method [{}] - passing through request", shapeTreeRequest.getMethod()); return JavaHttpClient.check(httpClient.send(clientRequest, java.net.http.HttpResponse.BodyHandlers.ofString())); @@ -116,9 +117,10 @@ export class JavaHttpValidatingShapeTreeInterceptor { override public getUrl(): URL { try { return this.request.uri().toURL(); - } catch (ex: MalformedURLException) { - throw new IllegalStateException("request has a malformed URL <" + request.uri() + ">: " + ex.getMessage()); - } + } catch (ex) { + if (ex instanceof MalformedURLException) { + throw new IllegalStateException("request has a malformed URL <" + request.uri() + ">: " + ex.getMessage()); + } } override public getHeaders(): ResourceAttributes { From 408f77def24e633f3fca2c04a54c7d90ae526284 Mon Sep 17 00:00:00 2001 From: Eric Prud'hommeaux Date: Wed, 22 Dec 2021 10:33:27 +0100 Subject: [PATCH 10/15] ~ feedback from shapetrees-js@2ef2476 --- .../client-http/src/HttpResourceAccessor.ts | 24 ++++--- .../packages/core/src/ManageableResource.ts | 7 ++- asTypescript/packages/core/src/ShapeTree.ts | 6 +- .../packages/core/src/ShapeTreeAssignment.ts | 3 +- .../packages/core/src/ShapeTreeFactory.ts | 9 ++- .../packages/core/src/ShapeTreeManager.ts | 10 +-- .../core/src/ShapeTreeManagerDelta.ts | 3 +- .../HttpExternalDocumentLoader.ts | 3 +- .../packages/core/src/helpers/GraphHelper.ts | 16 +++-- .../core/src/helpers/RequestHelper.ts | 17 ++--- .../packages/javahttp/src/JavaHttpClient.ts | 20 +++--- .../javahttp/src/JavaHttpClientFactory.ts | 3 +- .../JavaHttpValidatingShapeTreeInterceptor.ts | 6 +- .../client/core/ShapeTreeClient.java | 8 +-- .../client/http/HttpResourceAccessor.java | 21 ++++--- .../client/http/HttpShapeTreeClient.java | 28 ++++++--- .../shapetrees/core/DocumentResponse.java | 4 +- .../shapetrees/core/ManageableInstance.java | 2 +- .../core/MissingManagerResource.java | 10 +-- .../shapetrees/core/ResourceAttributes.java | 2 +- .../shapetrees/core/ShapeTree.java | 63 +++++++++---------- .../shapetrees/core/ShapeTreeFactory.java | 19 ++---- .../shapetrees/core/ShapeTreeManager.java | 6 +- .../core/ShapeTreeManagerDelta.java | 2 +- .../core/ShapeTreeRequestHandler.java | 15 +++-- .../shapetrees/core/ValidationResult.java | 27 -------- .../tests/ShapeTreeValidationTests.java | 36 +++++------ .../AbstractHttpClientProjectTests.java | 12 ++-- 28 files changed, 198 insertions(+), 184 deletions(-) diff --git a/asTypescript/packages/client-http/src/HttpResourceAccessor.ts b/asTypescript/packages/client-http/src/HttpResourceAccessor.ts index 6135bec9..921669ee 100644 --- a/asTypescript/packages/client-http/src/HttpResourceAccessor.ts +++ b/asTypescript/packages/client-http/src/HttpResourceAccessor.ts @@ -279,10 +279,11 @@ export class HttpResourceAccessor implements ResourceAccessor { if (location.isPresent()) { try { url = new URL(location.get()); - } catch (ex) { - if (ex instanceof MalformedURLException) { + } catch (e) { + if (e instanceof MalformedURLException) { throw new ShapeTreeException(500, "Retrieving <" + url + "> yielded a Location header \"" + location.get() + "\" which doesn't parse as a URL: " + e.getMessage()); - } + }} + } // Determine whether the resource exists based on the response. Even if the resource // doesn't exist, additional context and processing is done to provide the appropriate @@ -354,7 +355,8 @@ export class HttpResourceAccessor implements ResourceAccessor { } catch (ex) { if (ex instanceof Exception) { throw new ShapeTreeException(500, ex.getMessage()); - } + }} + } /** @@ -453,10 +455,11 @@ export class HttpResourceAccessor implements ResourceAccessor { let managerUrlString: string = optManagerString.get(); try { return Optional.of(new URL(url, managerUrlString)); - } catch (ex) { - if (ex instanceof MalformedURLException) { + } catch (e) { + if (e instanceof MalformedURLException) { throw new ShapeTreeException(500, "Malformed relative URL <" + managerUrlString + "> (resolved from <" + url + ">)"); - } + }} + } /** @@ -482,10 +485,11 @@ export class HttpResourceAccessor implements ResourceAccessor { } try { managedResourceUrl = new URL(managerUrl, managedUrlString); - } catch (ex) { - if (ex instanceof MalformedURLException) { + } catch (e) { + if (e instanceof MalformedURLException) { throw new ShapeTreeException(500, "Can't calculate managed resource for shape tree manager <" + managerUrl + ">"); - } + }} + return managedResourceUrl; } diff --git a/asTypescript/packages/core/src/ManageableResource.ts b/asTypescript/packages/core/src/ManageableResource.ts index d87aba13..ab8a442d 100644 --- a/asTypescript/packages/core/src/ManageableResource.ts +++ b/asTypescript/packages/core/src/ManageableResource.ts @@ -44,10 +44,11 @@ export class ManageableResource extends InstanceResource { const rel: string = this.isContainer() ? ".." : "."; try { return new URL(this.getUrl(), rel); - } catch (ex) { - if (ex instanceof MalformedURLException) { + } catch (e) { + if (e instanceof MalformedURLException) { throw new ShapeTreeException(500, "Malformed focus node when resolving <" + rel + "> against <" + this.getUrl() + ">"); - } + }} + } public getManagerResourceUrl(): URL | null { diff --git a/asTypescript/packages/core/src/ShapeTree.ts b/asTypescript/packages/core/src/ShapeTree.ts index 8d8ddc1e..9ca7c047 100644 --- a/asTypescript/packages/core/src/ShapeTree.ts +++ b/asTypescript/packages/core/src/ShapeTree.ts @@ -115,7 +115,8 @@ export class ShapeTree { } catch (ex) { if (ex instanceof Exception) { throw new ShapeTreeException(500, "Error parsing ShEx schema - " + ex.getMessage()); - } + }} + } // Tell ShExJava we want to use Jena as our graph library let jenaRDF: JenaRDF = new org.apache.commons.rdf.jena.JenaRDF(); @@ -151,7 +152,8 @@ export class ShapeTree { } catch (ex) { if (ex instanceof MalformedURLException) { throw new ShapeTreeException(500, "Error reporting validation success on malformed URL <" + focusUriString + ">: " + ex.getMessage()); - } + }} + return new ValidationResult(valid, this, this, matchingFocusNode); } } diff --git a/asTypescript/packages/core/src/ShapeTreeAssignment.ts b/asTypescript/packages/core/src/ShapeTreeAssignment.ts index f7b47f23..ed8ccabc 100644 --- a/asTypescript/packages/core/src/ShapeTreeAssignment.ts +++ b/asTypescript/packages/core/src/ShapeTreeAssignment.ts @@ -56,7 +56,8 @@ export class ShapeTreeAssignment { } catch (ex) { if (ex instanceof NullPointerException || ex instanceof IllegalStateException) { throw new ShapeTreeException(500, "Failed to initialize shape tree assignment: " + ex.getMessage()); - } + }} + } public static getFromGraph(url: URL, managerGraph: Graph): ShapeTreeAssignment /* throws MalformedURLException, ShapeTreeException */ { diff --git a/asTypescript/packages/core/src/ShapeTreeFactory.ts b/asTypescript/packages/core/src/ShapeTreeFactory.ts index a74fe58a..d4a045a0 100644 --- a/asTypescript/packages/core/src/ShapeTreeFactory.ts +++ b/asTypescript/packages/core/src/ShapeTreeFactory.ts @@ -91,7 +91,8 @@ export class ShapeTreeFactory { } catch (ex) { if (ex instanceof MalformedURLException || ex instanceof ShapeTreeException) { throw new ShapeTreeException(500, "List <" + shapeTreeUrl + "> contains malformed URL: " + ex.getMessage()); - } + }} + } /** @@ -117,7 +118,8 @@ export class ShapeTreeFactory { } catch (ex) { if (ex instanceof MalformedURLException) { throw new ShapeTreeException(500, "ShapeTree <" + shapeTreeUrl + "> references malformed URL <" + referencedShapeTreeUrlString + ">: " + ex.getMessage()); - } + }} + let viaShapePath: string = getStringValue(resourceModel, referenceResource, ShapeTreeVocabulary.VIA_SHAPE_PATH); let viaPredicate: URL = getUrlValue(resourceModel, referenceResource, ShapeTreeVocabulary.VIA_PREDICATE, shapeTreeUrl); referencedShapeTree = new ShapeTreeReference(referencedShapeTreeUrl, viaShapePath, viaPredicate); @@ -147,7 +149,8 @@ export class ShapeTreeFactory { } catch (ex) { if (ex instanceof MalformedURLException) { throw new IllegalStateException("Malformed ShapeTree <" + shapeTreeUrl + ">: Jena URIResource <" + object + "> didn't parse as URL - " + ex.getMessage()); - } + }} + } else { throw new ShapeTreeException(500, "Malformed ShapeTree <" + shapeTreeUrl + ">: expected " + object + " to be a URL"); } diff --git a/asTypescript/packages/core/src/ShapeTreeManager.ts b/asTypescript/packages/core/src/ShapeTreeManager.ts index 3d9e64f0..e4833f73 100644 --- a/asTypescript/packages/core/src/ShapeTreeManager.ts +++ b/asTypescript/packages/core/src/ShapeTreeManager.ts @@ -106,7 +106,8 @@ export class ShapeTreeManager { } catch (ex) { if (ex instanceof MalformedURLException) { throw new IllegalStateException("Minted illegal URL <" + assignmentString + "> - " + ex.getMessage()); - } + }} + return assignmentUrl; } @@ -164,10 +165,11 @@ export class ShapeTreeManager { let assignment: ShapeTreeAssignment = null; try { assignment = ShapeTreeAssignment.getFromGraph(new URL(assignmentNode.getObject().getURI()), managerGraph); - } catch (ex) { - if (ex instanceof MalformedURLException) { + } catch (e) { + if (e instanceof MalformedURLException) { throw new ShapeTreeException(500, "Object of { " + s + " " + stAssignment + " " + assignmentNode.getObject() + " } must be a URL."); - } + }} + manager.assignments.add(assignment); } return manager; diff --git a/asTypescript/packages/core/src/ShapeTreeManagerDelta.ts b/asTypescript/packages/core/src/ShapeTreeManagerDelta.ts index 3cc7643f..d8f56288 100644 --- a/asTypescript/packages/core/src/ShapeTreeManagerDelta.ts +++ b/asTypescript/packages/core/src/ShapeTreeManagerDelta.ts @@ -82,7 +82,8 @@ export class ShapeTreeManagerDelta { } catch (ex) { if (ex instanceof URISyntaxException) { throw new ShapeTreeException(500, "Unable to convert assignment URLs for comparison: " + ex.getMessage()); - } + }} + if (assignmentUri === targetAssignmentUri) { return targetAssignment; } diff --git a/asTypescript/packages/core/src/contentloaders/HttpExternalDocumentLoader.ts b/asTypescript/packages/core/src/contentloaders/HttpExternalDocumentLoader.ts index e0175d42..24e1a535 100644 --- a/asTypescript/packages/core/src/contentloaders/HttpExternalDocumentLoader.ts +++ b/asTypescript/packages/core/src/contentloaders/HttpExternalDocumentLoader.ts @@ -33,6 +33,7 @@ export class HttpExternalDocumentLoader implements ExternalDocumentLoader { throw new ShapeTreeException(500, "Error retrieving <" + resourceUrl + ">: " + ex.getMessage()); } else if (ex instanceof URISyntaxException) { throw new ShapeTreeException(500, "Malformed URL <" + resourceUrl + ">: " + ex.getMessage()); - } + }} + } } diff --git a/asTypescript/packages/core/src/helpers/GraphHelper.ts b/asTypescript/packages/core/src/helpers/GraphHelper.ts index e7c0c07c..58aae5bf 100644 --- a/asTypescript/packages/core/src/helpers/GraphHelper.ts +++ b/asTypescript/packages/core/src/helpers/GraphHelper.ts @@ -77,10 +77,11 @@ export class GraphHelper { let reader: StringReader = new StringReader(rawContent); RDFDataMgr.read(model.getGraph(), reader, baseURI.toString(), GraphHelper.getLangForContentType(contentType)); return model; - } catch (ex) { - if (ex instanceof RiotException) { + } catch (rex) { + if (rex instanceof RiotException) { throw new ShapeTreeException(422, "Error processing input - " + rex.getMessage()); - } + }} + } /** @@ -164,7 +165,8 @@ export class GraphHelper { } catch (ex) { if (ex instanceof URISyntaxException) { throw new IllegalStateException("can't convert URL <" + url + "> to IRI: " + ex); - } + }} + } /** @@ -183,7 +185,8 @@ export class GraphHelper { } catch (ex) { if (ex instanceof MalformedURLException || ex instanceof URISyntaxException) { throw new IllegalStateException("Unable to remove fragment from URL: " + ex.getMessage()); - } + }} + } public static knownUrl(urlString: string): URL { @@ -192,6 +195,7 @@ export class GraphHelper { } catch (ex) { if (ex instanceof MalformedURLException) { throw new IllegalStateException("Expected known URL <" + urlString + "> to parse as valid URL - " + ex.toString()); - } + }} + } } diff --git a/asTypescript/packages/core/src/helpers/RequestHelper.ts b/asTypescript/packages/core/src/helpers/RequestHelper.ts index 884612f0..eea3674e 100644 --- a/asTypescript/packages/core/src/helpers/RequestHelper.ts +++ b/asTypescript/packages/core/src/helpers/RequestHelper.ts @@ -97,10 +97,11 @@ export class RequestHelper { try { const focusNodeUrl: URL = new URL(baseUrl, focusNodeUrlString); focusNodeUrls.add(focusNodeUrl); - } catch (ex) { - if (ex instanceof MalformedURLException) { + } catch (e) { + if (e instanceof MalformedURLException) { throw new ShapeTreeException(500, "Malformed focus node when resolving <" + focusNodeUrlString + "> against <" + baseUrl + ">"); - } + }} + } } return focusNodeUrls; @@ -120,10 +121,11 @@ export class RequestHelper { try { const targetShapeTreeUrl: URL = new URL(targetShapeTreeUrlString); targetShapeTreeUrls.add(targetShapeTreeUrl); - } catch (ex) { - if (ex instanceof MalformedURLException) { + } catch (e) { + if (e instanceof MalformedURLException) { throw new ShapeTreeException(500, "Malformed focus node when resolving <" + targetShapeTreeUrlString + "> against <" + baseUrl + ">"); - } + }} + } } return targetShapeTreeUrls; @@ -158,7 +160,8 @@ export class RequestHelper { } catch (ex) { if (ex instanceof MalformedURLException) { throw new ShapeTreeException(500, "normalized to malformed URL <" + urlString + "> - " + ex.getMessage()); - } + }} + } /** diff --git a/asTypescript/packages/javahttp/src/JavaHttpClient.ts b/asTypescript/packages/javahttp/src/JavaHttpClient.ts index 8bffef1e..b97a641c 100644 --- a/asTypescript/packages/javahttp/src/JavaHttpClient.ts +++ b/asTypescript/packages/javahttp/src/JavaHttpClient.ts @@ -42,7 +42,8 @@ export class JavaHttpClient implements HttpClient { } catch (ex) { if (ex instanceof NullPointerException) { log.error("Exception retrieving body string"); - } + }} + return new DocumentResponse(new ResourceAttributes(response.headers().map()), body, response.statusCode()); } @@ -75,16 +76,18 @@ export class JavaHttpClient implements HttpClient { let sc: SSLContext = null; try { sc = SSLContext.getInstance("TLSv1.2"); - } catch (ex) { - if (ex instanceof NoSuchAlgorithmException) { + } catch (e) { + if (e instanceof NoSuchAlgorithmException) { e.printStackTrace(); - } + }} + try { sc.init(null, trustAllCerts, new java.security.SecureRandom()); - } catch (ex) { - if (ex instanceof KeyManagementException) { + } catch (e) { + if (e instanceof KeyManagementException) { e.printStackTrace(); - } + }} + HttpsURLConnection.setDefaultSSLSocketFactory(sc.getSocketFactory()); // Create all-trusting host name verifier let validHosts: HostnameVerifier = new HostnameVerifier() { @@ -142,7 +145,8 @@ export class JavaHttpClient implements HttpClient { throw new ShapeTreeException(500, ex.getMessage()); } else if (ex instanceof URISyntaxException) { throw new ShapeTreeException(500, "Malformed URL <" + request.resourceURL + ">: " + ex.getMessage()); - } + }} + } protected static check(resp: java.net.http.HttpResponse): java.net.http.HttpResponse { diff --git a/asTypescript/packages/javahttp/src/JavaHttpClientFactory.ts b/asTypescript/packages/javahttp/src/JavaHttpClientFactory.ts index 755b4977..a0c171c5 100644 --- a/asTypescript/packages/javahttp/src/JavaHttpClientFactory.ts +++ b/asTypescript/packages/javahttp/src/JavaHttpClientFactory.ts @@ -39,7 +39,8 @@ export class JavaHttpClientFactory implements HttpClientFactory, ExternalDocumen } catch (ex) { if (ex instanceof Exception) { throw new ShapeTreeException(500, ex.getMessage()); - } + }} + } /** diff --git a/asTypescript/packages/javahttp/src/JavaHttpValidatingShapeTreeInterceptor.ts b/asTypescript/packages/javahttp/src/JavaHttpValidatingShapeTreeInterceptor.ts index 74c361f8..f13dea98 100644 --- a/asTypescript/packages/javahttp/src/JavaHttpValidatingShapeTreeInterceptor.ts +++ b/asTypescript/packages/javahttp/src/JavaHttpValidatingShapeTreeInterceptor.ts @@ -54,7 +54,8 @@ export class JavaHttpValidatingShapeTreeInterceptor { } else if (ex instanceof Exception) { log.error("Error processing shape tree request: ", ex); return createErrorResponse(new ShapeTreeException(500, ex.getMessage()), clientRequest); - } + }} + } else { log.warn("No handler for method [{}] - passing through request", shapeTreeRequest.getMethod()); return JavaHttpClient.check(httpClient.send(clientRequest, java.net.http.HttpResponse.BodyHandlers.ofString())); @@ -120,7 +121,8 @@ export class JavaHttpValidatingShapeTreeInterceptor { } catch (ex) { if (ex instanceof MalformedURLException) { throw new IllegalStateException("request has a malformed URL <" + request.uri() + ">: " + ex.getMessage()); - } + }} + } override public getHeaders(): ResourceAttributes { diff --git a/shapetrees-java-client-core/src/main/java/com/janeirodigital/shapetrees/client/core/ShapeTreeClient.java b/shapetrees-java-client-core/src/main/java/com/janeirodigital/shapetrees/client/core/ShapeTreeClient.java index 711cb833..553e81a1 100644 --- a/shapetrees-java-client-core/src/main/java/com/janeirodigital/shapetrees/client/core/ShapeTreeClient.java +++ b/shapetrees-java-client-core/src/main/java/com/janeirodigital/shapetrees/client/core/ShapeTreeClient.java @@ -84,14 +84,14 @@ public interface ShapeTreeClient { * @param context ShapeTreeContext that would be used for authentication purposes * @param targetResource The target resource to be created or updated * @param focusNodes One or more nodes/subjects to use as the focus for shape validation - * @param targetShapeTrees The shape trees that a proposed resource to be created should be validated against - * @param isContainer Specifies whether a newly created resource should be created as a container or not * @param bodyString String representation of the body of the resource to create or update * @param contentType Content type to parse the bodyString parameter as + * @param targetShapeTrees The shape trees that a proposed resource to be created should be validated against + * @param isContainer Specifies whether a newly created resource should be created as a container or not * @return DocumentResponse containing status and response header / attributes * @throws ShapeTreeException */ - DocumentResponse putManagedInstance(ShapeTreeContext context, URL targetResource, List focusNodes, List targetShapeTrees, Boolean isContainer, String bodyString, String contentType) throws ShapeTreeException; + DocumentResponse putManagedInstance(ShapeTreeContext context, URL targetResource, List focusNodes, String bodyString, String contentType, List targetShapeTrees, Boolean isContainer) throws ShapeTreeException; /** * Updates a resource via HTTP PUT that has been validated against an associated shape tree @@ -103,7 +103,7 @@ public interface ShapeTreeClient { * @return DocumentResponse containing status and response header / attributes * @throws ShapeTreeException */ - DocumentResponse putManagedInstance(ShapeTreeContext context, URL targetResource, List focusNodes, String bodyString, String contentType) throws ShapeTreeException; + DocumentResponse updateManagedInstance(ShapeTreeContext context, URL targetResource, List focusNodes, String bodyString, String contentType) throws ShapeTreeException; /** * Updates a resource via HTTP PATCH that has been validated against an associated shape tree diff --git a/shapetrees-java-client-http/src/main/java/com/janeirodigital/shapetrees/client/http/HttpResourceAccessor.java b/shapetrees-java-client-http/src/main/java/com/janeirodigital/shapetrees/client/http/HttpResourceAccessor.java index 6dd65df0..224a4871 100644 --- a/shapetrees-java-client-http/src/main/java/com/janeirodigital/shapetrees/client/http/HttpResourceAccessor.java +++ b/shapetrees-java-client-http/src/main/java/com/janeirodigital/shapetrees/client/http/HttpResourceAccessor.java @@ -97,7 +97,7 @@ public class HttpResourceAccessor implements ResourceAccessor { private ManageableInstance getInstanceFromMissingManageableResource(ShapeTreeContext context, MissingManageableResource missing) { - MissingManagerResource missingManager = new MissingManagerResource(missing, null); + MissingManagerResource missingManager = new MissingManagerResource(missing.getUrl(), missing); return new ManageableInstance(context, this, false, missing, missingManager); } @@ -327,7 +327,9 @@ public class HttpResourceAccessor implements ResourceAccessor { generateResource(URL url, DocumentResponse response) throws ShapeTreeException { // If a resource was created, ensure the URL returned in the Location header is valid - Optional location = response.getResourceAttributes().firstValue(HttpHeaders.LOCATION.getValue()); + Optional location = response.getResourceAttributes() == null + ? Optional.empty() + : response.getResourceAttributes().firstValue(HttpHeaders.LOCATION.getValue()); if (location.isPresent()) { try { url = new URL(location.get()); @@ -341,13 +343,14 @@ public class HttpResourceAccessor implements ResourceAccessor { // typed resource with adequate context to the caller final boolean exists = response.isExists(); final boolean container = isContainerFromHeaders(response.getResourceAttributes(), url); - final ResourceAttributes attributes = response.getResourceAttributes(); + final ResourceAttributes attributes = response.getResourceAttributes(); // TODO: could be null final ShapeTreeResourceType resourceType = getResourceTypeFromHeaders(response.getResourceAttributes()); final String name = calculateName(url); - final String body = response.getBody(); + final String body = response.getBody(); // TODO: could be null. readStringIntoModel not set up for `rawContent=null` if (response.getBody() == null) { log.error("Could not retrieve the body string from response for " + url); + throw new IllegalStateException("Could not retrieve the body string from response for <" + url + ">"); // TODO: remove when TODO above is resolved. } // Parse Link headers from response and populate ResourceAttributes @@ -364,7 +367,7 @@ public class HttpResourceAccessor implements ResourceAccessor { if (exists) { return new ManagerResource(url, resourceType, attributes, body, name, true, managedResourceUrl); } else { - return new MissingManagerResource(url, resourceType, attributes, body, name, managedResourceUrl); + return new MissingManagerResource(managedResourceUrl, url, resourceType, attributes, body, name); } } else { // Look for presence of st:managedBy in link headers from response and get the target manager URL @@ -473,7 +476,9 @@ public class HttpResourceAccessor implements ResourceAccessor { private boolean isContainerFromHeaders(ResourceAttributes headers, URL url) { - List linkHeaders = headers.allValues(HttpHeaders.LINK.getValue()); + List linkHeaders = headers == null + ? Collections.emptyList() + : headers.allValues(HttpHeaders.LINK.getValue()); if (linkHeaders.isEmpty()) { return url.getPath().endsWith("/"); } @@ -495,7 +500,9 @@ public class HttpResourceAccessor implements ResourceAccessor { private ShapeTreeResourceType getResourceTypeFromHeaders(ResourceAttributes headers) { - List linkHeaders = headers.allValues(HttpHeaders.LINK.getValue()); + List linkHeaders = headers == null + ? null + : headers.allValues(HttpHeaders.LINK.getValue()); if (linkHeaders == null) { return null; } diff --git a/shapetrees-java-client-http/src/main/java/com/janeirodigital/shapetrees/client/http/HttpShapeTreeClient.java b/shapetrees-java-client-http/src/main/java/com/janeirodigital/shapetrees/client/http/HttpShapeTreeClient.java index a54d8365..f2f6c403 100644 --- a/shapetrees-java-client-http/src/main/java/com/janeirodigital/shapetrees/client/http/HttpShapeTreeClient.java +++ b/shapetrees-java-client-http/src/main/java/com/janeirodigital/shapetrees/client/http/HttpShapeTreeClient.java @@ -16,9 +16,13 @@ import lombok.extern.slf4j.Slf4j; import org.apache.jena.riot.Lang; import org.apache.jena.riot.RDFDataMgr; +import org.apache.jena.riot.RDFWriter; +import org.apache.jena.riot.RIOT; +import java.io.ByteArrayOutputStream; import java.io.StringWriter; import java.net.URL; +import java.nio.charset.StandardCharsets; import java.util.List; import java.util.Optional; @@ -117,13 +121,13 @@ public DocumentResponse plantShapeTree(ShapeTreeContext context, URL targetResou ManageableResource manageableResource = instance.getManageableResource(); if (!manageableResource.isExists()) { - return new DocumentResponse(null, "Cannot find target resource to plant: " + targetResource, 404); + return new DocumentResponse(new ResourceAttributes(), "Cannot find target resource to plant: " + targetResource, 404); } ShapeTreeManager manager; URL managerResourceUrl = instance.getManagerResource().getUrl(); if (instance.isManaged()) { - manager = instance.getManagerResource().getManager(); + manager = instance.getManagerResource().getManager(); // TODO: could be null } else { manager = new ShapeTreeManager(managerResourceUrl); } @@ -141,14 +145,22 @@ public DocumentResponse plantShapeTree(ShapeTreeContext context, URL targetResou manager.addAssignment(assignment); // Get an RDF version of the manager stored in a turtle string - StringWriter sw = new StringWriter(); - RDFDataMgr.write(sw, manager.getGraph(), Lang.TURTLE); + + ByteArrayOutputStream asBytes = new ByteArrayOutputStream(); + RDFWriter.create() + .base(targetShapeTree.toString()) + .set(RIOT.symTurtleOmitBase, false) + .set(RIOT.symTurtleDirectiveStyle, "rdf11") + .lang(Lang.TTL) + .source(manager.getGraph()) + .output(asBytes); + String asString = new String(asBytes.toByteArray(), StandardCharsets.UTF_8); // Build an HTTP PUT request with the manager graph in turtle as the content body + link header HttpClient fetcher = HttpClientFactoryManager.getFactory().get(this.useClientShapeTreeValidation); ResourceAttributes headers = new ResourceAttributes(); headers.maybeSet(HttpHeaders.AUTHORIZATION.getValue(), context.getAuthorizationHeaderValue()); - return fetcher.fetchShapeTreeResponse(new HttpRequest("PUT", managerResourceUrl, headers, sw.toString(), "text/turtle")); + return fetcher.fetchShapeTreeResponse(new HttpRequest("PUT", managerResourceUrl, headers, asString, "text/turtle")); } @Override @@ -170,7 +182,7 @@ public DocumentResponse postManagedInstance(ShapeTreeContext context, URL parent // Create via HTTP PUT @Override - public DocumentResponse putManagedInstance(ShapeTreeContext context, URL resourceUrl, List focusNodes, List targetShapeTrees, Boolean isContainer, String bodyString, String contentType) throws ShapeTreeException { + public DocumentResponse putManagedInstance(ShapeTreeContext context, URL resourceUrl, List focusNodes, String bodyString, String contentType, List targetShapeTrees, Boolean isContainer) throws ShapeTreeException { if (context == null || resourceUrl == null) { throw new ShapeTreeException(500, "Must provide a valid context and target resource to create shape tree instance via PUT"); @@ -187,7 +199,7 @@ public DocumentResponse putManagedInstance(ShapeTreeContext context, URL resourc // Update via HTTP PUT @Override - public DocumentResponse putManagedInstance(ShapeTreeContext context, URL resourceUrl, List focusNodes, String bodyString, String contentType) throws ShapeTreeException { + public DocumentResponse updateManagedInstance(ShapeTreeContext context, URL resourceUrl, List focusNodes, String bodyString, String contentType) throws ShapeTreeException { if (context == null || resourceUrl == null) { throw new ShapeTreeException(500, "Must provide a valid context and target resource to update shape tree instance via PUT"); @@ -256,7 +268,7 @@ public DocumentResponse unplantShapeTree(ShapeTreeContext context, URL targetRes } // Remove assignment from manager that corresponds with the provided shape tree - ShapeTreeManager manager = instance.getManagerResource().getManager(); + ShapeTreeManager manager = instance.getManagerResource().getManager(); // TODO: could be null manager.removeAssignmentForShapeTree(targetShapeTree); String method; diff --git a/shapetrees-java-core/src/main/java/com/janeirodigital/shapetrees/core/DocumentResponse.java b/shapetrees-java-core/src/main/java/com/janeirodigital/shapetrees/core/DocumentResponse.java index dfc474bb..3c5c7010 100644 --- a/shapetrees-java-core/src/main/java/com/janeirodigital/shapetrees/core/DocumentResponse.java +++ b/shapetrees-java-core/src/main/java/com/janeirodigital/shapetrees/core/DocumentResponse.java @@ -13,7 +13,9 @@ public class DocumentResponse { private final int statusCode; public Optional getContentType() { - return this.resourceAttributes.firstValue(HttpHeaders.CONTENT_TYPE.getValue()); + return this.resourceAttributes == null + ? Optional.empty() + : this.resourceAttributes.firstValue(HttpHeaders.CONTENT_TYPE.getValue()); } // TODO: lots of choices re non-404, not >= 4xx, not 3xx. not 201 (meaning there's no body) diff --git a/shapetrees-java-core/src/main/java/com/janeirodigital/shapetrees/core/ManageableInstance.java b/shapetrees-java-core/src/main/java/com/janeirodigital/shapetrees/core/ManageableInstance.java index caee906e..1c725729 100644 --- a/shapetrees-java-core/src/main/java/com/janeirodigital/shapetrees/core/ManageableInstance.java +++ b/shapetrees-java-core/src/main/java/com/janeirodigital/shapetrees/core/ManageableInstance.java @@ -25,7 +25,7 @@ * is immutable. */ @Slf4j @Getter -public class ManageableInstance { +public class ManageableInstance { public static final String TEXT_TURTLE = "text/turtle"; private final ResourceAccessor resourceAccessor; diff --git a/shapetrees-java-core/src/main/java/com/janeirodigital/shapetrees/core/MissingManagerResource.java b/shapetrees-java-core/src/main/java/com/janeirodigital/shapetrees/core/MissingManagerResource.java index a7bb0f87..3181ec26 100644 --- a/shapetrees-java-core/src/main/java/com/janeirodigital/shapetrees/core/MissingManagerResource.java +++ b/shapetrees-java-core/src/main/java/com/janeirodigital/shapetrees/core/MissingManagerResource.java @@ -12,23 +12,23 @@ public class MissingManagerResource extends ManagerResource { /** * Construct a missing manager resource based on a MissingManageableResource + * @param managedResourceUrl Corresponding URL of the resource that would be managed * @param manageable Missing manageable resource - * @param managedUrl Corresponding URL of the resource that would be managed */ - public MissingManagerResource(MissingManageableResource manageable, URL managedUrl) { - super(manageable.getUrl(), manageable.getResourceType(), manageable.getAttributes(), manageable.getBody(), manageable.getName(), manageable.isExists(), managedUrl); + public MissingManagerResource(URL managedResourceUrl, MissingManageableResource manageable) { + super(manageable.getUrl(), manageable.getResourceType(), manageable.getAttributes(), manageable.getBody(), manageable.getName(), manageable.isExists(), managedResourceUrl); } /** * Construct a missing manager resource. + * @param managedResourceUrl URL of the resource that would be managed * @param url URL of the resource * @param resourceType Identified shape tree resource type * @param attributes Associated resource attributes * @param body Body of the resource * @param name Name of the resource - * @param managedResourceUrl URL of the resource that would be managed */ - public MissingManagerResource(URL url, ShapeTreeResourceType resourceType, ResourceAttributes attributes, String body, String name, URL managedResourceUrl) { + public MissingManagerResource(URL managedResourceUrl, URL url, ShapeTreeResourceType resourceType, ResourceAttributes attributes, String body, String name) { super(url, resourceType, attributes, body, name, false, managedResourceUrl); } diff --git a/shapetrees-java-core/src/main/java/com/janeirodigital/shapetrees/core/ResourceAttributes.java b/shapetrees-java-core/src/main/java/com/janeirodigital/shapetrees/core/ResourceAttributes.java index 6f7c5bd2..f9a3d0d2 100644 --- a/shapetrees-java-core/src/main/java/com/janeirodigital/shapetrees/core/ResourceAttributes.java +++ b/shapetrees-java-core/src/main/java/com/janeirodigital/shapetrees/core/ResourceAttributes.java @@ -179,7 +179,7 @@ public List allValues(String name) { List values = toMultimap().get(name); // Making unmodifiable list out of empty in order to make a list which // throws UOE unconditionally - return values != null ? values : List.of(); + return values != null ? values : List.of(); // TODO: some callers, e.g. HttpResourceAccessor.getResourceTypeFromHeaders, expect null } public String toString() { diff --git a/shapetrees-java-core/src/main/java/com/janeirodigital/shapetrees/core/ShapeTree.java b/shapetrees-java-core/src/main/java/com/janeirodigital/shapetrees/core/ShapeTree.java index 0cbf244c..38170266 100644 --- a/shapetrees-java-core/src/main/java/com/janeirodigital/shapetrees/core/ShapeTree.java +++ b/shapetrees-java-core/src/main/java/com/janeirodigital/shapetrees/core/ShapeTree.java @@ -77,10 +77,10 @@ public ValidationResult validateResource(ManageableResource targetResource, List targetResource.getAttributes().firstValue(HttpHeaders.CONTENT_TYPE.getValue()).orElse(null)); } - return validateResource(targetResource.getName(), targetResource.getResourceType(), bodyGraph, focusNodeUrls); + return validateResource(targetResource.getName(), focusNodeUrls, targetResource.getResourceType(), bodyGraph); } - public ValidationResult validateResource(String requestedName, ShapeTreeResourceType resourceType, Graph bodyGraph, List focusNodeUrls) throws ShapeTreeException { + public ValidationResult validateResource(String requestedName, List focusNodeUrls, ShapeTreeResourceType resourceType, Graph bodyGraph) throws ShapeTreeException { // Check whether the proposed resource is the same type as what is expected by the shape tree if (!this.expectedResourceType.toString().equals(resourceType.getValue())) { @@ -122,7 +122,7 @@ public ValidationResult validateGraph(Graph graph, List focusNodeUrls) thro String shapeBody = shexShapeContents.getBody(); InputStream stream = new ByteArrayInputStream(shapeBody.getBytes(StandardCharsets.UTF_8)); - ShExCParser shexCParser = new ShExCParser(); + ShExCParser shexCParser = new ShExCParser(); // TODO: set base URL to this.shape try { schema = new ShexSchema(GlobalFactory.RDFFactory,shexCParser.getRules(stream),shexCParser.getStart()); if (SchemaCache.isInitialized()) { @@ -137,24 +137,24 @@ public ValidationResult validateGraph(Graph graph, List focusNodeUrls) thro JenaRDF jenaRDF = new org.apache.commons.rdf.jena.JenaRDF(); GlobalFactory.RDFFactory = jenaRDF; - ValidationAlgorithm validation = new RecursiveValidation(schema, jenaRDF.asGraph(graph)); + ValidationAlgorithm validator = new RecursiveValidation(schema, jenaRDF.asGraph(graph)); Label shapeLabel = new Label(GlobalFactory.RDFFactory.createIRI(this.shape.toString())); - if (!focusNodeUrls.isEmpty()) { // One or more focus nodes were provided for validation + if (!focusNodeUrls.isEmpty()) { // One or more focus nodes were provided for validator for (URL focusNodeUrl : focusNodeUrls) { // Evaluate each provided focus node IRI focusNode = GlobalFactory.RDFFactory.createIRI(focusNodeUrl.toString()); log.debug("Validating Shape Label = {}, Focus Node = {}", shapeLabel.toPrettyString(), focusNode.getIRIString()); - validation.validate(focusNode, shapeLabel); - boolean valid = validation.getTyping().isConformant(focusNode, shapeLabel); + validator.validate(focusNode, shapeLabel); + boolean valid = validator.getTyping().isConformant(focusNode, shapeLabel); if (valid) { return new ValidationResult(valid, this, this, focusNodeUrl); } } // None of the provided focus nodes were valid - this will return the last failure return new ValidationResult(false, this, "Failed to validate: " + shapeLabel.toPrettyString()); - } else { // No focus nodes were provided for validation, so all subject nodes will be evaluated + } else { // No focus nodes were provided for validator, so all subject nodes will be evaluated List evaluateNodes = GraphUtil.listSubjects(graph, Node.ANY, Node.ANY).toList(); @@ -162,17 +162,15 @@ public ValidationResult validateGraph(Graph graph, List focusNodeUrls) thro final String focusUriString = evaluateNode.getURI(); IRI node = GlobalFactory.RDFFactory.createIRI(focusUriString); - validation.validate(node, shapeLabel); - boolean valid = validation.getTyping().isConformant(node, shapeLabel); + validator.validate(node, shapeLabel); + boolean valid = validator.getTyping().isConformant(node, shapeLabel); if (valid) { - final URL matchingFocusNode; try { - matchingFocusNode = new URL(focusUriString); + return new ValidationResult(valid, this, this, new URL(focusUriString)); } catch (MalformedURLException ex) { - throw new ShapeTreeException(500, "Error reporting validation success on malformed URL <" + focusUriString + ">: " + ex.getMessage()); + throw new ShapeTreeException(500, "Error reporting validator success on malformed URL <" + focusUriString + ">: " + ex.getMessage()); } - return new ValidationResult(valid, this, this, matchingFocusNode); } } @@ -183,28 +181,25 @@ public ValidationResult validateGraph(Graph graph, List focusNodeUrls) thro } public ValidationResult validateContainedResource(ManageableResource containedResource) throws ShapeTreeException { - + // TODO: this same test gets performed in the call to the 2nd valdateContainedResource (after potentially parsing the graph) if (this.contains == null || this.contains.isEmpty()) { // TODO: say it can't be null? // The contained resource is permitted because this shape tree has no restrictions on what it contains return new ValidationResult(true, this, this, null); } - return validateContainedResource(containedResource, Collections.emptyList(), Collections.emptyList()); - - } - - public ValidationResult validateContainedResource(ManageableResource containedResource, List targetShapeTreeUrls, List focusNodeUrls) throws ShapeTreeException { - Graph containedResourceGraph = null; - if (containedResource.getResourceType() != ShapeTreeResourceType.NON_RDF) { + ShapeTreeResourceType resourceType = containedResource.getResourceType(); + if (resourceType != ShapeTreeResourceType.NON_RDF) { containedResourceGraph = GraphHelper.readStringIntoGraph(urlToUri(containedResource.getUrl()), containedResource.getBody(), containedResource.getAttributes().firstValue(HttpHeaders.CONTENT_TYPE.getValue()).orElse(null)); } - return validateContainedResource(containedResource.getName(), containedResource.getResourceType(), targetShapeTreeUrls, containedResourceGraph, focusNodeUrls); - + List targetShapeTreeUrls = Collections.emptyList(); + List focusNodeUrls = Collections.emptyList(); + String requestedName = containedResource.getName(); + return validateContainedResource(requestedName, resourceType, targetShapeTreeUrls, containedResourceGraph, focusNodeUrls); } public ValidationResult validateContainedResource(String requestedName, ShapeTreeResourceType resourceType, List targetShapeTreeUrls, Graph bodyGraph, List focusNodeUrls) throws ShapeTreeException { @@ -222,7 +217,7 @@ public ValidationResult validateContainedResource(String requestedName, ShapeTre if (this.contains.contains(targetShapeTreeUrl)) { ShapeTree targetShapeTree = ShapeTreeFactory.getShapeTree(targetShapeTreeUrl); // Evaluate the shape tree against the attributes of the proposed resources - ValidationResult result = targetShapeTree.validateResource(requestedName, resourceType, bodyGraph, focusNodeUrls); + ValidationResult result = targetShapeTree.validateResource(requestedName, focusNodeUrls, resourceType, bodyGraph); if (Boolean.TRUE.equals(result.getValid())) { // Return a successful validation result, including the matching shape tree return new ValidationResult(true, this, targetShapeTree, result.getMatchingFocusNode()); @@ -239,7 +234,7 @@ public ValidationResult validateContainedResource(String requestedName, ShapeTre if (containsShapeTree == null) { continue; } // Continue if the shape tree isn't gettable // Evaluate the shape tree against the attributes of the proposed resources - ValidationResult result = containsShapeTree.validateResource(requestedName, resourceType, bodyGraph, focusNodeUrls); + ValidationResult result = containsShapeTree.validateResource(requestedName, focusNodeUrls, resourceType, bodyGraph); // Continue if the proposed attributes were not a match if (Boolean.FALSE.equals(result.getValid())) { continue; } // Return the successful validation result @@ -251,14 +246,6 @@ public ValidationResult validateContainedResource(String requestedName, ShapeTre } - public Iterator getReferencedShapeTrees() throws ShapeTreeException { - return getReferencedShapeTrees(RecursionMethods.DEPTH_FIRST); - } - - public Iterator getReferencedShapeTrees(RecursionMethods recursionMethods) throws ShapeTreeException { - return getReferencedShapeTreesList(recursionMethods).iterator(); - } - // Return the list of shape tree contains by priority from most to least strict public List getPrioritizedContains() { @@ -268,6 +255,14 @@ public List getPrioritizedContains() { } + public Iterator getReferencedShapeTrees() throws ShapeTreeException { + return getReferencedShapeTrees(RecursionMethods.DEPTH_FIRST); + } + + public Iterator getReferencedShapeTrees(RecursionMethods recursionMethods) throws ShapeTreeException { + return getReferencedShapeTreesList(recursionMethods).iterator(); + } + private List getReferencedShapeTreesList(RecursionMethods recursionMethods) throws ShapeTreeException { if (recursionMethods.equals(RecursionMethods.BREADTH_FIRST)) { return getReferencedShapeTreesListBreadthFirst(); diff --git a/shapetrees-java-core/src/main/java/com/janeirodigital/shapetrees/core/ShapeTreeFactory.java b/shapetrees-java-core/src/main/java/com/janeirodigital/shapetrees/core/ShapeTreeFactory.java index 0503440e..1861b638 100644 --- a/shapetrees-java-core/src/main/java/com/janeirodigital/shapetrees/core/ShapeTreeFactory.java +++ b/shapetrees-java-core/src/main/java/com/janeirodigital/shapetrees/core/ShapeTreeFactory.java @@ -121,27 +121,20 @@ private ShapeTreeFactory() { } ArrayList references = new ArrayList<>(); Property referencesProperty = resourceModel.createProperty(ShapeTreeVocabulary.REFERENCES); - if (shapeTreeNode.hasProperty(referencesProperty)) { - List referenceStatements = shapeTreeNode.listProperties(referencesProperty).toList(); + if (shapeTreeNode.hasProperty(referencesProperty)) { // TODO: arbitrarily pics from n objects where 1 expected + List referenceStatements = shapeTreeNode.listProperties(referencesProperty).toList(); // TODO: test coverage (never hit) for (Statement referenceStatement : referenceStatements) { Resource referenceResource = referenceStatement.getObject().asResource(); - final String referencedShapeTreeUrlString = getStringValue(resourceModel, referenceResource, ShapeTreeVocabulary.REFERENCES_SHAPE_TREE); - final URL referencedShapeTreeUrl; - ShapeTreeReference referencedShapeTree; - - try { - referencedShapeTreeUrl = new URL(referencedShapeTreeUrlString); - } catch (MalformedURLException ex) { - throw new ShapeTreeException(500, "ShapeTree <" + shapeTreeUrl + "> references malformed URL <" + referencedShapeTreeUrlString + ">: " + ex.getMessage()); + final URL referencedShapeTreeUrl = getUrlValue(resourceModel, referenceResource, ShapeTreeVocabulary.REFERENCES_SHAPE_TREE, shapeTreeUrl); + if (referencedShapeTreeUrl == null) { + throw new ShapeTreeException(400, "expected <" + shapeTreeUrl + "> reference " + referenceResource.toString() + " to have one <" + ShapeTreeVocabulary.REFERENCES_SHAPE_TREE + "> property"); } String viaShapePath = getStringValue(resourceModel, referenceResource, ShapeTreeVocabulary.VIA_SHAPE_PATH); URL viaPredicate = getUrlValue(resourceModel, referenceResource, ShapeTreeVocabulary.VIA_PREDICATE, shapeTreeUrl); - referencedShapeTree = new ShapeTreeReference(referencedShapeTreeUrl, viaShapePath, viaPredicate); - - references.add(referencedShapeTree); + references.add(new ShapeTreeReference(referencedShapeTreeUrl, viaShapePath, viaPredicate)); } } return references; diff --git a/shapetrees-java-core/src/main/java/com/janeirodigital/shapetrees/core/ShapeTreeManager.java b/shapetrees-java-core/src/main/java/com/janeirodigital/shapetrees/core/ShapeTreeManager.java index 6a8a6760..ce6ecf7f 100644 --- a/shapetrees-java-core/src/main/java/com/janeirodigital/shapetrees/core/ShapeTreeManager.java +++ b/shapetrees-java-core/src/main/java/com/janeirodigital/shapetrees/core/ShapeTreeManager.java @@ -34,7 +34,7 @@ public class ShapeTreeManager { private final URL id; // Each ShapeTreeManager has one or more ShapeTreeAssignments - private final List assignments = new ArrayList<>(); + private final List assignments = new ArrayList<>(); // TODO: try Map, makes getContainingAssignments() redundant against getAssignments() /** * Constructor for a new ShapeTreeManager @@ -185,7 +185,7 @@ public static ShapeTreeManager getFromGraph(URL id, Graph managerGraph) throws S } else if (managerTriples.isEmpty()) { // Given the fact that a manager resource exists, there should never be a case where the manager resource // exists but no manager is found inside of it. - throw new IllegalStateException("No ShapeTreeManager instances found: " + managerTriples.size()); + throw new IllegalStateException("No ShapeTreeManager instances found: " + managerTriples.size()); // TODO: isn't that always 0? } // Get the URL of the ShapeTreeManager subject node @@ -214,7 +214,7 @@ public static ShapeTreeManager getFromGraph(URL id, Graph managerGraph) throws S } - public ShapeTreeAssignment getAssignmentForShapeTree(URL shapeTreeUrl) { + public ShapeTreeAssignment getAssignmentForShapeTree(URL shapeTreeUrl) { // TODO: return list of assignments with same ST but different roots if (this.assignments.isEmpty()) { return null; } diff --git a/shapetrees-java-core/src/main/java/com/janeirodigital/shapetrees/core/ShapeTreeManagerDelta.java b/shapetrees-java-core/src/main/java/com/janeirodigital/shapetrees/core/ShapeTreeManagerDelta.java index fed8779f..9d5f2ab7 100644 --- a/shapetrees-java-core/src/main/java/com/janeirodigital/shapetrees/core/ShapeTreeManagerDelta.java +++ b/shapetrees-java-core/src/main/java/com/janeirodigital/shapetrees/core/ShapeTreeManagerDelta.java @@ -101,7 +101,7 @@ public static ShapeTreeAssignment containsSameUrl(ShapeTreeAssignment assignment } public boolean allRemoved() { - return (!this.isUpdated() && this.removedAssignments.size() == this.existingManager.getAssignments().size()); + return (!this.isUpdated() && this.removedAssignments != null && this.removedAssignments.size() == this.existingManager.getAssignments().size()); } public boolean isUpdated() { diff --git a/shapetrees-java-core/src/main/java/com/janeirodigital/shapetrees/core/ShapeTreeRequestHandler.java b/shapetrees-java-core/src/main/java/com/janeirodigital/shapetrees/core/ShapeTreeRequestHandler.java index 192ba8a4..c65a7185 100644 --- a/shapetrees-java-core/src/main/java/com/janeirodigital/shapetrees/core/ShapeTreeRequestHandler.java +++ b/shapetrees-java-core/src/main/java/com/janeirodigital/shapetrees/core/ShapeTreeRequestHandler.java @@ -31,8 +31,8 @@ public ShapeTreeRequestHandler(ResourceAccessor resourceAccessor) { public DocumentResponse manageShapeTree(ManageableInstance manageableInstance, ShapeTreeRequest shapeTreeRequest) throws ShapeTreeException { Optional validationResponse; - ShapeTreeManager updatedRootManager = RequestHelper.getIncomingShapeTreeManager(shapeTreeRequest, manageableInstance.getManagerResource()); - ShapeTreeManager existingRootManager = manageableInstance.getManagerResource().getManager(); + ShapeTreeManager updatedRootManager = RequestHelper.getIncomingShapeTreeManager(shapeTreeRequest, manageableInstance.getManagerResource()); // TODO: could be null + ShapeTreeManager existingRootManager = manageableInstance.getManagerResource().getManager(); // TODO: could be null // Determine assignments that have been removed, added, and/or updated ShapeTreeManagerDelta delta = ShapeTreeManagerDelta.evaluate(existingRootManager, updatedRootManager); @@ -131,7 +131,7 @@ public Optional createShapeTreeInstance(ManageableInstance man // if any of the provided focus nodes weren't matched validation must fail List unmatchedNodes = getUnmatchedFocusNodes(validationResults.values(), incomingFocusNodes); - if (!unmatchedNodes.isEmpty()) { return failValidation(new ValidationResult(false, "Failed to match target focus nodes: " + unmatchedNodes)); } + if (!unmatchedNodes.isEmpty()) { return failValidation(new ValidationResult(false, null,"Failed to match target focus nodes: " + unmatchedNodes)); } log.debug("Creating shape tree instance at {}", targetResourceUrl); @@ -168,7 +168,8 @@ public Optional updateShapeTreeInstance(ManageableInstance tar // All must pass for the update to validate ShapeTree shapeTree = ShapeTreeFactory.getShapeTree(assignment.getShapeTree()); URL managedResourceUrl = targetResource.getManageableResource().getUrl(); - ValidationResult validationResult = shapeTree.validateResource(null, shapeTreeRequest.getResourceType(), RequestHelper.getIncomingBodyGraph(shapeTreeRequest, managedResourceUrl, targetResource.getManageableResource()), RequestHelper.getIncomingFocusNodes(shapeTreeRequest, managedResourceUrl)); + Graph bodyGraph = RequestHelper.getIncomingBodyGraph(shapeTreeRequest, managedResourceUrl, targetResource.getManageableResource()); // TODO: could be null + ValidationResult validationResult = shapeTree.validateResource(null, RequestHelper.getIncomingFocusNodes(shapeTreeRequest, managedResourceUrl), shapeTreeRequest.getResourceType(), bodyGraph); if (Boolean.FALSE.equals(validationResult.isValid())) { return failValidation(validationResult); } } @@ -323,6 +324,7 @@ private ShapeTreeManager getManagerForAssignment(ManageableInstance manageableIn URL managerResourceUrl = manageableInstance.getManagerResource().getUrl(); // When at the top of the plant hierarchy, use the root manager from the initial plant request body + // TODO: rootManager can be null so method could return null (does not in any test) if (atRootOfPlantHierarchy(rootAssignment, manageableInstance.getManageableResource())) { return rootManager; } if (!manageableInstance.getManagerResource().isExists()) { @@ -383,7 +385,7 @@ private ShapeTreeManager getRootManager(ShapeTreeContext shapeTreeContext, Shape // Return a root shape tree manager associated with a given shape tree assignment private ShapeTreeAssignment getRootAssignment(ShapeTreeContext shapeTreeContext, ShapeTreeAssignment assignment) throws ShapeTreeException { - ShapeTreeManager rootManager = getRootManager(shapeTreeContext, assignment); + ShapeTreeManager rootManager = getRootManager(shapeTreeContext, assignment); // TODO: could be null for (ShapeTreeAssignment rootAssignment : rootManager.getAssignments()) { if (rootAssignment.getUrl() != null && rootAssignment.getUrl().equals(assignment.getRootAssignment())) { @@ -485,7 +487,8 @@ private DocumentResponse successfulValidation() { } private Optional failValidation(ValidationResult validationResult) { - return Optional.of(new DocumentResponse(new ResourceAttributes(), validationResult.getMessage(),422)); + String message = validationResult.getMessage() != null ? validationResult.getMessage() : "Unspecified validation failure"; + return Optional.of(new DocumentResponse(new ResourceAttributes(), message,422)); } } diff --git a/shapetrees-java-core/src/main/java/com/janeirodigital/shapetrees/core/ValidationResult.java b/shapetrees-java-core/src/main/java/com/janeirodigital/shapetrees/core/ValidationResult.java index 316bbf1e..4b631ad3 100644 --- a/shapetrees-java-core/src/main/java/com/janeirodigital/shapetrees/core/ValidationResult.java +++ b/shapetrees-java-core/src/main/java/com/janeirodigital/shapetrees/core/ValidationResult.java @@ -19,24 +19,6 @@ public Boolean isValid() { return (this.valid != null && this.valid); } - public ValidationResult(Boolean valid, String message) { - this.valid = valid; - this.message = message; - this.validatingShapeTree = null; - this.matchingShapeTree = null; - this.managingAssignment = null; - this.matchingFocusNode = null; - } - - public ValidationResult(Boolean valid, ShapeTree validatingShapeTree) { - this.valid = valid; - this.message = null; - this.validatingShapeTree = validatingShapeTree; - this.matchingShapeTree = null; - this.managingAssignment = null; - this.matchingFocusNode = null; - } - public ValidationResult(Boolean valid, ShapeTree validatingShapeTree, String message) { this.valid = valid; this.message = message; @@ -46,15 +28,6 @@ public ValidationResult(Boolean valid, ShapeTree validatingShapeTree, String mes this.matchingFocusNode = null; } - public ValidationResult(Boolean valid, ShapeTree validatingShapeTree, URL matchingFocusNode) { - this.valid = valid; - this.message = null; - this.validatingShapeTree = validatingShapeTree; - this.matchingShapeTree = null; - this.managingAssignment = null; - this.matchingFocusNode = matchingFocusNode; - } - public ValidationResult(Boolean valid, ShapeTree validatingShapeTree, ShapeTree matchingShapeTree, URL matchingFocusNode) { this.valid = valid; this.message = null; diff --git a/shapetrees-java-tests/src/test/java/com/janeirodigital/shapetrees/tests/ShapeTreeValidationTests.java b/shapetrees-java-tests/src/test/java/com/janeirodigital/shapetrees/tests/ShapeTreeValidationTests.java index 7f64cd6b..4ca5fedc 100644 --- a/shapetrees-java-tests/src/test/java/com/janeirodigital/shapetrees/tests/ShapeTreeValidationTests.java +++ b/shapetrees-java-tests/src/test/java/com/janeirodigital/shapetrees/tests/ShapeTreeValidationTests.java @@ -67,13 +67,13 @@ void validateExpectsContainerType() { ValidationResult result; ShapeTree shapeTree = ShapeTreeFactory.getShapeTree(toUrl(server, "/static/shapetrees/validation/shapetree#ExpectsContainerTree")); - result = shapeTree.validateResource(null, ShapeTreeResourceType.CONTAINER, null, null); + result = shapeTree.validateResource(null, null, ShapeTreeResourceType.CONTAINER, null); Assertions.assertTrue(result.isValid()); - result = shapeTree.validateResource(null, ShapeTreeResourceType.RESOURCE, null, null); + result = shapeTree.validateResource(null, null, ShapeTreeResourceType.RESOURCE, null); Assertions.assertFalse(result.isValid()); - result = shapeTree.validateResource(null, ShapeTreeResourceType.NON_RDF, null, null); + result = shapeTree.validateResource(null, null, ShapeTreeResourceType.NON_RDF, null); Assertions.assertFalse(result.isValid()); } @@ -86,13 +86,13 @@ void validateExpectsResourceType() { ValidationResult result; ShapeTree shapeTree = ShapeTreeFactory.getShapeTree(toUrl(server, "/static/shapetrees/validation/shapetree#ExpectsResourceTree")); - result = shapeTree.validateResource(null, ShapeTreeResourceType.RESOURCE, null, null); + result = shapeTree.validateResource(null, null, ShapeTreeResourceType.RESOURCE, null); Assertions.assertTrue(result.isValid()); - result = shapeTree.validateResource(null, ShapeTreeResourceType.CONTAINER, null, null); + result = shapeTree.validateResource(null, null, ShapeTreeResourceType.CONTAINER, null); Assertions.assertFalse(result.isValid()); - result = shapeTree.validateResource(null, ShapeTreeResourceType.NON_RDF, null, null); + result = shapeTree.validateResource(null, null, ShapeTreeResourceType.NON_RDF, null); Assertions.assertFalse(result.isValid()); } @@ -105,13 +105,13 @@ void validateExpectsNonRDFResourceType() { ValidationResult result; ShapeTree shapeTree = ShapeTreeFactory.getShapeTree(toUrl(server, "/static/shapetrees/validation/shapetree#ExpectsNonRDFResourceTree")); - result = shapeTree.validateResource(null, ShapeTreeResourceType.NON_RDF, null, null); + result = shapeTree.validateResource(null, null, ShapeTreeResourceType.NON_RDF, null); Assertions.assertTrue(result.isValid()); - result = shapeTree.validateResource(null, ShapeTreeResourceType.RESOURCE, null, null); + result = shapeTree.validateResource(null, null, ShapeTreeResourceType.RESOURCE, null); Assertions.assertFalse(result.isValid()); - result = shapeTree.validateResource(null, ShapeTreeResourceType.CONTAINER, null, null); + result = shapeTree.validateResource(null, null, ShapeTreeResourceType.CONTAINER, null); Assertions.assertFalse(result.isValid()); } @@ -124,10 +124,10 @@ void validateLabel() { ValidationResult result; ShapeTree shapeTree = ShapeTreeFactory.getShapeTree(toUrl(server, "/static/shapetrees/validation/shapetree#LabelTree")); - result = shapeTree.validateResource("resource-name", ShapeTreeResourceType.RESOURCE, null, null); + result = shapeTree.validateResource("resource-name", null, ShapeTreeResourceType.RESOURCE, null); Assertions.assertTrue(result.isValid()); - result = shapeTree.validateResource("invalid-name", ShapeTreeResourceType.RESOURCE, null, null); + result = shapeTree.validateResource("invalid-name", null, ShapeTreeResourceType.RESOURCE, null); Assertions.assertFalse(result.isValid()); } @@ -143,11 +143,11 @@ void validateShape() { // Validate shape with focus node List focusNodeUrls = List.of(toUrl(server, "/validation/valid-resource#foo")); - result = shapeTree.validateResource(null, ShapeTreeResourceType.RESOURCE, getFooBodyGraph(toUrl(server, "/validation/valid-resource")), focusNodeUrls); + result = shapeTree.validateResource(null, focusNodeUrls, ShapeTreeResourceType.RESOURCE, getFooBodyGraph(toUrl(server, "/validation/valid-resource"))); Assertions.assertTrue(result.isValid()); // Validate shape without focus node - result = shapeTree.validateResource(null, ShapeTreeResourceType.RESOURCE, getFooBodyGraph(toUrl(server, "/validation/valid-resource")), null); + result = shapeTree.validateResource(null, null, ShapeTreeResourceType.RESOURCE, getFooBodyGraph(toUrl(server, "/validation/valid-resource"))); Assertions.assertTrue(result.isValid()); } @@ -164,7 +164,7 @@ void failToValidateShape() { // Pass in body content that will fail validation of the shape associated with FooTree List focusNodeUrls = List.of(toUrl(server,"/validation/valid-resource#foo")); - result = shapeTree.validateResource(null, ShapeTreeResourceType.RESOURCE, getInvalidFooBodyGraph(toUrl(server, "/validation/valid-resource")), focusNodeUrls); + result = shapeTree.validateResource(null, focusNodeUrls, ShapeTreeResourceType.RESOURCE, getInvalidFooBodyGraph(toUrl(server, "/validation/valid-resource"))); Assertions.assertFalse(result.isValid()); } @@ -183,7 +183,7 @@ void failToValidateMissingShape() { // Catch exception thrown when a shape in a shape tree cannot be found List focusNodeUrls = List.of(toUrl(server,"/validation/valid-resource#foo")); - Assertions.assertThrows(ShapeTreeException.class, () -> shapeTree.validateResource(null, ShapeTreeResourceType.RESOURCE, fooBodyGraph, focusNodeUrls)); + Assertions.assertThrows(ShapeTreeException.class, () -> shapeTree.validateResource(null, focusNodeUrls, ShapeTreeResourceType.RESOURCE, fooBodyGraph)); } @@ -201,7 +201,7 @@ void failToValidateMalformedShape() { // Catch exception thrown when a shape in a shape tree is invalid List focusNodeUrls = List.of(toUrl(server,"/validation/valid-resource#foo")); - Assertions.assertThrows(ShapeTreeException.class, () -> shapeTree.validateResource(null, ShapeTreeResourceType.RESOURCE, fooBodyGraph, focusNodeUrls)); + Assertions.assertThrows(ShapeTreeException.class, () -> shapeTree.validateResource(null, focusNodeUrls, ShapeTreeResourceType.RESOURCE, fooBodyGraph)); } @@ -238,7 +238,7 @@ void validateShapeBeforeCaching() { // Validate shape with focus node List focusNodeUrls = List.of(toUrl(server,"/validation/valid-resource#foo")); - result = shapeTree.validateResource(null, ShapeTreeResourceType.RESOURCE, getFooBodyGraph(toUrl(server, "/validation/valid-resource")), focusNodeUrls); + result = shapeTree.validateResource(null, focusNodeUrls, ShapeTreeResourceType.RESOURCE, getFooBodyGraph(toUrl(server, "/validation/valid-resource"))); Assertions.assertTrue(result.isValid()); } @@ -258,7 +258,7 @@ void validateShapeAfterCaching() { // Validate shape with focus node List focusNodeUrls = List.of(toUrl(server,"/validation/valid-resource#foo")); - result = shapeTree.validateResource(null, ShapeTreeResourceType.RESOURCE, getFooBodyGraph(toUrl(server, "/validation/valid-resource")), focusNodeUrls); + result = shapeTree.validateResource(null, focusNodeUrls, ShapeTreeResourceType.RESOURCE, getFooBodyGraph(toUrl(server, "/validation/valid-resource"))); Assertions.assertTrue(result.isValid()); } diff --git a/shapetrees-java-tests/src/test/java/com/janeirodigital/shapetrees/tests/clienthttp/AbstractHttpClientProjectTests.java b/shapetrees-java-tests/src/test/java/com/janeirodigital/shapetrees/tests/clienthttp/AbstractHttpClientProjectTests.java index 576265e0..953e62fd 100644 --- a/shapetrees-java-tests/src/test/java/com/janeirodigital/shapetrees/tests/clienthttp/AbstractHttpClientProjectTests.java +++ b/shapetrees-java-tests/src/test/java/com/janeirodigital/shapetrees/tests/clienthttp/AbstractHttpClientProjectTests.java @@ -259,7 +259,7 @@ void updateProjectInProjects() { // Update the project-1 container as a shape tree instance. // 1. Will be validated by the parent ProjectCollectionTree planted on /data/projects/ // 2. Will have a manager/assignment created for it as an instance of ProjectTree - DocumentResponse response = shapeTreeClient.putManagedInstance(context, targetResource, focusNodes, getProjectOneUpdatedBodyGraph(), TEXT_TURTLE); + DocumentResponse response = shapeTreeClient.updateManagedInstance(context, targetResource, focusNodes, getProjectOneUpdatedBodyGraph(), TEXT_TURTLE); Assertions.assertEquals(200, response.getStatusCode()); } @@ -284,7 +284,7 @@ void failToCreateMalformedProject() { // Create the project-1 container as a shape tree instance via PUT // 1. Will be validated by the parent ProjectCollectionTree planted on /data/projects/ - DocumentResponse response = shapeTreeClient.putManagedInstance(context, targetResource, focusNodes, targetShapeTrees, true, getProjectOneMalformedBodyGraph(), TEXT_TURTLE); + DocumentResponse response = shapeTreeClient.putManagedInstance(context, targetResource, focusNodes, getProjectOneMalformedBodyGraph(), TEXT_TURTLE, targetShapeTrees, true); // 2. Will fail validation because the body content doesn't validate against the assigned shape Assertions.assertEquals(422, response.getStatusCode()); @@ -313,7 +313,7 @@ void failToUpdateMalformedProject() { // Update the project-1 container as a shape tree instance via PUT // 1. Will be validated by the parent ProjectCollectionTree planted on /data/projects/ - DocumentResponse response = shapeTreeClient.putManagedInstance(context, targetResource, focusNodes, getProjectOneMalformedBodyGraph(), TEXT_TURTLE); + DocumentResponse response = shapeTreeClient.updateManagedInstance(context, targetResource, focusNodes, getProjectOneMalformedBodyGraph(), TEXT_TURTLE); // 2. Will fail validation because the body content doesn't validate against the assigned shape Assertions.assertEquals(422, response.getStatusCode()); @@ -346,7 +346,7 @@ void createMilestoneInProjectWithPut() { // Create the milestone-3 container in /projects/project-1/ as a shape tree instance using PUT to create // 1. Will be validated by the parent ProjectTree planted on /data/projects/project-1/ // 2. Will have a manager/assignment created for it as an instance of MilestoneTree - DocumentResponse response = shapeTreeClient.putManagedInstance(context, targetResource, focusNodes, targetShapeTrees, true, getMilestoneThreeBodyGraph(), TEXT_TURTLE); + DocumentResponse response = shapeTreeClient.putManagedInstance(context, targetResource, focusNodes, getMilestoneThreeBodyGraph(), TEXT_TURTLE, targetShapeTrees, true); Assertions.assertEquals(201, response.getStatusCode()); } @@ -540,7 +540,7 @@ void createAttachmentInTask() { URL targetResource = toUrl(server, "/data/projects/project-1/milestone-3/task-48/attachment-48"); List targetShapeTrees = Arrays.asList(toUrl(server, "/static/shapetrees/project/shapetree#AttachmentTree")); - DocumentResponse response = shapeTreeClient.putManagedInstance(context, targetResource, null, targetShapeTrees, false, null, "application/octet-stream"); + DocumentResponse response = shapeTreeClient.putManagedInstance(context, targetResource, null, null, "application/octet-stream", targetShapeTrees, false); Assertions.assertEquals(201, response.getStatusCode()); } @@ -575,7 +575,7 @@ void createSecondAttachmentInTask() { URL targetResource = toUrl(server, "/data/projects/project-1/milestone-3/task-48/random.png"); List targetShapeTrees = Arrays.asList(toUrl(server, "/static/shapetrees/project/shapetree#AttachmentTree")); - DocumentResponse response = shapeTreeClient.putManagedInstance(context, targetResource, null, targetShapeTrees, false, null, "application/octet-stream"); + DocumentResponse response = shapeTreeClient.putManagedInstance(context, targetResource, null, null, "application/octet-stream", targetShapeTrees, false); Assertions.assertEquals(201, response.getStatusCode()); } From 51e4fa05ced3fcf4d78277db9be92599fc987ed4 Mon Sep 17 00:00:00 2001 From: Eric Prud'hommeaux Date: Fri, 24 Dec 2021 19:08:41 +0100 Subject: [PATCH 11/15] ~ catching typescript up with .java changes --- .../client-core/src/ShapeTreeClient.ts | 8 +-- .../client-http/src/HttpResourceAccessor.ts | 38 ++++++----- .../client-http/src/HttpShapeTreeClient.ts | 18 +++-- .../packages/core/src/DocumentResponse.ts | 2 +- .../packages/core/src/ManageableResource.ts | 6 +- .../core/src/MissingManagerResource.ts | 10 +-- .../packages/core/src/ResourceAttributes.ts | 1 + asTypescript/packages/core/src/ShapeTree.ts | 66 +++++++++---------- .../packages/core/src/ShapeTreeAssignment.ts | 6 +- .../packages/core/src/ShapeTreeFactory.ts | 31 ++++----- .../packages/core/src/ShapeTreeManager.ts | 15 +++-- .../core/src/ShapeTreeManagerDelta.ts | 8 +-- .../core/src/ShapeTreeRequestHandler.ts | 13 +++- .../packages/core/src/ValidationResult.ts | 27 -------- .../HttpExternalDocumentLoader.ts | 16 ++--- .../packages/core/src/helpers/GraphHelper.ts | 24 +++---- .../core/src/helpers/RequestHelper.ts | 18 ++--- .../packages/javahttp/src/JavaHttpClient.ts | 28 ++++---- .../javahttp/src/JavaHttpClientFactory.ts | 6 +- .../JavaHttpValidatingShapeTreeInterceptor.ts | 20 +++--- 20 files changed, 175 insertions(+), 186 deletions(-) diff --git a/asTypescript/packages/client-core/src/ShapeTreeClient.ts b/asTypescript/packages/client-core/src/ShapeTreeClient.ts index 960498a8..709875d5 100644 --- a/asTypescript/packages/client-core/src/ShapeTreeClient.ts +++ b/asTypescript/packages/client-core/src/ShapeTreeClient.ts @@ -79,14 +79,14 @@ export interface ShapeTreeClient { * @param context ShapeTreeContext that would be used for authentication purposes * @param targetResource The target resource to be created or updated * @param focusNodes One or more nodes/subjects to use as the focus for shape validation - * @param targetShapeTrees The shape trees that a proposed resource to be created should be validated against - * @param isContainer Specifies whether a newly created resource should be created as a container or not * @param bodyString String representation of the body of the resource to create or update * @param contentType Content type to parse the bodyString parameter as + * @param targetShapeTrees The shape trees that a proposed resource to be created should be validated against + * @param isContainer Specifies whether a newly created resource should be created as a container or not * @return DocumentResponse containing status and response header / attributes * @throws ShapeTreeException */ - putManagedInstance(context: ShapeTreeContext, targetResource: URL, focusNodes: Array, targetShapeTrees: Array, isContainer: boolean, bodyString: string, contentType: string): DocumentResponse /* throws ShapeTreeException */; + putManagedInstance(context: ShapeTreeContext, targetResource: URL, focusNodes: Array, bodyString: string, contentType: string, targetShapeTrees: Array, isContainer: boolean): DocumentResponse /* throws ShapeTreeException */; /** * Updates a resource via HTTP PUT that has been validated against an associated shape tree @@ -98,7 +98,7 @@ export interface ShapeTreeClient { * @return DocumentResponse containing status and response header / attributes * @throws ShapeTreeException */ - putManagedInstance(context: ShapeTreeContext, targetResource: URL, focusNodes: Array, bodyString: string, contentType: string): DocumentResponse /* throws ShapeTreeException */; + updateManagedInstance(context: ShapeTreeContext, targetResource: URL, focusNodes: Array, bodyString: string, contentType: string): DocumentResponse /* throws ShapeTreeException */; /** * Updates a resource via HTTP PATCH that has been validated against an associated shape tree diff --git a/asTypescript/packages/client-http/src/HttpResourceAccessor.ts b/asTypescript/packages/client-http/src/HttpResourceAccessor.ts index 921669ee..ec525f2b 100644 --- a/asTypescript/packages/client-http/src/HttpResourceAccessor.ts +++ b/asTypescript/packages/client-http/src/HttpResourceAccessor.ts @@ -80,7 +80,7 @@ export class HttpResourceAccessor implements ResourceAccessor { * @return {@link ManageableInstance} including {@link MissingManageableResource} and {@link MissingManagerResource} */ private getInstanceFromMissingManageableResource(context: ShapeTreeContext, missing: MissingManageableResource): ManageableInstance { - let missingManager: MissingManagerResource = new MissingManagerResource(missing, null); + let missingManager: MissingManagerResource = new MissingManagerResource(missing.getUrl(), missing); return new ManageableInstance(context, this, false, missing, missingManager); } @@ -275,27 +275,31 @@ export class HttpResourceAccessor implements ResourceAccessor { */ private generateResource(url: URL, response: DocumentResponse): InstanceResource /* throws ShapeTreeException */ { // If a resource was created, ensure the URL returned in the Location header is valid - let location: string | null = response.getResourceAttributes().firstValue(HttpHeaders.LOCATION.getValue()); + let location: string | null = response.getResourceAttributes() === null ? Optional.empty() : response.getResourceAttributes().firstValue(HttpHeaders.LOCATION.getValue()); if (location.isPresent()) { try { url = new URL(location.get()); } catch (e) { if (e instanceof MalformedURLException) { - throw new ShapeTreeException(500, "Retrieving <" + url + "> yielded a Location header \"" + location.get() + "\" which doesn't parse as a URL: " + e.getMessage()); - }} - + throw new ShapeTreeException(500, "Retrieving <" + url + "> yielded a Location header \"" + location.get() + "\" which doesn't parse as a URL: " + e.getMessage()); + } +} } // Determine whether the resource exists based on the response. Even if the resource // doesn't exist, additional context and processing is done to provide the appropriate // typed resource with adequate context to the caller const exists: boolean = response.isExists(); const container: boolean = isContainerFromHeaders(response.getResourceAttributes(), url); + // TODO: could be null const attributes: ResourceAttributes = response.getResourceAttributes(); const resourceType: ShapeTreeResourceType = getResourceTypeFromHeaders(response.getResourceAttributes()); const name: string = calculateName(url); + // TODO: could be null. readStringIntoModel not set up for `rawContent=null` const body: string = response.getBody(); if (response.getBody() === null) { log.error("Could not retrieve the body string from response for " + url); + // TODO: remove when TODO above is resolved. + throw new IllegalStateException("Could not retrieve the body string from response for <" + url + ">"); } // Parse Link headers from response and populate ResourceAttributes const linkHeaders: Array = attributes.allValues(HttpHeaders.LINK.getValue()); @@ -308,7 +312,7 @@ export class HttpResourceAccessor implements ResourceAccessor { if (exists) { return new ManagerResource(url, resourceType, attributes, body, name, true, managedResourceUrl); } else { - return new MissingManagerResource(url, resourceType, attributes, body, name, managedResourceUrl); + return new MissingManagerResource(managedResourceUrl, url, resourceType, attributes, body, name); } } else { // Look for presence of st:managedBy in link headers from response and get the target manager URL @@ -354,9 +358,9 @@ export class HttpResourceAccessor implements ResourceAccessor { return containedInstances; } catch (ex) { if (ex instanceof Exception) { - throw new ShapeTreeException(500, ex.getMessage()); - }} - + throw new ShapeTreeException(500, ex.getMessage()); + } +} } /** @@ -403,7 +407,7 @@ export class HttpResourceAccessor implements ResourceAccessor { * @return True if headers indicating a container are found */ private isContainerFromHeaders(headers: ResourceAttributes, url: URL): boolean { - let linkHeaders: Array = headers.allValues(HttpHeaders.LINK.getValue()); + let linkHeaders: Array = headers === null ? Collections.emptyList() : headers.allValues(HttpHeaders.LINK.getValue()); if (linkHeaders.isEmpty()) { return url.getPath().endsWith("/"); } @@ -421,7 +425,7 @@ export class HttpResourceAccessor implements ResourceAccessor { * @return Type of resource */ private getResourceTypeFromHeaders(headers: ResourceAttributes): ShapeTreeResourceType { - let linkHeaders: Array = headers.allValues(HttpHeaders.LINK.getValue()); + let linkHeaders: Array = headers === null ? null : headers.allValues(HttpHeaders.LINK.getValue()); if (linkHeaders === null) { return null; } @@ -457,9 +461,9 @@ export class HttpResourceAccessor implements ResourceAccessor { return Optional.of(new URL(url, managerUrlString)); } catch (e) { if (e instanceof MalformedURLException) { - throw new ShapeTreeException(500, "Malformed relative URL <" + managerUrlString + "> (resolved from <" + url + ">)"); - }} - + throw new ShapeTreeException(500, "Malformed relative URL <" + managerUrlString + "> (resolved from <" + url + ">)"); + } +} } /** @@ -487,9 +491,9 @@ export class HttpResourceAccessor implements ResourceAccessor { managedResourceUrl = new URL(managerUrl, managedUrlString); } catch (e) { if (e instanceof MalformedURLException) { - throw new ShapeTreeException(500, "Can't calculate managed resource for shape tree manager <" + managerUrl + ">"); - }} - + throw new ShapeTreeException(500, "Can't calculate managed resource for shape tree manager <" + managerUrl + ">"); + } +} return managedResourceUrl; } diff --git a/asTypescript/packages/client-http/src/HttpShapeTreeClient.ts b/asTypescript/packages/client-http/src/HttpShapeTreeClient.ts index ced8a4c7..b0d45f87 100644 --- a/asTypescript/packages/client-http/src/HttpShapeTreeClient.ts +++ b/asTypescript/packages/client-http/src/HttpShapeTreeClient.ts @@ -14,7 +14,10 @@ import { LinkRelations } from '@shapetrees/enums/LinkRelations'; import { ShapeTreeException } from '@shapetrees/exceptions/ShapeTreeException'; import * as Lang from 'org/apache/jena/riot'; import * as RDFDataMgr from 'org/apache/jena/riot'; +import * as RDFWriter from 'org/apache/jena/riot'; +import * as RIOT from 'org/apache/jena/riot'; import { Writable } from 'stream'; +import * as StandardCharsets from 'java/nio/charset'; import { HttpRequest } from './HttpRequest'; import { HttpResourceAccessor } from './HttpResourceAccessor'; import { HttpClient } from './HttpClient'; @@ -99,11 +102,12 @@ export class HttpShapeTreeClient implements ShapeTreeClient { let instance: ManageableInstance = resourceAccessor.getInstance(context, targetResource); let manageableResource: ManageableResource = instance.getManageableResource(); if (!manageableResource.isExists()) { - return new DocumentResponse(null, "Cannot find target resource to plant: " + targetResource, 404); + return new DocumentResponse(new ResourceAttributes(), "Cannot find target resource to plant: " + targetResource, 404); } let manager: ShapeTreeManager; let managerResourceUrl: URL = instance.getManagerResource().getUrl(); if (instance.isManaged()) { + // TODO: could be null manager = instance.getManagerResource().getManager(); } else { manager = new ShapeTreeManager(managerResourceUrl); @@ -114,13 +118,14 @@ export class HttpShapeTreeClient implements ShapeTreeClient { // Add the assignment to the manager manager.addAssignment(assignment); // Get an RDF version of the manager stored in a turtle string - let sw: Writable = new Writable(); - RDFDataMgr.write(sw, manager.getGraph(), Lang.TURTLE); + let asBytes: ByteArrayOutputStream = new ByteArrayOutputStream(); + RDFWriter.create().base(targetShapeTree.toString()).set(RIOT.symTurtleOmitBase, false).set(RIOT.symTurtleDirectiveStyle, "rdf11").lang(Lang.TTL).source(manager.getGraph()).output(asBytes); + let asString: string = new string(asBytes.toByteArray(), StandardCharsets.UTF_8); // Build an HTTP PUT request with the manager graph in turtle as the content body + link header let fetcher: HttpClient = HttpClientFactoryManager.getFactory().get(this.useClientShapeTreeValidation); let headers: ResourceAttributes = new ResourceAttributes(); headers.maybeSet(HttpHeaders.AUTHORIZATION.getValue(), context.getAuthorizationHeaderValue()); - return fetcher.fetchShapeTreeResponse(new HttpRequest("PUT", managerResourceUrl, headers, sw.toString(), "text/turtle")); + return fetcher.fetchShapeTreeResponse(new HttpRequest("PUT", managerResourceUrl, headers, asString, "text/turtle")); } override public postManagedInstance(context: ShapeTreeContext, parentContainer: URL, focusNodes: Array, targetShapeTrees: Array, proposedResourceName: string, isContainer: boolean, bodyString: string, contentType: string): DocumentResponse /* throws ShapeTreeException */ { @@ -137,7 +142,7 @@ export class HttpShapeTreeClient implements ShapeTreeClient { } // Create via HTTP PUT - override public putManagedInstance(context: ShapeTreeContext, resourceUrl: URL, focusNodes: Array, targetShapeTrees: Array, isContainer: boolean, bodyString: string, contentType: string): DocumentResponse /* throws ShapeTreeException */ { + override public putManagedInstance(context: ShapeTreeContext, resourceUrl: URL, focusNodes: Array, bodyString: string, contentType: string, targetShapeTrees: Array, isContainer: boolean): DocumentResponse /* throws ShapeTreeException */ { if (context === null || resourceUrl === null) { throw new ShapeTreeException(500, "Must provide a valid context and target resource to create shape tree instance via PUT"); } @@ -150,7 +155,7 @@ export class HttpShapeTreeClient implements ShapeTreeClient { } // Update via HTTP PUT - override public putManagedInstance(context: ShapeTreeContext, resourceUrl: URL, focusNodes: Array, bodyString: string, contentType: string): DocumentResponse /* throws ShapeTreeException */ { + override public updateManagedInstance(context: ShapeTreeContext, resourceUrl: URL, focusNodes: Array, bodyString: string, contentType: string): DocumentResponse /* throws ShapeTreeException */ { if (context === null || resourceUrl === null) { throw new ShapeTreeException(500, "Must provide a valid context and target resource to update shape tree instance via PUT"); } @@ -200,6 +205,7 @@ export class HttpShapeTreeClient implements ShapeTreeClient { return new DocumentResponse(null, "Cannot unplant target resource that is not managed by a shapetree: " + targetResource, 500); } // Remove assignment from manager that corresponds with the provided shape tree + // TODO: could be null let manager: ShapeTreeManager = instance.getManagerResource().getManager(); manager.removeAssignmentForShapeTree(targetShapeTree); let method: string; diff --git a/asTypescript/packages/core/src/DocumentResponse.ts b/asTypescript/packages/core/src/DocumentResponse.ts index 470af99d..2b88777d 100644 --- a/asTypescript/packages/core/src/DocumentResponse.ts +++ b/asTypescript/packages/core/src/DocumentResponse.ts @@ -11,7 +11,7 @@ export class DocumentResponse { private readonly statusCode: number; public getContentType(): string | null { - return this.resourceAttributes.firstValue(HttpHeaders.CONTENT_TYPE.getValue()); + return this.resourceAttributes === null ? Optional.empty() : this.resourceAttributes.firstValue(HttpHeaders.CONTENT_TYPE.getValue()); } // TODO: lots of choices re non-404, not >= 4xx, not 3xx. not 201 (meaning there's no body) diff --git a/asTypescript/packages/core/src/ManageableResource.ts b/asTypescript/packages/core/src/ManageableResource.ts index ab8a442d..64b9ca36 100644 --- a/asTypescript/packages/core/src/ManageableResource.ts +++ b/asTypescript/packages/core/src/ManageableResource.ts @@ -46,9 +46,9 @@ export class ManageableResource extends InstanceResource { return new URL(this.getUrl(), rel); } catch (e) { if (e instanceof MalformedURLException) { - throw new ShapeTreeException(500, "Malformed focus node when resolving <" + rel + "> against <" + this.getUrl() + ">"); - }} - + throw new ShapeTreeException(500, "Malformed focus node when resolving <" + rel + "> against <" + this.getUrl() + ">"); + } +} } public getManagerResourceUrl(): URL | null { diff --git a/asTypescript/packages/core/src/MissingManagerResource.ts b/asTypescript/packages/core/src/MissingManagerResource.ts index b19209e9..9a75f7aa 100644 --- a/asTypescript/packages/core/src/MissingManagerResource.ts +++ b/asTypescript/packages/core/src/MissingManagerResource.ts @@ -12,23 +12,23 @@ export class MissingManagerResource extends ManagerResource { /** * Construct a missing manager resource based on a MissingManageableResource + * @param managedResourceUrl Corresponding URL of the resource that would be managed * @param manageable Missing manageable resource - * @param managedUrl Corresponding URL of the resource that would be managed */ - public constructor(manageable: MissingManageableResource, managedUrl: URL) { - super(manageable.getUrl(), manageable.getResourceType(), manageable.getAttributes(), manageable.getBody(), manageable.getName(), manageable.isExists(), managedUrl); + public constructor(managedResourceUrl: URL, manageable: MissingManageableResource) { + super(manageable.getUrl(), manageable.getResourceType(), manageable.getAttributes(), manageable.getBody(), manageable.getName(), manageable.isExists(), managedResourceUrl); } /** * Construct a missing manager resource. + * @param managedResourceUrl URL of the resource that would be managed * @param url URL of the resource * @param resourceType Identified shape tree resource type * @param attributes Associated resource attributes * @param body Body of the resource * @param name Name of the resource - * @param managedResourceUrl URL of the resource that would be managed */ - public constructor(url: URL, resourceType: ShapeTreeResourceType, attributes: ResourceAttributes, body: string, name: string, managedResourceUrl: URL) { + public constructor(managedResourceUrl: URL, url: URL, resourceType: ShapeTreeResourceType, attributes: ResourceAttributes, body: string, name: string) { super(url, resourceType, attributes, body, name, false, managedResourceUrl); } } diff --git a/asTypescript/packages/core/src/ResourceAttributes.ts b/asTypescript/packages/core/src/ResourceAttributes.ts index 7f6c85c1..c771d23b 100644 --- a/asTypescript/packages/core/src/ResourceAttributes.ts +++ b/asTypescript/packages/core/src/ResourceAttributes.ts @@ -174,6 +174,7 @@ export class ResourceAttributes { let values: Array = toMultimap().get(name); // Making unmodifiable list out of empty in order to make a list which // throws UOE unconditionally + // TODO: some callers, e.g. HttpResourceAccessor.getResourceTypeFromHeaders, expect null return values != null ? values : List.of(); } diff --git a/asTypescript/packages/core/src/ShapeTree.ts b/asTypescript/packages/core/src/ShapeTree.ts index 9ca7c047..00e8178e 100644 --- a/asTypescript/packages/core/src/ShapeTree.ts +++ b/asTypescript/packages/core/src/ShapeTree.ts @@ -66,10 +66,10 @@ export class ShapeTree { if (targetResource.getResourceType() != ShapeTreeResourceType.NON_RDF) { bodyGraph = GraphHelper.readStringIntoGraph(urlToUri(targetResource.getUrl()), targetResource.getBody(), targetResource.getAttributes().firstValue(HttpHeaders.CONTENT_TYPE.getValue()).orElse(null)); } - return validateResource(targetResource.getName(), targetResource.getResourceType(), bodyGraph, focusNodeUrls); + return validateResource(targetResource.getName(), focusNodeUrls, targetResource.getResourceType(), bodyGraph); } - public validateResource(requestedName: string, resourceType: ShapeTreeResourceType, bodyGraph: Graph, focusNodeUrls: Array): ValidationResult /* throws ShapeTreeException */ { + public validateResource(requestedName: string, focusNodeUrls: Array, resourceType: ShapeTreeResourceType, bodyGraph: Graph): ValidationResult /* throws ShapeTreeException */ { // Check whether the proposed resource is the same type as what is expected by the shape tree if (!this.expectedResourceType.toString() === resourceType.getValue()) { return new ValidationResult(false, this, "Resource type " + resourceType + " is invalid. Expected " + this.expectedResourceType); @@ -106,6 +106,7 @@ export class ShapeTree { } let shapeBody: string = shexShapeContents.getBody(); let stream: InputStream = new ByteArrayInputStream(shapeBody.getBytes(StandardCharsets.UTF_8)); + // TODO: set base URL to this.shape let shexCParser: ShExCParser = new ShExCParser(); try { schema = new ShexSchema(GlobalFactory.RDFFactory, shexCParser.getRules(stream), shexCParser.getStart()); @@ -114,23 +115,23 @@ export class ShapeTree { } } catch (ex) { if (ex instanceof Exception) { - throw new ShapeTreeException(500, "Error parsing ShEx schema - " + ex.getMessage()); - }} - + throw new ShapeTreeException(500, "Error parsing ShEx schema - " + ex.getMessage()); + } +} } // Tell ShExJava we want to use Jena as our graph library let jenaRDF: JenaRDF = new org.apache.commons.rdf.jena.JenaRDF(); GlobalFactory.RDFFactory = jenaRDF; - let validation: ValidationAlgorithm = new RecursiveValidation(schema, jenaRDF.asGraph(graph)); + let validator: ValidationAlgorithm = new RecursiveValidation(schema, jenaRDF.asGraph(graph)); let shapeLabel: Label = new Label(GlobalFactory.RDFFactory.createIRI(this.shape.toString())); if (!focusNodeUrls.isEmpty()) { - // One or more focus nodes were provided for validation + // One or more focus nodes were provided for validator for (const focusNodeUrl of focusNodeUrls) { // Evaluate each provided focus node let focusNode: IRI = GlobalFactory.RDFFactory.createIRI(focusNodeUrl.toString()); log.debug("Validating Shape Label = {}, Focus Node = {}", shapeLabel.toPrettyString(), focusNode.getIRIString()); - validation.validate(focusNode, shapeLabel); - let valid: boolean = validation.getTyping().isConformant(focusNode, shapeLabel); + validator.validate(focusNode, shapeLabel); + let valid: boolean = validator.getTyping().isConformant(focusNode, shapeLabel); if (valid) { return new ValidationResult(valid, this, this, focusNodeUrl); } @@ -138,23 +139,21 @@ export class ShapeTree { // None of the provided focus nodes were valid - this will return the last failure return new ValidationResult(false, this, "Failed to validate: " + shapeLabel.toPrettyString()); } else { - // No focus nodes were provided for validation, so all subject nodes will be evaluated + // No focus nodes were provided for validator, so all subject nodes will be evaluated let evaluateNodes: Array = GraphUtil.listSubjects(graph, Node.ANY, Node.ANY).toList(); for (const evaluateNode of evaluateNodes) { const focusUriString: string = evaluateNode.getURI(); let node: IRI = GlobalFactory.RDFFactory.createIRI(focusUriString); - validation.validate(node, shapeLabel); - let valid: boolean = validation.getTyping().isConformant(node, shapeLabel); + validator.validate(node, shapeLabel); + let valid: boolean = validator.getTyping().isConformant(node, shapeLabel); if (valid) { - const matchingFocusNode: URL; try { - matchingFocusNode = new URL(focusUriString); + return new ValidationResult(valid, this, this, new URL(focusUriString)); } catch (ex) { if (ex instanceof MalformedURLException) { - throw new ShapeTreeException(500, "Error reporting validation success on malformed URL <" + focusUriString + ">: " + ex.getMessage()); - }} - - return new ValidationResult(valid, this, this, matchingFocusNode); + throw new ShapeTreeException(500, "Error reporting validator success on malformed URL <" + focusUriString + ">: " + ex.getMessage()); + } +} } } return new ValidationResult(false, this, "Failed to validate: " + shapeLabel.toPrettyString()); @@ -162,20 +161,21 @@ export class ShapeTree { } public validateContainedResource(containedResource: ManageableResource): ValidationResult /* throws ShapeTreeException */ { + // TODO: this same test gets performed in the call to the 2nd valdateContainedResource (after potentially parsing the graph) if (this.contains === null || this.contains.isEmpty()) { // TODO: say it can't be null? // The contained resource is permitted because this shape tree has no restrictions on what it contains return new ValidationResult(true, this, this, null); } - return validateContainedResource(containedResource, Collections.emptyList(), Collections.emptyList()); - } - - public validateContainedResource(containedResource: ManageableResource, targetShapeTreeUrls: Array, focusNodeUrls: Array): ValidationResult /* throws ShapeTreeException */ { let containedResourceGraph: Graph = null; - if (containedResource.getResourceType() != ShapeTreeResourceType.NON_RDF) { + let resourceType: ShapeTreeResourceType = containedResource.getResourceType(); + if (resourceType != ShapeTreeResourceType.NON_RDF) { containedResourceGraph = GraphHelper.readStringIntoGraph(urlToUri(containedResource.getUrl()), containedResource.getBody(), containedResource.getAttributes().firstValue(HttpHeaders.CONTENT_TYPE.getValue()).orElse(null)); } - return validateContainedResource(containedResource.getName(), containedResource.getResourceType(), targetShapeTreeUrls, containedResourceGraph, focusNodeUrls); + let targetShapeTreeUrls: Array = Collections.emptyList(); + let focusNodeUrls: Array = Collections.emptyList(); + let requestedName: string = containedResource.getName(); + return validateContainedResource(requestedName, resourceType, targetShapeTreeUrls, containedResourceGraph, focusNodeUrls); } public validateContainedResource(requestedName: string, resourceType: ShapeTreeResourceType, targetShapeTreeUrls: Array, bodyGraph: Graph, focusNodeUrls: Array): ValidationResult /* throws ShapeTreeException */ { @@ -191,7 +191,7 @@ export class ShapeTree { if (this.contains.contains(targetShapeTreeUrl)) { let targetShapeTree: ShapeTree = ShapeTreeFactory.getShapeTree(targetShapeTreeUrl); // Evaluate the shape tree against the attributes of the proposed resources - let result: ValidationResult = targetShapeTree.validateResource(requestedName, resourceType, bodyGraph, focusNodeUrls); + let result: ValidationResult = targetShapeTree.validateResource(requestedName, focusNodeUrls, resourceType, bodyGraph); if (Boolean.TRUE === result.getValid()) { // Return a successful validation result, including the matching shape tree return new ValidationResult(true, this, targetShapeTree, result.getMatchingFocusNode()); @@ -209,7 +209,7 @@ export class ShapeTree { continue; } // Evaluate the shape tree against the attributes of the proposed resources - let result: ValidationResult = containsShapeTree.validateResource(requestedName, resourceType, bodyGraph, focusNodeUrls); + let result: ValidationResult = containsShapeTree.validateResource(requestedName, focusNodeUrls, resourceType, bodyGraph); // Continue if the proposed attributes were not a match if (Boolean.FALSE === result.getValid()) { continue; @@ -221,6 +221,13 @@ export class ShapeTree { return new ValidationResult(false, null, "Failed to validate shape tree: " + this.id); } + // Return the list of shape tree contains by priority from most to least strict + public getPrioritizedContains(): Array { + let prioritized: Array = new Array<>(this.contains); + Collections.sort(prioritized, new ShapeTreeContainsPriority()); + return prioritized; + } + public getReferencedShapeTrees(): Iterator /* throws ShapeTreeException */ { return getReferencedShapeTrees(RecursionMethods.DEPTH_FIRST); } @@ -229,13 +236,6 @@ export class ShapeTree { return getReferencedShapeTreesList(recursionMethods).iterator(); } - // Return the list of shape tree contains by priority from most to least strict - public getPrioritizedContains(): Array { - let prioritized: Array = new Array<>(this.contains); - Collections.sort(prioritized, new ShapeTreeContainsPriority()); - return prioritized; - } - private getReferencedShapeTreesList(recursionMethods: RecursionMethods): Array /* throws ShapeTreeException */ { if (recursionMethods === RecursionMethods.BREADTH_FIRST) { return getReferencedShapeTreesListBreadthFirst(); diff --git a/asTypescript/packages/core/src/ShapeTreeAssignment.ts b/asTypescript/packages/core/src/ShapeTreeAssignment.ts index ed8ccabc..6faa8330 100644 --- a/asTypescript/packages/core/src/ShapeTreeAssignment.ts +++ b/asTypescript/packages/core/src/ShapeTreeAssignment.ts @@ -55,9 +55,9 @@ export class ShapeTreeAssignment { } } catch (ex) { if (ex instanceof NullPointerException || ex instanceof IllegalStateException) { - throw new ShapeTreeException(500, "Failed to initialize shape tree assignment: " + ex.getMessage()); - }} - + throw new ShapeTreeException(500, "Failed to initialize shape tree assignment: " + ex.getMessage()); + } +} } public static getFromGraph(url: URL, managerGraph: Graph): ShapeTreeAssignment /* throws MalformedURLException, ShapeTreeException */ { diff --git a/asTypescript/packages/core/src/ShapeTreeFactory.ts b/asTypescript/packages/core/src/ShapeTreeFactory.ts index d4a045a0..e8c048c7 100644 --- a/asTypescript/packages/core/src/ShapeTreeFactory.ts +++ b/asTypescript/packages/core/src/ShapeTreeFactory.ts @@ -90,9 +90,9 @@ export class ShapeTreeFactory { return getURLListValue(resourceModel, shapeTreeNode, ShapeTreeVocabulary.CONTAINS); } catch (ex) { if (ex instanceof MalformedURLException || ex instanceof ShapeTreeException) { - throw new ShapeTreeException(500, "List <" + shapeTreeUrl + "> contains malformed URL: " + ex.getMessage()); - }} - + throw new ShapeTreeException(500, "List <" + shapeTreeUrl + "> contains malformed URL: " + ex.getMessage()); + } +} } /** @@ -107,23 +107,18 @@ export class ShapeTreeFactory { let references: Array = new Array<>(); let referencesProperty: Property = resourceModel.createProperty(ShapeTreeVocabulary.REFERENCES); if (shapeTreeNode.hasProperty(referencesProperty)) { + // TODO: arbitrarily pics from n objects where 1 expected + // TODO: test coverage (never hit) let referenceStatements: Array = shapeTreeNode.listProperties(referencesProperty).toList(); for (const referenceStatement of referenceStatements) { let referenceResource: Resource = referenceStatement.getObject().asResource(); - const referencedShapeTreeUrlString: string = getStringValue(resourceModel, referenceResource, ShapeTreeVocabulary.REFERENCES_SHAPE_TREE); - const referencedShapeTreeUrl: URL; - let referencedShapeTree: ShapeTreeReference; - try { - referencedShapeTreeUrl = new URL(referencedShapeTreeUrlString); - } catch (ex) { - if (ex instanceof MalformedURLException) { - throw new ShapeTreeException(500, "ShapeTree <" + shapeTreeUrl + "> references malformed URL <" + referencedShapeTreeUrlString + ">: " + ex.getMessage()); - }} - + const referencedShapeTreeUrl: URL = getUrlValue(resourceModel, referenceResource, ShapeTreeVocabulary.REFERENCES_SHAPE_TREE, shapeTreeUrl); + if (referencedShapeTreeUrl === null) { + throw new ShapeTreeException(400, "expected <" + shapeTreeUrl + "> reference " + referenceResource.toString() + " to have one <" + ShapeTreeVocabulary.REFERENCES_SHAPE_TREE + "> property"); + } let viaShapePath: string = getStringValue(resourceModel, referenceResource, ShapeTreeVocabulary.VIA_SHAPE_PATH); let viaPredicate: URL = getUrlValue(resourceModel, referenceResource, ShapeTreeVocabulary.VIA_PREDICATE, shapeTreeUrl); - referencedShapeTree = new ShapeTreeReference(referencedShapeTreeUrl, viaShapePath, viaPredicate); - references.add(referencedShapeTree); + references.add(new ShapeTreeReference(referencedShapeTreeUrl, viaShapePath, viaPredicate)); } } return references; @@ -148,9 +143,9 @@ export class ShapeTreeFactory { return new URL(object.asResource().getURI()); } catch (ex) { if (ex instanceof MalformedURLException) { - throw new IllegalStateException("Malformed ShapeTree <" + shapeTreeUrl + ">: Jena URIResource <" + object + "> didn't parse as URL - " + ex.getMessage()); - }} - + throw new IllegalStateException("Malformed ShapeTree <" + shapeTreeUrl + ">: Jena URIResource <" + object + "> didn't parse as URL - " + ex.getMessage()); + } +} } else { throw new ShapeTreeException(500, "Malformed ShapeTree <" + shapeTreeUrl + ">: expected " + object + " to be a URL"); } diff --git a/asTypescript/packages/core/src/ShapeTreeManager.ts b/asTypescript/packages/core/src/ShapeTreeManager.ts index e4833f73..e16c3904 100644 --- a/asTypescript/packages/core/src/ShapeTreeManager.ts +++ b/asTypescript/packages/core/src/ShapeTreeManager.ts @@ -28,6 +28,7 @@ export class ShapeTreeManager { private readonly id: URL; // Each ShapeTreeManager has one or more ShapeTreeAssignments + // TODO: try Map, makes getContainingAssignments() redundant against getAssignments() private readonly assignments: Array = new Array<>(); /** @@ -105,9 +106,9 @@ export class ShapeTreeManager { assignmentUrl = new URL(assignmentString); } catch (ex) { if (ex instanceof MalformedURLException) { - throw new IllegalStateException("Minted illegal URL <" + assignmentString + "> - " + ex.getMessage()); - }} - + throw new IllegalStateException("Minted illegal URL <" + assignmentString + "> - " + ex.getMessage()); + } +} return assignmentUrl; } @@ -149,6 +150,7 @@ export class ShapeTreeManager { } else if (managerTriples.isEmpty()) { // Given the fact that a manager resource exists, there should never be a case where the manager resource // exists but no manager is found inside of it. + // TODO: isn't that always 0? throw new IllegalStateException("No ShapeTreeManager instances found: " + managerTriples.size()); } // Get the URL of the ShapeTreeManager subject node @@ -167,15 +169,16 @@ export class ShapeTreeManager { assignment = ShapeTreeAssignment.getFromGraph(new URL(assignmentNode.getObject().getURI()), managerGraph); } catch (e) { if (e instanceof MalformedURLException) { - throw new ShapeTreeException(500, "Object of { " + s + " " + stAssignment + " " + assignmentNode.getObject() + " } must be a URL."); - }} - + throw new ShapeTreeException(500, "Object of { " + s + " " + stAssignment + " " + assignmentNode.getObject() + " } must be a URL."); + } +} manager.assignments.add(assignment); } return manager; } public getAssignmentForShapeTree(shapeTreeUrl: URL): ShapeTreeAssignment { + // TODO: return list of assignments with same ST but different roots if (this.assignments.isEmpty()) { return null; } diff --git a/asTypescript/packages/core/src/ShapeTreeManagerDelta.ts b/asTypescript/packages/core/src/ShapeTreeManagerDelta.ts index d8f56288..02206bea 100644 --- a/asTypescript/packages/core/src/ShapeTreeManagerDelta.ts +++ b/asTypescript/packages/core/src/ShapeTreeManagerDelta.ts @@ -81,9 +81,9 @@ export class ShapeTreeManagerDelta { targetAssignmentUri = targetAssignment.getUrl().toURI(); } catch (ex) { if (ex instanceof URISyntaxException) { - throw new ShapeTreeException(500, "Unable to convert assignment URLs for comparison: " + ex.getMessage()); - }} - + throw new ShapeTreeException(500, "Unable to convert assignment URLs for comparison: " + ex.getMessage()); + } +} if (assignmentUri === targetAssignmentUri) { return targetAssignment; } @@ -92,7 +92,7 @@ export class ShapeTreeManagerDelta { } public allRemoved(): boolean { - return (!this.isUpdated() && this.removedAssignments.size() === this.existingManager.getAssignments().size()); + return (!this.isUpdated() && this.removedAssignments != null && this.removedAssignments.size() === this.existingManager.getAssignments().size()); } public isUpdated(): boolean { diff --git a/asTypescript/packages/core/src/ShapeTreeRequestHandler.ts b/asTypescript/packages/core/src/ShapeTreeRequestHandler.ts index bfb62b39..f4a0460b 100644 --- a/asTypescript/packages/core/src/ShapeTreeRequestHandler.ts +++ b/asTypescript/packages/core/src/ShapeTreeRequestHandler.ts @@ -35,7 +35,9 @@ export class ShapeTreeRequestHandler { public manageShapeTree(manageableInstance: ManageableInstance, shapeTreeRequest: ShapeTreeRequest): DocumentResponse /* throws ShapeTreeException */ { let validationResponse: DocumentResponse | null; + // TODO: could be null let updatedRootManager: ShapeTreeManager = RequestHelper.getIncomingShapeTreeManager(shapeTreeRequest, manageableInstance.getManagerResource()); + // TODO: could be null let existingRootManager: ShapeTreeManager = manageableInstance.getManagerResource().getManager(); // Determine assignments that have been removed, added, and/or updated let delta: ShapeTreeManagerDelta = ShapeTreeManagerDelta.evaluate(existingRootManager, updatedRootManager); @@ -130,7 +132,7 @@ export class ShapeTreeRequestHandler { // if any of the provided focus nodes weren't matched validation must fail let unmatchedNodes: Array = getUnmatchedFocusNodes(validationResults.values(), incomingFocusNodes); if (!unmatchedNodes.isEmpty()) { - return failValidation(new ValidationResult(false, "Failed to match target focus nodes: " + unmatchedNodes)); + return failValidation(new ValidationResult(false, null, "Failed to match target focus nodes: " + unmatchedNodes)); } log.debug("Creating shape tree instance at {}", targetResourceUrl); let createdInstance: ManageableInstance = this.resourceAccessor.createInstance(manageableInstance.getShapeTreeContext(), shapeTreeRequest.getMethod(), targetResourceUrl, shapeTreeRequest.getHeaders(), shapeTreeRequest.getBody(), shapeTreeRequest.getContentType()); @@ -158,7 +160,9 @@ export class ShapeTreeRequestHandler { // All must pass for the update to validate let shapeTree: ShapeTree = ShapeTreeFactory.getShapeTree(assignment.getShapeTree()); let managedResourceUrl: URL = targetResource.getManageableResource().getUrl(); - let validationResult: ValidationResult = shapeTree.validateResource(null, shapeTreeRequest.getResourceType(), RequestHelper.getIncomingBodyGraph(shapeTreeRequest, managedResourceUrl, targetResource.getManageableResource()), RequestHelper.getIncomingFocusNodes(shapeTreeRequest, managedResourceUrl)); + // TODO: could be null + let bodyGraph: Graph = RequestHelper.getIncomingBodyGraph(shapeTreeRequest, managedResourceUrl, targetResource.getManageableResource()); + let validationResult: ValidationResult = shapeTree.validateResource(null, RequestHelper.getIncomingFocusNodes(shapeTreeRequest, managedResourceUrl), shapeTreeRequest.getResourceType(), bodyGraph); if (Boolean.FALSE === validationResult.isValid()) { return failValidation(validationResult); } @@ -289,6 +293,7 @@ export class ShapeTreeRequestHandler { let shapeTreeManager: ShapeTreeManager = null; let managerResourceUrl: URL = manageableInstance.getManagerResource().getUrl(); // When at the top of the plant hierarchy, use the root manager from the initial plant request body + // TODO: rootManager can be null so method could return null (does not in any test) if (atRootOfPlantHierarchy(rootAssignment, manageableInstance.getManageableResource())) { return rootManager; } @@ -331,6 +336,7 @@ export class ShapeTreeRequestHandler { // Return a root shape tree manager associated with a given shape tree assignment private getRootAssignment(shapeTreeContext: ShapeTreeContext, assignment: ShapeTreeAssignment): ShapeTreeAssignment /* throws ShapeTreeException */ { + // TODO: could be null let rootManager: ShapeTreeManager = getRootManager(shapeTreeContext, assignment); for (const rootAssignment of rootManager.getAssignments()) { if (rootAssignment.getUrl() != null && rootAssignment.getUrl() === assignment.getRootAssignment()) { @@ -431,6 +437,7 @@ export class ShapeTreeRequestHandler { } private failValidation(validationResult: ValidationResult): DocumentResponse | null { - return Optional.of(new DocumentResponse(new ResourceAttributes(), validationResult.getMessage(), 422)); + let message: string = validationResult.getMessage() != null ? validationResult.getMessage() : "Unspecified validation failure"; + return Optional.of(new DocumentResponse(new ResourceAttributes(), message, 422)); } } diff --git a/asTypescript/packages/core/src/ValidationResult.ts b/asTypescript/packages/core/src/ValidationResult.ts index e85e38ee..a4fd5a7f 100644 --- a/asTypescript/packages/core/src/ValidationResult.ts +++ b/asTypescript/packages/core/src/ValidationResult.ts @@ -20,24 +20,6 @@ export class ValidationResult { return (this.valid != null && this.valid); } - public constructor(valid: boolean, message: string) { - this.valid = valid; - this.message = message; - this.validatingShapeTree = null; - this.matchingShapeTree = null; - this.managingAssignment = null; - this.matchingFocusNode = null; - } - - public constructor(valid: boolean, validatingShapeTree: ShapeTree) { - this.valid = valid; - this.message = null; - this.validatingShapeTree = validatingShapeTree; - this.matchingShapeTree = null; - this.managingAssignment = null; - this.matchingFocusNode = null; - } - public constructor(valid: boolean, validatingShapeTree: ShapeTree, message: string) { this.valid = valid; this.message = message; @@ -47,15 +29,6 @@ export class ValidationResult { this.matchingFocusNode = null; } - public constructor(valid: boolean, validatingShapeTree: ShapeTree, matchingFocusNode: URL) { - this.valid = valid; - this.message = null; - this.validatingShapeTree = validatingShapeTree; - this.matchingShapeTree = null; - this.managingAssignment = null; - this.matchingFocusNode = matchingFocusNode; - } - public constructor(valid: boolean, validatingShapeTree: ShapeTree, matchingShapeTree: ShapeTree, matchingFocusNode: URL) { this.valid = valid; this.message = null; diff --git a/asTypescript/packages/core/src/contentloaders/HttpExternalDocumentLoader.ts b/asTypescript/packages/core/src/contentloaders/HttpExternalDocumentLoader.ts index 24e1a535..6e6638bd 100644 --- a/asTypescript/packages/core/src/contentloaders/HttpExternalDocumentLoader.ts +++ b/asTypescript/packages/core/src/contentloaders/HttpExternalDocumentLoader.ts @@ -27,13 +27,13 @@ export class HttpExternalDocumentLoader implements ExternalDocumentLoader { return new DocumentResponse(attributes, response.body(), response.statusCode()); } catch (ex) { if (ex instanceof IOException) { - throw new ShapeTreeException(500, "Error retrieving <" + resourceUrl + ">: " + ex.getMessage()); - } else if (ex instanceof InterruptedException) { - Thread.currentThread().interrupt(); - throw new ShapeTreeException(500, "Error retrieving <" + resourceUrl + ">: " + ex.getMessage()); - } else if (ex instanceof URISyntaxException) { - throw new ShapeTreeException(500, "Malformed URL <" + resourceUrl + ">: " + ex.getMessage()); - }} - + throw new ShapeTreeException(500, "Error retrieving <" + resourceUrl + ">: " + ex.getMessage()); + } else if (ex instanceof InterruptedException) { + Thread.currentThread().interrupt(); + throw new ShapeTreeException(500, "Error retrieving <" + resourceUrl + ">: " + ex.getMessage()); + } else if (ex instanceof URISyntaxException) { + throw new ShapeTreeException(500, "Malformed URL <" + resourceUrl + ">: " + ex.getMessage()); + } +} } } diff --git a/asTypescript/packages/core/src/helpers/GraphHelper.ts b/asTypescript/packages/core/src/helpers/GraphHelper.ts index 58aae5bf..bb455a49 100644 --- a/asTypescript/packages/core/src/helpers/GraphHelper.ts +++ b/asTypescript/packages/core/src/helpers/GraphHelper.ts @@ -79,9 +79,9 @@ export class GraphHelper { return model; } catch (rex) { if (rex instanceof RiotException) { - throw new ShapeTreeException(422, "Error processing input - " + rex.getMessage()); - }} - + throw new ShapeTreeException(422, "Error processing input - " + rex.getMessage()); + } +} } /** @@ -164,9 +164,9 @@ export class GraphHelper { return url.toURI(); } catch (ex) { if (ex instanceof URISyntaxException) { - throw new IllegalStateException("can't convert URL <" + url + "> to IRI: " + ex); - }} - + throw new IllegalStateException("can't convert URL <" + url + "> to IRI: " + ex); + } +} } /** @@ -184,9 +184,9 @@ export class GraphHelper { return noFragment.toURL(); } catch (ex) { if (ex instanceof MalformedURLException || ex instanceof URISyntaxException) { - throw new IllegalStateException("Unable to remove fragment from URL: " + ex.getMessage()); - }} - + throw new IllegalStateException("Unable to remove fragment from URL: " + ex.getMessage()); + } +} } public static knownUrl(urlString: string): URL { @@ -194,8 +194,8 @@ export class GraphHelper { return new URL(urlString); } catch (ex) { if (ex instanceof MalformedURLException) { - throw new IllegalStateException("Expected known URL <" + urlString + "> to parse as valid URL - " + ex.toString()); - }} - + throw new IllegalStateException("Expected known URL <" + urlString + "> to parse as valid URL - " + ex.toString()); + } +} } } diff --git a/asTypescript/packages/core/src/helpers/RequestHelper.ts b/asTypescript/packages/core/src/helpers/RequestHelper.ts index eea3674e..581c8ced 100644 --- a/asTypescript/packages/core/src/helpers/RequestHelper.ts +++ b/asTypescript/packages/core/src/helpers/RequestHelper.ts @@ -99,9 +99,9 @@ export class RequestHelper { focusNodeUrls.add(focusNodeUrl); } catch (e) { if (e instanceof MalformedURLException) { - throw new ShapeTreeException(500, "Malformed focus node when resolving <" + focusNodeUrlString + "> against <" + baseUrl + ">"); - }} - + throw new ShapeTreeException(500, "Malformed focus node when resolving <" + focusNodeUrlString + "> against <" + baseUrl + ">"); + } +} } } return focusNodeUrls; @@ -123,9 +123,9 @@ export class RequestHelper { targetShapeTreeUrls.add(targetShapeTreeUrl); } catch (e) { if (e instanceof MalformedURLException) { - throw new ShapeTreeException(500, "Malformed focus node when resolving <" + targetShapeTreeUrlString + "> against <" + baseUrl + ">"); - }} - + throw new ShapeTreeException(500, "Malformed focus node when resolving <" + targetShapeTreeUrlString + "> against <" + baseUrl + ">"); + } +} } } return targetShapeTreeUrls; @@ -159,9 +159,9 @@ export class RequestHelper { return new URL(urlString); } catch (ex) { if (ex instanceof MalformedURLException) { - throw new ShapeTreeException(500, "normalized to malformed URL <" + urlString + "> - " + ex.getMessage()); - }} - + throw new ShapeTreeException(500, "normalized to malformed URL <" + urlString + "> - " + ex.getMessage()); + } +} } /** diff --git a/asTypescript/packages/javahttp/src/JavaHttpClient.ts b/asTypescript/packages/javahttp/src/JavaHttpClient.ts index b97a641c..5cd4ace0 100644 --- a/asTypescript/packages/javahttp/src/JavaHttpClient.ts +++ b/asTypescript/packages/javahttp/src/JavaHttpClient.ts @@ -41,9 +41,9 @@ export class JavaHttpClient implements HttpClient { body = Objects.requireNonNull(response.body()).toString(); } catch (ex) { if (ex instanceof NullPointerException) { - log.error("Exception retrieving body string"); - }} - + log.error("Exception retrieving body string"); + } +} return new DocumentResponse(new ResourceAttributes(response.headers().map()), body, response.statusCode()); } @@ -78,16 +78,16 @@ export class JavaHttpClient implements HttpClient { sc = SSLContext.getInstance("TLSv1.2"); } catch (e) { if (e instanceof NoSuchAlgorithmException) { - e.printStackTrace(); - }} - + e.printStackTrace(); + } +} try { sc.init(null, trustAllCerts, new java.security.SecureRandom()); } catch (e) { if (e instanceof KeyManagementException) { - e.printStackTrace(); - }} - + e.printStackTrace(); + } +} HttpsURLConnection.setDefaultSSLSocketFactory(sc.getSocketFactory()); // Create all-trusting host name verifier let validHosts: HostnameVerifier = new HostnameVerifier() { @@ -142,11 +142,11 @@ export class JavaHttpClient implements HttpClient { } } catch (ex) { if (ex instanceof IOException || ex instanceof InterruptedException) { - throw new ShapeTreeException(500, ex.getMessage()); - } else if (ex instanceof URISyntaxException) { - throw new ShapeTreeException(500, "Malformed URL <" + request.resourceURL + ">: " + ex.getMessage()); - }} - + throw new ShapeTreeException(500, ex.getMessage()); + } else if (ex instanceof URISyntaxException) { + throw new ShapeTreeException(500, "Malformed URL <" + request.resourceURL + ">: " + ex.getMessage()); + } +} } protected static check(resp: java.net.http.HttpResponse): java.net.http.HttpResponse { diff --git a/asTypescript/packages/javahttp/src/JavaHttpClientFactory.ts b/asTypescript/packages/javahttp/src/JavaHttpClientFactory.ts index a0c171c5..ae4203a8 100644 --- a/asTypescript/packages/javahttp/src/JavaHttpClientFactory.ts +++ b/asTypescript/packages/javahttp/src/JavaHttpClientFactory.ts @@ -38,9 +38,9 @@ export class JavaHttpClientFactory implements HttpClientFactory, ExternalDocumen return new JavaHttpClient(this.useSslValidation, useShapeTreeValidation); } catch (ex) { if (ex instanceof Exception) { - throw new ShapeTreeException(500, ex.getMessage()); - }} - + throw new ShapeTreeException(500, ex.getMessage()); + } +} } /** diff --git a/asTypescript/packages/javahttp/src/JavaHttpValidatingShapeTreeInterceptor.ts b/asTypescript/packages/javahttp/src/JavaHttpValidatingShapeTreeInterceptor.ts index f13dea98..afa6d6fa 100644 --- a/asTypescript/packages/javahttp/src/JavaHttpValidatingShapeTreeInterceptor.ts +++ b/asTypescript/packages/javahttp/src/JavaHttpValidatingShapeTreeInterceptor.ts @@ -49,13 +49,13 @@ export class JavaHttpValidatingShapeTreeInterceptor { } } catch (ex) { if (ex instanceof ShapeTreeException) { - log.error("Error processing shape tree request: ", ex); - return createErrorResponse(ex, clientRequest); - } else if (ex instanceof Exception) { - log.error("Error processing shape tree request: ", ex); - return createErrorResponse(new ShapeTreeException(500, ex.getMessage()), clientRequest); - }} - + log.error("Error processing shape tree request: ", ex); + return createErrorResponse(ex, clientRequest); + } else if (ex instanceof Exception) { + log.error("Error processing shape tree request: ", ex); + return createErrorResponse(new ShapeTreeException(500, ex.getMessage()), clientRequest); + } +} } else { log.warn("No handler for method [{}] - passing through request", shapeTreeRequest.getMethod()); return JavaHttpClient.check(httpClient.send(clientRequest, java.net.http.HttpResponse.BodyHandlers.ofString())); @@ -120,9 +120,9 @@ export class JavaHttpValidatingShapeTreeInterceptor { return this.request.uri().toURL(); } catch (ex) { if (ex instanceof MalformedURLException) { - throw new IllegalStateException("request has a malformed URL <" + request.uri() + ">: " + ex.getMessage()); - }} - + throw new IllegalStateException("request has a malformed URL <" + request.uri() + ">: " + ex.getMessage()); + } +} } override public getHeaders(): ResourceAttributes { From 956a78d353d80ac6f9b38344a1f193d2ffa62cdf Mon Sep 17 00:00:00 2001 From: Eric Prud'hommeaux Date: Fri, 24 Dec 2021 19:29:35 +0100 Subject: [PATCH 12/15] ~ imports include full package path --- asTypescript/javatots-config.yaml | 37 +++++++++++++++++ .../client-core/src/ShapeTreeClient.ts | 8 ++-- .../packages/client-http/src/HttpClient.ts | 4 +- .../client-http/src/HttpClientFactory.ts | 2 +- .../src/HttpClientFactoryManager.ts | 2 +- .../packages/client-http/src/HttpRequest.ts | 2 +- .../client-http/src/HttpResourceAccessor.ts | 40 +++++++++---------- .../client-http/src/HttpShapeTreeClient.ts | 26 ++++++------ .../packages/javahttp/src/JavaHttpClient.ts | 10 ++--- .../javahttp/src/JavaHttpClientFactory.ts | 10 ++--- .../JavaHttpValidatingShapeTreeInterceptor.ts | 26 ++++++------ asTypescript/run-javatots.sh | 5 +++ 12 files changed, 107 insertions(+), 65 deletions(-) create mode 100644 asTypescript/javatots-config.yaml create mode 100755 asTypescript/run-javatots.sh diff --git a/asTypescript/javatots-config.yaml b/asTypescript/javatots-config.yaml new file mode 100644 index 00000000..a8adbbe0 --- /dev/null +++ b/asTypescript/javatots-config.yaml @@ -0,0 +1,37 @@ +inputDirectory: .. +outputDirectory: . +packageTemplate: "// Corresponding shapetrees-java package: %s" +indentation: 2 +unknownImportTemplate: "import { %s } from %s;" +commentThrows: true +unknownAnnotations: comment # comment, ignore, throw (everything else interpreted as throw) + +moduleMaps: + shapetrees-java-client-core: + srcRoot: src/main/java + outputPath: packages/client-core/src + tsModule: '@shapetrees/clientcore/src' + packageMaps: + - pkg: com.janeirodigital.shapetrees.client.core + destPath: + shapetrees-java-client-http: + srcRoot: src/main/java + outputPath: packages/client-http/src + tsModule: '@shapetrees/clienthttp/src' + packageMaps: + - pkg: com.janeirodigital.shapetrees.client.http + destPath: + shapetrees-java-core: + srcRoot: src/main/java + outputPath: packages/core/src + tsModule: '@shapetrees/core/src' + packageMaps: + - pkg: com.janeirodigital.shapetrees.core + destPath: + shapetrees-java-javahttp: + srcRoot: src/main/java + outputPath: packages/javahttp/src + tsModule: '@shapetrees/javahttp/src' + packageMaps: + - pkg: com.janeirodigital.shapetrees.javahttp + destPath: diff --git a/asTypescript/packages/client-core/src/ShapeTreeClient.ts b/asTypescript/packages/client-core/src/ShapeTreeClient.ts index 709875d5..e1d6f39d 100644 --- a/asTypescript/packages/client-core/src/ShapeTreeClient.ts +++ b/asTypescript/packages/client-core/src/ShapeTreeClient.ts @@ -1,8 +1,8 @@ // Corresponding shapetrees-java package: com.janeirodigital.shapetrees.client.core -import { DocumentResponse } from '@shapetrees/DocumentResponse'; -import { ShapeTreeContext } from '@shapetrees/ShapeTreeContext'; -import { ShapeTreeManager } from '@shapetrees/ShapeTreeManager'; -import { ShapeTreeException } from '@shapetrees/exceptions/ShapeTreeException'; +import { DocumentResponse } from '@shapetrees/core/src/DocumentResponse'; +import { ShapeTreeContext } from '@shapetrees/core/src/ShapeTreeContext'; +import { ShapeTreeManager } from '@shapetrees/core/src/ShapeTreeManager'; +import { ShapeTreeException } from '@shapetrees/core/src/exceptions/ShapeTreeException'; /** * This interface defines a proposed API to be used for any client-side implementations of diff --git a/asTypescript/packages/client-http/src/HttpClient.ts b/asTypescript/packages/client-http/src/HttpClient.ts index 4cd7b4eb..c552dbb5 100644 --- a/asTypescript/packages/client-http/src/HttpClient.ts +++ b/asTypescript/packages/client-http/src/HttpClient.ts @@ -1,6 +1,6 @@ // Corresponding shapetrees-java package: com.janeirodigital.shapetrees.client.http -import { DocumentResponse } from '@shapetrees/DocumentResponse'; -import { ShapeTreeException } from '@shapetrees/exceptions/ShapeTreeException'; +import { DocumentResponse } from '@shapetrees/core/src/DocumentResponse'; +import { ShapeTreeException } from '@shapetrees/core/src/exceptions/ShapeTreeException'; import { HttpRequest } from './HttpRequest'; /** diff --git a/asTypescript/packages/client-http/src/HttpClientFactory.ts b/asTypescript/packages/client-http/src/HttpClientFactory.ts index a36ead2f..c2ddcecc 100644 --- a/asTypescript/packages/client-http/src/HttpClientFactory.ts +++ b/asTypescript/packages/client-http/src/HttpClientFactory.ts @@ -1,5 +1,5 @@ // Corresponding shapetrees-java package: com.janeirodigital.shapetrees.client.http -import { ShapeTreeException } from '@shapetrees/exceptions/ShapeTreeException'; +import { ShapeTreeException } from '@shapetrees/core/src/exceptions/ShapeTreeException'; import { HttpClient } from './HttpClient'; /** diff --git a/asTypescript/packages/client-http/src/HttpClientFactoryManager.ts b/asTypescript/packages/client-http/src/HttpClientFactoryManager.ts index 2393572e..0b583078 100644 --- a/asTypescript/packages/client-http/src/HttpClientFactoryManager.ts +++ b/asTypescript/packages/client-http/src/HttpClientFactoryManager.ts @@ -1,5 +1,5 @@ // Corresponding shapetrees-java package: com.janeirodigital.shapetrees.client.http -import { ShapeTreeException } from '@shapetrees/exceptions/ShapeTreeException'; +import { ShapeTreeException } from '@shapetrees/core/src/exceptions/ShapeTreeException'; import { HttpClientFactory } from './HttpClientFactory'; export abstract class HttpClientFactoryManager { diff --git a/asTypescript/packages/client-http/src/HttpRequest.ts b/asTypescript/packages/client-http/src/HttpRequest.ts index d02d8e11..06ac2e92 100644 --- a/asTypescript/packages/client-http/src/HttpRequest.ts +++ b/asTypescript/packages/client-http/src/HttpRequest.ts @@ -1,5 +1,5 @@ // Corresponding shapetrees-java package: com.janeirodigital.shapetrees.client.http -import { ResourceAttributes } from '@shapetrees/ResourceAttributes'; +import { ResourceAttributes } from '@shapetrees/core/src/ResourceAttributes'; export class HttpRequest { diff --git a/asTypescript/packages/client-http/src/HttpResourceAccessor.ts b/asTypescript/packages/client-http/src/HttpResourceAccessor.ts index ec525f2b..71949b0a 100644 --- a/asTypescript/packages/client-http/src/HttpResourceAccessor.ts +++ b/asTypescript/packages/client-http/src/HttpResourceAccessor.ts @@ -1,22 +1,22 @@ // Corresponding shapetrees-java package: com.janeirodigital.shapetrees.client.http -import { ShapeTreeManager } from '@shapetrees/ShapeTreeManager'; -import { ShapeTreeContext } from '@shapetrees/ShapeTreeContext'; -import { ManageableInstance } from '@shapetrees/ManageableInstance'; -import { ManageableResource } from '@shapetrees/ManageableResource'; -import { DocumentResponse } from '@shapetrees/DocumentResponse'; -import { ResourceAttributes } from '@shapetrees/ResourceAttributes'; -import { InstanceResource } from '@shapetrees/InstanceResource'; -import { ResourceAccessor } from '@shapetrees/ResourceAccessor'; -import { ManagerResource } from '@shapetrees/ManagerResource'; -import { MissingManageableResource } from '@shapetrees/MissingManageableResource'; -import { MissingManagerResource } from '@shapetrees/MissingManagerResource'; -import { UnmanagedResource } from '@shapetrees/UnmanagedResource'; -import { ManagedResource } from '@shapetrees/ManagedResource'; -import { HttpHeaders } from '@shapetrees/enums/HttpHeaders'; -import { LinkRelations } from '@shapetrees/enums/LinkRelations'; -import { ShapeTreeResourceType } from '@shapetrees/enums/ShapeTreeResourceType'; -import { ShapeTreeException } from '@shapetrees/exceptions/ShapeTreeException'; -import { LdpVocabulary } from '@shapetrees/vocabularies/LdpVocabulary'; +import { ShapeTreeManager } from '@shapetrees/core/src/ShapeTreeManager'; +import { ShapeTreeContext } from '@shapetrees/core/src/ShapeTreeContext'; +import { ManageableInstance } from '@shapetrees/core/src/ManageableInstance'; +import { ManageableResource } from '@shapetrees/core/src/ManageableResource'; +import { DocumentResponse } from '@shapetrees/core/src/DocumentResponse'; +import { ResourceAttributes } from '@shapetrees/core/src/ResourceAttributes'; +import { InstanceResource } from '@shapetrees/core/src/InstanceResource'; +import { ResourceAccessor } from '@shapetrees/core/src/ResourceAccessor'; +import { ManagerResource } from '@shapetrees/core/src/ManagerResource'; +import { MissingManageableResource } from '@shapetrees/core/src/MissingManageableResource'; +import { MissingManagerResource } from '@shapetrees/core/src/MissingManagerResource'; +import { UnmanagedResource } from '@shapetrees/core/src/UnmanagedResource'; +import { ManagedResource } from '@shapetrees/core/src/ManagedResource'; +import { HttpHeaders } from '@shapetrees/core/src/enums/HttpHeaders'; +import { LinkRelations } from '@shapetrees/core/src/enums/LinkRelations'; +import { ShapeTreeResourceType } from '@shapetrees/core/src/enums/ShapeTreeResourceType'; +import { ShapeTreeException } from '@shapetrees/core/src/exceptions/ShapeTreeException'; +import { LdpVocabulary } from '@shapetrees/core/src/vocabularies/LdpVocabulary'; import * as Graph from 'org/apache/jena/graph'; import * as Node from 'org/apache/jena/graph'; import * as NodeFactory from 'org/apache/jena/graph'; @@ -24,8 +24,8 @@ import * as Triple from 'org/apache/jena/graph'; import * as MalformedURLException from 'java/net'; import * as Set from 'java/util'; import * as Collections from 'java/util'; -import { readStringIntoGraph } from '@shapetrees/helpers/GraphHelper/readStringIntoGraph'; -import { urlToUri } from '@shapetrees/helpers/GraphHelper/urlToUri'; +import { readStringIntoGraph } from '@shapetrees/core/src/helpers/GraphHelper/readStringIntoGraph'; +import { urlToUri } from '@shapetrees/core/src/helpers/GraphHelper/urlToUri'; import { HttpRequest } from './HttpRequest'; import { HttpClient } from './HttpClient'; diff --git a/asTypescript/packages/client-http/src/HttpShapeTreeClient.ts b/asTypescript/packages/client-http/src/HttpShapeTreeClient.ts index b0d45f87..7b262149 100644 --- a/asTypescript/packages/client-http/src/HttpShapeTreeClient.ts +++ b/asTypescript/packages/client-http/src/HttpShapeTreeClient.ts @@ -1,17 +1,17 @@ // Corresponding shapetrees-java package: com.janeirodigital.shapetrees.client.http -import { ShapeTreeClient } from '@shapetrees/ShapeTreeClient'; -import { ShapeTreeManager } from '@shapetrees/ShapeTreeManager'; -import { ShapeTreeContext } from '@shapetrees/ShapeTreeContext'; -import { ManageableInstance } from '@shapetrees/ManageableInstance'; -import { ManageableResource } from '@shapetrees/ManageableResource'; -import { DocumentResponse } from '@shapetrees/DocumentResponse'; -import { ShapeTree } from '@shapetrees/ShapeTree'; -import { ShapeTreeFactory } from '@shapetrees/ShapeTreeFactory'; -import { ShapeTreeAssignment } from '@shapetrees/ShapeTreeAssignment'; -import { ResourceAttributes } from '@shapetrees/ResourceAttributes'; -import { HttpHeaders } from '@shapetrees/enums/HttpHeaders'; -import { LinkRelations } from '@shapetrees/enums/LinkRelations'; -import { ShapeTreeException } from '@shapetrees/exceptions/ShapeTreeException'; +import { ShapeTreeClient } from '@shapetrees/clientcore/src/ShapeTreeClient'; +import { ShapeTreeManager } from '@shapetrees/core/src/ShapeTreeManager'; +import { ShapeTreeContext } from '@shapetrees/core/src/ShapeTreeContext'; +import { ManageableInstance } from '@shapetrees/core/src/ManageableInstance'; +import { ManageableResource } from '@shapetrees/core/src/ManageableResource'; +import { DocumentResponse } from '@shapetrees/core/src/DocumentResponse'; +import { ShapeTree } from '@shapetrees/core/src/ShapeTree'; +import { ShapeTreeFactory } from '@shapetrees/core/src/ShapeTreeFactory'; +import { ShapeTreeAssignment } from '@shapetrees/core/src/ShapeTreeAssignment'; +import { ResourceAttributes } from '@shapetrees/core/src/ResourceAttributes'; +import { HttpHeaders } from '@shapetrees/core/src/enums/HttpHeaders'; +import { LinkRelations } from '@shapetrees/core/src/enums/LinkRelations'; +import { ShapeTreeException } from '@shapetrees/core/src/exceptions/ShapeTreeException'; import * as Lang from 'org/apache/jena/riot'; import * as RDFDataMgr from 'org/apache/jena/riot'; import * as RDFWriter from 'org/apache/jena/riot'; diff --git a/asTypescript/packages/javahttp/src/JavaHttpClient.ts b/asTypescript/packages/javahttp/src/JavaHttpClient.ts index 5cd4ace0..f7f27a44 100644 --- a/asTypescript/packages/javahttp/src/JavaHttpClient.ts +++ b/asTypescript/packages/javahttp/src/JavaHttpClient.ts @@ -1,9 +1,9 @@ // Corresponding shapetrees-java package: com.janeirodigital.shapetrees.javahttp -import { HttpClient } from '@shapetrees/HttpClient'; -import { HttpRequest } from '@shapetrees/HttpRequest'; -import { DocumentResponse } from '@shapetrees/DocumentResponse'; -import { ResourceAttributes } from '@shapetrees/ResourceAttributes'; -import { ShapeTreeException } from '@shapetrees/exceptions/ShapeTreeException'; +import { HttpClient } from '@shapetrees/clienthttp/src/HttpClient'; +import { HttpRequest } from '@shapetrees/clienthttp/src/HttpRequest'; +import { DocumentResponse } from '@shapetrees/core/src/DocumentResponse'; +import { ResourceAttributes } from '@shapetrees/core/src/ResourceAttributes'; +import { ShapeTreeException } from '@shapetrees/core/src/exceptions/ShapeTreeException'; import * as TrustManager from 'javax/net/ssl'; import * as X509TrustManager from 'javax/net/ssl'; import * as SSLContext from 'javax/net/ssl'; diff --git a/asTypescript/packages/javahttp/src/JavaHttpClientFactory.ts b/asTypescript/packages/javahttp/src/JavaHttpClientFactory.ts index ae4203a8..0733cf78 100644 --- a/asTypescript/packages/javahttp/src/JavaHttpClientFactory.ts +++ b/asTypescript/packages/javahttp/src/JavaHttpClientFactory.ts @@ -1,9 +1,9 @@ // Corresponding shapetrees-java package: com.janeirodigital.shapetrees.javahttp -import { HttpClientFactory } from '@shapetrees/HttpClientFactory'; -import { HttpRequest } from '@shapetrees/HttpRequest'; -import { DocumentResponse } from '@shapetrees/DocumentResponse'; -import { ExternalDocumentLoader } from '@shapetrees/contentloaders/ExternalDocumentLoader'; -import { ShapeTreeException } from '@shapetrees/exceptions/ShapeTreeException'; +import { HttpClientFactory } from '@shapetrees/clienthttp/src/HttpClientFactory'; +import { HttpRequest } from '@shapetrees/clienthttp/src/HttpRequest'; +import { DocumentResponse } from '@shapetrees/core/src/DocumentResponse'; +import { ExternalDocumentLoader } from '@shapetrees/core/src/contentloaders/ExternalDocumentLoader'; +import { ShapeTreeException } from '@shapetrees/core/src/exceptions/ShapeTreeException'; import { JavaHttpClient } from './JavaHttpClient'; /** diff --git a/asTypescript/packages/javahttp/src/JavaHttpValidatingShapeTreeInterceptor.ts b/asTypescript/packages/javahttp/src/JavaHttpValidatingShapeTreeInterceptor.ts index afa6d6fa..57cf0778 100644 --- a/asTypescript/packages/javahttp/src/JavaHttpValidatingShapeTreeInterceptor.ts +++ b/asTypescript/packages/javahttp/src/JavaHttpValidatingShapeTreeInterceptor.ts @@ -1,17 +1,17 @@ // Corresponding shapetrees-java package: com.janeirodigital.shapetrees.javahttp -import { HttpResourceAccessor } from '@shapetrees/HttpResourceAccessor'; -import { DocumentResponse } from '@shapetrees/DocumentResponse'; -import { ResourceAttributes } from '@shapetrees/ResourceAttributes'; -import { ResourceAccessor } from '@shapetrees/ResourceAccessor'; -import { ShapeTreeRequest } from '@shapetrees/ShapeTreeRequest'; -import { HttpHeaders } from '@shapetrees/enums/HttpHeaders'; -import { ShapeTreeResourceType } from '@shapetrees/enums/ShapeTreeResourceType'; -import { ShapeTreeException } from '@shapetrees/exceptions/ShapeTreeException'; -import { ValidatingMethodHandler } from '@shapetrees/methodhandlers/ValidatingMethodHandler'; -import { ValidatingDeleteMethodHandler } from '@shapetrees/methodhandlers/ValidatingDeleteMethodHandler'; -import { ValidatingPutMethodHandler } from '@shapetrees/methodhandlers/ValidatingPutMethodHandler'; -import { ValidatingPatchMethodHandler } from '@shapetrees/methodhandlers/ValidatingPatchMethodHandler'; -import { ValidatingPostMethodHandler } from '@shapetrees/methodhandlers/ValidatingPostMethodHandler'; +import { HttpResourceAccessor } from '@shapetrees/clienthttp/src/HttpResourceAccessor'; +import { DocumentResponse } from '@shapetrees/core/src/DocumentResponse'; +import { ResourceAttributes } from '@shapetrees/core/src/ResourceAttributes'; +import { ResourceAccessor } from '@shapetrees/core/src/ResourceAccessor'; +import { ShapeTreeRequest } from '@shapetrees/core/src/ShapeTreeRequest'; +import { HttpHeaders } from '@shapetrees/core/src/enums/HttpHeaders'; +import { ShapeTreeResourceType } from '@shapetrees/core/src/enums/ShapeTreeResourceType'; +import { ShapeTreeException } from '@shapetrees/core/src/exceptions/ShapeTreeException'; +import { ValidatingMethodHandler } from '@shapetrees/core/src/methodhandlers/ValidatingMethodHandler'; +import { ValidatingDeleteMethodHandler } from '@shapetrees/core/src/methodhandlers/ValidatingDeleteMethodHandler'; +import { ValidatingPutMethodHandler } from '@shapetrees/core/src/methodhandlers/ValidatingPutMethodHandler'; +import { ValidatingPatchMethodHandler } from '@shapetrees/core/src/methodhandlers/ValidatingPatchMethodHandler'; +import { ValidatingPostMethodHandler } from '@shapetrees/core/src/methodhandlers/ValidatingPostMethodHandler'; import * as NotNull from 'org/jetbrains/annotations'; import * as SSLSession from 'javax/net/ssl'; import * as MalformedURLException from 'java/net'; diff --git a/asTypescript/run-javatots.sh b/asTypescript/run-javatots.sh new file mode 100755 index 00000000..e438516f --- /dev/null +++ b/asTypescript/run-javatots.sh @@ -0,0 +1,5 @@ +JAR_REPO=/home/eric/.m2/repository/ + +java -Dfile.encoding=UTF-8 \ + -classpath ../../../ericprud/java-to-typescript/javatots/target/classes:$JAR_REPO/org/yaml/snakeyaml/1.29/snakeyaml-1.29.jar:$JAR_REPO/com/github/javaparser/javaparser-core/3.23.1/javaparser-core-3.23.1.jar \ + org.javatots.main.JavaToTypescript javatots-config.yaml From b270fcf405d8e61ae94ee6e2e757c82cc99f2823 Mon Sep 17 00:00:00 2001 From: Eric Prud'hommeaux Date: Fri, 24 Dec 2021 19:30:44 +0100 Subject: [PATCH 13/15] + tests --- asTypescript/javatots-config.yaml | 7 + .../src/AbstractResourceAccessorTests.ts | 260 ++++++++ .../packages/tests/src/DocumentLoaderTests.ts | 34 + .../packages/tests/src/GraphHelperTests.ts | 162 +++++ .../tests/src/HttpDocumentLoaderTests.ts | 66 ++ .../tests/src/ResourceAttributesTests.ts | 83 +++ .../packages/tests/src/SchemaCacheTests.ts | 121 ++++ .../src/ShapeTreeContainsPriorityTests.ts | 67 ++ .../tests/src/ShapeTreeManagerDeltaTests.ts | 215 ++++++ .../tests/src/ShapeTreeManagerTests.ts | 207 ++++++ .../tests/src/ShapeTreeParsingTests.ts | 230 +++++++ .../tests/src/ShapeTreeValidationTests.ts | 202 ++++++ .../AbstractHttpClientDiscoverTests.ts | 97 +++ .../AbstractHttpClientInitializeTests.ts | 27 + ...AbstractHttpClientProjectRecursiveTests.ts | 72 ++ .../AbstractHttpClientProjectTests.ts | 616 ++++++++++++++++++ ...AbstractHttpClientResourceAccessorTests.ts | 33 + .../src/clienthttp/AbstractHttpClientTests.ts | 40 ++ .../clienthttp/AbstractHttpClientTypeTests.ts | 127 ++++ .../AbstractHttpClientValidationTests.ts | 142 ++++ .../tests/src/fixtures/DispatcherEntry.ts | 38 ++ .../packages/tests/src/fixtures/Fixture.ts | 102 +++ .../tests/src/fixtures/MockWebServerHelper.ts | 10 + .../packages/tests/src/fixtures/Parser.ts | 7 + .../RequestMatchingFixtureDispatcher.ts | 127 ++++ .../packages/tests/src/fixtures/YamlParser.ts | 11 + 26 files changed, 3103 insertions(+) create mode 100644 asTypescript/packages/tests/src/AbstractResourceAccessorTests.ts create mode 100644 asTypescript/packages/tests/src/DocumentLoaderTests.ts create mode 100644 asTypescript/packages/tests/src/GraphHelperTests.ts create mode 100644 asTypescript/packages/tests/src/HttpDocumentLoaderTests.ts create mode 100644 asTypescript/packages/tests/src/ResourceAttributesTests.ts create mode 100644 asTypescript/packages/tests/src/SchemaCacheTests.ts create mode 100644 asTypescript/packages/tests/src/ShapeTreeContainsPriorityTests.ts create mode 100644 asTypescript/packages/tests/src/ShapeTreeManagerDeltaTests.ts create mode 100644 asTypescript/packages/tests/src/ShapeTreeManagerTests.ts create mode 100644 asTypescript/packages/tests/src/ShapeTreeParsingTests.ts create mode 100644 asTypescript/packages/tests/src/ShapeTreeValidationTests.ts create mode 100644 asTypescript/packages/tests/src/clienthttp/AbstractHttpClientDiscoverTests.ts create mode 100644 asTypescript/packages/tests/src/clienthttp/AbstractHttpClientInitializeTests.ts create mode 100644 asTypescript/packages/tests/src/clienthttp/AbstractHttpClientProjectRecursiveTests.ts create mode 100644 asTypescript/packages/tests/src/clienthttp/AbstractHttpClientProjectTests.ts create mode 100644 asTypescript/packages/tests/src/clienthttp/AbstractHttpClientResourceAccessorTests.ts create mode 100644 asTypescript/packages/tests/src/clienthttp/AbstractHttpClientTests.ts create mode 100644 asTypescript/packages/tests/src/clienthttp/AbstractHttpClientTypeTests.ts create mode 100644 asTypescript/packages/tests/src/clienthttp/AbstractHttpClientValidationTests.ts create mode 100644 asTypescript/packages/tests/src/fixtures/DispatcherEntry.ts create mode 100644 asTypescript/packages/tests/src/fixtures/Fixture.ts create mode 100644 asTypescript/packages/tests/src/fixtures/MockWebServerHelper.ts create mode 100644 asTypescript/packages/tests/src/fixtures/Parser.ts create mode 100644 asTypescript/packages/tests/src/fixtures/RequestMatchingFixtureDispatcher.ts create mode 100644 asTypescript/packages/tests/src/fixtures/YamlParser.ts diff --git a/asTypescript/javatots-config.yaml b/asTypescript/javatots-config.yaml index a8adbbe0..02822f0a 100644 --- a/asTypescript/javatots-config.yaml +++ b/asTypescript/javatots-config.yaml @@ -35,3 +35,10 @@ moduleMaps: packageMaps: - pkg: com.janeirodigital.shapetrees.javahttp destPath: + shapetrees-java-tests: + srcRoot: src/test/java + outputPath: packages/tests/src + tsModule: '@shapetrees/tests' + packageMaps: + - pkg: com.janeirodigital.shapetrees.tests + destPath: diff --git a/asTypescript/packages/tests/src/AbstractResourceAccessorTests.ts b/asTypescript/packages/tests/src/AbstractResourceAccessorTests.ts new file mode 100644 index 00000000..8e1b4688 --- /dev/null +++ b/asTypescript/packages/tests/src/AbstractResourceAccessorTests.ts @@ -0,0 +1,260 @@ +// Corresponding shapetrees-java package: com.janeirodigital.shapetrees.tests +import { ShapeTreeException } from '@shapetrees/core/src/exceptions/ShapeTreeException'; +import { ShapeTreeContext } from '@shapetrees/core/src/ShapeTreeContext'; +import { ShapeTreeManager } from '@shapetrees/core/src/ShapeTreeManager'; +import { DispatcherEntry } from './fixtures/DispatcherEntry'; +import { RequestMatchingFixtureDispatcher } from './fixtures/RequestMatchingFixtureDispatcher'; +import * as MockWebServer from 'okhttp3/mockwebserver'; +import * as Assertions from 'org/junit/jupiter/api'; +import * as BeforeAll from 'org/junit/jupiter/api'; +import * as DisplayName from 'org/junit/jupiter/api'; +import * as Test from 'org/junit/jupiter/api'; +import * as MalformedURLException from 'java/net'; +import * as assertFalse from 'org/junit/jupiter/api/Assertions'; +import * as assertTrue from 'org/junit/jupiter/api/Assertions'; + +export class AbstractResourceAccessorTests { + + protected resourceAccessor: ResourceAccessor = null; + + protected readonly context: ShapeTreeContext; + + protected static server: MockWebServer = null; + + protected static dispatcher: RequestMatchingFixtureDispatcher = null; + + public constructor() { + this.context = new ShapeTreeContext(null); + } + + public toUrl(server: MockWebServer, path: string): URL /* throws MalformedURLException */ { + // TODO: duplicates com.janeirodigital.shapetrees.tests.fixtures.MockWebServerHelper.getURL; + return new URL(server.url(path).toString()); + } + + // @BeforeAll + static beforeAll(): void { + dispatcher = new RequestMatchingFixtureDispatcher(List.of(new DispatcherEntry(List.of("resourceAccessor/resource-no-link-headers"), "GET", "/static/resource/resource-no-link-headers", null), new DispatcherEntry(List.of("resourceAccessor/resource-empty-link-header"), "GET", "/static/resource/resource-empty-link-header", null), new DispatcherEntry(List.of("resourceAccessor/resource-container-link-header"), "GET", "/static/resource/resource-container-link-header", null), new DispatcherEntry(List.of("resourceAccessor/resource-container-link-header"), "GET", "/static/resource/resource-container-link-header/", null), new DispatcherEntry(List.of("resourceAccessor/resource-container-invalid-link-header"), "GET", "/static/resource/resource-container-invalid-link-header/", null), new DispatcherEntry(List.of("resourceAccessor/managed-container-1"), "GET", "/static/resource/managed-container-1/", null), new DispatcherEntry(List.of("resourceAccessor/managed-resource-1-create"), "PUT", "/static/resource/managed-container-1/managed-resource-1/", null), new DispatcherEntry(List.of("resourceAccessor/managed-resource-1-manager"), "GET", "/static/resource/managed-container-1/managed-resource-1/.shapetree", null), new DispatcherEntry(List.of("resourceAccessor/managed-container-1-manager"), "GET", "/static/resource/managed-container-1/.shapetree", null), new DispatcherEntry(List.of("resourceAccessor/unmanaged-container-2"), "GET", "/static/resource/unmanaged-container-2/", null), new DispatcherEntry(List.of("resourceAccessor/managed-container-2"), "GET", "/static/resource/managed-container-2/", null), new DispatcherEntry(List.of("resourceAccessor/unmanaged-resource-1-create"), "PUT", "/static/resource/unmanaged-resource-1", null), new DispatcherEntry(List.of("resourceAccessor/managed-container-2-manager-create"), "PUT", "/static/resource/managed-container-2/.shapetree", null), new DispatcherEntry(List.of("errors/404"), "GET", "/static/resource/missing-resource-1.shapetree", null), new DispatcherEntry(List.of("errors/404"), "GET", "/static/resource/missing-resource-2", null), new DispatcherEntry(List.of("resourceAccessor/missing-resource-2-manager-create"), "PUT", "/static/resource/missing-resource-2.shapetree", null), new DispatcherEntry(List.of("shapetrees/project-shapetree-ttl"), "GET", "/static/shapetrees/project/shapetree", null), new DispatcherEntry(List.of("schemas/project-shex"), "GET", "/static/shex/project/shex", null), new DispatcherEntry(List.of("errors/404"), "GET", "/static/resource/notpresent", null))); + server = new MockWebServer(); + server.setDispatcher(dispatcher); + } + + // Tests to Get ManageableInstances + // @Test, @SneakyThrows, @DisplayName("Get instance from missing resource") + getInstanceFromMissingResource(): void { + let instance: ManageableInstance = this.resourceAccessor.getInstance(context, toUrl(server, "/static/resource/notpresent")); + Assertions.assertTrue(instance.getManageableResource() instanceof MissingManageableResource); + Assertions.assertTrue(instance.getManagerResource() instanceof MissingManagerResource); + Assertions.assertFalse(instance.isManaged()); + Assertions.assertEquals(instance.getManageableResource().getUrl(), toUrl(server, "/static/resource/notpresent")); + Assertions.assertFalse(instance.getManageableResource().isExists()); + Assertions.assertFalse(instance.getManagerResource().isExists()); + } + + // @Test, @SneakyThrows, @DisplayName("Get instance from managed resource") + getInstanceFromManagedResource(): void { + // If the resource is Manageable - determine if it is managed by getting manager + // Get and store a ManagedResource in instance - Manager exists - store manager in instance too + let instance: ManageableInstance = this.resourceAccessor.getInstance(context, toUrl(server, "/static/resource/managed-container-1/")); + Assertions.assertTrue(instance.getManageableResource() instanceof ManagedResource); + Assertions.assertNotNull(instance.getManagerResource()); + Assertions.assertFalse(instance.getManagerResource() instanceof MissingManagerResource); + Assertions.assertTrue(instance.isManaged()); + Assertions.assertFalse(instance.isUnmanaged()); + let managerResource: ManagerResource = instance.getManagerResource(); + let manager: ShapeTreeManager = managerResource.getManager(); + Assertions.assertEquals(1, manager.getAssignments().size()); + } + + // @Test, @SneakyThrows, @DisplayName("Get instance for managed resource from manager request") + getInstanceFromManagedResourceFromManager(): void { + let instance: ManageableInstance = this.resourceAccessor.getInstance(context, toUrl(server, "/static/resource/managed-container-1/.shapetree")); + Assertions.assertNotNull(instance.getManagerResource()); + Assertions.assertFalse(instance.getManagerResource() instanceof MissingManagerResource); + Assertions.assertTrue(instance.getManagerResource().isExists()); + Assertions.assertTrue(instance.getManageableResource() instanceof ManagedResource); + Assertions.assertEquals(instance.getManagerResource().getManagedResourceUrl(), instance.getManageableResource().getUrl()); + } + + // @Test, @SneakyThrows, @DisplayName("Fail to get instance for missing resource from manager request") + failToGetInstanceForMissingManageableResourceFromManager(): void { + // Note that in this request, the manager is also non-existent + Assertions.assertThrows(ShapeTreeException.class, () -> { + let instance: ManageableInstance = this.resourceAccessor.getInstance(context, toUrl(server, "/static/resource/missing-resource-1.shapetree")); + }); + } + + // @Test, @SneakyThrows, @DisplayName("Get instance from unmanaged resource") + getInstanceFromUnmanagedResource(): void { + // If the resource is Manageable - determine if it is managed by getting manager + // Get and store an UnmanagedResource in instance - No manager exists - store the location of the manager url + let instance: ManageableInstance = this.resourceAccessor.getInstance(context, toUrl(server, "/static/resource/unmanaged-container-2/")); + Assertions.assertTrue(instance.getManageableResource() instanceof UnmanagedResource); + Assertions.assertTrue(instance.getManagerResource() instanceof MissingManagerResource); + Assertions.assertTrue(instance.isUnmanaged()); + Assertions.assertFalse(instance.isManaged()); + } + + // @Test, @SneakyThrows, @DisplayName("Get instance from unmanaged resource from manager request") + getInstanceFromUnmanagedResourceFromManager(): void { + // Manager resource doesn't exist. Unmanaged resource associated with it does exist + let instance: ManageableInstance = this.resourceAccessor.getInstance(context, toUrl(server, "/static/resource/unmanaged-container-2/.shapetree")); + Assertions.assertTrue(instance.getManageableResource() instanceof UnmanagedResource); + Assertions.assertTrue(instance.getManagerResource() instanceof MissingManagerResource); + Assertions.assertTrue(instance.wasRequestForManager()); + Assertions.assertTrue(instance.isUnmanaged()); + Assertions.assertFalse(instance.isManaged()); + } + + // Tests to Create ManageableInstances + // @Test, @SneakyThrows, @DisplayName("Create instance from managed resource") + createInstanceFromManagedResource(): void { + let headers: ResourceAttributes = new ResourceAttributes(); + let instance: ManageableInstance = this.resourceAccessor.createInstance(context, "PUT", toUrl(server, "/static/resource/managed-container-1/managed-resource-1/"), headers, getMilestoneThreeBodyGraph(), "text/turtle"); + Assertions.assertTrue(instance.isManaged()); + Assertions.assertTrue(instance.getManageableResource() instanceof ManagedResource); + Assertions.assertFalse(instance.getManageableResource() instanceof MissingManageableResource); + Assertions.assertTrue(instance.getManagerResource().isExists()); + Assertions.assertEquals(instance.getManagerResource().getManagedResourceUrl(), instance.getManageableResource().getUrl()); + let managerResource: ManagerResource = instance.getManagerResource(); + let manager: ShapeTreeManager = managerResource.getManager(); + Assertions.assertEquals(1, manager.getAssignments().size()); + } + + // @Test, @SneakyThrows, @DisplayName("Create instance from unmanaged resource") + createInstanceFromUnmanagedResource(): void { + let headers: ResourceAttributes = new ResourceAttributes(); + let instance: ManageableInstance = this.resourceAccessor.createInstance(context, "PUT", toUrl(server, "/static/resource/unmanaged-resource-1"), headers, "<#a> <#b> <#c>", "text/turtle"); + Assertions.assertTrue(instance.isUnmanaged()); + Assertions.assertTrue(instance.getManageableResource() instanceof UnmanagedResource); + Assertions.assertTrue(instance.getManagerResource() instanceof MissingManagerResource); + } + + // @Test, @SneakyThrows, @DisplayName("Fail to create instance from existing manageable resource") + failToCreateInstanceFromExistingResource(): void { + // Resource exists - ERROR - can't create a manageable resource when one already exists + // May need to populate this + let headers: ResourceAttributes = new ResourceAttributes(); + Assertions.assertThrows(ShapeTreeException.class, () -> { + let instance: ManageableInstance = this.resourceAccessor.createInstance(context, "PUT", toUrl(server, "/static/resource/unmanaged-container-2/"), headers, "<#a> <#b> <#c>", "text/turtle"); + }); + } + + // @Test, @SneakyThrows, @DisplayName("Create instance from manager resource") + createInstanceFromManagerResource(): void { + // Create a new manager and store in instance and load the managed resource and store in instance (possibly just pre-fetch metadata if lazily loading) + let headers: ResourceAttributes = new ResourceAttributes(); + let instance: ManageableInstance = this.resourceAccessor.createInstance(context, "PUT", toUrl(server, "/static/resource/managed-container-2/.shapetree"), headers, getProjectTwoManagerGraph(), "text/turtle"); + Assertions.assertTrue(instance.isManaged()); + Assertions.assertTrue(instance.getManageableResource() instanceof ManagedResource); + Assertions.assertFalse(instance.getManagerResource() instanceof MissingManagerResource); + // Probably need some additional tests + } + + // @Test, @DisplayName("Fail to create instance from isolated manager resource") + failToCreateInstanceFromIsolatedManagerResource(): void { + let headers: ResourceAttributes = new ResourceAttributes(); + Assertions.assertThrows(ShapeTreeException.class, () -> { + let instance: ManageableInstance = this.resourceAccessor.createInstance(context, "PUT", toUrl(server, "/static/resource/missing-resource-2.shapetree"), headers, getProjectTwoManagerGraph(), "text/turtle"); + }); + } + + // TODO - currently missing dedicated tests for create and delete. only one test for update (which is a failure test) + // @Test, @DisplayName("Get a resource without any link headers") + getResourceWithNoLinkHeaders(): void /* throws MalformedURLException, ShapeTreeException */ { + let resource: InstanceResource = this.resourceAccessor.getResource(this.context, toUrl(server, "/static/resource/resource-no-link-headers")); + // This is a strange way to check whether something has no link headers + assertTrue(resource.isExists()); + assertTrue(((ManageableResource) resource).getManagerResourceUrl().isEmpty()); + } + + // @Test, @DisplayName("Get a resource with an empty link header") + getResourceWithEmptyLinkHeader(): void /* throws MalformedURLException, ShapeTreeException */ { + // Link header is present but has nothing in it + let resource: InstanceResource = this.resourceAccessor.getResource(context, toUrl(server, "/static/resource/resource-empty-link-header")); + assertTrue(resource.isExists()); + Assertions.assertTrue(((ManageableResource) resource).getManagerResourceUrl().isEmpty()); + } + + // @Test, @DisplayName("Fail to get a resource with an invalid URL string") + failToAccessResourceWithInvalidUrlString(): void /* throws MalformedURLException, ShapeTreeException */ { + // TODO: Test: may as well deleted as it's only testing URL.create() + Assertions.assertThrows(MalformedURLException.class, () -> this.resourceAccessor.getResource(context, new URL(":invalid"))); + // TODO - this should also test create, update, delete, getContained, (also get/create instance) + } + + // @Test, @DisplayName("Get a missing resource with no slash") + getMissingResourceWithNoSlash(): void /* throws MalformedURLException, ShapeTreeException */ { + let resource: InstanceResource = this.resourceAccessor.getResource(context, toUrl(server, "/static/resource/not-existing-no-slash")); + assertFalse(resource.isExists()); + assertFalse(((ManageableResource) resource).isContainer()); + } + + // @Test, @DisplayName("Get a missing container with slash") + getMissingContainerWithSlash(): void /* throws MalformedURLException, ShapeTreeException */ { + let resource: InstanceResource = this.resourceAccessor.getResource(context, toUrl(server, "/static/resource/not-existing-slash/")); + assertFalse(resource.isExists()); + assertTrue(((ManageableResource) resource).isContainer()); + } + + // @Test, @DisplayName("Get a missing container with slash and fragment") + getMissingContainerWithSlashAndFragment(): void /* throws MalformedURLException, ShapeTreeException */ { + let resource: InstanceResource = this.resourceAccessor.getResource(context, toUrl(server, "/static/resource/not-existing-slash/#withfragment")); + assertFalse(resource.isExists()); + assertTrue(((ManageableResource) resource).isContainer()); + } + + // @Test, @DisplayName("Get an existing container with no slash") + getExistingContainerNoSlash(): void /* throws MalformedURLException, ShapeTreeException */ { + // TODO - In Solid at least, the slash must be present, so I question whether setting this as a container helps or hurts + let resource: InstanceResource = this.resourceAccessor.getResource(context, toUrl(server, "/static/resource/resource-container-link-header")); + assertTrue(resource.isExists()); + assertTrue(((ManageableResource) resource).isContainer()); + } + + // @Test, @DisplayName("Get an existing container") + getExistingContainer(): void /* throws MalformedURLException, ShapeTreeException */ { + let resource: InstanceResource = this.resourceAccessor.getResource(context, toUrl(server, "/static/resource/resource-container-link-header/")); + assertTrue(resource.isExists()); + assertTrue(((ManageableResource) resource).isContainer()); + } + + // @Test, @DisplayName("Fail to lookup invalid resource attributes") + failToLookupInvalidAttributes(): void /* throws MalformedURLException, ShapeTreeException */ { + let resource: InstanceResource = this.resourceAccessor.getResource(context, toUrl(server, "/static/resource/resource-container-link-header")); + assertTrue(resource.isExists()); + Assertions.assertNull(resource.getAttributes().firstValue("invalid").orElse(null)); + } + + // @Test, @DisplayName("Get a missing resource") + getMissingResource(): void /* throws MalformedURLException, ShapeTreeException */ { + let resource: InstanceResource = this.resourceAccessor.getResource(context, toUrl(server, "/static/resource/notpresent")); + Assertions.assertEquals("", resource.getBody()); + // TODO - what other tests and assertions should be included here? isExists()? + } + + // @Test, @DisplayName("Get a container with an invalid link type header") + getContainerWithInvalidLinkTypeHeader(): void /* throws MalformedURLException, ShapeTreeException */ { + // TODO - at the moment we process this happily. Aside from not marking it as a container, should there be a more severe handling? + let resource: InstanceResource = this.resourceAccessor.getResource(context, toUrl(server, "/static/resource/resource-container-invalid-link-header/")); + assertTrue(resource.isExists()); + assertFalse(((ManageableResource) resource).isContainer()); + } + + // @Test, @DisplayName("Fail to update resource") + failToUpdateResource(): void /* throws MalformedURLException, ShapeTreeException */ { + // Succeed in getting a resource + let resource: InstanceResource = this.resourceAccessor.getResource(context, toUrl(server, "/static/resource/resource-container-link-header/")); + // Fail to update it + let response: DocumentResponse = this.resourceAccessor.updateResource(context, "PUT", resource, "BODY"); + assertFalse(response.isExists()); + } + + private getMilestoneThreeBodyGraph(): string { + return "PREFIX rdf: \n" + "PREFIX rdfs: \n" + "PREFIX xml: \n" + "PREFIX xsd: \n" + "PREFIX ex: \n" + "\n" + "\n" + "<#milestone> \n" + " ex:uri ; \n" + " ex:id 12345 ; \n" + " ex:name \"Milestone 3 of Project 1\" ; \n" + " ex:created_at \"2021-04-04T20:15:47.000Z\"^^xsd:dateTime ; \n" + " ex:target \"2021-06-05T20:15:47.000Z\"^^xsd:dateTime ; \n" + " ex:inProject . \n"; + } + + private getProjectTwoManagerGraph(): string { + return "PREFIX rdf: \n" + "PREFIX rdfs: \n" + "PREFIX xml: \n" + "PREFIX xsd: \n" + "PREFIX ex: \n" + "\n" + "<> \n" + " a st:Manager ; \n" + " st:hasAssignment <#ln1> . \n" + "\n" + "<#ln1> \n" + " st:assigns <${SERVER_BASE}/static/shapetrees/project/shapetree#ProjectTree> ; \n" + " st:manages ; \n" + " st:hasRootAssignment <#ln1> ; \n" + " st:focusNode ; \n" + " st:shape <${SERVER_BASE}/static/shex/project/shex#ProjectShape> . \n"; + } +} diff --git a/asTypescript/packages/tests/src/DocumentLoaderTests.ts b/asTypescript/packages/tests/src/DocumentLoaderTests.ts new file mode 100644 index 00000000..65422378 --- /dev/null +++ b/asTypescript/packages/tests/src/DocumentLoaderTests.ts @@ -0,0 +1,34 @@ +// Corresponding shapetrees-java package: com.janeirodigital.shapetrees.tests +import { DocumentResponse } from '@shapetrees/core/src/DocumentResponse'; +import { DocumentLoaderManager } from '@shapetrees/core/src/contentloaders/DocumentLoaderManager'; +import { ExternalDocumentLoader } from '@shapetrees/core/src/contentloaders/ExternalDocumentLoader'; +import { ShapeTreeException } from '@shapetrees/core/src/exceptions/ShapeTreeException'; + +@TestMethodOrder(MethodOrderer.OrderAnnotation.class) +class DocumentLoaderTests { + + // @BeforeAll, @AfterAll + static clearDocumentManager(): void { + DocumentLoaderManager.setLoader(null); + } + + // @Test, @Order(1), @DisplayName("Fail to get missing document loader"), @SneakyThrows + failToGetMissingDocumentLoader(): void { + Assertions.assertThrows(ShapeTreeException.class, () -> { + DocumentLoaderManager.getLoader(); + }); + } + + // @Test, @Order(2), @DisplayName("Get document loader"), @SneakyThrows + getDocumentLoader(): void { + DocumentLoaderManager.setLoader(new TestDocumentLoader()); + Assertions.assertNotNull(DocumentLoaderManager.getLoader()); + } +} + +class TestDocumentLoader implements ExternalDocumentLoader { + + public loadExternalDocument(resourceURL: URL): DocumentResponse /* throws ShapeTreeException */ { + return new DocumentResponse(null, null, 200); + } +} diff --git a/asTypescript/packages/tests/src/GraphHelperTests.ts b/asTypescript/packages/tests/src/GraphHelperTests.ts new file mode 100644 index 00000000..42e36872 --- /dev/null +++ b/asTypescript/packages/tests/src/GraphHelperTests.ts @@ -0,0 +1,162 @@ +// Corresponding shapetrees-java package: com.janeirodigital.shapetrees.tests +import { ShapeTreeException } from '@shapetrees/core/src/exceptions/ShapeTreeException'; +import { GraphHelper } from '@shapetrees/core/src/helpers/GraphHelper'; +import * as Graph from 'org/apache/jena/graph'; +import * as NodeFactory from 'org/apache/jena/graph'; +import * as Triple from 'org/apache/jena/graph'; +import * as ModelFactory from 'org/apache/jena/rdf/model'; +import * as Lang from 'org/apache/jena/riot'; +import * as Assertions from 'org/junit/jupiter/api'; +import * as DisplayName from 'org/junit/jupiter/api'; +import * as Test from 'org/junit/jupiter/api'; +import * as ParameterizedTest from 'org/junit/jupiter/params'; +import * as NullAndEmptySource from 'org/junit/jupiter/params/provider'; +import * as ValueSource from 'org/junit/jupiter/params/provider'; +import * as URI from 'java/net'; +import * as OffsetDateTime from 'java/time'; +import { newTriple } from '@shapetrees/core/src/helpers/GraphHelper/newTriple'; + +class GraphHelperTests { + + // @ParameterizedTest, @NullAndEmptySource, @DisplayName("Handle null or empty content types with defaults"), @SneakyThrows + handleNullOrEmptyContentTypes(type: string): void { + let lang: Lang = GraphHelper.getLangForContentType(type); + Assertions.assertEquals(lang, Lang.TURTLE); + } + + // @ParameterizedTest, @ValueSource(strings = { "text/turtle", "something/bogus" }), @DisplayName("Handle turtle content type when specified or as default"), @SneakyThrows + handleTurtleContentType(type: string): void { + let lang: Lang = GraphHelper.getLangForContentType(type); + Assertions.assertEquals(lang, Lang.TURTLE); + } + + // @Test, @DisplayName("JSON LD content type"), @SneakyThrows + handleJsonLD(): void { + let lang: Lang = GraphHelper.getLangForContentType("application/ld+json"); + Assertions.assertEquals(lang, Lang.JSONLD); + } + + // @Test, @DisplayName("N-Triples content type"), @SneakyThrows + hanldeNTriples(): void { + let lang: Lang = GraphHelper.getLangForContentType("application/n-triples"); + Assertions.assertEquals(lang, Lang.NTRIPLES); + } + + // @Test, @DisplayName("rdf+xml content type"), @SneakyThrows + hanldeRDFXMLTriples(): void { + let lang: Lang = GraphHelper.getLangForContentType("application/rdf+xml"); + Assertions.assertEquals(lang, Lang.RDFXML); + } + + // @Test, @DisplayName("Parse invalid TTL"), @SneakyThrows + parseInvalidTTL(): void { + let invalidTtl: string = "<#a> b c"; + Assertions.assertThrows(ShapeTreeException.class, () -> GraphHelper.readStringIntoGraph(URI.create("https://example.com/a"), invalidTtl, "text/turtle")); + } + + // @Test, @DisplayName("Parse valid TTL"), @SneakyThrows + parseValidTTL(): void { + let invalidTtl: string = "<#a> <#b> <#c> ."; + Assertions.assertNotNull(GraphHelper.readStringIntoGraph(URI.create("https://example.com/a"), invalidTtl, "text/turtle")); + } + + // @Test, @DisplayName("Write graph to TTL String"), @SneakyThrows + writeGraphToTTLString(): void { + let graph: Graph = ModelFactory.createDefaultModel().getGraph(); + graph.add(new Triple(NodeFactory.createURI("<#b>"), NodeFactory.createURI("<#c>"), NodeFactory.createURI("<#d>"))); + Assertions.assertNotNull(GraphHelper.writeGraphToTurtleString(graph)); + } + + // @Test, @DisplayName("Write null graph to TTL String"), @SneakyThrows + writeNullGraphToTTLString(): void { + Assertions.assertNull(GraphHelper.writeGraphToTurtleString(null)); + } + + // @Test, @DisplayName("Write closed graph to TTL String"), @SneakyThrows + writeClosedGraphtoTTLString(): void { + let graph: Graph = ModelFactory.createDefaultModel().getGraph(); + graph.add(new Triple(NodeFactory.createURI("<#b>"), NodeFactory.createURI("<#c>"), NodeFactory.createURI("<#d>"))); + graph.close(); + Assertions.assertNull(GraphHelper.writeGraphToTurtleString(graph)); + } + + // @Test, @DisplayName("Fail to store null string objects in new Triple helper"), @SneakyThrows + failToStoreNullTripleStringObjects(): void { + Assertions.assertThrows(ShapeTreeException.class, () -> { + newTriple("<#b>", "<#c>", null); + }); + Assertions.assertThrows(ShapeTreeException.class, () -> { + newTriple("<#b>", null, "<#d>"); + }); + Assertions.assertThrows(ShapeTreeException.class, () -> { + newTriple(null, "<#c>", "<#d>"); + }); + } + + // @Test, @DisplayName("Fail to store null URI objects in new Triple helper"), @SneakyThrows + failToStoreNullTripleURIObjects(): void { + let subjectURI: URI = URI.create("https://site.example/#a"); + let predicateURI: URI = URI.create("https://site.example/#b"); + let objectURI: URI = URI.create("https://site.example/#c"); + Assertions.assertThrows(ShapeTreeException.class, () -> { + newTriple(subjectURI, predicateURI, null); + }); + Assertions.assertThrows(ShapeTreeException.class, () -> { + newTriple(subjectURI, null, objectURI); + }); + Assertions.assertThrows(ShapeTreeException.class, () -> { + newTriple(null, predicateURI, objectURI); + }); + } + + // @Test, @DisplayName("Store URI as subject and predicate with new Triple helper"), @SneakyThrows + storeURISubjectAndPredicate(): void { + let uriTriple: Triple = newTriple(URI.create("https://site.example/#a"), URI.create("https://site.example/#b"), URI.create("https://site.example/#a")); + Assertions.assertNotNull(uriTriple); + Assertions.assertTrue(uriTriple.getSubject().isURI()); + Assertions.assertTrue(uriTriple.getPredicate().isURI()); + Assertions.assertTrue(uriTriple.getObject().isURI()); + } + + // @Test, @DisplayName("Store URI object with new Triple helper"), @SneakyThrows + storeURIasTripleObject(): void { + let uriTriple: Triple = newTriple("https://site.example/#a", "https://site.example/#b", URI.create("https://site.example/#c")); + Assertions.assertNotNull(uriTriple); + Assertions.assertTrue(uriTriple.getObject().isURI()); + } + + // @Test, @DisplayName("Store String object with new Triple helper"), @SneakyThrows + storeStringAsTripleObject(): void { + let uriTriple: Triple = newTriple("https://site.example/#a", "https://site.example/#b", "This is a test string"); + Assertions.assertNotNull(uriTriple); + Assertions.assertTrue(uriTriple.getObject().isLiteral()); + } + + // @Test, @DisplayName("Store DateTime object with new Triple helper"), @SneakyThrows + storeDateTimeAsTripleObject(): void { + let uriTriple: Triple = newTriple("https://site.example/#a", "https://site.example/#b", OffsetDateTime.now()); + Assertions.assertNotNull(uriTriple); + Assertions.assertTrue(uriTriple.getObject().isLiteral()); + } + + // @Test, @DisplayName("Store Boolean object with new Triple helper"), @SneakyThrows + storeBooleanAsTripleObject(): void { + let uriTriple: Triple = newTriple("https://site.example/#a", "https://site.example/#b", Boolean.TRUE); + Assertions.assertNotNull(uriTriple); + Assertions.assertTrue(uriTriple.getObject().isLiteral()); + } + + // @Test, @DisplayName("Store Blank Node with new Triple helper"), @SneakyThrows + storeBlankNodeAsTripleObject(): void { + let uriTriple: Triple = newTriple("https://site.example/#a", "https://site.example/#b", NodeFactory.createBlankNode()); + Assertions.assertNotNull(uriTriple); + Assertions.assertTrue(uriTriple.getObject().isBlank()); + } + + // @Test, @DisplayName("Fail to store Unsupported Type with new Triple helper"), @SneakyThrows + failedToStoreUnsupportedTypeAsTripleObject(): void { + Assertions.assertThrows(ShapeTreeException.class, () -> { + newTriple("https://site.example/#a", "https://site.example/#b", (float) 35.6); + }); + } +} diff --git a/asTypescript/packages/tests/src/HttpDocumentLoaderTests.ts b/asTypescript/packages/tests/src/HttpDocumentLoaderTests.ts new file mode 100644 index 00000000..cd422d2c --- /dev/null +++ b/asTypescript/packages/tests/src/HttpDocumentLoaderTests.ts @@ -0,0 +1,66 @@ +// Corresponding shapetrees-java package: com.janeirodigital.shapetrees.tests +import { DocumentResponse } from '@shapetrees/core/src/DocumentResponse'; +import { DocumentLoaderManager } from '@shapetrees/core/src/contentloaders/DocumentLoaderManager'; +import { HttpExternalDocumentLoader } from '@shapetrees/core/src/contentloaders/HttpExternalDocumentLoader'; +import { ShapeTreeException } from '@shapetrees/core/src/exceptions/ShapeTreeException'; +import { DispatcherEntry } from './fixtures/DispatcherEntry'; +import { RequestMatchingFixtureDispatcher } from './fixtures/RequestMatchingFixtureDispatcher'; +import * as MockWebServer from 'okhttp3/mockwebserver'; +import * as MalformedURLException from 'java/net'; + +class HttpDocumentLoaderTests { + + private static dispatcher: RequestMatchingFixtureDispatcher = null; + + private static httpExternalDocumentLoader: HttpExternalDocumentLoader; + + public constructor() { + httpExternalDocumentLoader = new HttpExternalDocumentLoader(); + DocumentLoaderManager.setLoader(httpExternalDocumentLoader); + } + + protected getURL(server: MockWebServer, path: string): URL /* throws MalformedURLException */ { + return new URL(server.url(path).toString()); + } + + // @BeforeAll + static beforeAll(): void { + dispatcher = new RequestMatchingFixtureDispatcher(List.of(new DispatcherEntry(List.of("shapetrees/validation-shapetree-ttl"), "GET", "/static/shapetrees/validation/shapetree", null), new DispatcherEntry(List.of("http/404"), "GET", "/static/shex/missing", null))); + } + + // @AfterAll + static clearDocumentManager(): void { + DocumentLoaderManager.setLoader(null); + } + + // @Test, @DisplayName("Fail to load missing document over http") + failToLoadMissingHttpDocument(): void { + let server: MockWebServer = new MockWebServer(); + server.setDispatcher(dispatcher); + Assertions.assertThrows(ShapeTreeException.class, () -> { + httpExternalDocumentLoader.loadExternalDocument(getURL(server, "/static/shex/missing")); + }); + } + + // @SneakyThrows, @Test, @DisplayName("Successfully load shape tree document over http") + loadHttpDocument(): void { + let server: MockWebServer = new MockWebServer(); + server.setDispatcher(dispatcher); + let shapeTreeDocument: DocumentResponse = httpExternalDocumentLoader.loadExternalDocument(getURL(server, "/static/shapetrees/validation/shapetree")); + Assertions.assertNotNull(shapeTreeDocument); + Assertions.assertEquals(200, shapeTreeDocument.getStatusCode()); + Assertions.assertTrue(shapeTreeDocument.isExists()); + Assertions.assertNotNull(shapeTreeDocument.getBody()); + Assertions.assertNotNull(shapeTreeDocument.getResourceAttributes()); + } + + // @SneakyThrows, @Test, @DisplayName("Successfully handle thread interruption") + handleInterruptedThreadOnLoadHttpDocument(): void { + let server: MockWebServer = new MockWebServer(); + server.setDispatcher(dispatcher); + Thread.currentThread().interrupt(); + Assertions.assertThrows(ShapeTreeException.class, () -> { + let shapeTreeDocument: DocumentResponse = httpExternalDocumentLoader.loadExternalDocument(getURL(server, "/static/shapetrees/validation/shapetree")); + }); + } +} diff --git a/asTypescript/packages/tests/src/ResourceAttributesTests.ts b/asTypescript/packages/tests/src/ResourceAttributesTests.ts new file mode 100644 index 00000000..36326920 --- /dev/null +++ b/asTypescript/packages/tests/src/ResourceAttributesTests.ts @@ -0,0 +1,83 @@ +// Corresponding shapetrees-java package: com.janeirodigital.shapetrees.tests +import { ResourceAttributes } from '@shapetrees/core/src/ResourceAttributes'; +import * as Assertions from 'org/junit/jupiter/api'; +import * as DisplayName from 'org/junit/jupiter/api'; +import * as Test from 'org/junit/jupiter/api'; + +class ResourceAttributesTests { + + // @Test, @DisplayName("Initialize empty ResourceAttributes"), @SneakyThrows + initializeEmptyResourceAttributes(): void { + let resourceAttributes: ResourceAttributes = new ResourceAttributes(); + Assertions.assertNotNull(resourceAttributes); + } + + // @Test, @DisplayName("Initialize ResourceAttributes with value pair"), @SneakyThrows + initializeResourceAttributesWithPair(): void { + let attribute: string = "Attribute"; + let value: string = "Value"; + let resourceAttributes: ResourceAttributes = new ResourceAttributes(attribute, value); + Assertions.assertNotNull(resourceAttributes); + Assertions.assertTrue(resourceAttributes.toMultimap().containsKey(attribute)); + } + + // @Test, @DisplayName("Initialize ResourceAttributes with Map"), @SneakyThrows + initializeResourceAttributesWithMap(): void { + let attributeStrings1: List = new Array(); + let attributeStrings2: List = new Array(); + let attributesMap: Map> = new Map>(); + attributeStrings1.add("string1"); + attributeStrings1.add("string2"); + attributeStrings2.add("string3"); + attributeStrings2.add("string4"); + attributesMap.put("attribute1", attributeStrings1); + attributesMap.put("attribute2", attributeStrings2); + let resourceAttributes: ResourceAttributes = new ResourceAttributes(attributesMap); + Assertions.assertNotNull(resourceAttributes); + Assertions.assertEquals(resourceAttributes.toMultimap(), attributesMap); + } + + // @Test, @DisplayName("Add pairs with MaybePlus"), @SneakyThrows + addPairsWithMaybePlus(): void { + let resourceAttributes: ResourceAttributes = new ResourceAttributes(); + Assertions.assertNotNull(resourceAttributes); + let resourceAttributes2: ResourceAttributes = resourceAttributes.maybePlus(null, "value"); + Assertions.assertTrue(resourceAttributes2.toMultimap().isEmpty()); + resourceAttributes2 = resourceAttributes.maybePlus("attribute", null); + Assertions.assertTrue(resourceAttributes2.toMultimap().isEmpty()); + resourceAttributes2 = resourceAttributes.maybePlus(null, null); + Assertions.assertTrue(resourceAttributes2.toMultimap().isEmpty()); + Assertions.assertEquals(resourceAttributes, resourceAttributes2); + resourceAttributes2 = resourceAttributes.maybePlus("Attributes", "First Value"); + Assertions.assertNotEquals(resourceAttributes, resourceAttributes2); + Assertions.assertFalse(resourceAttributes2.toMultimap().isEmpty()); + Assertions.assertTrue(resourceAttributes2.toMultimap().containsKey("Attributes")); + let resourceAttributes3: ResourceAttributes = resourceAttributes2.maybePlus("Attributes2", "Another First Value"); + Assertions.assertNotEquals(resourceAttributes2, resourceAttributes3); + Assertions.assertFalse(resourceAttributes3.toMultimap().isEmpty()); + Assertions.assertTrue(resourceAttributes3.toMultimap().containsKey("Attributes")); + Assertions.assertTrue(resourceAttributes3.toMultimap().containsKey("Attributes2")); + } + + // @Test, @DisplayName("Add pairs with MaybeSet"), @SneakyThrows + addPairsWithMaybeSet(): void { + let resourceAttributes: ResourceAttributes = new ResourceAttributes(); + Assertions.assertNotNull(resourceAttributes); + resourceAttributes.maybeSet(null, "value"); + Assertions.assertTrue(resourceAttributes.toMultimap().isEmpty()); + resourceAttributes.maybeSet("attribute", null); + Assertions.assertTrue(resourceAttributes.toMultimap().isEmpty()); + resourceAttributes.maybeSet(null, null); + Assertions.assertTrue(resourceAttributes.toMultimap().isEmpty()); + resourceAttributes.maybeSet("First Attribute", "First Attribute First Value"); + Assertions.assertEquals(resourceAttributes.firstValue("First Attribute"), Optional.of("First Attribute First Value")); + // Try to reset with the same attribute and value (to no change) + resourceAttributes.maybeSet("First Attribute", "First Attribute First Value"); + Assertions.assertEquals(1, resourceAttributes.toMultimap().size()); + Assertions.assertEquals(Optional.of("First Attribute First Value"), resourceAttributes.firstValue("First Attribute")); + // Add to the same attribute with a different value, growing the list size + resourceAttributes.maybeSet("First Attribute", "First Attribute Second Value"); + Assertions.assertEquals(1, resourceAttributes.toMultimap().size()); + Assertions.assertEquals(2, resourceAttributes.allValues("First Attribute").size()); + } +} diff --git a/asTypescript/packages/tests/src/SchemaCacheTests.ts b/asTypescript/packages/tests/src/SchemaCacheTests.ts new file mode 100644 index 00000000..c2e794ee --- /dev/null +++ b/asTypescript/packages/tests/src/SchemaCacheTests.ts @@ -0,0 +1,121 @@ +// Corresponding shapetrees-java package: com.janeirodigital.shapetrees.tests +import { DocumentResponse } from '@shapetrees/core/src/DocumentResponse'; +import { SchemaCache } from '@shapetrees/core/src/SchemaCache'; +import { DocumentLoaderManager } from '@shapetrees/core/src/contentloaders/DocumentLoaderManager'; +import { HttpExternalDocumentLoader } from '@shapetrees/core/src/contentloaders/HttpExternalDocumentLoader'; +import { ShapeTreeException } from '@shapetrees/core/src/exceptions/ShapeTreeException'; +import { DispatcherEntry } from './fixtures/DispatcherEntry'; +import { RequestMatchingFixtureDispatcher } from './fixtures/RequestMatchingFixtureDispatcher'; +import * as GlobalFactory from 'fr/inria/lille/shexjava'; +import * as ShexSchema from 'fr/inria/lille/shexjava/schema'; +import * as ShExCParser from 'fr/inria/lille/shexjava/schema/parsing'; +import * as MockWebServer from 'okhttp3/mockwebserver'; +import * as MalformedURLException from 'java/net'; +import { toUrl } from './fixtures/MockWebServerHelper/toUrl'; +import * as assertFalse from 'org/junit/jupiter/api/Assertions'; +import * as assertTrue from 'org/junit/jupiter/api/Assertions'; + +@TestMethodOrder(MethodOrderer.OrderAnnotation.class) +export class SchemaCacheTests { + + private static dispatcher: RequestMatchingFixtureDispatcher = null; + + private static httpExternalDocumentLoader: HttpExternalDocumentLoader; + + public constructor() { + httpExternalDocumentLoader = new HttpExternalDocumentLoader(); + DocumentLoaderManager.setLoader(httpExternalDocumentLoader); + } + + // @BeforeAll + static beforeAll(): void /* throws ShapeTreeException */ { + dispatcher = new RequestMatchingFixtureDispatcher(List.of(new DispatcherEntry(List.of("schemas/project-shex"), "GET", "/static/shex/project", null))); + SchemaCache.unInitializeCache(); + } + + // @Test, @Order(1) + testFailToOperateOnUninitializedCache(): void /* throws MalformedURLException, ShapeTreeException */ { + assertFalse(SchemaCache.isInitialized()); + // containsSchema + let containsException: Throwable = Assertions.assertThrows(ShapeTreeException.class, () -> SchemaCache.containsSchema(new URL("http://schema.example"))); + Assertions.assertEquals(SchemaCache.CACHE_IS_NOT_INITIALIZED, containsException.getMessage()); + // getSchema + let getException: Throwable = Assertions.assertThrows(ShapeTreeException.class, () -> SchemaCache.getSchema(new URL("http://schema.example"))); + Assertions.assertEquals(SchemaCache.CACHE_IS_NOT_INITIALIZED, getException.getMessage()); + // putSchema + let putException: Throwable = Assertions.assertThrows(ShapeTreeException.class, () -> SchemaCache.putSchema(new URL("http://schema.example"), null)); + Assertions.assertEquals(SchemaCache.CACHE_IS_NOT_INITIALIZED, putException.getMessage()); + // clearSchema + let clearException: Throwable = Assertions.assertThrows(ShapeTreeException.class, () -> SchemaCache.clearCache()); + Assertions.assertEquals(SchemaCache.CACHE_IS_NOT_INITIALIZED, clearException.getMessage()); + } + + // @Test, @Order(2) + testInitializeCache(): void /* throws MalformedURLException, ShapeTreeException */ { + SchemaCache.initializeCache(); + assertTrue(SchemaCache.isInitialized()); + assertFalse(SchemaCache.containsSchema(new URL("http://schema.example"))); + } + + // @Test, @Order(3) + testPreloadCache(): void /* throws MalformedURLException, ShapeTreeException */ { + let server: MockWebServer = new MockWebServer(); + server.setDispatcher(dispatcher); + let schemas: Map = buildSchemaCache(List.of(toUrl(server, "/static/shex/project").toString())); + SchemaCache.initializeCache(schemas); + assertTrue(SchemaCache.containsSchema(toUrl(server, "/static/shex/project"))); + } + + // @Test, @Order(4) + testClearPutGet(): void /* throws MalformedURLException, ShapeTreeException */ { + let server: MockWebServer = new MockWebServer(); + server.setDispatcher(dispatcher); + SchemaCache.clearCache(); + Assertions.assertNull(SchemaCache.getSchema(toUrl(server, "/static/shex/project"))); + let schemas: Map = buildSchemaCache(List.of(toUrl(server, "/static/shex/project").toString())); + let firstEntry: Map.Entry = schemas.entrySet().stream().findFirst().orElse(null); + if (firstEntry === null) + return; + SchemaCache.putSchema(firstEntry.getKey(), firstEntry.getValue()); + Assertions.assertNotNull(SchemaCache.getSchema(toUrl(server, "/static/shex/project"))); + } + + // @Test, @Order(5) + testNullOnCacheContains(): void /* throws MalformedURLException, ShapeTreeException */ { + let server: MockWebServer = new MockWebServer(); + server.setDispatcher(dispatcher); + SchemaCache.clearCache(); + Assertions.assertNull(SchemaCache.getSchema(toUrl(server, "/static/shex/project"))); + let schemas: Map = buildSchemaCache(List.of(toUrl(server, "/static/shex/project").toString())); + let firstEntry: Map.Entry = schemas.entrySet().stream().findFirst().orElse(null); + if (firstEntry === null) + return; + SchemaCache.putSchema(firstEntry.getKey(), firstEntry.getValue()); + Assertions.assertNotNull(SchemaCache.getSchema(toUrl(server, "/static/shex/project"))); + } + + public static buildSchemaCache(schemasToCache: Array): Map /* throws MalformedURLException, ShapeTreeException */ { + let schemaCache: Map = new Map<>(); + log.info("Building schema cache"); + for (const schemaUrl of schemasToCache) { + log.debug("Caching schema {}", schemaUrl); + let shexShapeSchema: DocumentResponse = DocumentLoaderManager.getLoader().loadExternalDocument(new URL(schemaUrl)); + if (Boolean.FALSE === shexShapeSchema.isExists() || shexShapeSchema.getBody() === null) { + log.warn("Schema at {} doesn't exist or is empty", schemaUrl); + continue; + } + let shapeBody: string = shexShapeSchema.getBody(); + try (let stream: InputStream = new ByteArrayInputStream(shapeBody.getBytes())) { + let shexCParser: ShExCParser = new ShExCParser(); + let schema: ShexSchema = new ShexSchema(GlobalFactory.RDFFactory, shexCParser.getRules(stream), shexCParser.getStart()); + schemaCache.put(new URL(schemaUrl), schema); + } catch (ex) { + if (ex instanceof Exception) { + log.error("Error parsing schema {}", schemaUrl); + log.error("Exception:", ex); + } +} + } + return schemaCache; + } +} diff --git a/asTypescript/packages/tests/src/ShapeTreeContainsPriorityTests.ts b/asTypescript/packages/tests/src/ShapeTreeContainsPriorityTests.ts new file mode 100644 index 00000000..99bf67b7 --- /dev/null +++ b/asTypescript/packages/tests/src/ShapeTreeContainsPriorityTests.ts @@ -0,0 +1,67 @@ +// Corresponding shapetrees-java package: com.janeirodigital.shapetrees.tests +import { ShapeTreeFactory } from '@shapetrees/core/src/ShapeTreeFactory'; +import { DocumentLoaderManager } from '@shapetrees/core/src/contentloaders/DocumentLoaderManager'; +import { HttpExternalDocumentLoader } from '@shapetrees/core/src/contentloaders/HttpExternalDocumentLoader'; +import { ShapeTree } from '@shapetrees/core/src/ShapeTree'; +import { DispatcherEntry } from './fixtures/DispatcherEntry'; +import { RequestMatchingFixtureDispatcher } from './fixtures/RequestMatchingFixtureDispatcher'; +import * as MockWebServer from 'okhttp3/mockwebserver'; +import * as Assertions from 'org/junit/jupiter/api'; +import * as BeforeAll from 'org/junit/jupiter/api'; +import * as DisplayName from 'org/junit/jupiter/api'; +import * as Test from 'org/junit/jupiter/api'; +import { toUrl } from './fixtures/MockWebServerHelper/toUrl'; + +class ShapeTreeContainsPriorityTests { + + private static dispatcher: RequestMatchingFixtureDispatcher = null; + + private static httpExternalDocumentLoader: HttpExternalDocumentLoader; + + public constructor() { + httpExternalDocumentLoader = new HttpExternalDocumentLoader(); + DocumentLoaderManager.setLoader(httpExternalDocumentLoader); + } + + // @BeforeAll + static beforeAll(): void { + dispatcher = new RequestMatchingFixtureDispatcher(List.of(new DispatcherEntry(List.of("shapetrees/contains-priority-shapetree-ttl"), "GET", "/static/shapetrees/contains-priority/shapetree", null))); + } + + // @SneakyThrows, @Test, @DisplayName("Validate prioritized retrieval of all shape tree types") + testContainsPriorityOrderOfAllTreeTypes(): void { + let server: MockWebServer = new MockWebServer(); + server.setDispatcher(dispatcher); + let containingShapeTree: ShapeTree = ShapeTreeFactory.getShapeTree(toUrl(server, "/static/shapetrees/contains-priority/shapetree#ContainsAllTypesTree")); + // Ensure the ordered result is correct + let prioritizedContains: Array = containingShapeTree.getPrioritizedContains(); + Assertions.assertEquals(3, prioritizedContains.size()); + Assertions.assertEquals(toUrl(server, "/static/shapetrees/contains-priority/shapetree#LabelShapeTypeTree"), prioritizedContains.get(0)); + Assertions.assertEquals(toUrl(server, "/static/shapetrees/contains-priority/shapetree#LabelTypeTree"), prioritizedContains.get(1)); + Assertions.assertEquals(toUrl(server, "/static/shapetrees/contains-priority/shapetree#TypeOnlyTree"), prioritizedContains.get(2)); + } + + // @SneakyThrows, @Test, @DisplayName("Validate prioritized retrieval of shape trees with shape and resource type validation") + testContainsPriorityOrderOfShapeTypeTrees(): void { + let server: MockWebServer = new MockWebServer(); + server.setDispatcher(dispatcher); + let containingShapeTree: ShapeTree = ShapeTreeFactory.getShapeTree(toUrl(server, "/static/shapetrees/contains-priority/shapetree#ContainsShapeTypeTree")); + // Ensure the ordered result is correct + let prioritizedContains: Array = containingShapeTree.getPrioritizedContains(); + Assertions.assertEquals(2, prioritizedContains.size()); + Assertions.assertEquals(toUrl(server, "/static/shapetrees/contains-priority/shapetree#ShapeTypeTree"), prioritizedContains.get(0)); + Assertions.assertEquals(toUrl(server, "/static/shapetrees/contains-priority/shapetree#TypeOnlyTree"), prioritizedContains.get(1)); + } + + // @SneakyThrows, @Test, @DisplayName("Validate prioritized retrieval of shape tree trees with label and resource type validation") + testContainsPriorityOrderOfLabelTypeTrees(): void { + let server: MockWebServer = new MockWebServer(); + server.setDispatcher(dispatcher); + let containingShapeTree: ShapeTree = ShapeTreeFactory.getShapeTree(toUrl(server, "/static/shapetrees/contains-priority/shapetree#ContainsLabelTypeTree")); + // Ensure the ordered result is correct + let prioritizedContains: Array = containingShapeTree.getPrioritizedContains(); + Assertions.assertEquals(2, prioritizedContains.size()); + Assertions.assertEquals(toUrl(server, "/static/shapetrees/contains-priority/shapetree#LabelTypeTree"), prioritizedContains.get(0)); + Assertions.assertEquals(toUrl(server, "/static/shapetrees/contains-priority/shapetree#TypeOnlyTree"), prioritizedContains.get(1)); + } +} diff --git a/asTypescript/packages/tests/src/ShapeTreeManagerDeltaTests.ts b/asTypescript/packages/tests/src/ShapeTreeManagerDeltaTests.ts new file mode 100644 index 00000000..4e802036 --- /dev/null +++ b/asTypescript/packages/tests/src/ShapeTreeManagerDeltaTests.ts @@ -0,0 +1,215 @@ +// Corresponding shapetrees-java package: com.janeirodigital.shapetrees.tests +import { ShapeTreeException } from '@shapetrees/core/src/exceptions/ShapeTreeException'; +import { ShapeTreeAssignment } from '@shapetrees/core/src/ShapeTreeAssignment'; +import { ShapeTreeManager } from '@shapetrees/core/src/ShapeTreeManager'; +import { ShapeTreeManagerDelta } from '@shapetrees/core/src/ShapeTreeManagerDelta'; +import * as Label from 'jdk/jfr'; +import * as MalformedURLException from 'java/net'; + +@TestMethodOrder(MethodOrderer.OrderAnnotation.class) +class ShapeTreeManagerDeltaTests { + + private static existingManager: ShapeTreeManager = null; + + private static updatedManager: ShapeTreeManager = null; + + private static assignmentOne: ShapeTreeAssignment = null; + + private static assignmentTwo: ShapeTreeAssignment = null; + + private static assignmentThree: ShapeTreeAssignment = null; + + private static assignmentFour: ShapeTreeAssignment = null; + + private static assignmentFive: ShapeTreeAssignment = null; + + // @BeforeEach + beforeEach(): void /* throws ShapeTreeException, MalformedURLException */ { + existingManager = new ShapeTreeManager(new URL("https://manager.example/#existing")); + updatedManager = new ShapeTreeManager(new URL("https://manager.example/#updated")); + assignmentOne = new ShapeTreeAssignment(// ShapeTree + new URL("http://shapetrees.example/#firstTree"), // ManageableResource + new URL("http://data.example/resourceOne"), // RootAssignment + new URL("http://data.example/resourceOne.shapetree#assignmentOne"), // FocusNode + new URL("http://data.example/resourceOne#focus"), // Shape + new URL("http://shapes.example/#firstShape"), // Uri + new URL("http://data.example/resourceOne.shapetree#assignmentOne")); + assignmentTwo = new ShapeTreeAssignment(// ShapeTree + new URL("http://shapetrees.example/#secondTree"), // ManageableResource + new URL("http://data.example/resourceTwo"), // RootAssignment + new URL("http://data.example/resourceTwo.shapetree#assignmentTwo"), // FocusNode + new URL("http://data.example/resourceTwo#focus"), // Shape + new URL("http://shapes.example/#secondShape"), // Uri + new URL("http://data.example/resourceTwo.shapetree#assignmentTwo")); + assignmentThree = new ShapeTreeAssignment(// ShapeTree + new URL("http://shapetrees.example/#thirdTree"), // ManageableResource + new URL("http://data.example/resourceThree"), // RootAssignment + new URL("http://data.example/resourceThree.shapetree#assignmentThree"), // FocusNode + new URL("http://data.example/resourceThree#focus"), // Shape + new URL("http://shapes.example/#thirdShape"), // Uri + new URL("http://data.example/resourceThree.shapetree#assignmentThree")); + assignmentFour = new ShapeTreeAssignment(// ShapeTree + new URL("http://shapetrees.example/#fourthTree"), // ManageableResource + new URL("http://data.example/resourceFour"), // RootAssignment + new URL("http://data.example/resourceFour.shapetree#assignmentFour"), // FocusNode + new URL("http://data.example/resourceFour#focus"), // Shape + new URL("http://shapes.example/#fourthShape"), // Uri + new URL("http://data.example/resourceFour.shapetree#assignmentFour")); + assignmentFive = new ShapeTreeAssignment(// ShapeTree + new URL("http://shapetrees.example/#fifthTree"), // ManageableResource + new URL("http://data.example/resourceFive"), // RootAssignment + new URL("http://data.example/resourceFive.shapetree#assignmentFive"), // FocusNode + new URL("http://data.example/resourceFive#focus"), // Shape + new URL("http://shapes.example/#fifthShape"), // Uri + new URL("http://data.example/resourceFive.shapetree#assignmentFive")); + } + + // @SneakyThrows, @Test, @Label("Delete all existing assignments") + deleteAllExistingAssignments(): void { + // Compare an existing manager with multiple assignments with an empty updated manager + // This should show that all assignments are removed with none left + existingManager.addAssignment(assignmentOne); + existingManager.addAssignment(assignmentTwo); + let delta: ShapeTreeManagerDelta = ShapeTreeManagerDelta.evaluate(existingManager, updatedManager); + Assertions.assertTrue(delta.getUpdatedAssignments().isEmpty()); + Assertions.assertEquals(2, delta.getRemovedAssignments().size()); + Assertions.assertTrue(delta.allRemoved()); + Assertions.assertTrue(delta.getRemovedAssignments().contains(assignmentOne)); + Assertions.assertTrue(delta.getRemovedAssignments().contains(assignmentTwo)); + } + + // @SneakyThrows, @Test, @Label("Delete existing assignments and add new ones") + deleteAllExistingAssignmentsAndAddNew(): void { + existingManager.addAssignment(assignmentOne); + existingManager.addAssignment(assignmentTwo); + updatedManager.addAssignment(assignmentThree); + let delta: ShapeTreeManagerDelta = ShapeTreeManagerDelta.evaluate(existingManager, updatedManager); + Assertions.assertTrue(delta.isUpdated()); + Assertions.assertTrue(delta.wasReduced()); + Assertions.assertFalse(delta.allRemoved()); + Assertions.assertEquals(1, delta.getUpdatedAssignments().size()); + Assertions.assertEquals(2, delta.getRemovedAssignments().size()); + Assertions.assertTrue(delta.getUpdatedAssignments().contains(assignmentThree)); + Assertions.assertTrue(delta.getRemovedAssignments().contains(assignmentOne)); + Assertions.assertTrue(delta.getRemovedAssignments().contains(assignmentTwo)); + } + + // @SneakyThrows, @Test, @Label("Delete an assignment, update another, and add one") + deleteUpdateAndAddAssignments(): void { + // remove assignment one + // update assignment two + // add assignment four + let assignmentThreeUpdated: ShapeTreeAssignment = duplicateAssignment(assignmentThree, new URL("http://shapetrees.pub/appleTree"), null); + existingManager.addAssignment(assignmentOne); + existingManager.addAssignment(assignmentTwo); + existingManager.addAssignment(assignmentThree); + updatedManager.addAssignment(assignmentTwo); + updatedManager.addAssignment(assignmentThreeUpdated); + updatedManager.addAssignment(assignmentFour); + let delta: ShapeTreeManagerDelta = ShapeTreeManagerDelta.evaluate(existingManager, updatedManager); + Assertions.assertTrue(delta.isUpdated()); + Assertions.assertTrue(delta.wasReduced()); + Assertions.assertFalse(delta.allRemoved()); + Assertions.assertEquals(2, delta.getUpdatedAssignments().size()); + Assertions.assertEquals(1, delta.getRemovedAssignments().size()); + Assertions.assertTrue(delta.getUpdatedAssignments().contains(assignmentThreeUpdated)); + Assertions.assertTrue(delta.getUpdatedAssignments().contains(assignmentFour)); + Assertions.assertTrue(delta.getRemovedAssignments().contains(assignmentOne)); + } + + // @SneakyThrows, @Test, @Label("Update assignment and add another") + updateAssignmentAndAddAnother(): void { + let assignmentThreeUpdated: ShapeTreeAssignment = duplicateAssignment(assignmentThree, new URL("http://shapetrees.pub/appleTree"), null); + existingManager.addAssignment(assignmentThree); + updatedManager.addAssignment(assignmentThreeUpdated); + updatedManager.addAssignment(assignmentFour); + let delta: ShapeTreeManagerDelta = ShapeTreeManagerDelta.evaluate(existingManager, updatedManager); + Assertions.assertTrue(delta.isUpdated()); + Assertions.assertFalse(delta.wasReduced()); + Assertions.assertFalse(delta.allRemoved()); + Assertions.assertEquals(2, delta.getUpdatedAssignments().size()); + Assertions.assertEquals(0, delta.getRemovedAssignments().size()); + Assertions.assertTrue(delta.getUpdatedAssignments().contains(assignmentThreeUpdated)); + Assertions.assertTrue(delta.getUpdatedAssignments().contains(assignmentFour)); + } + + // @SneakyThrows, @Test, @Label("Delete assignment and update another") + DeleteAssignmentAndUpdateAnother(): void { + let assignmentThreeUpdated: ShapeTreeAssignment = duplicateAssignment(assignmentThree, new URL("http://shapetrees.pub/appleTree"), null); + existingManager.addAssignment(assignmentTwo); + existingManager.addAssignment(assignmentThree); + updatedManager.addAssignment(assignmentThreeUpdated); + let delta: ShapeTreeManagerDelta = ShapeTreeManagerDelta.evaluate(existingManager, updatedManager); + Assertions.assertTrue(delta.isUpdated()); + Assertions.assertTrue(delta.wasReduced()); + Assertions.assertFalse(delta.allRemoved()); + Assertions.assertEquals(1, delta.getUpdatedAssignments().size()); + Assertions.assertEquals(1, delta.getRemovedAssignments().size()); + Assertions.assertTrue(delta.getUpdatedAssignments().contains(assignmentThreeUpdated)); + Assertions.assertTrue(delta.getRemovedAssignments().contains(assignmentTwo)); + } + + // @SneakyThrows, @Test, @Label("Add a new assignments to an empty set") + AddNewAssignmentToEmptySet(): void { + updatedManager.addAssignment(assignmentOne); + updatedManager.addAssignment(assignmentTwo); + let delta: ShapeTreeManagerDelta = ShapeTreeManagerDelta.evaluate(existingManager, updatedManager); + Assertions.assertTrue(delta.isUpdated()); + Assertions.assertFalse(delta.wasReduced()); + Assertions.assertFalse(delta.allRemoved()); + Assertions.assertEquals(2, delta.getUpdatedAssignments().size()); + Assertions.assertEquals(0, delta.getRemovedAssignments().size()); + Assertions.assertTrue(delta.getUpdatedAssignments().contains(assignmentOne)); + Assertions.assertTrue(delta.getUpdatedAssignments().contains(assignmentTwo)); + } + + // @SneakyThrows, @Test, @Label("Update existing assignments") + UpdateExistingAssignment(): void { + let assignmentOneUpdated: ShapeTreeAssignment = duplicateAssignment(assignmentOne, null, new URL("http://data.example/resourceOne#Otherfocus")); + let assignmentTwoUpdated: ShapeTreeAssignment = duplicateAssignment(assignmentTwo, null, new URL("http://data.example/resourceTwo#Otherfocus")); + existingManager.addAssignment(assignmentOne); + existingManager.addAssignment(assignmentTwo); + updatedManager.addAssignment(assignmentOneUpdated); + updatedManager.addAssignment(assignmentTwoUpdated); + let delta: ShapeTreeManagerDelta = ShapeTreeManagerDelta.evaluate(existingManager, updatedManager); + Assertions.assertTrue(delta.isUpdated()); + Assertions.assertFalse(delta.wasReduced()); + Assertions.assertFalse(delta.allRemoved()); + Assertions.assertEquals(2, delta.getUpdatedAssignments().size()); + Assertions.assertEquals(0, delta.getRemovedAssignments().size()); + Assertions.assertTrue(delta.getUpdatedAssignments().contains(assignmentOneUpdated)); + Assertions.assertTrue(delta.getUpdatedAssignments().contains(assignmentTwoUpdated)); + } + + // @Test, @Label("Compare two null managers") + compareTwoNullManagers(): void { + Assertions.assertThrows(ShapeTreeException.class, () -> ShapeTreeManagerDelta.evaluate(null, null)); + } + + // @SneakyThrows, @Test, @Label("Check null values on updated manager") + checkNullsOnUpdatedManager(): void { + existingManager.addAssignment(assignmentOne); + existingManager.addAssignment(assignmentTwo); + let delta: ShapeTreeManagerDelta = ShapeTreeManagerDelta.evaluate(existingManager, null); + Assertions.assertTrue(delta.allRemoved()); + updatedManager.getAssignments().clear(); + delta = ShapeTreeManagerDelta.evaluate(existingManager, updatedManager); + Assertions.assertTrue(delta.allRemoved()); + } + + // @SneakyThrows, @Test, @Label("Check null values on existing manager") + checkNullsOnExistingManager(): void { + updatedManager.addAssignment(assignmentOne); + updatedManager.addAssignment(assignmentTwo); + let delta: ShapeTreeManagerDelta = ShapeTreeManagerDelta.evaluate(null, updatedManager); + Assertions.assertTrue(delta.isUpdated()); + existingManager.getAssignments().clear(); + delta = ShapeTreeManagerDelta.evaluate(existingManager, updatedManager); + Assertions.assertTrue(delta.isUpdated()); + } + + private duplicateAssignment(assignment: ShapeTreeAssignment, /*const*/ shapeTree: URL, /*const*/ focusNode: URL): ShapeTreeAssignment /* throws MalformedURLException, ShapeTreeException */ { + let duplicateAssignment: ShapeTreeAssignment = new ShapeTreeAssignment(shapeTree != null ? shapeTree : assignment.getShapeTree(), assignment.getManagedResource(), assignment.getRootAssignment(), focusNode != null ? focusNode : assignment.getFocusNode(), assignment.getShape(), assignment.getUrl()); + return duplicateAssignment; + } +} diff --git a/asTypescript/packages/tests/src/ShapeTreeManagerTests.ts b/asTypescript/packages/tests/src/ShapeTreeManagerTests.ts new file mode 100644 index 00000000..b14387a2 --- /dev/null +++ b/asTypescript/packages/tests/src/ShapeTreeManagerTests.ts @@ -0,0 +1,207 @@ +// Corresponding shapetrees-java package: com.janeirodigital.shapetrees.tests +import { ShapeTreeAssignment } from '@shapetrees/core/src/ShapeTreeAssignment'; +import { ShapeTreeManager } from '@shapetrees/core/src/ShapeTreeManager'; +import { DocumentLoaderManager } from '@shapetrees/core/src/contentloaders/DocumentLoaderManager'; +import { HttpExternalDocumentLoader } from '@shapetrees/core/src/contentloaders/HttpExternalDocumentLoader'; +import { ShapeTreeException } from '@shapetrees/core/src/exceptions/ShapeTreeException'; +import { GraphHelper } from '@shapetrees/core/src/helpers/GraphHelper'; +import { DispatcherEntry } from './fixtures/DispatcherEntry'; +import { RequestMatchingFixtureDispatcher } from './fixtures/RequestMatchingFixtureDispatcher'; +import * as MockWebServer from 'okhttp3/mockwebserver'; +import * as Graph from 'org/apache/jena/graph'; +import * as MalformedURLException from 'java/net'; +import * as URI from 'java/net'; +import { toUrl } from './fixtures/MockWebServerHelper/toUrl'; + +class ShapeTreeManagerTests { + + private static managerUrl: URL; + + private static manager: ShapeTreeManager; + + private static server: MockWebServer; + + private static assignment1: ShapeTreeAssignmentprivate static assignment2: ShapeTreeAssignmentprivate static assignment3: ShapeTreeAssignmentprivate static nonContainingAssignment1: ShapeTreeAssignmentprivate static nonContainingAssignment2: ShapeTreeAssignmentprivate static containingAssignment1: ShapeTreeAssignment; + + private static dispatcher: RequestMatchingFixtureDispatcher = null; + + private static httpExternalDocumentLoader: HttpExternalDocumentLoader; + + public constructor() { + httpExternalDocumentLoader = new HttpExternalDocumentLoader(); + DocumentLoaderManager.setLoader(httpExternalDocumentLoader); + } + + // @BeforeAll + static beforeAll(): void /* throws MalformedURLException, ShapeTreeException */ { + dispatcher = new RequestMatchingFixtureDispatcher(List.of(new DispatcherEntry(List.of("shapetrees/manager-shapetree-ttl"), "GET", "/static/shapetrees/managers/shapetree", null))); + server = new MockWebServer(); + server.setDispatcher(dispatcher); + managerUrl = new URL("https://site.example/resource.shapetree"); + assignment1 = new ShapeTreeAssignment(new URL("https://tree.example/tree#TreeOne"), new URL("https://site.example/resource"), new URL("https://site.example/resource.shapetree#ln1"), new URL("https://site.example/resource#node"), new URL("https://shapes.example/schema#ShapeOne"), new URL("https://site.example/resource.shapetree#ln1")); + assignment2 = new ShapeTreeAssignment(new URL("https://tree.example/tree#TreeTwo"), new URL("https://site.example/resource"), new URL("https://site.example/resource.shapetree#ln2"), new URL("https://site.example/resource#node"), new URL("https://shapes.example/schema#ShapeTwo"), new URL("https://site.example/resource.shapetree#ln2")); + assignment3 = new ShapeTreeAssignment(new URL("https://tree.example/tree#TreeThree"), new URL("https://site.example/resource"), new URL("https://site.example/resource.shapetree#ln3"), new URL("https://site.example/resource#node"), new URL("https://shapes.example/schema#ShapeThree"), new URL("https://site.example/resource.shapetree#ln3")); + nonContainingAssignment1 = new ShapeTreeAssignment(toUrl(server, "/static/shapetrees/managers/shapetree#NonContainingTree"), toUrl(server, "/data/container/"), toUrl(server, "/data/container/.shapetree#ln1"), null, null, toUrl(server, "/data/container/.shapetree#ln1")); + containingAssignment1 = new ShapeTreeAssignment(toUrl(server, "/static/shapetrees/managers/shapetree#ContainingTree"), toUrl(server, "/data/container/"), toUrl(server, "/data/container/.shapetree#ln2"), null, null, toUrl(server, "/data/container/.shapetree#ln2")); + nonContainingAssignment2 = new ShapeTreeAssignment(toUrl(server, "/static/shapetrees/managers/shapetree#NonContainingTree2"), toUrl(server, "/data/container/"), toUrl(server, "/data/container/.shapetree#ln3"), null, null, toUrl(server, "/data/container/.shapetree#ln3")); + } + + // @BeforeEach + beforeEach(): void { + manager = new ShapeTreeManager(managerUrl); + } + + // @SneakyThrows, @Test, @DisplayName("Initialize a new manager") + initializeShapeTreeManager(): void { + let newManager: ShapeTreeManager = new ShapeTreeManager(managerUrl); + Assertions.assertNotNull(newManager); + Assertions.assertEquals(newManager.getId(), managerUrl); + } + + // @SneakyThrows, @Test, @DisplayName("Add a new assignment") + addNewShapeTreeAssignmentToManager(): void { + Assertions.assertTrue(manager.getAssignments().isEmpty()); + manager.addAssignment(assignment1); + Assertions.assertFalse(manager.getAssignments().isEmpty()); + Assertions.assertEquals(manager.getAssignments().size(), 1); + } + + // @SneakyThrows, @Test, @DisplayName("Fail to add a null assignment") + failToAddNullAssignmentToManager(): void { + Assertions.assertThrows(ShapeTreeException.class, () -> { + manager.addAssignment(null); + }); + } + + // @SneakyThrows, @Test, @DisplayName("Fail to add a duplicate assignment") + failToAddDuplicateAssignment(): void { + manager.addAssignment(assignment1); + Assertions.assertThrows(ShapeTreeException.class, () -> { + manager.addAssignment(assignment1); + }); + } + + // @Test, @DisplayName("Fail to add assignment with certain null values") + failToAddAssignmentWithBadValues(): void { + Assertions.assertThrows(ShapeTreeException.class, () -> { + new ShapeTreeAssignment(null, new URL("https://site.example/resource"), null, new URL("https://site.example/resource#node"), new URL("https://shapes.example/schema#ShapeThree"), new URL("https://site.example/resource.shapetree#ln3")); + }); + Assertions.assertThrows(ShapeTreeException.class, () -> { + // focus node with no shape + new ShapeTreeAssignment(new URL("https://tree.example/tree#TreeThree"), new URL("https://site.example/resource"), new URL("https://site.example/resource.shapetree#ln3"), new URL("https://site.example/resource#node"), null, new URL("https://site.example/resource.shapetree#ln3")); + }); + Assertions.assertThrows(ShapeTreeException.class, () -> { + // shape with no focus node + new ShapeTreeAssignment(new URL("https://tree.example/tree#TreeThree"), new URL("https://site.example/resource"), new URL("https://site.example/resource.shapetree#ln3"), null, new URL("https://shapes.example/schema#ShapeThree"), new URL("https://site.example/resource.shapetree#ln3")); + }); + } + + // @SneakyThrows, @Test, @DisplayName("Fail to mint the same assignment twice") + failToMintDuplicateAssignment(): void { + manager.addAssignment(assignment1); + let adjustedUrl: URL = manager.mintAssignmentUrl(assignment1.getUrl()); + Assertions.assertNotEquals(assignment1.getUrl(), adjustedUrl); + } + + // @SneakyThrows, @Test, @DisplayName("Get containing shape tree assignment from shape tree manager") + getContainingShapeTreeAssignmentsFromManager(): void { + manager.addAssignment(nonContainingAssignment1); + manager.addAssignment(containingAssignment1); + Assertions.assertEquals(1, manager.getContainingAssignments().size()); + Assertions.assertTrue(manager.getContainingAssignments().contains(containingAssignment1)); + Assertions.assertFalse(manager.getContainingAssignments().contains(nonContainingAssignment1)); + } + + // @SneakyThrows, @Test, @DisplayName("Get no containing shape tree assignment for shape tree manager") + getNoContainingShapeTreeAssignmentFromManager(): void { + manager.addAssignment(nonContainingAssignment1); + manager.addAssignment(nonContainingAssignment2); + Assertions.assertTrue(manager.getContainingAssignments().isEmpty()); + } + + // @SneakyThrows, @Test, @DisplayName("Get no shape tree assignment for shape tree from manager with no assignments") + getNoShapeTreeAssignmentsFromEmptyManager(): void { + Assertions.assertNull(manager.getAssignmentForShapeTree(new URL("https://tree.example/shapetree#ExampleTree"))); + } + + // @SneakyThrows, @Test, @DisplayName("Get shape tree assignment from manager for shape tree") + getShapeTreeAssignmentFromManagerForShapeTree(): void { + manager.addAssignment(nonContainingAssignment1); + manager.addAssignment(nonContainingAssignment2); + manager.addAssignment(containingAssignment1); + Assertions.assertEquals(containingAssignment1, manager.getAssignmentForShapeTree(containingAssignment1.getShapeTree())); + } + + // @SneakyThrows, @Test, @DisplayName("Get no shape tree assignment from manager without matching shape tree") + getNoShapeTreeAssignmentForShapeTree(): void { + manager.addAssignment(nonContainingAssignment1); + manager.addAssignment(nonContainingAssignment2); + manager.addAssignment(containingAssignment1); + Assertions.assertNull(manager.getAssignmentForShapeTree(new URL("https://tree.example/shapetree#ExampleTree"))); + } + + // @SneakyThrows, @Test, @DisplayName("Fail to remove assignment from empty manager") + failToRemoveAssignmentFromEmptyManager(): void { + Assertions.assertThrows(IllegalStateException.class, () -> { + manager.removeAssignment(containingAssignment1); + }); + } + + // @SneakyThrows, @Test, @DisplayName("Fail to remove assignment from empty manager") + failToRemoveAssignmentMissingFromManager(): void { + manager.addAssignment(nonContainingAssignment1); + manager.addAssignment(nonContainingAssignment2); + Assertions.assertThrows(IllegalStateException.class, () -> { + manager.removeAssignment(containingAssignment1); + }); + } + + // @SneakyThrows, @Test, @DisplayName("Remove assignment from manager") + removeAssignmentFromManager(): void { + manager.addAssignment(nonContainingAssignment1); + manager.addAssignment(nonContainingAssignment2); + manager.addAssignment(containingAssignment1); + Assertions.assertEquals(manager.getAssignmentForShapeTree(containingAssignment1.getShapeTree()), containingAssignment1); + manager.removeAssignment(containingAssignment1); + Assertions.assertNull(manager.getAssignmentForShapeTree(containingAssignment1.getShapeTree())); + } + + // @SneakyThrows, @Test, @DisplayName("Get valid assignment from graph") + getAssignmentFromGraph(): void { + let managerUri: URI = URI.create("https://data.example/container.shapetree"); + let managerGraph: Graph = GraphHelper.readStringIntoGraph(managerUri, getValidManagerString(), "text/turtle"); + let manager: ShapeTreeManager = ShapeTreeManager.getFromGraph(managerUri.toURL(), managerGraph); + Assertions.assertNotNull(manager); + Assertions.assertNotNull(manager.getAssignmentForShapeTree(new URL("https://tree.example/#Tree1"))); + } + + // @SneakyThrows, @Test, @DisplayName("Fail to get assignment from graph due to missing triples") + failToGetAssignmentFromGraphMissingTriples(): void { + let managerUri: URI = URI.create("https://data.example/container.shapetree"); + let managerGraph: Graph = GraphHelper.readStringIntoGraph(managerUri, getInvalidManagerMissingTriplesString(), "text/turtle"); + Assertions.assertThrows(IllegalStateException.class, () -> { + ShapeTreeManager.getFromGraph(managerUrl, managerGraph); + }); + } + + // @SneakyThrows, @Test, @DisplayName("Fail to get assignment from graph due to unexpected values") + failToGetAssignmentFromGraphUnexpectedValues(): void { + let managerUri: URI = URI.create("https://data.example/container.shapetree"); + let managerGraph: Graph = GraphHelper.readStringIntoGraph(managerUri, getInvalidManagerUnexpectedTriplesString(), "text/turtle"); + Assertions.assertThrows(IllegalStateException.class, () -> { + ShapeTreeManager.getFromGraph(managerUrl, managerGraph); + }); + } + + private getValidManagerString(): string { + return "PREFIX rdf: \n" + "PREFIX rdfs: \n" + "PREFIX xml: \n" + "PREFIX st: \n \n" + "PREFIX ex: \n" + "\n" + "\n" + " \n" + " a st:Manager ; \n" + " st:hasAssignment . \n" + "\n" + " \n" + " st:assigns ; \n" + " st:hasRootAssignment ; \n" + " st:manages ; \n" + " st:shape ; \n" + " st:focusNode . \n" + "\n"; + } + + private getInvalidManagerMissingTriplesString(): string { + return "PREFIX rdf: \n" + "PREFIX rdfs: \n" + "PREFIX xml: \n" + "PREFIX st: \n \n" + "PREFIX ex: \n" + "\n" + "\n" + " \n" + " a st:Manager ; \n" + " st:hasAssignment . \n" + "\n" + " \n" + " st:assigns ; \n" + "\n"; + } + + private getInvalidManagerUnexpectedTriplesString(): string { + return "PREFIX rdf: \n" + "PREFIX rdfs: \n" + "PREFIX xml: \n" + "PREFIX st: \n \n" + "PREFIX ex: \n" + "\n" + "\n" + " \n" + " a st:Manager ; \n" + " st:hasAssignment . \n" + "\n" + " \n" + " st:assigns ; \n" + " st:hasRootAssignment ; \n" + " st:manages ; \n" + " st:shape ; \n" + " st:focusNode ; \n" + " st:unexpected \"why am i here\" . \n" + "\n"; + } +} diff --git a/asTypescript/packages/tests/src/ShapeTreeParsingTests.ts b/asTypescript/packages/tests/src/ShapeTreeParsingTests.ts new file mode 100644 index 00000000..08a3181a --- /dev/null +++ b/asTypescript/packages/tests/src/ShapeTreeParsingTests.ts @@ -0,0 +1,230 @@ +// Corresponding shapetrees-java package: com.janeirodigital.shapetrees.tests +import { ShapeTreeFactory } from '@shapetrees/core/src/ShapeTreeFactory'; +import { ShapeTreeResource } from '@shapetrees/core/src/ShapeTreeResource'; +import { DocumentLoaderManager } from '@shapetrees/core/src/contentloaders/DocumentLoaderManager'; +import { HttpExternalDocumentLoader } from '@shapetrees/core/src/contentloaders/HttpExternalDocumentLoader'; +import { RecursionMethods } from '@shapetrees/core/src/enums/RecursionMethods'; +import { ShapeTreeException } from '@shapetrees/core/src/exceptions/ShapeTreeException'; +import { ShapeTree } from '@shapetrees/core/src/ShapeTree'; +import { DispatcherEntry } from './fixtures/DispatcherEntry'; +import { RequestMatchingFixtureDispatcher } from './fixtures/RequestMatchingFixtureDispatcher'; +import * as MockWebServer from 'okhttp3/mockwebserver'; +import { toUrl } from './fixtures/MockWebServerHelper/toUrl'; + +class ShapeTreeParsingTests { + + private static httpExternalDocumentLoader: HttpExternalDocumentLoader; + + private static dispatcher: RequestMatchingFixtureDispatcher = null; + + protected static server: MockWebServer = null; + + public constructor() { + httpExternalDocumentLoader = new HttpExternalDocumentLoader(); + DocumentLoaderManager.setLoader(httpExternalDocumentLoader); + } + + // @BeforeEach, @SneakyThrows + beforeEach(): void { + ShapeTreeFactory.clearCache(); + ShapeTreeResource.clearCache(); + } + + // @BeforeAll + static beforeAll(): void { + dispatcher = new RequestMatchingFixtureDispatcher(List.of(new DispatcherEntry(List.of("shapetrees/project-shapetree-ttl"), "GET", "/static/shapetrees/project/shapetree", null), new DispatcherEntry(List.of("shapetrees/business-shapetree-ttl"), "GET", "/static/shapetrees/business/shapetree", null), new DispatcherEntry(List.of("shapetrees/reserved-type-shapetree-ttl"), "GET", "/static/shapetrees/reserved/shapetree", null), new DispatcherEntry(List.of("shapetrees/project-shapetree-virtual-ttl"), "GET", "/static/shapetrees/project/shapetree-virtual", null), new DispatcherEntry(List.of("shapetrees/project-shapetree-invalid-ttl"), "GET", "/static/shapetrees/project/shapetree-invalid", null), new DispatcherEntry(List.of("shapetrees/project-shapetree-invalid-2-ttl"), "GET", "/static/shapetrees/project/shapetree-invalid2", null), new DispatcherEntry(List.of("shapetrees/content-type-invalid-shapetree-ttl"), "GET", "/static/shapetrees/project/shapetree-bad-content-type", null), new DispatcherEntry(List.of("shapetrees/missing-expects-type-shapetree-ttl"), "GET", "/static/shapetrees/invalid/missing-expects-type", null), new DispatcherEntry(List.of("shapetrees/contains-with-bad-expects-type-shapetree-ttl"), "GET", "/static/shapetrees/invalid/contains-with-bad-expects-type", null), new DispatcherEntry(List.of("shapetrees/bad-object-type-shapetree-ttl"), "GET", "/static/shapetrees/invalid/bad-object-type", null), new DispatcherEntry(List.of("shapetrees/invalid-contains-objects-shapetree-ttl"), "GET", "/static/shapetrees/invalid/shapetree-invalid-contains-objects", null), new DispatcherEntry(List.of("shapetrees/contains-with-nonrdf-expects-type-shapetree-ttl"), "GET", "/static/shapetrees/invalid/contains-with-nonrdf-expects-type", null), new DispatcherEntry(List.of("parsing/contains/contains-1-ttl"), "GET", "/static/shapetrees/parsing/contains-1", null), new DispatcherEntry(List.of("parsing/contains/contains-2-ttl"), "GET", "/static/shapetrees/parsing/contains-2", null), new DispatcherEntry(List.of("parsing/contains/contains-2A-ttl"), "GET", "/static/shapetrees/parsing/contains-2A", null), new DispatcherEntry(List.of("parsing/contains/contains-2B-ttl"), "GET", "/static/shapetrees/parsing/contains-2B", null), new DispatcherEntry(List.of("parsing/contains/contains-2C-ttl"), "GET", "/static/shapetrees/parsing/contains-2C", null), new DispatcherEntry(List.of("parsing/contains/contains-2C2-ttl"), "GET", "/static/shapetrees/parsing/contains-2C2", null), new DispatcherEntry(List.of("parsing/references/references-1-ttl"), "GET", "/static/shapetrees/parsing/references-1", null), new DispatcherEntry(List.of("parsing/references/references-2-ttl"), "GET", "/static/shapetrees/parsing/references-2", null), new DispatcherEntry(List.of("parsing/references/references-2A-ttl"), "GET", "/static/shapetrees/parsing/references-2A", null), new DispatcherEntry(List.of("parsing/references/references-2B-ttl"), "GET", "/static/shapetrees/parsing/references-2B", null), new DispatcherEntry(List.of("parsing/references/references-2C-ttl"), "GET", "/static/shapetrees/parsing/references-2C", null), new DispatcherEntry(List.of("parsing/references/references-2C2-ttl"), "GET", "/static/shapetrees/parsing/references-2C2", null), new DispatcherEntry(List.of("parsing/mixed/mixed-1-ttl"), "GET", "/static/shapetrees/parsing/mixed-1", null), new DispatcherEntry(List.of("parsing/mixed/mixed-2-ttl"), "GET", "/static/shapetrees/parsing/mixed-2", null), new DispatcherEntry(List.of("parsing/mixed/mixed-2A-ttl"), "GET", "/static/shapetrees/parsing/mixed-2A", null), new DispatcherEntry(List.of("parsing/mixed/mixed-2B-ttl"), "GET", "/static/shapetrees/parsing/mixed-2B", null), new DispatcherEntry(List.of("parsing/mixed/mixed-2C-ttl"), "GET", "/static/shapetrees/parsing/mixed-2C", null), new DispatcherEntry(List.of("parsing/mixed/mixed-2C2-ttl"), "GET", "/static/shapetrees/parsing/mixed-2C2", null), new DispatcherEntry(List.of("parsing/mixed/mixed-2D-ttl"), "GET", "/static/shapetrees/parsing/mixed-2D", null), new DispatcherEntry(List.of("parsing/cycle-ttl"), "GET", "/static/shapetrees/parsing/cycle", null), new DispatcherEntry(List.of("http/404"), "GET", "/static/shapetrees/invalid/shapetree-missing", null))); + server = new MockWebServer(); + server.setDispatcher(dispatcher); + } + + // @SneakyThrows, @Test, @DisplayName("Reuse previously cached shapetree") + parseShapeTreeReuse(): void { + let projectShapeTree1: ShapeTree = ShapeTreeFactory.getShapeTree(toUrl(server, "/static/shapetrees/project/shapetree#ProjectTree")); + Assertions.assertNotNull(projectShapeTree1); + let projectShapeTree2: ShapeTree = ShapeTreeFactory.getShapeTree(toUrl(server, "/static/shapetrees/project/shapetree#ProjectTree")); + Assertions.assertNotNull(projectShapeTree2); + assertEquals(projectShapeTree1.hashCode(), projectShapeTree2.hashCode()); + // The "business" shape tree won't be in the cache, but it cross-contains pm:MilestoneTree, which should be. + let businessShapeTree: ShapeTree = ShapeTreeFactory.getShapeTree(toUrl(server, "/static/shapetrees/business/shapetree#BusinessTree")); + Assertions.assertNotNull(businessShapeTree); + } + + // @SneakyThrows, @Test, @DisplayName("Ensure reuse within recursion") + ensureCacheWithRecursion(): void { + // Retrieve the MilestoneTree shapetree (which is referred to by the ProjectTree shapetree) + let milestoneShapeTree1: ShapeTree = ShapeTreeFactory.getShapeTree(toUrl(server, "/static/shapetrees/project/shapetree-virtual#MilestoneTree")); + Assertions.assertNotNull(milestoneShapeTree1); + // Retrieve the ProjectTree shapetree which will recursively cache the MilestoneTree shapetree + let projectShapeTree1: ShapeTree = ShapeTreeFactory.getShapeTree(toUrl(server, "/static/shapetrees/project/shapetree-virtual#ProjectTree")); + Assertions.assertNotNull(projectShapeTree1); + // Retrieve the MilestoneTree shapetree again, ensuring the same instance is used + let milestoneShapeTree2: ShapeTree = ShapeTreeFactory.getShapeTree(toUrl(server, "/static/shapetrees/project/shapetree-virtual#MilestoneTree")); + Assertions.assertNotNull(milestoneShapeTree2); + assertEquals(milestoneShapeTree1.hashCode(), milestoneShapeTree2.hashCode()); + } + + // @SneakyThrows, @Test, @DisplayName("Parse Tree with references") + parseShapeTreeReferences(): void { + let projectShapeTree: ShapeTree = ShapeTreeFactory.getShapeTree(toUrl(server, "/static/shapetrees/project/shapetree-virtual#ProjectTree")); + Assertions.assertNotNull(projectShapeTree); + assertFalse(projectShapeTree.getReferences().isEmpty()); + } + + // @SneakyThrows, @Test, @DisplayName("Parse Tree with contains") + parseShapeTreeContains(): void { + let projectShapeTree: ShapeTree = ShapeTreeFactory.getShapeTree(toUrl(server, "/static/shapetrees/project/shapetree#ProjectTree")); + Assertions.assertNotNull(projectShapeTree); + assertTrue(projectShapeTree.getContains().contains(toUrl(server, "/static/shapetrees/project/shapetree#MilestoneTree"))); + } + + // @SneakyThrows, @Test, @DisplayName("Parse Tree that allows reserved resource types") + parseShapeTreeContainsReservedTypes(): void { + let reservedShapeTree: ShapeTree = ShapeTreeFactory.getShapeTree(toUrl(server, "/static/shapetrees/reserved/shapetree#EverythingTree")); + Assertions.assertNotNull(reservedShapeTree); + assertTrue(reservedShapeTree.getContains().contains(toUrl(server, "http://www.w3.org/ns/shapetrees#ResourceTree"))); + assertTrue(reservedShapeTree.getContains().contains(toUrl(server, "http://www.w3.org/ns/shapetrees#NonRDFResourceTree"))); + assertTrue(reservedShapeTree.getContains().contains(toUrl(server, "http://www.w3.org/ns/shapetrees#ContainerTree"))); + } + + // @SneakyThrows, @Test, @DisplayName("Traverse References") + testTraverseReferences(): void { + let projectShapeTree: ShapeTree = ShapeTreeFactory.getShapeTree(toUrl(server, "/static/shapetrees/project/shapetree-virtual#ProjectTree")); + projectShapeTree.getReferencedShapeTrees(); + Assertions.assertTrue(projectShapeTree.getReferencedShapeTrees(RecursionMethods.BREADTH_FIRST).hasNext()); + Assertions.assertTrue(projectShapeTree.getReferencedShapeTrees(RecursionMethods.DEPTH_FIRST).hasNext()); + } + + // @SneakyThrows, @Test, @DisplayName("Fail to parse shape tree with missing expectsType") + failToParseMissingExpectsType(): void { + Assertions.assertThrows(ShapeTreeException.class, () -> ShapeTreeFactory.getShapeTree(toUrl(server, "/static/shapetrees/invalid/missing-expects-type#DataRepositoryTree"))); + } + + // @SneakyThrows, @Test, @DisplayName("Fail to parse shape tree with st:contains but expects a non-container resource") + failToParseBadExpectsTypeOnContains(): void { + Assertions.assertThrows(ShapeTreeException.class, () -> ShapeTreeFactory.getShapeTree(toUrl(server, "/static/shapetrees/invalid/contains-with-bad-expects-type#DataRepositoryTree"))); + Assertions.assertThrows(ShapeTreeException.class, () -> ShapeTreeFactory.getShapeTree(toUrl(server, "/static/shapetrees/invalid/contains-with-nonrdf-expects-type#DataRepositoryTree"))); + } + + // @SneakyThrows, @Test, @DisplayName("Fail to parse shape tree with invalid object type") + failToParseBadObjectTypeOnContains(): void { + Assertions.assertThrows(ShapeTreeException.class, () -> ShapeTreeFactory.getShapeTree(toUrl(server, "/static/shapetrees/invalid/bad-object-type#DataRepositoryTree"))); + } + + // @SneakyThrows, @Test, @DisplayName("Fail to parse missing shape tree") + failToParseMissingShapeTree(): void { + Assertions.assertThrows(ShapeTreeException.class, () -> ShapeTreeFactory.getShapeTree(toUrl(server, "/static/shapetrees/invalid/shapetree-missing#missing"))); + } + + // @Test, @DisplayName("Fail to parse shape tree with invalid content type") + failToParseShapeTreeWithInvalidContentType(): void { + Assertions.assertThrows(ShapeTreeException.class, () -> ShapeTreeFactory.getShapeTree(toUrl(server, "/static/shapetrees/project/shapetree-bad-content-type#bad"))); + } + + // @SneakyThrows, @Test, @DisplayName("Fail to parse shape tree with invalid contains objects") + failToParseInvalidContainsObjects(): void { + Assertions.assertThrows(ShapeTreeException.class, () -> ShapeTreeFactory.getShapeTree(toUrl(server, "/static/shapetrees/invalid/shapetree-invalid-contains-objects#DataRepositoryTree"))); + } + + // @SneakyThrows, @Test, @DisplayName("Parse st:contains across multiple documents") + parseContainsAcrossMultipleDocuments(): void { + // Parse for recursive st:contains (use contains across multiple documents) + let containsShapeTree: ShapeTree = ShapeTreeFactory.getShapeTree(toUrl(server, "/static/shapetrees/parsing/contains-1#1ATree")); + // Check the shape tree cache to ensure every contains shape tree was visited, parsed, and cached + Assertions.assertEquals(11, ShapeTreeFactory.getLocalShapeTreeCache().size()); + ShapeTreeFactory.getLocalShapeTreeCache().containsKey(toUrl(server, "/static/shapetrees/parsing/contains-1#1ATree")); + ShapeTreeFactory.getLocalShapeTreeCache().containsKey(toUrl(server, "/static/shapetrees/parsing/contains-2#2ATree")); + ShapeTreeFactory.getLocalShapeTreeCache().containsKey(toUrl(server, "/static/shapetrees/parsing/contains-2#2BTree")); + ShapeTreeFactory.getLocalShapeTreeCache().containsKey(toUrl(server, "/static/shapetrees/parsing/contains-2#2CTree")); + ShapeTreeFactory.getLocalShapeTreeCache().containsKey(toUrl(server, "/static/shapetrees/parsing/contains-2A#2A1Tree")); + ShapeTreeFactory.getLocalShapeTreeCache().containsKey(toUrl(server, "/static/shapetrees/parsing/contains-2A#2A2Tree")); + ShapeTreeFactory.getLocalShapeTreeCache().containsKey(toUrl(server, "/static/shapetrees/parsing/contains-2B#2B1Tree")); + ShapeTreeFactory.getLocalShapeTreeCache().containsKey(toUrl(server, "/static/shapetrees/parsing/contains-2C#2C1Tree")); + ShapeTreeFactory.getLocalShapeTreeCache().containsKey(toUrl(server, "/static/shapetrees/parsing/contains-2C#2C2Tree")); + ShapeTreeFactory.getLocalShapeTreeCache().containsKey(toUrl(server, "/static/shapetrees/parsing/contains-2C#2C3Tree")); + ShapeTreeFactory.getLocalShapeTreeCache().containsKey(toUrl(server, "/static/shapetrees/parsing/contains-2C2#2C21Tree")); + // Check the resource cache to ensure every visited resource was cached + Assertions.assertEquals(6, ShapeTreeResource.getLocalResourceCache().size()); + ShapeTreeResource.getLocalResourceCache().containsKey(toUrl(server, "/static/shapetrees/parsing/contains-1")); + ShapeTreeResource.getLocalResourceCache().containsKey(toUrl(server, "/static/shapetrees/parsing/contains-2")); + ShapeTreeResource.getLocalResourceCache().containsKey(toUrl(server, "/static/shapetrees/parsing/contains-2A")); + ShapeTreeResource.getLocalResourceCache().containsKey(toUrl(server, "/static/shapetrees/parsing/contains-2B")); + ShapeTreeResource.getLocalResourceCache().containsKey(toUrl(server, "/static/shapetrees/parsing/contains-2C2")); + ShapeTreeResource.getLocalResourceCache().containsKey(toUrl(server, "/static/shapetrees/parsing/contains-2C")); + } + + // @SneakyThrows, @Test, @DisplayName("Parse st:references across multiple documents") + parseReferencesAcrossMultipleDocuments(): void { + // Parse for recursive st:references (use references across multiple documents) + let referencesShapeTree: ShapeTree = ShapeTreeFactory.getShapeTree(toUrl(server, "/static/shapetrees/parsing/references-1#1ATree")); + // Check the shape tree cache to ensure every referenced shape tree was visited, parsed, and cached + Assertions.assertEquals(11, ShapeTreeFactory.getLocalShapeTreeCache().size()); + ShapeTreeFactory.getLocalShapeTreeCache().containsKey(toUrl(server, "/static/shapetrees/parsing/references-1#1ATree")); + ShapeTreeFactory.getLocalShapeTreeCache().containsKey(toUrl(server, "/static/shapetrees/parsing/references-2#2ATree")); + ShapeTreeFactory.getLocalShapeTreeCache().containsKey(toUrl(server, "/static/shapetrees/parsing/references-2#2BTree")); + ShapeTreeFactory.getLocalShapeTreeCache().containsKey(toUrl(server, "/static/shapetrees/parsing/references-2#2CTree")); + ShapeTreeFactory.getLocalShapeTreeCache().containsKey(toUrl(server, "/static/shapetrees/parsing/references-2A#2A1Tree")); + ShapeTreeFactory.getLocalShapeTreeCache().containsKey(toUrl(server, "/static/shapetrees/parsing/references-2A#2A2Tree")); + ShapeTreeFactory.getLocalShapeTreeCache().containsKey(toUrl(server, "/static/shapetrees/parsing/references-2B#2B1Tree")); + ShapeTreeFactory.getLocalShapeTreeCache().containsKey(toUrl(server, "/static/shapetrees/parsing/references-2C#2C1Tree")); + ShapeTreeFactory.getLocalShapeTreeCache().containsKey(toUrl(server, "/static/shapetrees/parsing/references-2C#2C2Tree")); + ShapeTreeFactory.getLocalShapeTreeCache().containsKey(toUrl(server, "/static/shapetrees/parsing/references-2C#2C3Tree")); + ShapeTreeFactory.getLocalShapeTreeCache().containsKey(toUrl(server, "/static/shapetrees/parsing/references-2C2#2C21Tree")); + // Check the resource cache to ensure every visited resource was cached + Assertions.assertEquals(6, ShapeTreeResource.getLocalResourceCache().size()); + ShapeTreeResource.getLocalResourceCache().containsKey(toUrl(server, "/static/shapetrees/parsing/references-1")); + ShapeTreeResource.getLocalResourceCache().containsKey(toUrl(server, "/static/shapetrees/parsing/references-2")); + ShapeTreeResource.getLocalResourceCache().containsKey(toUrl(server, "/static/shapetrees/parsing/references-2A")); + ShapeTreeResource.getLocalResourceCache().containsKey(toUrl(server, "/static/shapetrees/parsing/references-2B")); + ShapeTreeResource.getLocalResourceCache().containsKey(toUrl(server, "/static/shapetrees/parsing/references-2C2")); + ShapeTreeResource.getLocalResourceCache().containsKey(toUrl(server, "/static/shapetrees/parsing/references-2C")); + } + + // @SneakyThrows, @Test, @DisplayName("Parse st:contains and st:references across multiple documents") + parseContainsAndReferencesAcrossMultipleDocuments(): void { + // Parse for mix of st:contains and references + let referencesShapeTree: ShapeTree = ShapeTreeFactory.getShapeTree(toUrl(server, "/static/shapetrees/parsing/mixed-1#1ATree")); + // Check the shape tree cache to ensure every referenced shape tree was visited, parsed, and cached + Assertions.assertEquals(13, ShapeTreeFactory.getLocalShapeTreeCache().size()); + ShapeTreeFactory.getLocalShapeTreeCache().containsKey(toUrl(server, "/static/shapetrees/parsing/mixed-1#1ATree")); + ShapeTreeFactory.getLocalShapeTreeCache().containsKey(toUrl(server, "/static/shapetrees/parsing/mixed-2#2ATree")); + ShapeTreeFactory.getLocalShapeTreeCache().containsKey(toUrl(server, "/static/shapetrees/parsing/mixed-2#2BTree")); + ShapeTreeFactory.getLocalShapeTreeCache().containsKey(toUrl(server, "/static/shapetrees/parsing/mixed-2#2CTree")); + ShapeTreeFactory.getLocalShapeTreeCache().containsKey(toUrl(server, "/static/shapetrees/parsing/mixed-2#2DTree")); + ShapeTreeFactory.getLocalShapeTreeCache().containsKey(toUrl(server, "/static/shapetrees/parsing/mixed-2A#2A1Tree")); + ShapeTreeFactory.getLocalShapeTreeCache().containsKey(toUrl(server, "/static/shapetrees/parsing/mixed-2A#2A2Tree")); + ShapeTreeFactory.getLocalShapeTreeCache().containsKey(toUrl(server, "/static/shapetrees/parsing/mixed-2B#2B1Tree")); + ShapeTreeFactory.getLocalShapeTreeCache().containsKey(toUrl(server, "/static/shapetrees/parsing/mixed-2C#2C1Tree")); + ShapeTreeFactory.getLocalShapeTreeCache().containsKey(toUrl(server, "/static/shapetrees/parsing/mixed-2C#2C2Tree")); + ShapeTreeFactory.getLocalShapeTreeCache().containsKey(toUrl(server, "/static/shapetrees/parsing/mixed-2C#2C3Tree")); + ShapeTreeFactory.getLocalShapeTreeCache().containsKey(toUrl(server, "/static/shapetrees/parsing/mixed-2C2#2C21Tree")); + ShapeTreeFactory.getLocalShapeTreeCache().containsKey(toUrl(server, "/static/shapetrees/parsing/mixed-2D#2D1Tree")); + // Check the resource cache to ensure every visited resource was cached + Assertions.assertEquals(7, ShapeTreeResource.getLocalResourceCache().size()); + ShapeTreeResource.getLocalResourceCache().containsKey(toUrl(server, "/static/shapetrees/parsing/mixed-1")); + ShapeTreeResource.getLocalResourceCache().containsKey(toUrl(server, "/static/shapetrees/parsing/mixed-2")); + ShapeTreeResource.getLocalResourceCache().containsKey(toUrl(server, "/static/shapetrees/parsing/mixed-2A")); + ShapeTreeResource.getLocalResourceCache().containsKey(toUrl(server, "/static/shapetrees/parsing/mixed-2B")); + ShapeTreeResource.getLocalResourceCache().containsKey(toUrl(server, "/static/shapetrees/parsing/mixed-2C2")); + ShapeTreeResource.getLocalResourceCache().containsKey(toUrl(server, "/static/shapetrees/parsing/mixed-2C")); + ShapeTreeResource.getLocalResourceCache().containsKey(toUrl(server, "/static/shapetrees/parsing/mixed-2D")); + } + + // @SneakyThrows, @Test, @DisplayName("Parse shape tree hierarchy with circular reference") + parseWithCircularReference(): void { + // Ensure the parser correctly handles circular references + let circularShapeTree: ShapeTree = ShapeTreeFactory.getShapeTree(toUrl(server, "/static/shapetrees/parsing/cycle#1ATree")); + Assertions.assertEquals(12, ShapeTreeFactory.getLocalShapeTreeCache().size()); + ShapeTreeFactory.getLocalShapeTreeCache().containsKey(toUrl(server, "/static/shapetrees/parsing/mixed-1#1ATree")); + ShapeTreeFactory.getLocalShapeTreeCache().containsKey(toUrl(server, "/static/shapetrees/parsing/mixed-2#2ATree")); + ShapeTreeFactory.getLocalShapeTreeCache().containsKey(toUrl(server, "/static/shapetrees/parsing/mixed-2#2BTree")); + ShapeTreeFactory.getLocalShapeTreeCache().containsKey(toUrl(server, "/static/shapetrees/parsing/mixed-2#2CTree")); + ShapeTreeFactory.getLocalShapeTreeCache().containsKey(toUrl(server, "/static/shapetrees/parsing/mixed-2#2DTree")); + ShapeTreeFactory.getLocalShapeTreeCache().containsKey(toUrl(server, "/static/shapetrees/parsing/mixed-2A#2A1Tree")); + ShapeTreeFactory.getLocalShapeTreeCache().containsKey(toUrl(server, "/static/shapetrees/parsing/mixed-2A#2A2Tree")); + ShapeTreeFactory.getLocalShapeTreeCache().containsKey(toUrl(server, "/static/shapetrees/parsing/mixed-2B#2B1Tree")); + ShapeTreeFactory.getLocalShapeTreeCache().containsKey(toUrl(server, "/static/shapetrees/parsing/mixed-2C#2C1Tree")); + ShapeTreeFactory.getLocalShapeTreeCache().containsKey(toUrl(server, "/static/shapetrees/parsing/mixed-2C#2C2Tree")); + ShapeTreeFactory.getLocalShapeTreeCache().containsKey(toUrl(server, "/static/shapetrees/parsing/mixed-2C#2C3Tree")); + ShapeTreeFactory.getLocalShapeTreeCache().containsKey(toUrl(server, "/static/shapetrees/parsing/mixed-2D#2D1Tree")); + } +} diff --git a/asTypescript/packages/tests/src/ShapeTreeValidationTests.ts b/asTypescript/packages/tests/src/ShapeTreeValidationTests.ts new file mode 100644 index 00000000..0ea954e3 --- /dev/null +++ b/asTypescript/packages/tests/src/ShapeTreeValidationTests.ts @@ -0,0 +1,202 @@ +// Corresponding shapetrees-java package: com.janeirodigital.shapetrees.tests +import { SchemaCache } from '@shapetrees/core/src/SchemaCache'; +import { ShapeTree } from '@shapetrees/core/src/ShapeTree'; +import { ShapeTreeFactory } from '@shapetrees/core/src/ShapeTreeFactory'; +import { ValidationResult } from '@shapetrees/core/src/ValidationResult'; +import { DocumentLoaderManager } from '@shapetrees/core/src/contentloaders/DocumentLoaderManager'; +import { HttpExternalDocumentLoader } from '@shapetrees/core/src/contentloaders/HttpExternalDocumentLoader'; +import { ShapeTreeResourceType } from '@shapetrees/core/src/enums/ShapeTreeResourceType'; +import { ShapeTreeException } from '@shapetrees/core/src/exceptions/ShapeTreeException'; +import { GraphHelper } from '@shapetrees/core/src/helpers/GraphHelper'; +import { DispatcherEntry } from './fixtures/DispatcherEntry'; +import { RequestMatchingFixtureDispatcher } from './fixtures/RequestMatchingFixtureDispatcher'; +import * as ShexSchema from 'fr/inria/lille/shexjava/schema'; +import * as Label from 'jdk/jfr'; +import * as MockWebServer from 'okhttp3/mockwebserver'; +import * as Graph from 'org/apache/jena/graph'; +import * as Model from 'org/apache/jena/rdf/model'; +import * as ModelFactory from 'org/apache/jena/rdf/model'; +import * as Lang from 'org/apache/jena/riot'; +import * as RDFDataMgr from 'org/apache/jena/riot'; +import * as Assertions from 'org/junit/jupiter/api'; +import * as BeforeAll from 'org/junit/jupiter/api'; +import * as Test from 'org/junit/jupiter/api'; +import { toUrl } from './fixtures/MockWebServerHelper/toUrl'; + +class ShapeTreeValidationTests { + + private static dispatcher: RequestMatchingFixtureDispatcher = null; + + private static httpExternalDocumentLoader: HttpExternalDocumentLoader; + + public constructor() { + httpExternalDocumentLoader = new HttpExternalDocumentLoader(); + DocumentLoaderManager.setLoader(httpExternalDocumentLoader); + } + + // @BeforeAll + static beforeAll(): void { + dispatcher = new RequestMatchingFixtureDispatcher(List.of(new DispatcherEntry(List.of("shapetrees/validation-shapetree-ttl"), "GET", "/static/shapetrees/validation/shapetree", null), new DispatcherEntry(List.of("shapetrees/containment-shapetree-ttl"), "GET", "/static/shapetrees/containment/shapetree", null), new DispatcherEntry(List.of("validation/validation-container"), "GET", "/validation/", null), new DispatcherEntry(List.of("validation/valid-resource"), "GET", "/validation/valid-resource", null), new DispatcherEntry(List.of("validation/containment/container-1"), "GET", "/validation/container-1/", null), new DispatcherEntry(List.of("validation/containment/container-1-multiplecontains-manager"), "GET", "/validation/container-1/.shapetree", null), new DispatcherEntry(List.of("http/404"), "GET", "/static/shex/missing", null), new DispatcherEntry(List.of("http/404"), "GET", "/static/shapetrees/missing", null), new DispatcherEntry(List.of("schemas/validation-shex"), "GET", "/static/shex/validation", null), new DispatcherEntry(List.of("schemas/containment-shex"), "GET", "/static/shex/containment", null), new DispatcherEntry(List.of("schemas/invalid-shex"), "GET", "/static/shex/invalid", null))); + } + + // @SneakyThrows, @Test, @Label("Validate expectsType of Container") + validateExpectsContainerType(): void { + let server: MockWebServer = new MockWebServer(); + server.setDispatcher(dispatcher); + let result: ValidationResult; + let shapeTree: ShapeTree = ShapeTreeFactory.getShapeTree(toUrl(server, "/static/shapetrees/validation/shapetree#ExpectsContainerTree")); + result = shapeTree.validateResource(null, null, ShapeTreeResourceType.CONTAINER, null); + Assertions.assertTrue(result.isValid()); + result = shapeTree.validateResource(null, null, ShapeTreeResourceType.RESOURCE, null); + Assertions.assertFalse(result.isValid()); + result = shapeTree.validateResource(null, null, ShapeTreeResourceType.NON_RDF, null); + Assertions.assertFalse(result.isValid()); + } + + // @SneakyThrows, @Test, @Label("Validate expectsType of Resource") + validateExpectsResourceType(): void { + let server: MockWebServer = new MockWebServer(); + server.setDispatcher(dispatcher); + let result: ValidationResult; + let shapeTree: ShapeTree = ShapeTreeFactory.getShapeTree(toUrl(server, "/static/shapetrees/validation/shapetree#ExpectsResourceTree")); + result = shapeTree.validateResource(null, null, ShapeTreeResourceType.RESOURCE, null); + Assertions.assertTrue(result.isValid()); + result = shapeTree.validateResource(null, null, ShapeTreeResourceType.CONTAINER, null); + Assertions.assertFalse(result.isValid()); + result = shapeTree.validateResource(null, null, ShapeTreeResourceType.NON_RDF, null); + Assertions.assertFalse(result.isValid()); + } + + // @SneakyThrows, @Test, @Label("Validate expectsType of NonRDFResource") + validateExpectsNonRDFResourceType(): void { + let server: MockWebServer = new MockWebServer(); + server.setDispatcher(dispatcher); + let result: ValidationResult; + let shapeTree: ShapeTree = ShapeTreeFactory.getShapeTree(toUrl(server, "/static/shapetrees/validation/shapetree#ExpectsNonRDFResourceTree")); + result = shapeTree.validateResource(null, null, ShapeTreeResourceType.NON_RDF, null); + Assertions.assertTrue(result.isValid()); + result = shapeTree.validateResource(null, null, ShapeTreeResourceType.RESOURCE, null); + Assertions.assertFalse(result.isValid()); + result = shapeTree.validateResource(null, null, ShapeTreeResourceType.CONTAINER, null); + Assertions.assertFalse(result.isValid()); + } + + // @SneakyThrows, @Test, @Label("Validate label") + validateLabel(): void { + let server: MockWebServer = new MockWebServer(); + server.setDispatcher(dispatcher); + let result: ValidationResult; + let shapeTree: ShapeTree = ShapeTreeFactory.getShapeTree(toUrl(server, "/static/shapetrees/validation/shapetree#LabelTree")); + result = shapeTree.validateResource("resource-name", null, ShapeTreeResourceType.RESOURCE, null); + Assertions.assertTrue(result.isValid()); + result = shapeTree.validateResource("invalid-name", null, ShapeTreeResourceType.RESOURCE, null); + Assertions.assertFalse(result.isValid()); + } + + // @SneakyThrows, @Test, @Label("Validate shape") + validateShape(): void { + let server: MockWebServer = new MockWebServer(); + server.setDispatcher(dispatcher); + let result: ValidationResult; + let shapeTree: ShapeTree = ShapeTreeFactory.getShapeTree(toUrl(server, "/static/shapetrees/validation/shapetree#FooTree")); + // Validate shape with focus node + let focusNodeUrls: Array = List.of(toUrl(server, "/validation/valid-resource#foo")); + result = shapeTree.validateResource(null, focusNodeUrls, ShapeTreeResourceType.RESOURCE, getFooBodyGraph(toUrl(server, "/validation/valid-resource"))); + Assertions.assertTrue(result.isValid()); + // Validate shape without focus node + result = shapeTree.validateResource(null, null, ShapeTreeResourceType.RESOURCE, getFooBodyGraph(toUrl(server, "/validation/valid-resource"))); + Assertions.assertTrue(result.isValid()); + } + + // @SneakyThrows, @Test, @Label("Fail to validate shape") + failToValidateShape(): void { + let server: MockWebServer = new MockWebServer(); + server.setDispatcher(dispatcher); + let result: ValidationResult; + let shapeTree: ShapeTree = ShapeTreeFactory.getShapeTree(toUrl(server, "/static/shapetrees/validation/shapetree#FooTree")); + // Pass in body content that will fail validation of the shape associated with FooTree + let focusNodeUrls: Array = List.of(toUrl(server, "/validation/valid-resource#foo")); + result = shapeTree.validateResource(null, focusNodeUrls, ShapeTreeResourceType.RESOURCE, getInvalidFooBodyGraph(toUrl(server, "/validation/valid-resource"))); + Assertions.assertFalse(result.isValid()); + } + + // @SneakyThrows, @Test, @Label("Fail to validate shape when the shape resource cannot be found") + failToValidateMissingShape(): void { + let server: MockWebServer = new MockWebServer(); + server.setDispatcher(dispatcher); + let result: ValidationResult; + let shapeTree: ShapeTree = ShapeTreeFactory.getShapeTree(toUrl(server, "/static/shapetrees/validation/shapetree#MissingShapeSchemaTree")); + let fooBodyGraph: Graph = getFooBodyGraph(toUrl(server, "/validation/valid-resource")); + // Catch exception thrown when a shape in a shape tree cannot be found + let focusNodeUrls: Array = List.of(toUrl(server, "/validation/valid-resource#foo")); + Assertions.assertThrows(ShapeTreeException.class, () -> shapeTree.validateResource(null, focusNodeUrls, ShapeTreeResourceType.RESOURCE, fooBodyGraph)); + } + + // @SneakyThrows, @Test, @Label("Fail to validate shape when the shape resource is malformed") + failToValidateMalformedShape(): void { + let server: MockWebServer = new MockWebServer(); + server.setDispatcher(dispatcher); + let result: ValidationResult; + let shapeTree: ShapeTree = ShapeTreeFactory.getShapeTree(toUrl(server, "/static/shapetrees/validation/shapetree#InvalidShapeSchemaTree")); + let fooBodyGraph: Graph = getFooBodyGraph(toUrl(server, "/validation/valid-resource")); + // Catch exception thrown when a shape in a shape tree is invalid + let focusNodeUrls: Array = List.of(toUrl(server, "/validation/valid-resource#foo")); + Assertions.assertThrows(ShapeTreeException.class, () -> shapeTree.validateResource(null, focusNodeUrls, ShapeTreeResourceType.RESOURCE, fooBodyGraph)); + } + + // @SneakyThrows, @Test, @Label("Fail shape validation when shape tree doesn't validate a shape") + failToValidateWhenNoShapeInShapeTree(): void { + let server: MockWebServer = new MockWebServer(); + server.setDispatcher(dispatcher); + // Get the NoShapeValidationTree shape tree. This shape tree doesn't enforce shape validation, + // so it should return an error when using to validate + let noShapeValidationTree: ShapeTree = ShapeTreeFactory.getShapeTree(toUrl(server, "/static/shapetrees/validation/shapetree#NoShapeValidationTree")); + let graphTtl: string = "<#a> <#b> <#c> ."; + let focusNodeUrls: Array = List.of(toUrl(server, "http://a.example/b/c.d#a")); + let sr: StringReader = new StringReader(graphTtl); + let model: Model = ModelFactory.createDefaultModel(); + RDFDataMgr.read(model, sr, "http://example.com/", Lang.TTL); + Assertions.assertThrows(ShapeTreeException.class, () -> noShapeValidationTree.validateGraph(model.getGraph(), focusNodeUrls)); + } + + // @SneakyThrows, @Test, @Label("Validate shape before it is cached in schema cache") + validateShapeBeforeCaching(): void { + let server: MockWebServer = new MockWebServer(); + server.setDispatcher(dispatcher); + let result: ValidationResult; + SchemaCache.initializeCache(); + let shapeTree: ShapeTree = ShapeTreeFactory.getShapeTree(toUrl(server, "/static/shapetrees/validation/shapetree#FooTree")); + // Validate shape with focus node + let focusNodeUrls: Array = List.of(toUrl(server, "/validation/valid-resource#foo")); + result = shapeTree.validateResource(null, focusNodeUrls, ShapeTreeResourceType.RESOURCE, getFooBodyGraph(toUrl(server, "/validation/valid-resource"))); + Assertions.assertTrue(result.isValid()); + } + + // @SneakyThrows, @Test, @Label("Validate shape after it is cached in schema cache") + validateShapeAfterCaching(): void { + let server: MockWebServer = new MockWebServer(); + server.setDispatcher(dispatcher); + let result: ValidationResult; + let schemas: Map = SchemaCacheTests.buildSchemaCache(List.of(toUrl(server, "/static/shex/validation").toString())); + SchemaCache.initializeCache(schemas); + let shapeTree: ShapeTree = ShapeTreeFactory.getShapeTree(toUrl(server, "/static/shapetrees/validation/shapetree#FooTree")); + // Validate shape with focus node + let focusNodeUrls: Array = List.of(toUrl(server, "/validation/valid-resource#foo")); + result = shapeTree.validateResource(null, focusNodeUrls, ShapeTreeResourceType.RESOURCE, getFooBodyGraph(toUrl(server, "/validation/valid-resource"))); + Assertions.assertTrue(result.isValid()); + } + + private getFooBodyGraph(baseUrl: URL): Graph /* throws ShapeTreeException */ { + let body: string = "PREFIX rdf: \n" + "PREFIX rdfs: \n" + "PREFIX xml: \n" + "PREFIX xsd: \n" + "PREFIX ex: \n" + "<#foo> \n" + " ex:id 56789 ; \n" + " ex:name \"Footastic\" ; \n" + " ex:created_at \"2021-04-04T20:15:47.000Z\"^^xsd:dateTime . \n"; + return GraphHelper.readStringIntoGraph(GraphHelper.urlToUri(baseUrl), body, "text/turtle"); + } + + private getInvalidFooBodyGraph(baseUrl: URL): Graph /* throws ShapeTreeException */ { + let body: string = "PREFIX rdf: \n" + "PREFIX rdfs: \n" + "PREFIX xml: \n" + "PREFIX xsd: \n" + "PREFIX ex: \n" + "<#foo> \n" + " ex:id 56789 ; \n" + " ex:created_at \"2021-04-04T20:15:47.000Z\"^^xsd:dateTime . \n"; + return GraphHelper.readStringIntoGraph(GraphHelper.urlToUri(baseUrl), body, "text/turtle"); + } + + private getAttributeOneBodyGraph(): string { + return "PREFIX rdf: \n" + "PREFIX rdfs: \n" + "PREFIX xml: \n" + "PREFIX xsd: \n" + "PREFIX ex: \n" + "<#resource> \n" + " ex:name \"Attribute 1\" ; \n" + " ex:created_at \"2021-04-04T20:15:47.000Z\"^^xsd:dateTime . \n"; + } +} diff --git a/asTypescript/packages/tests/src/clienthttp/AbstractHttpClientDiscoverTests.ts b/asTypescript/packages/tests/src/clienthttp/AbstractHttpClientDiscoverTests.ts new file mode 100644 index 00000000..e8d331e5 --- /dev/null +++ b/asTypescript/packages/tests/src/clienthttp/AbstractHttpClientDiscoverTests.ts @@ -0,0 +1,97 @@ +// Corresponding shapetrees-java package: com.janeirodigital.shapetrees.tests.clienthttp +import { ShapeTreeException } from '@shapetrees/core/src/exceptions/ShapeTreeException'; +import { ShapeTreeAssignment } from '@shapetrees/core/src/ShapeTreeAssignment'; +import { ShapeTreeManager } from '@shapetrees/core/src/ShapeTreeManager'; +import { DispatcherEntry } from '../fixtures/DispatcherEntry'; +import { RequestMatchingFixtureDispatcher } from '../fixtures/RequestMatchingFixtureDispatcher'; +import * as Label from 'jdk/jfr'; +import * as MockWebServer from 'okhttp3/mockwebserver'; +import { AbstractHttpClientTests } from './AbstractHttpClientTests'; + +@TestMethodOrder(MethodOrderer.OrderAnnotation.class) +export class AbstractHttpClientDiscoverTests extends AbstractHttpClientTests { + + private static dispatcher: RequestMatchingFixtureDispatcher = null; + + public constructor() { + // Call AbstractHttpClient constructor + super(); + } + + // @BeforeAll + static beforeAll(): void { + let dispatcherList: Array = new Array(); + dispatcherList.add(new DispatcherEntry(List.of("discover/unmanaged"), "GET", "/unmanaged", null)); + dispatcherList.add(new DispatcherEntry(List.of("discover/unmanaged-manager"), "GET", "/unmanaged.shapetree", null)); + dispatcherList.add(new DispatcherEntry(List.of("discover/managed"), "GET", "/managed", null)); + dispatcherList.add(new DispatcherEntry(List.of("discover/managed-manager"), "GET", "/managed.shapetree", null)); + dispatcherList.add(new DispatcherEntry(List.of("discover/managed-invalid-1"), "GET", "/managed-invalid-1", null)); + dispatcherList.add(new DispatcherEntry(List.of("discover/managed-invalid-1-manager"), "GET", "/managed-invalid-1.shapetree", null)); + dispatcherList.add(new DispatcherEntry(List.of("discover/managed-invalid-2"), "GET", "/managed-invalid-2", null)); + dispatcherList.add(new DispatcherEntry(List.of("discover/managed-invalid-2-manager"), "GET", "/managed-invalid-2.shapetree", null)); + dispatcherList.add(new DispatcherEntry(List.of("discover/no-manager"), "GET", "/no-manager", null)); + dispatcher = new RequestMatchingFixtureDispatcher(dispatcherList); + } + + // @Order(1), @SneakyThrows, @Test, @Label("Discover unmanaged resource") + discoverUnmanagedResource(): void { + let server: MockWebServer = new MockWebServer(); + server.setDispatcher(dispatcher); + let targetResource: URL = toUrl(server, "/unmanaged"); + // Use the discover operation to see if the root container is managed + let manager: ShapeTreeManager = this.shapeTreeClient.discoverShapeTree(this.context, targetResource).orElse(null); + // The root container isn't managed so check to ensure that a NULL value is returned + Assertions.assertNull(manager); + } + + // @Order(2), @SneakyThrows, @Test, @Label("Discover managed resource") + discoverManagedResource(): void { + let server: MockWebServer = new MockWebServer(); + server.setDispatcher(dispatcher); + let targetResource: URL = toUrl(server, "/managed"); + // Perform a discover on a resource that has a shape tree manager already planted + let manager: ShapeTreeManager = this.shapeTreeClient.discoverShapeTree(this.context, targetResource).orElse(null); + // Ensure that it was planted successfully + Assertions.assertNotNull(manager); + Assertions.assertEquals(1, manager.getAssignments().size()); + let assignment: ShapeTreeAssignment = manager.getAssignments().get(0); + Assertions.assertEquals(new URL("http://www.example.com/ns/ex#DataTree"), assignment.getShapeTree()); + Assertions.assertEquals(targetResource.toString(), assignment.getManagedResource().toString()); + Assertions.assertEquals(assignment.getUrl(), assignment.getRootAssignment()); + Assertions.assertEquals(toUrl(server, "/managed").toString() + "#set", assignment.getFocusNode().toString()); + Assertions.assertEquals("http://www.example.com/ns/ex#DataSetShape", assignment.getShape().toString()); + } + + // @Order(3), @SneakyThrows, @Test, @Label("Fail to discover managed resource with multiple managers") + failToDiscoverDueToMultipleManagers(): void { + let server: MockWebServer = new MockWebServer(); + server.setDispatcher(dispatcher); + let targetResource: URL = toUrl(server, "/managed-invalid-1"); + // If a manager resource has multiple shapetree managers it is considered invalid + Assertions.assertThrows(IllegalStateException.class, () -> { + let manager: ShapeTreeManager | null = this.shapeTreeClient.discoverShapeTree(this.context, targetResource); + }); + } + + // @Order(4), @SneakyThrows, @Test, @Label("Fail to discover managed resource with no managers") + failToDiscoverDueToNoManagers(): void { + let server: MockWebServer = new MockWebServer(); + server.setDispatcher(dispatcher); + let targetResource: URL = toUrl(server, "/managed-invalid-2"); + // If a manager resource exists, but has no managers it is considered invalid + Assertions.assertThrows(IllegalStateException.class, () -> { + let manager: ShapeTreeManager | null = this.shapeTreeClient.discoverShapeTree(this.context, targetResource); + }); + } + + // @Order(5), @SneakyThrows, @Test, @Label("Discover server doesn't support ShapeTrees") + failToDiscoverDueToNoManagerLink(): void { + let server: MockWebServer = new MockWebServer(); + server.setDispatcher(dispatcher); + let targetResource: URL = toUrl(server, "/no-manager"); + // If a manager resource exists, but has no managers it is considered invalid + Assertions.assertThrows(ShapeTreeException.class, () -> { + let manager: ShapeTreeManager | null = this.shapeTreeClient.discoverShapeTree(this.context, targetResource); + }); + } +} diff --git a/asTypescript/packages/tests/src/clienthttp/AbstractHttpClientInitializeTests.ts b/asTypescript/packages/tests/src/clienthttp/AbstractHttpClientInitializeTests.ts new file mode 100644 index 00000000..579ac59e --- /dev/null +++ b/asTypescript/packages/tests/src/clienthttp/AbstractHttpClientInitializeTests.ts @@ -0,0 +1,27 @@ +// Corresponding shapetrees-java package: com.janeirodigital.shapetrees.tests.clienthttp +import { HttpClient } from '@shapetrees/clienthttp/src/HttpClient'; +import { RequestMatchingFixtureDispatcher } from '../fixtures/RequestMatchingFixtureDispatcher'; +import { AbstractHttpClientTests } from './AbstractHttpClientTests'; + +@TestMethodOrder(MethodOrderer.OrderAnnotation.class) +export class AbstractHttpClientInitializeTests extends AbstractHttpClientTests { + + private static dispatcher: RequestMatchingFixtureDispatcher = null; + + public constructor() { + // Call AbstractHttpClient constructor + super(); + } + + // @Test, @SneakyThrows + testNonValidatingHandler(): void { + let client: HttpClient = this.factory.get(false); + Assertions.assertNotNull(client); + } + + // @Test, @SneakyThrows + testValidatingHandler(): void { + let client: HttpClient = this.factory.get(true); + Assertions.assertNotNull(client); + } +} diff --git a/asTypescript/packages/tests/src/clienthttp/AbstractHttpClientProjectRecursiveTests.ts b/asTypescript/packages/tests/src/clienthttp/AbstractHttpClientProjectRecursiveTests.ts new file mode 100644 index 00000000..c7bba1c6 --- /dev/null +++ b/asTypescript/packages/tests/src/clienthttp/AbstractHttpClientProjectRecursiveTests.ts @@ -0,0 +1,72 @@ +// Corresponding shapetrees-java package: com.janeirodigital.shapetrees.tests.clienthttp +import { DocumentResponse } from '@shapetrees/core/src/DocumentResponse'; +import { DispatcherEntry } from '../fixtures/DispatcherEntry'; +import { RequestMatchingFixtureDispatcher } from '../fixtures/RequestMatchingFixtureDispatcher'; +import * as Label from 'jdk/jfr'; +import * as MockWebServer from 'okhttp3/mockwebserver'; +import { AbstractHttpClientTests } from './AbstractHttpClientTests'; + +@TestMethodOrder(MethodOrderer.OrderAnnotation.class) +export class AbstractHttpClientProjectRecursiveTests extends AbstractHttpClientTests { + + private static dispatcher: RequestMatchingFixtureDispatcher = null; + + public constructor() { + // Call AbstractHttpClient constructor + super(); + } + + // @BeforeAll + static beforeAll(): void { + let dispatcherList: Array = new Array(); + dispatcherList.add(new DispatcherEntry(List.of("project/data-container"), "GET", "/data/", null)); + dispatcherList.add(new DispatcherEntry(List.of("project/projects-container"), "GET", "/data/projects/", null)); + dispatcherList.add(new DispatcherEntry(List.of("project/project-1-container"), "GET", "/data/projects/project-1/", null)); + dispatcherList.add(new DispatcherEntry(List.of("project/milestone-3-container"), "GET", "/data/projects/project-1/milestone-3/", null)); + dispatcherList.add(new DispatcherEntry(List.of("project/task-48-container"), "GET", "/data/projects/project-1/milestone-3/task-48/", null)); + dispatcherList.add(new DispatcherEntry(List.of("project/task-6-container-no-contains"), "GET", "/data/projects/project-1/milestone-3/task-6/", null)); + dispatcherList.add(new DispatcherEntry(List.of("project/issue-2"), "GET", "/data/projects/project-1/milestone-3/issue-2", null)); + dispatcherList.add(new DispatcherEntry(List.of("project/issue-3"), "GET", "/data/projects/project-1/milestone-3/issue-3", null)); + dispatcherList.add(new DispatcherEntry(List.of("project/attachment-48"), "GET", "/data/projects/project-1/milestone-3/task-48/attachment-48", null)); + dispatcherList.add(new DispatcherEntry(List.of("project/random-png"), "GET", "/data/projects/project-1/milestone-3/task-48/random.png", null)); + dispatcherList.add(new DispatcherEntry(List.of("shapetrees/project-shapetree-ttl"), "GET", "/static/shapetrees/project/shapetree", null)); + dispatcherList.add(new DispatcherEntry(List.of("schemas/project-shex"), "GET", "/static/shex/project/shex", null)); + dispatcher = new RequestMatchingFixtureDispatcher(dispatcherList); + } + + // @Order(1), @SneakyThrows, @Test, @Label("Recursively Plant Data Set") + plantDataRecursively(): void { + let server: MockWebServer = new MockWebServer(); + server.setDispatcher(dispatcher); + dispatcher.getConfiguredFixtures().add(new DispatcherEntry(List.of("http/201"), "POST", "/data/.shapetree", null)); + dispatcher.getConfiguredFixtures().add(new DispatcherEntry(List.of("http/201"), "POST", "/data/projects/.shapetree", null)); + let targetResource: URL = toUrl(server, "/data/"); + let targetShapeTree: URL = toUrl(server, "/static/shapetrees/project/shapetree#DataRepositoryTree"); + let focusNode: URL = toUrl(server, "/data/#repository"); + // Plant the data collection recursively on already existing hierarchy + let response: DocumentResponse = this.shapeTreeClient.plantShapeTree(this.context, targetResource, targetShapeTree, focusNode); + Assertions.assertEquals(201, response.getStatusCode()); + } + + // @Order(2), @SneakyThrows, @Test, @Label("Recursively Plant Projects Collection") + plantProjectsRecursively(): void { + let server: MockWebServer = new MockWebServer(); + server.setDispatcher(dispatcher); + // Add planted data set + dispatcher.getConfiguredFixtures().add(new DispatcherEntry(List.of("project/data-container-manager"), "GET", "/data/.shapetree", null)); + dispatcher.getConfiguredFixtures().add(new DispatcherEntry(List.of("project/projects-container-manager"), "GET", "/data/projects/.shapetree", null)); + dispatcher.getConfiguredFixtures().add(new DispatcherEntry(List.of("http/201"), "POST", "/data/projects/project-1/milestone-3/task-48/attachment-48.shapetree", null)); + dispatcher.getConfiguredFixtures().add(new DispatcherEntry(List.of("http/201"), "POST", "/data/projects/project-1/milestone-3/task-48/random.png.shapetree", null)); + dispatcher.getConfiguredFixtures().add(new DispatcherEntry(List.of("http/201"), "POST", "/data/projects/project-1/milestone-3/task-48/.shapetree", null)); + dispatcher.getConfiguredFixtures().add(new DispatcherEntry(List.of("http/201"), "POST", "/data/projects/project-1/milestone-3/task-6/.shapetree", null)); + dispatcher.getConfiguredFixtures().add(new DispatcherEntry(List.of("http/201"), "POST", "/data/projects/project-1/milestone-3/issue-3.shapetree", null)); + dispatcher.getConfiguredFixtures().add(new DispatcherEntry(List.of("http/201"), "POST", "/data/projects/project-1/milestone-3/issue-2.shapetree", null)); + dispatcher.getConfiguredFixtures().add(new DispatcherEntry(List.of("http/201"), "POST", "/data/projects/project-1/milestone-3/.shapetree", null)); + dispatcher.getConfiguredFixtures().add(new DispatcherEntry(List.of("http/201"), "POST", "/data/projects/project-1/.shapetree", null)); + let targetResource: URL = toUrl(server, "/data/projects/"); + let targetShapeTree: URL = toUrl(server, "/static/shapetrees/project/shapetree#ProjectCollectionTree"); + // Plant the projects collection recursively on already existing hierarchy + let response: DocumentResponse = this.shapeTreeClient.plantShapeTree(this.context, targetResource, targetShapeTree, null); + Assertions.assertEquals(201, response.getStatusCode()); + } +} diff --git a/asTypescript/packages/tests/src/clienthttp/AbstractHttpClientProjectTests.ts b/asTypescript/packages/tests/src/clienthttp/AbstractHttpClientProjectTests.ts new file mode 100644 index 00000000..ffa8a398 --- /dev/null +++ b/asTypescript/packages/tests/src/clienthttp/AbstractHttpClientProjectTests.ts @@ -0,0 +1,616 @@ +// Corresponding shapetrees-java package: com.janeirodigital.shapetrees.tests.clienthttp +import { DocumentResponse } from '@shapetrees/core/src/DocumentResponse'; +import { ShapeTreeManager } from '@shapetrees/core/src/ShapeTreeManager'; +import { ShapeTreeException } from '@shapetrees/core/src/exceptions/ShapeTreeException'; +import { DispatcherEntry } from '../fixtures/DispatcherEntry'; +import { RequestMatchingFixtureDispatcher } from '../fixtures/RequestMatchingFixtureDispatcher'; +import * as MockWebServer from 'okhttp3/mockwebserver'; +import * as MalformedURLException from 'java/net'; +import * as Arrays from 'java/util'; +import { AbstractHttpClientTests } from './AbstractHttpClientTests'; + +@TestMethodOrder(MethodOrderer.OrderAnnotation.class) +export class AbstractHttpClientProjectTests extends AbstractHttpClientTests { + + private static dispatcher: RequestMatchingFixtureDispatcher = null; + + public constructor() { + // Call AbstractHttpClient constructor + super(); + } + + // @BeforeEach + initializeDispatcher(): void { + // For this set of tests, we reinitialize the dispatcher set for every test, because almost every test needs a + // slightly different context. Consequently, we could either modify the state from test to test (which felt a + // little dirty as we couldn't run tests standalone, or set the context for each test (which we're doing) + let dispatcherList: Array = new Array(); + dispatcherList.add(new DispatcherEntry(List.of("project/root-container"), "GET", "/", null)); + dispatcherList.add(new DispatcherEntry(List.of("project/root-container-manager"), "GET", "/.shapetree", null)); + dispatcherList.add(new DispatcherEntry(List.of("shapetrees/project-shapetree-ttl"), "GET", "/static/shapetrees/project/shapetree", null)); + dispatcherList.add(new DispatcherEntry(List.of("shapetrees/information-shapetree-ttl"), "GET", "/static/shapetrees/information/shapetree", null)); + dispatcherList.add(new DispatcherEntry(List.of("schemas/project-shex"), "GET", "/static/shex/project/shex", null)); + dispatcherList.add(new DispatcherEntry(List.of("schemas/information-shex"), "GET", "/static/shex/information/shex", null)); + dispatcher = new RequestMatchingFixtureDispatcher(dispatcherList); + } + + // @SneakyThrows, @Test, @DisplayName("Discover unmanaged root resource") + discoverUnmanagedRoot(): void { + let server: MockWebServer = new MockWebServer(); + server.setDispatcher(dispatcher); + let targetResource: URL = toUrl(server, "/"); + // Use the discover operation to see if the root container is managed + let manager: ShapeTreeManager = this.shapeTreeClient.discoverShapeTree(this.context, targetResource).orElse(null); + // The root container isn't managed so check to ensure that a NULL value is returned + Assertions.assertNull(manager); + } + + // @SneakyThrows, @Test, @DisplayName("Fail to plant on a non-existent data container") + failPlantOnMissingDataContainer(): void { + let server: MockWebServer = new MockWebServer(); + server.setDispatcher(dispatcher); + let targetResource: URL = toUrl(server, "/data/"); + let targetShapeTree: URL = toUrl(server, "/static/shapetrees/project/shapetree#DataRepositoryTree"); + // Perform plant on /data container that doesn't exist yet (fails) + let response: DocumentResponse = this.shapeTreeClient.plantShapeTree(this.context, targetResource, targetShapeTree, null); + // Look for 404 because /data doesn't exist + Assertions.assertEquals(404, response.getStatusCode()); + } + + // @SneakyThrows, @Test, @DisplayName("Plant Data Repository") + plantDataRepository(): void { + let server: MockWebServer = new MockWebServer(); + server.setDispatcher(dispatcher); + // Create the data container + dispatcher.getConfiguredFixtures().add(new DispatcherEntry(List.of("project/data-container-no-contains"), "GET", "/data/", null)); + dispatcher.getConfiguredFixtures().add(new DispatcherEntry(List.of("http/201"), "POST", "/data/.shapetree", null)); + let targetResource: URL = toUrl(server, "/data/"); + let targetShapeTree: URL = toUrl(server, "/static/shapetrees/project/shapetree#DataRepositoryTree"); + let focusNode: URL = toUrl(server, "/data/#repository"); + // Plant the data repository on newly created data container + let response: DocumentResponse = this.shapeTreeClient.plantShapeTree(this.context, targetResource, targetShapeTree, focusNode); + Assertions.assertEquals(201, response.getStatusCode()); + } + + // @SneakyThrows, @Test, @DisplayName("Fail to plant on missing shape tree") + failPlantOnMissingShapeTree(): void { + let server: MockWebServer = new MockWebServer(); + server.setDispatcher(dispatcher); + // Create the data container + dispatcher.getConfiguredFixtures().add(new DispatcherEntry(List.of("project/data-container-no-contains"), "GET", "/data/", null)); + let targetResource: URL = toUrl(server, "/data/"); + let targetShapeTree: URL = toUrl(server, "/static/shapetrees/missing/shapetree#NonExistentTree"); + let focusNode: URL = toUrl(server, "/data/#repository"); + // Plant will fail and throw an exception when the shape tree to plant cannot be looked up + Assertions.assertThrows(ShapeTreeException.class, () -> { + let response: DocumentResponse = this.shapeTreeClient.plantShapeTree(this.context, targetResource, targetShapeTree, focusNode); + }); + } + + // @SneakyThrows, @Test, @DisplayName("Create Projects Container and Validate DataCollectionTree and InformationSetTree") + createAndValidateProjectsWithMultipleContains(): void { + let server: MockWebServer = new MockWebServer(); + server.setDispatcher(dispatcher); + // Setup initial fixtures for /data/ + dispatcher.getConfiguredFixtures().add(new DispatcherEntry(List.of("project/data-container"), "GET", "/data/", null)); + dispatcher.getConfiguredFixtures().add(new DispatcherEntry(List.of("project/data-container-multiplecontains-manager"), "GET", "/data/.shapetree", null)); + // Add fixture for /projects/ to handle the POST response + dispatcher.getConfiguredFixtures().add(new DispatcherEntry(List.of("project/projects-container-create-response"), "POST", "/data/projects/", null)); + dispatcher.getConfiguredFixtures().add(new DispatcherEntry(List.of("http/201"), "POST", "/data/projects/.shapetree", null)); + let parentContainer: URL = toUrl(server, "/data/"); + let focusNodes: Array = Arrays.asList(toUrl(server, "/data/projects/#collection")); + let targetShapeTrees: Array = Arrays.asList(toUrl(server, "/static/shapetrees/project/shapetree#DataCollectionTree"), toUrl(server, "/static/shapetrees/information/shapetree#InformationSetTree")); + // Create the projects container as a managed instance. + // 1. Will be validated by the parent DataRepositoryTree and the InformationSetTree both planted on /data (multiple contains) + // 2. Will have a manager/assignment created for it as an instance of DataCollectionTree and InformationSetTree + let response: DocumentResponse = shapeTreeClient.postManagedInstance(context, parentContainer, focusNodes, targetShapeTrees, "projects", true, getProjectsBodyGraph(), TEXT_TURTLE); + Assertions.assertEquals(201, response.getStatusCode()); + // Another attempt without any target shape trees + response = shapeTreeClient.postManagedInstance(context, parentContainer, focusNodes, null, "projects", true, getProjectsBodyGraph(), TEXT_TURTLE); + Assertions.assertEquals(201, response.getStatusCode()); + // Another attempt without any target focus nodes + response = shapeTreeClient.postManagedInstance(context, parentContainer, null, targetShapeTrees, "projects", true, getProjectsBodyGraph(), TEXT_TURTLE); + Assertions.assertEquals(201, response.getStatusCode()); + // Another attempt without any only one of two target shape trees + response = shapeTreeClient.postManagedInstance(context, parentContainer, null, targetShapeTrees, "projects", true, getProjectsBodyGraph(), TEXT_TURTLE); + Assertions.assertEquals(201, response.getStatusCode()); + } + + // @SneakyThrows, @Test, @DisplayName("Create Projects Container and Validate DataCollectionTree") + createAndValidateProjects(): void { + let server: MockWebServer = new MockWebServer(); + server.setDispatcher(dispatcher); + // Setup initial fixtures for /data/ + dispatcher.getConfiguredFixtures().add(new DispatcherEntry(List.of("project/data-container"), "GET", "/data/", null)); + dispatcher.getConfiguredFixtures().add(new DispatcherEntry(List.of("project/data-container-manager"), "GET", "/data/.shapetree", null)); + // Add fixture for /projects/ to handle the POST response + dispatcher.getConfiguredFixtures().add(new DispatcherEntry(List.of("project/projects-container-create-response"), "POST", "/data/projects/", null)); + dispatcher.getConfiguredFixtures().add(new DispatcherEntry(List.of("http/201"), "POST", "/data/projects/.shapetree", null)); + let parentContainer: URL = toUrl(server, "/data/"); + let focusNodes: Array = Arrays.asList(toUrl(server, "/data/projects/#collection")); + let targetShapeTrees: Array = Arrays.asList(toUrl(server, "/static/shapetrees/project/shapetree#DataCollectionTree")); + // Create the projects container as a shape tree instance. + // 1. Will be validated by the parent DataRepositoryTree planted on /data + // 2. Will have a manager/assignment created for it as an instance of DataCollectionTree + let response: DocumentResponse = shapeTreeClient.postManagedInstance(context, parentContainer, focusNodes, targetShapeTrees, "projects", true, getProjectsBodyGraph(), TEXT_TURTLE); + Assertions.assertEquals(201, response.getStatusCode()); + } + + // @SneakyThrows, @Test, @DisplayName("Plant ProjectCollectionTree on Projects Container") + plantSecondShapeTreeOnProjects(): void { + let server: MockWebServer = new MockWebServer(); + server.setDispatcher(dispatcher); + // Add fixtures for /data/ + dispatcher.getConfiguredFixtures().add(new DispatcherEntry(List.of("project/data-container"), "GET", "/data/", null)); + dispatcher.getConfiguredFixtures().add(new DispatcherEntry(List.of("project/data-container-manager"), "GET", "/data/.shapetree", null)); + // Add fixtures for /projects/ + dispatcher.getConfiguredFixtures().add(new DispatcherEntry(List.of("project/projects-container-no-contains"), "GET", "/data/projects/", null)); + dispatcher.getConfiguredFixtures().add(new DispatcherEntry(List.of("project/projects-container-manager"), "GET", "/data/projects/.shapetree", null)); + let targetResource: URL = toUrl(server, "/data/projects/"); + let targetShapeTree: URL = toUrl(server, "/static/shapetrees/project/shapetree#ProjectCollectionTree"); + // Plant the second shape tree (ProjectCollectionTree) on /data/projects/ + let response: DocumentResponse = this.shapeTreeClient.plantShapeTree(this.context, targetResource, targetShapeTree, null); + Assertions.assertEquals(201, response.getStatusCode()); + } + + // @SneakyThrows, @Test, @DisplayName("Create Project in the Projects Collection") + createProjectInProjects(): void { + let server: MockWebServer = new MockWebServer(); + server.setDispatcher(dispatcher); + // Add fixtures for /data/ + dispatcher.getConfiguredFixtures().add(new DispatcherEntry(List.of("project/data-container"), "GET", "/data/", null)); + dispatcher.getConfiguredFixtures().add(new DispatcherEntry(List.of("project/data-container-manager"), "GET", "/data/.shapetree", null)); + // Add fixtures for /projects/ + dispatcher.getConfiguredFixtures().add(new DispatcherEntry(List.of("project/projects-container-no-contains"), "GET", "/data/projects/", null)); + dispatcher.getConfiguredFixtures().add(new DispatcherEntry(List.of("project/projects-container-manager-two-assignments"), "GET", "/data/projects/.shapetree", null)); + // Add fixture for /projects/project-1/ to handle the POST response + dispatcher.getConfiguredFixtures().add(new DispatcherEntry(List.of("project/project-1-create-response"), "POST", "/data/projects/project-1/", null)); + dispatcher.getConfiguredFixtures().add(new DispatcherEntry(List.of("http/201"), "POST", "/data/projects/project-1/.shapetree", null)); + let parentContainer: URL = toUrl(server, "/data/projects/"); + let focusNodes: Array = Arrays.asList(toUrl(server, "/data/projects/project-1/#project")); + let targetShapeTrees: Array = Arrays.asList(toUrl(server, "/static/shapetrees/project/shapetree#ProjectTree")); + // Create the project-1 container as a shape tree instance. + // 1. Will be validated by the parent ProjectCollectionTree planted on /data/projects/ + // 2. Will have a manager/assignment created for it as an instance of ProjectTree + let response: DocumentResponse = shapeTreeClient.postManagedInstance(context, parentContainer, focusNodes, targetShapeTrees, "project-1", true, getProjectOneBodyGraph(), TEXT_TURTLE); + Assertions.assertEquals(201, response.getStatusCode()); + } + + // @SneakyThrows, @Test, @DisplayName("Update Project in the Projects Collection") + updateProjectInProjects(): void { + let server: MockWebServer = new MockWebServer(); + server.setDispatcher(dispatcher); + // Add fixtures for /data/ + dispatcher.getConfiguredFixtures().add(new DispatcherEntry(List.of("project/data-container"), "GET", "/data/", null)); + dispatcher.getConfiguredFixtures().add(new DispatcherEntry(List.of("project/data-container-manager"), "GET", "/data/.shapetree", null)); + // Add fixtures for /projects/ + dispatcher.getConfiguredFixtures().add(new DispatcherEntry(List.of("project/projects-container"), "GET", "/data/projects/", null)); + dispatcher.getConfiguredFixtures().add(new DispatcherEntry(List.of("project/projects-container-manager-two-assignments"), "GET", "/data/projects/.shapetree", null)); + // Add fixture for /projects/project-1/ + dispatcher.getConfiguredFixtures().add(new DispatcherEntry(List.of("project/project-1-container-no-contains"), "GET", "/data/projects/project-1/", null)); + dispatcher.getConfiguredFixtures().add(new DispatcherEntry(List.of("project/project-1-container-manager"), "GET", "/data/projects/project-1/.shapetree", null)); + // Add fixture for updated project-1 + dispatcher.getConfiguredFixtures().add(new DispatcherEntry(List.of("project/project-1-container-no-contains-updated"), "PUT", "/data/projects/project-1/", null)); + let targetResource: URL = toUrl(server, "/data/projects/project-1/"); + let focusNodes: Array = Arrays.asList(toUrl(server, "/data/projects/project-1/#project")); + // Update the project-1 container as a shape tree instance. + // 1. Will be validated by the parent ProjectCollectionTree planted on /data/projects/ + // 2. Will have a manager/assignment created for it as an instance of ProjectTree + let response: DocumentResponse = shapeTreeClient.updateManagedInstance(context, targetResource, focusNodes, getProjectOneUpdatedBodyGraph(), TEXT_TURTLE); + Assertions.assertEquals(200, response.getStatusCode()); + } + + // @SneakyThrows, @Test, @DisplayName("Fail to Create a Malformed Project in the Projects Collection") + failToCreateMalformedProject(): void { + let server: MockWebServer = new MockWebServer(); + server.setDispatcher(dispatcher); + // Add fixtures for /data/ + dispatcher.getConfiguredFixtures().add(new DispatcherEntry(List.of("project/data-container"), "GET", "/data/", null)); + dispatcher.getConfiguredFixtures().add(new DispatcherEntry(List.of("project/data-container-manager"), "GET", "/data/.shapetree", null)); + // Add fixtures for /projects/ + dispatcher.getConfiguredFixtures().add(new DispatcherEntry(List.of("project/projects-container"), "GET", "/data/projects/", null)); + dispatcher.getConfiguredFixtures().add(new DispatcherEntry(List.of("project/projects-container-manager-two-assignments"), "GET", "/data/projects/.shapetree", null)); + let targetResource: URL = toUrl(server, "/data/projects/project-1/"); + let focusNodes: Array = Arrays.asList(toUrl(server, "/data/projects/project-1/#project")); + let targetShapeTrees: Array = Arrays.asList(toUrl(server, "/static/shapetrees/project/shapetree#ProjectTree")); + // Create the project-1 container as a shape tree instance via PUT + // 1. Will be validated by the parent ProjectCollectionTree planted on /data/projects/ + let response: DocumentResponse = shapeTreeClient.putManagedInstance(context, targetResource, focusNodes, getProjectOneMalformedBodyGraph(), TEXT_TURTLE, targetShapeTrees, true); + // 2. Will fail validation because the body content doesn't validate against the assigned shape + Assertions.assertEquals(422, response.getStatusCode()); + } + + // @SneakyThrows, @Test, @DisplayName("Fail to Update a Project to be Malformed in the Projects Collection") + failToUpdateMalformedProject(): void { + let server: MockWebServer = new MockWebServer(); + server.setDispatcher(dispatcher); + // try to update an existing project-1 to be malformed and fail validation + // Add fixtures for /data/ + dispatcher.getConfiguredFixtures().add(new DispatcherEntry(List.of("project/data-container"), "GET", "/data/", null)); + dispatcher.getConfiguredFixtures().add(new DispatcherEntry(List.of("project/data-container-manager"), "GET", "/data/.shapetree", null)); + // Add fixtures for /projects/ + dispatcher.getConfiguredFixtures().add(new DispatcherEntry(List.of("project/projects-container"), "GET", "/data/projects/", null)); + dispatcher.getConfiguredFixtures().add(new DispatcherEntry(List.of("project/projects-container-manager-two-assignments"), "GET", "/data/projects/.shapetree", null)); + // Add fixture for /projects/project-1/ + dispatcher.getConfiguredFixtures().add(new DispatcherEntry(List.of("project/project-1-container-no-contains"), "GET", "/data/projects/project-1/", null)); + dispatcher.getConfiguredFixtures().add(new DispatcherEntry(List.of("project/project-1-container-manager"), "GET", "/data/projects/project-1/.shapetree", null)); + let targetResource: URL = toUrl(server, "/data/projects/project-1/"); + let focusNodes: Array = Arrays.asList(toUrl(server, "/data/projects/project-1/#project")); + // Update the project-1 container as a shape tree instance via PUT + // 1. Will be validated by the parent ProjectCollectionTree planted on /data/projects/ + let response: DocumentResponse = shapeTreeClient.updateManagedInstance(context, targetResource, focusNodes, getProjectOneMalformedBodyGraph(), TEXT_TURTLE); + // 2. Will fail validation because the body content doesn't validate against the assigned shape + Assertions.assertEquals(422, response.getStatusCode()); + } + + // @SneakyThrows, @Test, @DisplayName("Create Milestone in Project With Put") + createMilestoneInProjectWithPut(): void { + let server: MockWebServer = new MockWebServer(); + server.setDispatcher(dispatcher); + // Add fixtures for /data/ + dispatcher.getConfiguredFixtures().add(new DispatcherEntry(List.of("project/data-container"), "GET", "/data/", null)); + dispatcher.getConfiguredFixtures().add(new DispatcherEntry(List.of("project/data-container-manager"), "GET", "/data/.shapetree", null)); + // Add fixtures for /projects/ + dispatcher.getConfiguredFixtures().add(new DispatcherEntry(List.of("project/projects-container"), "GET", "/data/projects/", null)); + dispatcher.getConfiguredFixtures().add(new DispatcherEntry(List.of("project/projects-container-manager-two-assignments"), "GET", "/data/projects/.shapetree", null)); + // Add fixture for /projects/project-1/ + dispatcher.getConfiguredFixtures().add(new DispatcherEntry(List.of("project/project-1-container-no-contains"), "GET", "/data/projects/project-1/", null)); + dispatcher.getConfiguredFixtures().add(new DispatcherEntry(List.of("project/project-1-container-manager"), "GET", "/data/projects/project-1/.shapetree", null)); + // Add fixture for /projects/project-1/milestone-3 to handle response to create via PUT + dispatcher.getConfiguredFixtures().add(new DispatcherEntry(List.of("project/milestone-3-container-no-contains"), "PUT", "/data/projects/project-1/milestone-3/", null)); + dispatcher.getConfiguredFixtures().add(new DispatcherEntry(List.of("http/201"), "POST", "/data/projects/project-1/milestone-3/.shapetree", null)); + let targetResource: URL = toUrl(server, "/data/projects/project-1/milestone-3/"); + let focusNodes: Array = Arrays.asList(toUrl(server, "/data/projects/project-1/milestone-3/#milestone")); + let targetShapeTrees: Array = Arrays.asList(toUrl(server, "/static/shapetrees/project/shapetree#MilestoneTree")); + // Create the milestone-3 container in /projects/project-1/ as a shape tree instance using PUT to create + // 1. Will be validated by the parent ProjectTree planted on /data/projects/project-1/ + // 2. Will have a manager/assignment created for it as an instance of MilestoneTree + let response: DocumentResponse = shapeTreeClient.putManagedInstance(context, targetResource, focusNodes, getMilestoneThreeBodyGraph(), TEXT_TURTLE, targetShapeTrees, true); + Assertions.assertEquals(201, response.getStatusCode()); + } + + // @SneakyThrows, @Test, @DisplayName("Update Milestone in Project With Patch") + updateMilestoneInProjectWithPatch(): void { + let server: MockWebServer = new MockWebServer(); + server.setDispatcher(dispatcher); + // Add fixtures for /data/ + dispatcher.getConfiguredFixtures().add(new DispatcherEntry(List.of("project/data-container"), "GET", "/data/", null)); + dispatcher.getConfiguredFixtures().add(new DispatcherEntry(List.of("project/data-container-manager"), "GET", "/data/.shapetree", null)); + // Add fixtures for /projects/ + dispatcher.getConfiguredFixtures().add(new DispatcherEntry(List.of("project/projects-container"), "GET", "/data/projects/", null)); + dispatcher.getConfiguredFixtures().add(new DispatcherEntry(List.of("project/projects-container-manager-two-assignments"), "GET", "/data/projects/.shapetree", null)); + // Add fixture for /projects/project-1/ + dispatcher.getConfiguredFixtures().add(new DispatcherEntry(List.of("project/project-1-container-no-contains"), "GET", "/data/projects/project-1/", null)); + dispatcher.getConfiguredFixtures().add(new DispatcherEntry(List.of("project/project-1-container-manager"), "GET", "/data/projects/project-1/.shapetree", null)); + // Add fixture for /projects/project-1/milestone-3/ + dispatcher.getConfiguredFixtures().add(new DispatcherEntry(List.of("project/milestone-3-container-no-contains"), "GET", "/data/projects/project-1/milestone-3/", null)); + dispatcher.getConfiguredFixtures().add(new DispatcherEntry(List.of("project/milestone-3-container-manager"), "GET", "/data/projects/project-1/milestone-3/.shapetree", null)); + // Add fixture for /projects/project-1/milestone-3 to handle response to update via PATCH + dispatcher.getConfiguredFixtures().add(new DispatcherEntry(List.of("project/milestone-3-container-no-contains-updated"), "PATCH", "/data/projects/project-1/milestone-3/", null)); + let targetResource: URL = toUrl(server, "/data/projects/project-1/milestone-3/"); + let focusNodes: Array = Arrays.asList(toUrl(server, "/data/projects/project-1/milestone-3/#milestone")); + // Update the milestone-3 container in /projects/project-1/ using PATCH + // 1. Will be validated by the MilestoneTree planted on /data/projects/project-1/milestone-3/ + let response: DocumentResponse = shapeTreeClient.patchManagedInstance(context, targetResource, focusNodes, getMilestoneThreeSparqlPatch()); + Assertions.assertEquals(201, response.getStatusCode()); + } + + // @SneakyThrows, @Test, @DisplayName("Create First Task in Project With Patch") + createFirstTaskInProjectWithPatch(): void { + let server: MockWebServer = new MockWebServer(); + server.setDispatcher(dispatcher); + // Add fixtures for /data/ + dispatcher.getConfiguredFixtures().add(new DispatcherEntry(List.of("project/data-container"), "GET", "/data/", null)); + dispatcher.getConfiguredFixtures().add(new DispatcherEntry(List.of("project/data-container-manager"), "GET", "/data/.shapetree", null)); + // Add fixtures for /projects/ + dispatcher.getConfiguredFixtures().add(new DispatcherEntry(List.of("project/projects-container"), "GET", "/data/projects/", null)); + dispatcher.getConfiguredFixtures().add(new DispatcherEntry(List.of("project/projects-container-manager-two-assignments"), "GET", "/data/projects/.shapetree", null)); + // Add fixture for /projects/project-1/ + dispatcher.getConfiguredFixtures().add(new DispatcherEntry(List.of("project/project-1-container-no-contains"), "GET", "/data/projects/project-1/", null)); + dispatcher.getConfiguredFixtures().add(new DispatcherEntry(List.of("project/project-1-container-manager"), "GET", "/data/projects/project-1/.shapetree", null)); + // Add fixture for /projects/project-1/milestone-3/ + dispatcher.getConfiguredFixtures().add(new DispatcherEntry(List.of("project/milestone-3-container-no-contains"), "GET", "/data/projects/project-1/milestone-3/", null)); + dispatcher.getConfiguredFixtures().add(new DispatcherEntry(List.of("project/milestone-3-container-manager"), "GET", "/data/projects/project-1/milestone-3/.shapetree", null)); + // Add fixture for /projects/project-1/milestone-3/task-6/ to handle response to update via PATCH + dispatcher.getConfiguredFixtures().add(new DispatcherEntry(List.of("project/task-6-container-no-contains-updated"), "PATCH", "/data/projects/project-1/milestone-3/task-6/", null)); + dispatcher.getConfiguredFixtures().add(new DispatcherEntry(List.of("http/201"), "POST", "/data/projects/project-1/milestone-3/task-6/.shapetree", null)); + let targetResource: URL = toUrl(server, "/data/projects/project-1/milestone-3/task-6/"); + let focusNodes: Array = Arrays.asList(toUrl(server, "/data/projects/project-1/milestone-3/task-6/#task")); + // Create the task-6 container in /projects/project-1/milestone-3/ using PATCH + // 1. Will be validated by the parent MilestoneTree planted on /data/projects/project-1/milestone-3/ + let response: DocumentResponse = shapeTreeClient.patchManagedInstance(context, targetResource, focusNodes, getTaskSixSparqlPatch()); + Assertions.assertEquals(201, response.getStatusCode()); + } + + // @SneakyThrows, @Test, @DisplayName("Create Second Task in Project Without Focus Node") + createSecondTaskInProjectWithoutFocusNode(): void { + let server: MockWebServer = new MockWebServer(); + server.setDispatcher(dispatcher); + // Add fixtures for /data/ + dispatcher.getConfiguredFixtures().add(new DispatcherEntry(List.of("project/data-container"), "GET", "/data/", null)); + dispatcher.getConfiguredFixtures().add(new DispatcherEntry(List.of("project/data-container-manager"), "GET", "/data/.shapetree", null)); + // Add fixtures for /projects/ + dispatcher.getConfiguredFixtures().add(new DispatcherEntry(List.of("project/projects-container"), "GET", "/data/projects/", null)); + dispatcher.getConfiguredFixtures().add(new DispatcherEntry(List.of("project/projects-container-manager-two-assignments"), "GET", "/data/projects/.shapetree", null)); + // Add fixture for /projects/project-1/ + dispatcher.getConfiguredFixtures().add(new DispatcherEntry(List.of("project/project-1-container-no-contains"), "GET", "/data/projects/project-1/", null)); + dispatcher.getConfiguredFixtures().add(new DispatcherEntry(List.of("project/project-1-container-manager"), "GET", "/data/projects/project-1/.shapetree", null)); + // Add fixture for /projects/project-1/milestone-3/ + dispatcher.getConfiguredFixtures().add(new DispatcherEntry(List.of("project/milestone-3-container-no-contains"), "GET", "/data/projects/project-1/milestone-3/", null)); + dispatcher.getConfiguredFixtures().add(new DispatcherEntry(List.of("project/milestone-3-container-manager"), "GET", "/data/projects/project-1/milestone-3/.shapetree", null)); + // Add fixture for /projects/project-1/milestone-3/task-6/ to handle response to create via POST + dispatcher.getConfiguredFixtures().add(new DispatcherEntry(List.of("project/task-48-create-response"), "POST", "/data/projects/project-1/milestone-3/task-48/", null)); + dispatcher.getConfiguredFixtures().add(new DispatcherEntry(List.of("http/201"), "POST", "/data/projects/project-1/milestone-3/task-48/.shapetree", null)); + let targetContainer: URL = toUrl(server, "/data/projects/project-1/milestone-3/"); + let targetShapeTrees: Array = Arrays.asList(toUrl(server, "/static/shapetrees/project/shapetree#TaskTree")); + // create task-48 in milestone-3 - supply a target shape tree, but not a focus node + let response: DocumentResponse = shapeTreeClient.postManagedInstance(context, targetContainer, null, targetShapeTrees, "task-48", true, getTaskFortyEightBodyGraph(), TEXT_TURTLE); + Assertions.assertEquals(201, response.getStatusCode()); + } + + // @SneakyThrows, @Test, @DisplayName("Create Third Task in Project Without Target Shape Tree or Focus Node") + createThirdTaskInProjectWithoutAnyContext(): void { + let server: MockWebServer = new MockWebServer(); + server.setDispatcher(dispatcher); + // Add fixtures for /data/ + dispatcher.getConfiguredFixtures().add(new DispatcherEntry(List.of("project/data-container"), "GET", "/data/", null)); + dispatcher.getConfiguredFixtures().add(new DispatcherEntry(List.of("project/data-container-manager"), "GET", "/data/.shapetree", null)); + // Add fixtures for /projects/ + dispatcher.getConfiguredFixtures().add(new DispatcherEntry(List.of("project/projects-container"), "GET", "/data/projects/", null)); + dispatcher.getConfiguredFixtures().add(new DispatcherEntry(List.of("project/projects-container-manager-two-assignments"), "GET", "/data/projects/.shapetree", null)); + // Add fixture for /projects/project-1/ + dispatcher.getConfiguredFixtures().add(new DispatcherEntry(List.of("project/project-1-container-no-contains"), "GET", "/data/projects/project-1/", null)); + dispatcher.getConfiguredFixtures().add(new DispatcherEntry(List.of("project/project-1-container-manager"), "GET", "/data/projects/project-1/.shapetree", null)); + // Add fixture for /projects/project-1/milestone-3/ + dispatcher.getConfiguredFixtures().add(new DispatcherEntry(List.of("project/milestone-3-container-no-contains"), "GET", "/data/projects/project-1/milestone-3/", null)); + dispatcher.getConfiguredFixtures().add(new DispatcherEntry(List.of("project/milestone-3-container-manager"), "GET", "/data/projects/project-1/milestone-3/.shapetree", null)); + // Add fixture for /projects/project-1/milestone-3/task-6/ to handle response to create via POST + dispatcher.getConfiguredFixtures().add(new DispatcherEntry(List.of("project/task-48-create-response"), "POST", "/data/projects/project-1/milestone-3/task-48/", null)); + dispatcher.getConfiguredFixtures().add(new DispatcherEntry(List.of("http/201"), "POST", "/data/projects/project-1/milestone-3/task-48/.shapetree", null)); + let targetContainer: URL = toUrl(server, "/data/projects/project-1/milestone-3/"); + // create task-48 in milestone-3 - don't supply a target shape tree or focus node + let response: DocumentResponse = shapeTreeClient.postManagedInstance(context, targetContainer, null, null, "task-48", true, getTaskFortyEightBodyGraph(), TEXT_TURTLE); + Assertions.assertEquals(201, response.getStatusCode()); + } + + // @SneakyThrows, @Test, @DisplayName("Create Second Task in Project With Focus Node Without Target Shape Tree") + createSecondTaskInProjectWithFocusNodeWithoutTargetShapeTree(): void { + let server: MockWebServer = new MockWebServer(); + server.setDispatcher(dispatcher); + // Add fixtures for /data/ + dispatcher.getConfiguredFixtures().add(new DispatcherEntry(List.of("project/data-container"), "GET", "/data/", null)); + dispatcher.getConfiguredFixtures().add(new DispatcherEntry(List.of("project/data-container-manager"), "GET", "/data/.shapetree", null)); + // Add fixtures for /projects/ + dispatcher.getConfiguredFixtures().add(new DispatcherEntry(List.of("project/projects-container"), "GET", "/data/projects/", null)); + dispatcher.getConfiguredFixtures().add(new DispatcherEntry(List.of("project/projects-container-manager-two-assignments"), "GET", "/data/projects/.shapetree", null)); + // Add fixture for /projects/project-1/ + dispatcher.getConfiguredFixtures().add(new DispatcherEntry(List.of("project/project-1-container-no-contains"), "GET", "/data/projects/project-1/", null)); + dispatcher.getConfiguredFixtures().add(new DispatcherEntry(List.of("project/project-1-container-manager"), "GET", "/data/projects/project-1/.shapetree", null)); + // Add fixture for /projects/project-1/milestone-3/ + dispatcher.getConfiguredFixtures().add(new DispatcherEntry(List.of("project/milestone-3-container-no-contains"), "GET", "/data/projects/project-1/milestone-3/", null)); + dispatcher.getConfiguredFixtures().add(new DispatcherEntry(List.of("project/milestone-3-container-manager"), "GET", "/data/projects/project-1/milestone-3/.shapetree", null)); + // Add fixture for /projects/project-1/milestone-3/task-6/ to handle response to create via POST + dispatcher.getConfiguredFixtures().add(new DispatcherEntry(List.of("project/task-48-create-response"), "POST", "/data/projects/project-1/milestone-3/task-48/", null)); + dispatcher.getConfiguredFixtures().add(new DispatcherEntry(List.of("http/201"), "POST", "/data/projects/project-1/milestone-3/task-48/.shapetree", null)); + let targetContainer: URL = toUrl(server, "/data/projects/project-1/milestone-3/"); + let focusNodes: Array = Arrays.asList(toUrl(server, "/data/projects/project-1/milestone-3/task-48/#task")); + // create task-48 in milestone-3 - supply a focus node but no target shape tree + let response: DocumentResponse = shapeTreeClient.postManagedInstance(context, targetContainer, focusNodes, null, "task-48", true, getTaskFortyEightBodyGraph(), TEXT_TURTLE); + Assertions.assertEquals(201, response.getStatusCode()); + } + + // @SneakyThrows, @Test, @DisplayName("Create Attachment in Task") + createAttachmentInTask(): void { + let server: MockWebServer = new MockWebServer(); + server.setDispatcher(dispatcher); + // create an attachment in task-48 (success) + // Add fixtures for /data/ + dispatcher.getConfiguredFixtures().add(new DispatcherEntry(List.of("project/data-container"), "GET", "/data/", null)); + dispatcher.getConfiguredFixtures().add(new DispatcherEntry(List.of("project/data-container-manager"), "GET", "/data/.shapetree", null)); + // Add fixtures for /projects/ + dispatcher.getConfiguredFixtures().add(new DispatcherEntry(List.of("project/projects-container"), "GET", "/data/projects/", null)); + dispatcher.getConfiguredFixtures().add(new DispatcherEntry(List.of("project/projects-container-manager-two-assignments"), "GET", "/data/projects/.shapetree", null)); + // Add fixture for /projects/project-1/ + dispatcher.getConfiguredFixtures().add(new DispatcherEntry(List.of("project/project-1-container-no-contains"), "GET", "/data/projects/project-1/", null)); + dispatcher.getConfiguredFixtures().add(new DispatcherEntry(List.of("project/project-1-container-manager"), "GET", "/data/projects/project-1/.shapetree", null)); + // Add fixture for /projects/project-1/milestone-3/ + dispatcher.getConfiguredFixtures().add(new DispatcherEntry(List.of("project/milestone-3-container-no-contains"), "GET", "/data/projects/project-1/milestone-3/", null)); + dispatcher.getConfiguredFixtures().add(new DispatcherEntry(List.of("project/milestone-3-container-manager"), "GET", "/data/projects/project-1/milestone-3/.shapetree", null)); + // Add fixture for /projects/project-1/milestone-3/task-6/ to handle response to create via POST + dispatcher.getConfiguredFixtures().add(new DispatcherEntry(List.of("project/task-48-container-no-contains"), "GET", "/data/projects/project-1/milestone-3/task-48/", null)); + dispatcher.getConfiguredFixtures().add(new DispatcherEntry(List.of("project/task-48-container-manager"), "GET", "/data/projects/project-1/milestone-3/task-48/.shapetree", null)); + // Add fixture to handle PUT response and follow-up request + dispatcher.getConfiguredFixtures().add(new DispatcherEntry(List.of("project/attachment-48"), "PUT", "/data/projects/project-1/milestone-3/task-48/attachment-48", null)); + dispatcher.getConfiguredFixtures().add(new DispatcherEntry(List.of("http/201"), "POST", "/data/projects/project-1/milestone-3/task-48/attachment-48.shapetree", null)); + let targetResource: URL = toUrl(server, "/data/projects/project-1/milestone-3/task-48/attachment-48"); + let targetShapeTrees: Array = Arrays.asList(toUrl(server, "/static/shapetrees/project/shapetree#AttachmentTree")); + let response: DocumentResponse = shapeTreeClient.putManagedInstance(context, targetResource, null, null, "application/octet-stream", targetShapeTrees, false); + Assertions.assertEquals(201, response.getStatusCode()); + } + + // @SneakyThrows, @Test, @DisplayName("Create Second Attachment in Task") + createSecondAttachmentInTask(): void { + let server: MockWebServer = new MockWebServer(); + server.setDispatcher(dispatcher); + // create an attachment in task-48 (success) + // Add fixtures for /data/ + dispatcher.getConfiguredFixtures().add(new DispatcherEntry(List.of("project/data-container"), "GET", "/data/", null)); + dispatcher.getConfiguredFixtures().add(new DispatcherEntry(List.of("project/data-container-manager"), "GET", "/data/.shapetree", null)); + // Add fixtures for /projects/ + dispatcher.getConfiguredFixtures().add(new DispatcherEntry(List.of("project/projects-container"), "GET", "/data/projects/", null)); + dispatcher.getConfiguredFixtures().add(new DispatcherEntry(List.of("project/projects-container-manager-two-assignments"), "GET", "/data/projects/.shapetree", null)); + // Add fixture for /projects/project-1/ + dispatcher.getConfiguredFixtures().add(new DispatcherEntry(List.of("project/project-1-container-no-contains"), "GET", "/data/projects/project-1/", null)); + dispatcher.getConfiguredFixtures().add(new DispatcherEntry(List.of("project/project-1-container-manager"), "GET", "/data/projects/project-1/.shapetree", null)); + // Add fixture for /projects/project-1/milestone-3/ + dispatcher.getConfiguredFixtures().add(new DispatcherEntry(List.of("project/milestone-3-container-no-contains"), "GET", "/data/projects/project-1/milestone-3/", null)); + dispatcher.getConfiguredFixtures().add(new DispatcherEntry(List.of("project/milestone-3-container-manager"), "GET", "/data/projects/project-1/milestone-3/.shapetree", null)); + // Add fixture for /projects/project-1/milestone-3/task-6/ to handle response to create via POST + dispatcher.getConfiguredFixtures().add(new DispatcherEntry(List.of("project/task-48-container-no-contains"), "GET", "/data/projects/project-1/milestone-3/task-48/", null)); + dispatcher.getConfiguredFixtures().add(new DispatcherEntry(List.of("project/task-48-container-manager"), "GET", "/data/projects/project-1/milestone-3/task-48/.shapetree", null)); + // Add fixture to handle PUT response and follow-up request + dispatcher.getConfiguredFixtures().add(new DispatcherEntry(List.of("project/random-png"), "PUT", "/data/projects/project-1/milestone-3/task-48/random.png", null)); + dispatcher.getConfiguredFixtures().add(new DispatcherEntry(List.of("http/201"), "POST", "/data/projects/project-1/milestone-3/task-48/random.png.shapetree", null)); + let targetResource: URL = toUrl(server, "/data/projects/project-1/milestone-3/task-48/random.png"); + let targetShapeTrees: Array = Arrays.asList(toUrl(server, "/static/shapetrees/project/shapetree#AttachmentTree")); + let response: DocumentResponse = shapeTreeClient.putManagedInstance(context, targetResource, null, null, "application/octet-stream", targetShapeTrees, false); + Assertions.assertEquals(201, response.getStatusCode()); + } + + // @SneakyThrows, @Test, @DisplayName("Fail to Unplant Non-Root Task") + failToUnplantNonRootTask(): void { + let server: MockWebServer = new MockWebServer(); + server.setDispatcher(dispatcher); + // Add fixture for /data/projects/project-1/milestone-3/, which is not the root of the project hierarchy according to its manager + dispatcher.getConfiguredFixtures().add(new DispatcherEntry(List.of("project/milestone-3-container"), "GET", "/data/projects/project-1/milestone-3/", null)); + dispatcher.getConfiguredFixtures().add(new DispatcherEntry(List.of("project/milestone-3-container-manager"), "GET", "/data/projects/project-1/milestone-3/.shapetree", null)); + let targetResource: URL = toUrl(server, "/data/projects/project-1/milestone-3/"); + let targetShapeTreeOne: URL = toUrl(server, "/static/shapetrees/project/shapetree#MilestoneTree"); + let targetShapeTreeTwo: URL = toUrl(server, "/static/shapetrees/project/shapetree#ProjectsTree"); + // Try first by providing the Milestone Shape Tree as the unplant target + let responseOne: DocumentResponse = shapeTreeClient.unplantShapeTree(context, targetResource, targetShapeTreeOne); + Assertions.assertEquals(500, responseOne.getStatusCode()); + // Try again by providing the (incorrect) Project Shape Tree as the unplant target (which is the shape tree at the root of the hierarchy) - this will be caught by the client immediately + Assertions.assertThrows(IllegalStateException.class, () -> { + let responseTwo: DocumentResponse = shapeTreeClient.unplantShapeTree(context, targetResource, targetShapeTreeTwo); + }); + } + + // @SneakyThrows, @Test, @DisplayName("Unplant Projects") + unplantProjects(): void { + let server: MockWebServer = new MockWebServer(); + server.setDispatcher(dispatcher); + // Unplant the project collection, recursing down the tree (success) + dispatcher.getConfiguredFixtures().add(new DispatcherEntry(List.of("project/data-container"), "GET", "/data/", null)); + dispatcher.getConfiguredFixtures().add(new DispatcherEntry(List.of("project/data-container-manager"), "GET", "/data/.shapetree", null)); + // Add fixtures for /projects/ + dispatcher.getConfiguredFixtures().add(new DispatcherEntry(List.of("project/projects-container"), "GET", "/data/projects/", null)); + dispatcher.getConfiguredFixtures().add(new DispatcherEntry(List.of("project/projects-container-manager-two-assignments"), "GET", "/data/projects/.shapetree", null)); + dispatcher.getConfiguredFixtures().add(new DispatcherEntry(List.of("project/204"), "DELETE", "/data/projects/.shapetree", null)); + // Add fixture for /projects/project-1/ + dispatcher.getConfiguredFixtures().add(new DispatcherEntry(List.of("project/project-1-container"), "GET", "/data/projects/project-1/", null)); + dispatcher.getConfiguredFixtures().add(new DispatcherEntry(List.of("project/project-1-container-manager"), "GET", "/data/projects/project-1/.shapetree", null)); + dispatcher.getConfiguredFixtures().add(new DispatcherEntry(List.of("project/204"), "DELETE", "/data/projects/project-1/.shapetree", null)); + // Add fixture for /projects/project-1/milestone-3/ + dispatcher.getConfiguredFixtures().add(new DispatcherEntry(List.of("project/milestone-3-container"), "GET", "/data/projects/project-1/milestone-3/", null)); + dispatcher.getConfiguredFixtures().add(new DispatcherEntry(List.of("project/milestone-3-container-manager"), "GET", "/data/projects/project-1/milestone-3/.shapetree", null)); + dispatcher.getConfiguredFixtures().add(new DispatcherEntry(List.of("project/204"), "DELETE", "/data/projects/project-1/milestone-3/.shapetree", null)); + // Add fixtures for tasks in /projects/project-1/milestone-3/ + dispatcher.getConfiguredFixtures().add(new DispatcherEntry(List.of("project/task-6-container-no-contains"), "GET", "/data/projects/project-1/milestone-3/task-6/", null)); + dispatcher.getConfiguredFixtures().add(new DispatcherEntry(List.of("project/task-6-container-manager"), "GET", "/data/projects/project-1/milestone-3/task-6/.shapetree", null)); + dispatcher.getConfiguredFixtures().add(new DispatcherEntry(List.of("project/204"), "DELETE", "/data/projects/project-1/milestone-3/task-6/.shapetree", null)); + dispatcher.getConfiguredFixtures().add(new DispatcherEntry(List.of("project/task-48-container"), "GET", "/data/projects/project-1/milestone-3/task-48/", null)); + dispatcher.getConfiguredFixtures().add(new DispatcherEntry(List.of("project/task-48-container-manager"), "GET", "/data/projects/project-1/milestone-3/task-48/.shapetree", null)); + dispatcher.getConfiguredFixtures().add(new DispatcherEntry(List.of("project/204"), "DELETE", "/data/projects/project-1/milestone-3/task-48/.shapetree", null)); + // Add fixtures for attachments in task-48 + dispatcher.getConfiguredFixtures().add(new DispatcherEntry(List.of("project/random-png"), "GET", "/data/projects/project-1/milestone-3/task-48/random.png", null)); + dispatcher.getConfiguredFixtures().add(new DispatcherEntry(List.of("project/random-png-manager"), "GET", "/data/projects/project-1/milestone-3/task-48/random.png.shapetree", null)); + dispatcher.getConfiguredFixtures().add(new DispatcherEntry(List.of("project/204"), "DELETE", "/data/projects/project-1/milestone-3/task-48/random.png.shapetree", null)); + dispatcher.getConfiguredFixtures().add(new DispatcherEntry(List.of("project/attachment-48"), "GET", "/data/projects/project-1/milestone-3/task-48/attachment-48", null)); + dispatcher.getConfiguredFixtures().add(new DispatcherEntry(List.of("project/attachment-48-manager"), "GET", "/data/projects/project-1/milestone-3/task-48/attachment-48.shapetree", null)); + dispatcher.getConfiguredFixtures().add(new DispatcherEntry(List.of("project/204"), "DELETE", "/data/projects/project-1/milestone-3/task-48/attachment-48.shapetree", null)); + // Add fixtures for issues in /projects/project-1/milestone-3/ + dispatcher.getConfiguredFixtures().add(new DispatcherEntry(List.of("project/issue-2"), "GET", "/data/projects/project-1/milestone-3/issue-2", null)); + dispatcher.getConfiguredFixtures().add(new DispatcherEntry(List.of("project/issue-2-manager"), "GET", "/data/projects/project-1/milestone-3/issue-2.shapetree", null)); + dispatcher.getConfiguredFixtures().add(new DispatcherEntry(List.of("project/204"), "DELETE", "/data/projects/project-1/milestone-3/issue-2.shapetree", null)); + dispatcher.getConfiguredFixtures().add(new DispatcherEntry(List.of("project/issue-3"), "GET", "/data/projects/project-1/milestone-3/issue-3", null)); + dispatcher.getConfiguredFixtures().add(new DispatcherEntry(List.of("project/issue-3-manager"), "GET", "/data/projects/project-1/milestone-3/issue-3.shapetree", null)); + dispatcher.getConfiguredFixtures().add(new DispatcherEntry(List.of("project/204"), "DELETE", "/data/projects/project-1/milestone-3/issue-3.shapetree", null)); + let targetResource: URL = toUrl(server, "/data/projects/"); + let targetShapeTree: URL = toUrl(server, "/static/shapetrees/project/shapetree#ProjectCollectionTree"); + let response: DocumentResponse = shapeTreeClient.unplantShapeTree(context, targetResource, targetShapeTree); + Assertions.assertEquals(201, response.getStatusCode()); + } + + // @SneakyThrows, @Test, @DisplayName("Unplant Data Set") + unplantData(): void { + let server: MockWebServer = new MockWebServer(); + server.setDispatcher(dispatcher); + // Unplant the data collection, recursing down the tree (success). The root level (pre-loaded) and one level below projects included for completeness + dispatcher.getConfiguredFixtures().add(new DispatcherEntry(List.of("project/data-container"), "GET", "/data/", null)); + dispatcher.getConfiguredFixtures().add(new DispatcherEntry(List.of("project/data-container-manager"), "GET", "/data/.shapetree", null)); + dispatcher.getConfiguredFixtures().add(new DispatcherEntry(List.of("project/204"), "DELETE", "/data/.shapetree", null)); + // Add fixtures for /projects/ + dispatcher.getConfiguredFixtures().add(new DispatcherEntry(List.of("project/projects-container"), "GET", "/data/projects/", null)); + dispatcher.getConfiguredFixtures().add(new DispatcherEntry(List.of("project/projects-container-manager-two-assignments"), "GET", "/data/projects/.shapetree", null)); + dispatcher.getConfiguredFixtures().add(new DispatcherEntry(List.of("project/204"), "PUT", "/data/projects/.shapetree", null)); + // Add fixture for /projects/project-1/ + dispatcher.getConfiguredFixtures().add(new DispatcherEntry(List.of("project/project-1-container"), "GET", "/data/projects/project-1/", null)); + dispatcher.getConfiguredFixtures().add(new DispatcherEntry(List.of("project/project-1-container-manager"), "GET", "/data/projects/project-1/.shapetree", null)); + let targetResource: URL = toUrl(server, "/data/"); + let targetShapeTree: URL = toUrl(server, "/static/shapetrees/project/shapetree#DataRepositoryTree"); + // Unplant the data collection, recursing down the tree (only two levels) + // Since the projects collection still manages /data/projects/, it should not delete the manager, only update it + let response: DocumentResponse = shapeTreeClient.unplantShapeTree(context, targetResource, targetShapeTree); + Assertions.assertEquals(201, response.getStatusCode()); + } + + // @SneakyThrows, @Test, @DisplayName("Plant Data Repository with Patch") + plantDataRepositoryWithPatch(): void { + let server: MockWebServer = new MockWebServer(); + server.setDispatcher(dispatcher); + // Create the data container + dispatcher.getConfiguredFixtures().add(new DispatcherEntry(List.of("project/data-container-no-contains"), "GET", "/data/", null)); + dispatcher.getConfiguredFixtures().add(new DispatcherEntry(List.of("http/201"), "POST", "/data/.shapetree", null)); + let targetResource: URL = toUrl(server, "/data/.shapetree"); + // Plant the data repository on newly created data container + let response: DocumentResponse = shapeTreeClient.patchManagedInstance(context, targetResource, null, getPlantDataRepositorySparqlPatch(server)); + Assertions.assertEquals(201, response.getStatusCode()); + } + + // @SneakyThrows, @Test, @DisplayName("Update Project Collection manager with Patch") + updateProjectsManagerWithPatch(): void { + let server: MockWebServer = new MockWebServer(); + server.setDispatcher(dispatcher); + // Add fixtures for data repository container and manager + dispatcher.getConfiguredFixtures().add(new DispatcherEntry(List.of("project/data-container"), "GET", "/data/", null)); + dispatcher.getConfiguredFixtures().add(new DispatcherEntry(List.of("project/data-container-manager"), "GET", "/data/.shapetree", null)); + // Add fixtures for /projects/ + dispatcher.getConfiguredFixtures().add(new DispatcherEntry(List.of("project/projects-container-no-contains"), "GET", "/data/projects/", null)); + dispatcher.getConfiguredFixtures().add(new DispatcherEntry(List.of("project/projects-container-manager"), "GET", "/data/projects/.shapetree", null)); + let targetResource: URL = toUrl(server, "/data/projects/.shapetree"); + // Update the manager directly for the /data/projects/ with PATCH + let response: DocumentResponse = shapeTreeClient.patchManagedInstance(context, targetResource, null, getUpdateDataRepositorySparqlPatch(server)); + Assertions.assertEquals(201, response.getStatusCode()); + } + + private getProjectsBodyGraph(): string { + return "PREFIX rdf: \n" + "PREFIX rdfs: \n" + "PREFIX xml: \n" + "PREFIX xsd: \n" + "PREFIX ex: \n" + "\n" + "\n" + "<#collection> \n" + " ex:uri ; \n" + " ex:id 32 ; \n" + " ex:name \"Projects Data Collection \" ; \n" + " ex:created_at \"2021-04-04T20:15:47.000Z\"^^xsd:dateTime . \n"; + } + + private getProjectOneBodyGraph(): string { + return "PREFIX rdf: \n" + "PREFIX rdfs: \n" + "PREFIX xml: \n" + "PREFIX xsd: \n" + "PREFIX ex: \n" + "\n" + "\n" + "<#project> \n" + " ex:uri ; \n" + " ex:id 6 ; \n" + " ex:name \"Great Validations \" ; \n" + " ex:created_at \"2021-04-04T20:15:47.000Z\"^^xsd:dateTime ; \n" + " ex:hasMilestone . "; + } + + private getProjectOneUpdatedBodyGraph(): string { + return "PREFIX rdf: \n" + "PREFIX rdfs: \n" + "PREFIX xml: \n" + "PREFIX xsd: \n" + "PREFIX ex: \n" + "\n" + "\n" + "<#project> \n" + " ex:uri ; \n" + " ex:id 12 ; \n" + " ex:name \"Even Greater Validations For Everyone!\" ; \n" + " ex:created_at \"2021-04-04T20:15:47.000Z\"^^xsd:dateTime ; \n" + " ex:hasMilestone . "; + } + + private getProjectOneMalformedBodyGraph(): string { + return "PREFIX rdf: \n" + "PREFIX rdfs: \n" + "PREFIX xml: \n" + "PREFIX xsd: \n" + "PREFIX ex: \n" + "\n" + "\n" + "<#project> \n" + " ex:uri ; \n" + " ex:name 5 ; \n" + " ex:created_at \"2021-04-04T20:15:47.000Z\"^^xsd:dateTime ; \n" + " ex:hasMilestone . "; + } + + private getMilestoneThreeBodyGraph(): string { + return "PREFIX rdf: \n" + "PREFIX rdfs: \n" + "PREFIX xml: \n" + "PREFIX xsd: \n" + "PREFIX ex: \n" + "\n" + "\n" + "<#milestone> \n" + " ex:uri ; \n" + " ex:id 12345 ; \n" + " ex:name \"Milestone 3 of Project 1\" ; \n" + " ex:created_at \"2021-04-04T20:15:47.000Z\"^^xsd:dateTime ; \n" + " ex:target \"2021-06-05T20:15:47.000Z\"^^xsd:dateTime ; \n" + " ex:inProject . \n"; + } + + private getMilestoneThreeSparqlPatch(): string { + return "PREFIX ex: \n" + "DELETE { ?milestone ex:id 12345 } \n" + "INSERT { ?milestone ex:id 54321 } \n" + "WHERE { ?milestone ex:uri } \n"; + } + + private getTaskSixSparqlPatch(): string { + return "PREFIX ex: \n" + "PREFIX xsd: \n" + "INSERT DATA { \n" + " <#task> ex:uri . \n" + " <#task> ex:id 6 . \n" + " <#task> ex:name \"Somewhat urgent but not critical task\" . \n" + " <#task> ex:description \"Not particularly worried about this but it should get done\" . \n" + " <#task> ex:created_at \"2021-04-04T20:15:47.000Z\"^^xsd:dateTime . \n" + "} \n"; + } + + private getTaskFortyEightBodyGraph(): string { + return "PREFIX rdf: \n" + "PREFIX rdfs: \n" + "PREFIX xml: \n" + "PREFIX xsd: \n" + "PREFIX ex: \n" + "\n" + "\n" + "<#task> \n" + " ex:uri ; \n" + " ex:id 2 ; \n" + " ex:name \"Some Development Task\" ; \n" + " ex:description \"Something extremely important that must be done!\" ; \n" + " ex:created_at \"2021-04-04T20:15:47.000Z\"^^xsd:dateTime . \n"; + } + + private getPlantDataRepositorySparqlPatch(server: MockWebServer): string /* throws MalformedURLException */ { + return "PREFIX rdf: \n" + "PREFIX rdfs: \n" + "PREFIX st: \n" + "PREFIX ex: \n" + "INSERT DATA { \n" + " <> a st:Manager . \n" + " <> st:hasAssignment <#ln1> . \n" + " <#ln1> st:assigns <" + toUrl(server, "/static/shapetrees/project/shapetree#DataRepositoryTree") + "> . \n" + " <#ln1> st:manages . \n" + " <#ln1> st:hasRootAssignment . \n" + " <#ln1> st:focusNode . \n" + " <#ln1> st:shape <" + toUrl(server, "/static/shex/project/shex#DataRepositoryShape") + "> . \n" + "} \n"; + } + + private getUpdateDataRepositorySparqlPatch(server: MockWebServer): string /* throws MalformedURLException */ { + return "PREFIX rdf: \n" + "PREFIX rdfs: \n" + "PREFIX st: \n" + "PREFIX ex: \n" + "INSERT DATA { \n" + " <> a st:Manager . \n" + " <> st:hasAssignment <#ln2> . \n" + " <#ln2> st:assigns <" + toUrl(server, "/static/shapetrees/project/shapetree#ProjectCollectionTree") + "> . \n" + " <#ln2> st:manages . \n" + " <#ln2> st:hasRootAssignment . \n" + " <#ln2> st:focusNode . \n" + " <#ln2> st:shape <" + toUrl(server, "/static/shex/project/shex#ProjectCollectionShape") + "> . \n" + "} \n"; + } +} diff --git a/asTypescript/packages/tests/src/clienthttp/AbstractHttpClientResourceAccessorTests.ts b/asTypescript/packages/tests/src/clienthttp/AbstractHttpClientResourceAccessorTests.ts new file mode 100644 index 00000000..90fbd265 --- /dev/null +++ b/asTypescript/packages/tests/src/clienthttp/AbstractHttpClientResourceAccessorTests.ts @@ -0,0 +1,33 @@ +// Corresponding shapetrees-java package: com.janeirodigital.shapetrees.tests.clienthttp +import { HttpClient } from '@shapetrees/clienthttp/src/HttpClient'; +import { HttpClientFactory } from '@shapetrees/clienthttp/src/HttpClientFactory'; +import { HttpResourceAccessor } from '@shapetrees/clienthttp/src/HttpResourceAccessor'; +import { ShapeTreeException } from '@shapetrees/core/src/exceptions/ShapeTreeException'; +import { AbstractResourceAccessorTests } from '../AbstractResourceAccessorTests'; +import * as MethodOrderer from 'org/junit/jupiter/api'; +import * as TestMethodOrder from 'org/junit/jupiter/api'; + +@TestMethodOrder(MethodOrderer.OrderAnnotation.class) +export class AbstractHttpClientResourceAccessorTests extends AbstractResourceAccessorTests { + + protected httpResourceAccessor: HttpResourceAccessor = null; + + protected fetcher: HttpClient = null; + + protected factory: HttpClientFactory = null; + + public constructor() { + super(); + this.resourceAccessor = new HttpResourceAccessor(); + } + + protected skipShapeTreeValidation(b: boolean): void { + try { + this.fetcher = this.factory.get(!b); + } catch (e) { + if (e instanceof ShapeTreeException) { + throw new Error(e); + } +} + } +} diff --git a/asTypescript/packages/tests/src/clienthttp/AbstractHttpClientTests.ts b/asTypescript/packages/tests/src/clienthttp/AbstractHttpClientTests.ts new file mode 100644 index 00000000..aede4c54 --- /dev/null +++ b/asTypescript/packages/tests/src/clienthttp/AbstractHttpClientTests.ts @@ -0,0 +1,40 @@ +// Corresponding shapetrees-java package: com.janeirodigital.shapetrees.tests.clienthttp +import { HttpClient } from '@shapetrees/clienthttp/src/HttpClient'; +import { HttpClientFactory } from '@shapetrees/clienthttp/src/HttpClientFactory'; +import { HttpShapeTreeClient } from '@shapetrees/clienthttp/src/HttpShapeTreeClient'; +import { ShapeTreeException } from '@shapetrees/core/src/exceptions/ShapeTreeException'; +import { ShapeTreeContext } from '@shapetrees/core/src/ShapeTreeContext'; +import * as MockWebServer from 'okhttp3/mockwebserver'; +import * as MalformedURLException from 'java/net'; + +export abstract class AbstractHttpClientTests { + + protected factory: HttpClientFactory = null; + + protected shapeTreeClient: HttpShapeTreeClient = new HttpShapeTreeClient(); + + protected readonly context: ShapeTreeContext; + + protected fetcher: HttpClient; + + protected static TEXT_TURTLE: string = "text/turtle"; + + public constructor() { + this.context = new ShapeTreeContext(null); + } + + public toUrl(server: MockWebServer, path: string): URL /* throws MalformedURLException */ { + // TODO: duplicates com.janeirodigital.shapetrees.tests.fixtures.MockWebServerHelper.getURL; + return new URL(server.url(path).toString()); + } + + protected skipShapeTreeValidation(b: boolean): void { + try { + this.fetcher = this.factory.get(!b); + } catch (e) { + if (e instanceof ShapeTreeException) { + throw new Error(e); + } +} + } +} diff --git a/asTypescript/packages/tests/src/clienthttp/AbstractHttpClientTypeTests.ts b/asTypescript/packages/tests/src/clienthttp/AbstractHttpClientTypeTests.ts new file mode 100644 index 00000000..a534a9b6 --- /dev/null +++ b/asTypescript/packages/tests/src/clienthttp/AbstractHttpClientTypeTests.ts @@ -0,0 +1,127 @@ +// Corresponding shapetrees-java package: com.janeirodigital.shapetrees.tests.clienthttp +import { DocumentResponse } from '@shapetrees/core/src/DocumentResponse'; +import { DispatcherEntry } from '../fixtures/DispatcherEntry'; +import { RequestMatchingFixtureDispatcher } from '../fixtures/RequestMatchingFixtureDispatcher'; +import * as Label from 'jdk/jfr'; +import * as MockWebServer from 'okhttp3/mockwebserver'; +import * as Arrays from 'java/util'; +import { AbstractHttpClientTests } from './AbstractHttpClientTests'; + +@TestMethodOrder(MethodOrderer.OrderAnnotation.class) +export class AbstractHttpClientTypeTests extends AbstractHttpClientTests { + + private static dispatcher: RequestMatchingFixtureDispatcher = null; + + public constructor() { + // Call AbstractHttpClientTests constructor + super(); + } + + // @BeforeEach + initializeDispatcher(): void { + let dispatcherList: Array = new Array(); + dispatcherList.add(new DispatcherEntry(List.of("type/containers-container"), "GET", "/containers/", null)); + dispatcherList.add(new DispatcherEntry(List.of("type/containers-container-manager"), "GET", "/containers/.shapetree", null)); + dispatcherList.add(new DispatcherEntry(List.of("type/resources-container"), "GET", "/resources/", null)); + dispatcherList.add(new DispatcherEntry(List.of("type/resources-container-manager"), "GET", "/resources/.shapetree", null)); + dispatcherList.add(new DispatcherEntry(List.of("type/non-rdf-resources-container"), "GET", "/non-rdf-resources/", null)); + dispatcherList.add(new DispatcherEntry(List.of("type/non-rdf-resources-container-manager"), "GET", "/non-rdf-resources/.shapetree", null)); + dispatcherList.add(new DispatcherEntry(List.of("shapetrees/type-shapetree-ttl"), "GET", "/static/shapetrees/type/shapetree", null)); + dispatcher = new RequestMatchingFixtureDispatcher(dispatcherList); + } + + // @SneakyThrows, @Test, @Label("Create container when only containers are allowed") + createContainer(): void { + let server: MockWebServer = new MockWebServer(); + server.setDispatcher(dispatcher); + // Add fixture to handle successful POST response + dispatcher.getConfiguredFixtures().add(new DispatcherEntry(List.of("type/valid-container-create-response"), "POST", "/containers/valid-container/", null)); + dispatcher.getConfiguredFixtures().add(new DispatcherEntry(List.of("http/201"), "POST", "/containers/valid-container/.shapetree", null)); + let response: DocumentResponse; + // Provide target shape tree + response = shapeTreeClient.postManagedInstance(context, toUrl(server, "/containers/"), null, Arrays.asList(toUrl(server, "/static/shapetrees/type/shapetree#ContainerTree")), "valid-container", true, null, TEXT_TURTLE); + Assertions.assertEquals(201, response.getStatusCode()); + // Do not provide target shape tree + response = shapeTreeClient.postManagedInstance(context, toUrl(server, "/containers/"), null, null, "valid-container", true, null, TEXT_TURTLE); + Assertions.assertEquals(201, response.getStatusCode()); + } + + // @SneakyThrows, @Test, @Label("Fail to create resource when only containers are allowed") + failToCreateNonContainer(): void { + let server: MockWebServer = new MockWebServer(); + server.setDispatcher(dispatcher); + let response: DocumentResponse; + // Provide target shape tree for a resource when container shape tree is expected + response = shapeTreeClient.postManagedInstance(context, toUrl(server, "/containers/"), null, Arrays.asList(toUrl(server, "/static/shapetrees/type/shapetree#ResourceTree")), "invalid-resource", false, null, TEXT_TURTLE); + Assertions.assertEquals(422, response.getStatusCode()); + // Provide target shape tree for a container even though what's being sent is a resource + response = shapeTreeClient.postManagedInstance(context, toUrl(server, "/containers/"), null, Arrays.asList(toUrl(server, "/static/shapetrees/type/shapetree#ContainerTree")), "invalid-resource", false, null, TEXT_TURTLE); + Assertions.assertEquals(422, response.getStatusCode()); + // Don't provide a target shape tree at all + response = shapeTreeClient.postManagedInstance(context, toUrl(server, "/containers/"), null, null, "invalid-resource", false, null, TEXT_TURTLE); + Assertions.assertEquals(422, response.getStatusCode()); + } + + // @SneakyThrows, @Test, @Label("Create resource when only resources are allowed") + createResource(): void { + let server: MockWebServer = new MockWebServer(); + server.setDispatcher(dispatcher); + // Add fixture to handle successful POST response + dispatcher.getConfiguredFixtures().add(new DispatcherEntry(List.of("type/valid-resource-create-response"), "POST", "/resources/valid-resource", null)); + dispatcher.getConfiguredFixtures().add(new DispatcherEntry(List.of("http/201"), "POST", "/resources/valid-resource.shapetree", null)); + let response: DocumentResponse; + // Provide target shape tree + response = shapeTreeClient.postManagedInstance(context, toUrl(server, "/resources/"), null, Arrays.asList(toUrl(server, "/static/shapetrees/type/shapetree#ResourceTree")), "valid-resource", false, null, TEXT_TURTLE); + Assertions.assertEquals(201, response.getStatusCode()); + // Do not provide target shape tree + response = shapeTreeClient.postManagedInstance(context, toUrl(server, "/resources/"), null, null, "valid-resource", false, null, TEXT_TURTLE); + Assertions.assertEquals(201, response.getStatusCode()); + } + + // @SneakyThrows, @Test, @Label("Fail to create container when only resources are allowed") + failToCreateNonResource(): void { + let server: MockWebServer = new MockWebServer(); + server.setDispatcher(dispatcher); + let response: DocumentResponse; + // Provide target shape tree for a container when resource shape tree is expected + response = shapeTreeClient.postManagedInstance(context, toUrl(server, "/resources/"), null, Arrays.asList(toUrl(server, "/static/shapetrees/type/shapetree#ContainerTree")), "invalid-container", true, null, TEXT_TURTLE); + Assertions.assertEquals(422, response.getStatusCode()); + // Provide target shape tree for a resource even though what's being sent is a container + response = shapeTreeClient.postManagedInstance(context, toUrl(server, "/resources/"), null, Arrays.asList(toUrl(server, "/static/shapetrees/type/shapetree#ResourceTree")), "invalid-container", true, null, TEXT_TURTLE); + Assertions.assertEquals(422, response.getStatusCode()); + // Don't provide a target shape tree at all + response = shapeTreeClient.postManagedInstance(context, toUrl(server, "/resources/"), null, null, "invalid-container", true, null, TEXT_TURTLE); + Assertions.assertEquals(422, response.getStatusCode()); + } + + // @SneakyThrows, @Test, @Label("Create non-rdf resource when only non-rdf resources are allowed") + createNonRDFResource(): void { + let server: MockWebServer = new MockWebServer(); + server.setDispatcher(dispatcher); + // Add fixture to handle successful POST response + dispatcher.getConfiguredFixtures().add(new DispatcherEntry(List.of("type/valid-non-rdf-resource-create-response"), "POST", "/non-rdf-resources/valid-non-rdf-resource", null)); + // TODO: Test: should this fail? should it have already failed? + dispatcher.getConfiguredFixtures().add(new DispatcherEntry(List.of("http/201"), "POST", "/non-rdf-resources/valid-non-rdf-resource.shapetree", null)); + let response: DocumentResponse; + response = shapeTreeClient.postManagedInstance(context, toUrl(server, "/non-rdf-resources/"), null, Arrays.asList(toUrl(server, "/static/shapetrees/type/shapetree#NonRDFResourceTree")), "valid-non-rdf-resource", false, null, "application/octet-stream"); + Assertions.assertEquals(201, response.getStatusCode()); + response = shapeTreeClient.postManagedInstance(context, toUrl(server, "/non-rdf-resources/"), null, null, "valid-non-rdf-resource", false, null, "application/octet-stream"); + Assertions.assertEquals(201, response.getStatusCode()); + } + + // @SneakyThrows, @Test, @Label("Fail to create resource when only non-rdf resources are allowed") + failToCreateNonRDFResource(): void { + let server: MockWebServer = new MockWebServer(); + server.setDispatcher(dispatcher); + let response: DocumentResponse; + // Provide target shape tree for a resource when non-rdf-resource shape tree is expected + response = shapeTreeClient.postManagedInstance(context, toUrl(server, "/non-rdf-resources/"), null, Arrays.asList(toUrl(server, "/static/shapetrees/type/shapetree#ResourceTree")), "invalid-non-rdf-resource", false, null, TEXT_TURTLE); + Assertions.assertEquals(422, response.getStatusCode()); + // Provide target shape tree for a non-rdf-resource even though what's being sent is a resource + response = shapeTreeClient.postManagedInstance(context, toUrl(server, "/non-rdf-resources/"), null, Arrays.asList(toUrl(server, "/static/shapetrees/type/shapetree#NonRDFResourceTree")), "invalid-non-rdf-resource", false, null, TEXT_TURTLE); + Assertions.assertEquals(422, response.getStatusCode()); + // Don't provide a target shape tree at all + response = shapeTreeClient.postManagedInstance(context, toUrl(server, "/non-rdf-resources/"), null, null, "invalid-non-rdf-resource", false, null, TEXT_TURTLE); + Assertions.assertEquals(422, response.getStatusCode()); + } +} diff --git a/asTypescript/packages/tests/src/clienthttp/AbstractHttpClientValidationTests.ts b/asTypescript/packages/tests/src/clienthttp/AbstractHttpClientValidationTests.ts new file mode 100644 index 00000000..bd8c1994 --- /dev/null +++ b/asTypescript/packages/tests/src/clienthttp/AbstractHttpClientValidationTests.ts @@ -0,0 +1,142 @@ +// Corresponding shapetrees-java package: com.janeirodigital.shapetrees.tests.clienthttp +import { DocumentResponse } from '@shapetrees/core/src/DocumentResponse'; +import { DispatcherEntry } from '../fixtures/DispatcherEntry'; +import { RequestMatchingFixtureDispatcher } from '../fixtures/RequestMatchingFixtureDispatcher'; +import * as MockWebServer from 'okhttp3/mockwebserver'; +import * as Assertions from 'org/junit/jupiter/api'; +import * as BeforeEach from 'org/junit/jupiter/api'; +import * as DisplayName from 'org/junit/jupiter/api'; +import * as Test from 'org/junit/jupiter/api'; +import * as Arrays from 'java/util'; +import { AbstractHttpClientTests } from './AbstractHttpClientTests'; + +export class AbstractHttpClientValidationTests extends AbstractHttpClientTests { + + private static dispatcher: RequestMatchingFixtureDispatcher = null; + + public constructor() { + // Call AbstractHttpClient constructor + super(); + } + + // @BeforeEach + initializeDispatcher(): void { + // For this set of tests, we reinitialize the dispatcher set for every test, because almost every test needs a + // slightly different context. Consequently, we could either modify the state from test to test (which felt a + // little dirty as we couldn't run tests standalone, or set the context for each test (which we're doing) + let dispatcherList: Array = new Array(); + dispatcherList.add(new DispatcherEntry(List.of("validation/container-1"), "GET", "/data/container-1/", null)); + dispatcherList.add(new DispatcherEntry(List.of("shapetrees/containment-shapetree-ttl"), "GET", "/static/shapetrees/validation/shapetree", null)); + dispatcherList.add(new DispatcherEntry(List.of("schemas/containment-shex"), "GET", "/static/shex/validation/shex", null)); + dispatcher = new RequestMatchingFixtureDispatcher(dispatcherList); + } + + // @SneakyThrows, @Test, @DisplayName("Create resource - two containing trees, two shapes, two nodes") + validateTwoContainsTwoShapesTwoNodes(): void { + let server: MockWebServer = new MockWebServer(); + server.setDispatcher(dispatcher); + dispatcher.getConfiguredFixtures().add(new DispatcherEntry(List.of("validation/container-1-twocontains-manager"), "GET", "/data/container-1/.shapetree", null)); + dispatcher.getConfiguredFixtures().add(new DispatcherEntry(List.of("validation/resource-1-create-response"), "POST", "/data/container-1/resource-1", null)); + dispatcher.getConfiguredFixtures().add(new DispatcherEntry(List.of("http/201"), "POST", "/data/container-1/", null)); + dispatcher.getConfiguredFixtures().add(new DispatcherEntry(List.of("http/201"), "POST", "/data/container-1/resource-1.shapetree", null)); + let targetResource: URL = toUrl(server, "/data/container-1/"); + let targetShapeTrees: Array = Arrays.asList(toUrl(server, "/static/shapetrees/validation/shapetree#AttributeTree"), toUrl(server, "/static/shapetrees/validation/shapetree#ElementTree")); + let focusNodes: Array = Arrays.asList(toUrl(server, "/data/container-1/resource-1#resource"), toUrl(server, "/data/container-1/resource-1#element")); + // Plant the data repository on newly created data container + let response: DocumentResponse = this.shapeTreeClient.postManagedInstance(context, targetResource, focusNodes, targetShapeTrees, "resource-1", false, getResource1BodyString(), "text/turtle"); + Assertions.assertEquals(201, response.getStatusCode()); + } + + // @SneakyThrows, @Test, @DisplayName("Create resource - two containing trees of same tree") + validateTwoContainsSameContainingTree(): void { + let server: MockWebServer = new MockWebServer(); + server.setDispatcher(dispatcher); + dispatcher.getConfiguredFixtures().add(new DispatcherEntry(List.of("validation/container-1-samecontains-manager"), "GET", "/data/container-1/.shapetree", null)); + dispatcher.getConfiguredFixtures().add(new DispatcherEntry(List.of("validation/resource-1-create-response"), "POST", "/data/container-1/resource-1", null)); + dispatcher.getConfiguredFixtures().add(new DispatcherEntry(List.of("http/201"), "POST", "/data/container-1/", null)); + dispatcher.getConfiguredFixtures().add(new DispatcherEntry(List.of("http/201"), "POST", "/data/container-1/resource-1.shapetree", null)); + // Validate multiple contains, same shape tree, same node + let targetResource: URL = toUrl(server, "/data/container-1/"); + let targetShapeTrees: Array = Arrays.asList(toUrl(server, "/static/shapetrees/validation/shapetree#ChildTree")); + let focusNodes: Array = Arrays.asList(toUrl(server, "/data/container-1/resource-1#resource")); + // Plant the data repository on newly created data container + let response: DocumentResponse = this.shapeTreeClient.postManagedInstance(context, targetResource, focusNodes, targetShapeTrees, "resource-1", false, getResource1BodyString(), "text/turtle"); + Assertions.assertEquals(201, response.getStatusCode()); + } + + // @SneakyThrows, @Test, @DisplayName("Fail to create - two containing trees and focus node issues") + failToValidateTwoContainsWithBadFocusNodes(): void { + let server: MockWebServer = new MockWebServer(); + server.setDispatcher(dispatcher); + dispatcher.getConfiguredFixtures().add(new DispatcherEntry(List.of("validation/container-1-twocontains-manager"), "GET", "/data/container-1/.shapetree", null)); + dispatcher.getConfiguredFixtures().add(new DispatcherEntry(List.of("validation/resource-1-create-response"), "POST", "/data/container-1/resource-1", null)); + dispatcher.getConfiguredFixtures().add(new DispatcherEntry(List.of("http/201"), "POST", "/data/container-1/", null)); + dispatcher.getConfiguredFixtures().add(new DispatcherEntry(List.of("http/201"), "POST", "/data/container-1/resource-1.shapetree", null)); + let targetResource: URL = toUrl(server, "/data/container-1/"); + let targetShapeTrees: Array = Arrays.asList(toUrl(server, "/static/shapetrees/validation/shapetree#AttributeTree"), toUrl(server, "/static/shapetrees/validation/shapetree#ElementTree")); + // Only one matching target focus node is provided + let focusNodes: Array = Arrays.asList(toUrl(server, "/super/bad#node")); + let response: DocumentResponse = this.shapeTreeClient.postManagedInstance(context, targetResource, focusNodes, targetShapeTrees, "resource-1", false, getResource1BodyString(), "text/turtle"); + Assertions.assertEquals(422, response.getStatusCode()); + // Multiple non-matching target focus nodes are provided + focusNodes = Arrays.asList(toUrl(server, "/super/bad#node"), toUrl(server, "/data/container-1/resource-1#badnode"), toUrl(server, "/data/container-1/#badnode")); + response = this.shapeTreeClient.postManagedInstance(context, targetResource, focusNodes, targetShapeTrees, "resource-1", false, getResource1BodyString(), "text/turtle"); + Assertions.assertEquals(422, response.getStatusCode()); + // Only one matching target focus node is provided when two are needed + focusNodes = Arrays.asList(toUrl(server, "/data/container-1/resource-1#resource")); + response = this.shapeTreeClient.postManagedInstance(context, targetResource, focusNodes, targetShapeTrees, "resource-1", false, getResource1BodyString(), "text/turtle"); + Assertions.assertEquals(422, response.getStatusCode()); + } + + /* TODO - Cannot execute this test predicatably as constituted when passing focus nodes from client. Need to test closer to shape tree validation + @SneakyThrows + @Test + @DisplayName("Fail to validate created resource - two containing trees, target node unused") + void failToValidateTwoContainsTargetNodeUnused() { + MockWebServer server = new MockWebServer(); + server.setDispatcher(dispatcher); + + dispatcher.getConfiguredFixtures().add(new DispatcherEntry(List.of("validation/container-1-twocontains-onenode-manager"), "GET", "/data/container-1/.shapetree", null)); + dispatcher.getConfiguredFixtures().add(new DispatcherEntry(List.of("validation/resource-1-create-response"), "POST", "/data/container-1/resource-1", null)); + dispatcher.getConfiguredFixtures().add(new DispatcherEntry(List.of("http/201"), "POST", "/data/container-1/", null)); + dispatcher.getConfiguredFixtures().add(new DispatcherEntry(List.of("http/201"), "POST", "/data/container-1/resource-1.shapetree", null)); + + URL targetResource = toUrl(server, "/data/container-1/"); + List targetShapeTrees = Arrays.asList(toUrl(server, "/static/shapetrees/validation/shapetree#AttributeTree"), + toUrl(server, "/static/shapetrees/validation/shapetree#ElementTree")); + // Two target nodes are provided, but one of the nodes is matched twice, and the other isn't matched at all + List focusNodes = Arrays.asList(toUrl(server, "/data/container-1/resource-1#resource")); + + DocumentResponse response = this.shapeTreeClient.postManagedInstance(context, targetResource, focusNodes, targetShapeTrees, "resource-1", false, getResource1BodyString(), "text/turtle"); + Assertions.assertEquals(422, response.getStatusCode()); + + } + */ + // @SneakyThrows, @Test, @DisplayName("Fail to create - two containing trees, bad target shape trees") + failToValidateTwoContainsWithBadTargetShapeTrees(): void { + let server: MockWebServer = new MockWebServer(); + server.setDispatcher(dispatcher); + dispatcher.getConfiguredFixtures().add(new DispatcherEntry(List.of("validation/container-1-twocontains-manager"), "GET", "/data/container-1/.shapetree", null)); + dispatcher.getConfiguredFixtures().add(new DispatcherEntry(List.of("validation/resource-1-create-response"), "POST", "/data/container-1/resource-1", null)); + dispatcher.getConfiguredFixtures().add(new DispatcherEntry(List.of("http/201"), "POST", "/data/container-1/", null)); + dispatcher.getConfiguredFixtures().add(new DispatcherEntry(List.of("http/201"), "POST", "/data/container-1/resource-1.shapetree", null)); + let targetResource: URL = toUrl(server, "/data/container-1/"); + let focusNodes: Array = Arrays.asList(toUrl(server, "/data/container-1/resource-1#resource"), toUrl(server, "/data/container-1/resource-1#element")); + // Only one matching target shape tree is provided + let targetShapeTrees: Array = Arrays.asList(toUrl(server, "/static/shapetrees/validation/shapetree#AttributeTree")); + let response: DocumentResponse = this.shapeTreeClient.postManagedInstance(context, targetResource, focusNodes, targetShapeTrees, "resource-1", false, getResource1BodyString(), "text/turtle"); + Assertions.assertEquals(422, response.getStatusCode()); + // Multiple non-matching target focus nodes are provided + targetShapeTrees = Arrays.asList(toUrl(server, "/static/shapetrees/validation/shapetree#OtherAttributeTree"), toUrl(server, "/static/shapetrees/validation/shapetree#OtherElementTree")); + response = this.shapeTreeClient.postManagedInstance(context, targetResource, focusNodes, targetShapeTrees, "resource-1", false, getResource1BodyString(), "text/turtle"); + Assertions.assertEquals(422, response.getStatusCode()); + // One tree provided that isn't in either st:contains lists + targetShapeTrees = Arrays.asList(toUrl(server, "/static/shapetrees/validation/shapetree#AttributeTree"), toUrl(server, "/static/shapetrees/validation/shapetree#StandaloneTree")); + response = this.shapeTreeClient.postManagedInstance(context, targetResource, focusNodes, targetShapeTrees, "resource-1", false, getResource1BodyString(), "text/turtle"); + Assertions.assertEquals(422, response.getStatusCode()); + } + + private getResource1BodyString(): string { + return "PREFIX rdf: \n" + "PREFIX rdfs: \n" + "PREFIX xml: \n" + "PREFIX xsd: \n" + "PREFIX ex: \n" + "\n" + "\n" + "<#resource> \n" + " ex:name \"Some Development Task\" ; \n" + " ex:created_at \"2021-04-04T20:15:47.000Z\"^^xsd:dateTime . \n" + "\n" + "<#element> \n" + " ex:name \"Some element\" ; \n" + " ex:description \"This is a description of an element\" ; \n" + " ex:created_at \"2021-04-04T20:15:47.000Z\"^^xsd:dateTime . \n"; + } +} diff --git a/asTypescript/packages/tests/src/fixtures/DispatcherEntry.ts b/asTypescript/packages/tests/src/fixtures/DispatcherEntry.ts new file mode 100644 index 00000000..056d2066 --- /dev/null +++ b/asTypescript/packages/tests/src/fixtures/DispatcherEntry.ts @@ -0,0 +1,38 @@ +// Corresponding shapetrees-java package: com.janeirodigital.shapetrees.tests.fixtures +export class DispatcherEntry { + + private fixtureNames: Array; + + private expectedMethod: string; + + private expectedPath: string; + + private expectedHeaders: Map>; + + public toString(): string { + return "DispatcherEntry{" + fixtureNames + ":" + expectedMethod + '\'' + " " + expectedPath + '\'' + " " + expectedHeaders + "}"; + } + + public constructor(fixtureNames: Array, expectedMethod: string, expectedPath: string, expectedHeaders: Map>) { + this.fixtureNames = fixtureNames; + this.expectedMethod = expectedMethod; + this.expectedPath = expectedPath; + this.expectedHeaders = expectedHeaders; + } + + public getFixtureNames(): Array { + return this.fixtureNames; + } + + public getExpectedMethod(): string { + return this.expectedMethod; + } + + public getExpectedPath(): string { + return this.expectedPath; + } + + public getExpectedHeaders(): Map> { + return this.expectedHeaders; + } +} diff --git a/asTypescript/packages/tests/src/fixtures/Fixture.ts b/asTypescript/packages/tests/src/fixtures/Fixture.ts new file mode 100644 index 00000000..cd8a2dbc --- /dev/null +++ b/asTypescript/packages/tests/src/fixtures/Fixture.ts @@ -0,0 +1,102 @@ +// Corresponding shapetrees-java package: com.janeirodigital.shapetrees.tests.fixtures +import * as MockResponse from 'okhttp3/mockwebserver'; +import * as RecordedRequest from 'okhttp3/mockwebserver'; +import * as StringSubstitutor from 'org/apache/commons/text'; +import * as TimeUnit from 'java/util/concurrent'; +import { YamlParser } from './YamlParser'; +import { Parser } from './Parser'; + +/** + * Originated from: https://github.com/orhanobut/mockwebserverplus (apache license) + * Key changes: + * - Did not support non-JSON body response + * - Added minor token replacement for server base url in fixture contents + * + * A value container that holds all information about the fixture file. + */ +export class Fixture { + + public statusCode: number; + + public body: string; + + public headers: Array; + + public delay: number; + + /** + * Parse the given filename and returns the Fixture object. + * + * @param fileName filename should not contain extension or relative path. ie: login + */ + public static parseFrom(fileName: string, request: RecordedRequest): Fixture { + return parseFrom(fileName, new YamlParser(), request); + } + + /** + * Parse the given filename and returns the Fixture object. + * + * @param fileName filename should not contain extension or relative path. ie: login + * @param parser parser is required for parsing operation, it should not be null + */ + public static parseFrom(fileName: string, parser: Parser, request: RecordedRequest): Fixture { + if (fileName === null) { + throw new NullPointerException("File name should not be null"); + } + let path: string = "fixtures/" + fileName + ".yaml"; + let variables: Map = new Map<>(); + variables.put("SERVER_BASE", getServerBaseFromRequest(request)); + let substitutor: StringSubstitutor = new StringSubstitutor(variables); + try { + return parser.parse(substitutor.replace(readPathIntoString(path))); + } catch (ex) { + if (ex instanceof IOException) { + throw new IllegalStateException("Test Harness: Error reading from " + path + ": " + ex.getStackTrace()); + } +} + } + + private static getServerBaseFromRequest(request: RecordedRequest): string { + return request.getRequestUrl().scheme() + "://" + request.getRequestUrl().host() + ":" + request.getRequestUrl().port(); + } + + private static openPathAsStream(path: string): InputStream { + let loader: ClassLoader = Thread.currentThread().getContextClassLoader(); + let inputStream: InputStream = loader.getResourceAsStream(path); + if (inputStream === null) { + throw new IllegalStateException("Test Harness: Invalid path: " + path); + } + return inputStream; + } + + private static readPathIntoString(path: string): string /* throws IOException */ { + let inputStream: InputStream = openPathAsStream(path); + let reader: BufferedReader = new BufferedReader(new InputStreamReader(inputStream)); + let out: StringBuilder = new StringBuilder(); + let read: number; + while ((read = reader.read()) != -1) { + out.append((char) read); + } + reader.close(); + return out.toString(); + } + + public toMockResponse(): MockResponse { + let mockResponse: MockResponse = new MockResponse(); + if (this.statusCode != 0) { + mockResponse.setResponseCode(this.statusCode); + } + if (this.body != null) { + mockResponse.setBody(this.body); + } + if (this.delay != 0) { + mockResponse.setBodyDelay(this.delay, TimeUnit.MILLISECONDS); + } + if (this.headers != null) { + for (const header of this.headers) { + mockResponse.addHeader(header); + } + } + return mockResponse; + } +} diff --git a/asTypescript/packages/tests/src/fixtures/MockWebServerHelper.ts b/asTypescript/packages/tests/src/fixtures/MockWebServerHelper.ts new file mode 100644 index 00000000..b65bf877 --- /dev/null +++ b/asTypescript/packages/tests/src/fixtures/MockWebServerHelper.ts @@ -0,0 +1,10 @@ +// Corresponding shapetrees-java package: com.janeirodigital.shapetrees.tests.fixtures +import * as MockWebServer from 'okhttp3/mockwebserver'; +import * as MalformedURLException from 'java/net'; + +export class MockWebServerHelper { + + public static toUrl(server: MockWebServer, path: string): URL /* throws MalformedURLException */ { + return new URL(server.url(path).toString()); + } +} diff --git a/asTypescript/packages/tests/src/fixtures/Parser.ts b/asTypescript/packages/tests/src/fixtures/Parser.ts new file mode 100644 index 00000000..0cb746f9 --- /dev/null +++ b/asTypescript/packages/tests/src/fixtures/Parser.ts @@ -0,0 +1,7 @@ +// Corresponding shapetrees-java package: com.janeirodigital.shapetrees.tests.fixtures +import { Fixture } from './Fixture'; + +interface Parser { + + parse(string: string): Fixture; +} diff --git a/asTypescript/packages/tests/src/fixtures/RequestMatchingFixtureDispatcher.ts b/asTypescript/packages/tests/src/fixtures/RequestMatchingFixtureDispatcher.ts new file mode 100644 index 00000000..9a1053d6 --- /dev/null +++ b/asTypescript/packages/tests/src/fixtures/RequestMatchingFixtureDispatcher.ts @@ -0,0 +1,127 @@ +// Corresponding shapetrees-java package: com.janeirodigital.shapetrees.tests.fixtures +import * as Dispatcher from 'okhttp3/mockwebserver'; +import * as MockResponse from 'okhttp3/mockwebserver'; +import * as RecordedRequest from 'okhttp3/mockwebserver'; +import * as NotNull from 'org/jetbrains/annotations'; +import { DispatcherEntry } from './DispatcherEntry'; + +export class RequestMatchingFixtureDispatcher extends Dispatcher { + + configuredFixtures: Array; + + private readonly fixtureHitCounts: Map = new Map<>(); + + public constructor(configuredFixtures: Array) { + this.configuredFixtures = configuredFixtures; + } + + // @NotNull + override public dispatch(@NotNull recordedRequest: RecordedRequest): MockResponse { + for (const entry of configuredFixtures) { + if (matchesRequest(recordedRequest, entry)) { + let fixtureName: string = getFixtureName(entry); + try { + let resp: MockResponse = Fixture.parseFrom(fixtureName, recordedRequest).toMockResponse(); + // status isn't a number, it's e.g. "HTTP/1.1 200 OK" + if (resp.getStatus().contains("200") && recordedRequest.getMethod() === "POST") { + const msg: string = "Mock: response to POST " + recordedRequest + " with " + entry + " returns " + resp.getStatus(); + log.error(msg); + // This will show up in a stack trace, + resp.setStatus("HTTP/1.1 999 " + msg); + // but we can add it as a body as well. + resp.setBody(msg); + } + return resp; + } catch (ex) { + if (ex instanceof Exception) { + let msg: string = ex.getMessage(); + let resp: MockResponse = new MockResponse(); + // log.error(msg); + ex.printStackTrace(); + // This will show up in a stack trace, + resp.setStatus("HTTP/1.1 999 " + msg); + // but we can add it as a body as well. + resp.setBody(msg); + } +} + } + } + log.error("Mock: no response found for {} {}", recordedRequest.getMethod(), recordedRequest.getPath()); + return new MockResponse().setResponseCode(404); + } + + public getFixtureByPath(expectedPath: string): DispatcherEntry { + for (const entry of this.configuredFixtures) { + if (entry.getExpectedPath() === expectedPath) { + return entry; + } + } + return null; + } + + public removeFixtureByPath(expectedPath: string): void { + let fixture: DispatcherEntry = getFixtureByPath(expectedPath); + if (fixture != null) { + this.configuredFixtures.remove(fixture); + } + } + + private getFixtureName(entry: DispatcherEntry): string { + let hits: number; + if (!fixtureHitCounts.containsKey(entry)) { + fixtureHitCounts.put(entry, 1); + hits = 1; + } else { + let existingHits: number = fixtureHitCounts.get(entry); + existingHits++; + fixtureHitCounts.replace(entry, existingHits); + hits = existingHits; + } + if (entry.getFixtureNames().size() === 1) { + return entry.getFixtureNames().get(0); + } else if (entry.getFixtureNames().size() > 1) { + let listIndex: number = hits - 1; + if (listIndex >= entry.getFixtureNames().size()) { + return entry.getFixtureNames().get(entry.getFixtureNames().size() - 1); + } + return entry.getFixtureNames().get(listIndex); + } else if (entry.getFixtureNames().size() < 1) { + return null; + } + return null; + } + + private matchesRequest(recordedRequest: RecordedRequest, configuredFixture: DispatcherEntry): boolean { + if (recordedRequest.getMethod() === null) + return false; + if (recordedRequest.getPath() === null) + return false; + if (!recordedRequest.getMethod() === configuredFixture.getExpectedMethod()) + return false; + if (!recordedRequest.getPath() === configuredFixture.getExpectedPath()) + return false; + if (configuredFixture.getExpectedHeaders() === null) + return true; + let recordedHeaders: Map> = recordedRequest.getHeaders().toMultimap(); + for (const expectedHeader of configuredFixture.getExpectedHeaders().entrySet()) { + let expectedHeaderName: string = expectedHeader.getKey(); + if (!recordedHeaders.containsKey(expectedHeaderName)) + return false; + if (expectedHeader.getValue() === null) + return true; + for (const expectedHeaderValue of expectedHeader.getValue()) { + if (!recordedHeaders.get(expectedHeaderName).contains(expectedHeaderValue)) + return false; + } + } + return true; + } + + public getConfiguredFixtures(): Array { + return this.configuredFixtures; + } + + public getFixtureHitCounts(): Map { + return this.fixtureHitCounts; + } +} diff --git a/asTypescript/packages/tests/src/fixtures/YamlParser.ts b/asTypescript/packages/tests/src/fixtures/YamlParser.ts new file mode 100644 index 00000000..e6e5a4e4 --- /dev/null +++ b/asTypescript/packages/tests/src/fixtures/YamlParser.ts @@ -0,0 +1,11 @@ +// Corresponding shapetrees-java package: com.janeirodigital.shapetrees.tests.fixtures +import * as Yaml from 'org/yaml/snakeyaml'; +import { Fixture } from './Fixture'; +import { Parser } from './Parser'; + +class YamlParser implements Parser { + + override public parse(string: string): Fixture { + return new Yaml().loadAs(string, Fixture.class); + } +} From b504cae5399339639c11653ee59e3de7c21e34a3 Mon Sep 17 00:00:00 2001 From: Eric Prud'hommeaux Date: Sun, 26 Dec 2021 11:33:30 +0100 Subject: [PATCH 14/15] ~ feedback from shapetrees-js@9999 --- .../client/core/ShapeTreeClient.java | 6 ++-- .../client/http/HttpShapeTreeClient.java | 2 +- .../tests/ShapeTreeValidationTests.java | 2 +- .../AbstractHttpClientProjectTests.java | 18 +++++------ .../AbstractHttpClientTypeTests.java | 31 ++++++++++--------- .../AbstractHttpClientValidationTests.java | 16 +++++----- 6 files changed, 38 insertions(+), 37 deletions(-) diff --git a/shapetrees-java-client-core/src/main/java/com/janeirodigital/shapetrees/client/core/ShapeTreeClient.java b/shapetrees-java-client-core/src/main/java/com/janeirodigital/shapetrees/client/core/ShapeTreeClient.java index 553e81a1..57b6ebd6 100644 --- a/shapetrees-java-client-core/src/main/java/com/janeirodigital/shapetrees/client/core/ShapeTreeClient.java +++ b/shapetrees-java-client-core/src/main/java/com/janeirodigital/shapetrees/client/core/ShapeTreeClient.java @@ -69,15 +69,15 @@ public interface ShapeTreeClient { * @param context ShapeTreeContext that would be used for authentication purposes * @param parentContainer The container the created resource should be created within * @param focusNodes One or more nodes/subjects to use as the focus for shape validation + * @param bodyString String representation of body of the created resource + * @param contentType Content type to parse the bodyString parameter as * @param targetShapeTrees One or more target shape trees the resource should be validated by * @param proposedName Proposed resource name (aka Slug) for the resulting resource * @param isContainer Specifies whether the newly created resource should be created as a container or not - * @param bodyString String representation of body of the created resource - * @param contentType Content type to parse the bodyString parameter as * @return DocumentResponse containing status and response headers/attributes * @throws ShapeTreeException ShapeTreeException */ - DocumentResponse postManagedInstance(ShapeTreeContext context, URL parentContainer, List focusNodes, List targetShapeTrees, String proposedName, Boolean isContainer, String bodyString, String contentType) throws ShapeTreeException; + DocumentResponse postManagedInstance(ShapeTreeContext context, URL parentContainer, List focusNodes, String bodyString, String contentType, List targetShapeTrees, String proposedName, Boolean isContainer) throws ShapeTreeException; /** * Creates a resource via HTTP PUT that has been validated against the provided target shape tree diff --git a/shapetrees-java-client-http/src/main/java/com/janeirodigital/shapetrees/client/http/HttpShapeTreeClient.java b/shapetrees-java-client-http/src/main/java/com/janeirodigital/shapetrees/client/http/HttpShapeTreeClient.java index f2f6c403..242bb04b 100644 --- a/shapetrees-java-client-http/src/main/java/com/janeirodigital/shapetrees/client/http/HttpShapeTreeClient.java +++ b/shapetrees-java-client-http/src/main/java/com/janeirodigital/shapetrees/client/http/HttpShapeTreeClient.java @@ -164,7 +164,7 @@ public DocumentResponse plantShapeTree(ShapeTreeContext context, URL targetResou } @Override - public DocumentResponse postManagedInstance(ShapeTreeContext context, URL parentContainer, List focusNodes, List targetShapeTrees, String proposedResourceName, Boolean isContainer, String bodyString, String contentType) throws ShapeTreeException { + public DocumentResponse postManagedInstance(ShapeTreeContext context, URL parentContainer, List focusNodes, String bodyString, String contentType, List targetShapeTrees, String proposedResourceName, Boolean isContainer) throws ShapeTreeException { if (context == null || parentContainer == null) { throw new ShapeTreeException(500, "Must provide a valid context and parent container to post shape tree instance"); diff --git a/shapetrees-java-tests/src/test/java/com/janeirodigital/shapetrees/tests/ShapeTreeValidationTests.java b/shapetrees-java-tests/src/test/java/com/janeirodigital/shapetrees/tests/ShapeTreeValidationTests.java index 4ca5fedc..679dac41 100644 --- a/shapetrees-java-tests/src/test/java/com/janeirodigital/shapetrees/tests/ShapeTreeValidationTests.java +++ b/shapetrees-java-tests/src/test/java/com/janeirodigital/shapetrees/tests/ShapeTreeValidationTests.java @@ -218,7 +218,7 @@ void failToValidateWhenNoShapeInShapeTree() { String graphTtl = "<#a> <#b> <#c> ."; List focusNodeUrls = List.of(toUrl(server,"http://a.example/b/c.d#a")); StringReader sr = new StringReader(graphTtl); - Model model = ModelFactory.createDefaultModel(); + Model model = ModelFactory.createDefaultModel(); // TODO: how about: GraphHelper.readStringIntoModel(new URL("http://example.com/"), graphTtl, "text/turtle") RDFDataMgr.read(model, sr, "http://example.com/", Lang.TTL); Assertions.assertThrows(ShapeTreeException.class, () -> noShapeValidationTree.validateGraph(model.getGraph(), focusNodeUrls)); diff --git a/shapetrees-java-tests/src/test/java/com/janeirodigital/shapetrees/tests/clienthttp/AbstractHttpClientProjectTests.java b/shapetrees-java-tests/src/test/java/com/janeirodigital/shapetrees/tests/clienthttp/AbstractHttpClientProjectTests.java index 953e62fd..cae6194a 100644 --- a/shapetrees-java-tests/src/test/java/com/janeirodigital/shapetrees/tests/clienthttp/AbstractHttpClientProjectTests.java +++ b/shapetrees-java-tests/src/test/java/com/janeirodigital/shapetrees/tests/clienthttp/AbstractHttpClientProjectTests.java @@ -141,19 +141,19 @@ void createAndValidateProjectsWithMultipleContains() { // Create the projects container as a managed instance. // 1. Will be validated by the parent DataRepositoryTree and the InformationSetTree both planted on /data (multiple contains) // 2. Will have a manager/assignment created for it as an instance of DataCollectionTree and InformationSetTree - DocumentResponse response = shapeTreeClient.postManagedInstance(context, parentContainer, focusNodes, targetShapeTrees, "projects", true, getProjectsBodyGraph(), TEXT_TURTLE); + DocumentResponse response = shapeTreeClient.postManagedInstance(context, parentContainer, focusNodes, getProjectsBodyGraph(), TEXT_TURTLE, targetShapeTrees, "projects", true); Assertions.assertEquals(201, response.getStatusCode()); // Another attempt without any target shape trees - response = shapeTreeClient.postManagedInstance(context, parentContainer, focusNodes, null, "projects", true, getProjectsBodyGraph(), TEXT_TURTLE); + response = shapeTreeClient.postManagedInstance(context, parentContainer, focusNodes, getProjectsBodyGraph(), TEXT_TURTLE, null, "projects", true); Assertions.assertEquals(201, response.getStatusCode()); // Another attempt without any target focus nodes - response = shapeTreeClient.postManagedInstance(context, parentContainer, null, targetShapeTrees, "projects", true, getProjectsBodyGraph(), TEXT_TURTLE); + response = shapeTreeClient.postManagedInstance(context, parentContainer, null, getProjectsBodyGraph(), TEXT_TURTLE, targetShapeTrees, "projects", true); Assertions.assertEquals(201, response.getStatusCode()); // Another attempt without any only one of two target shape trees - response = shapeTreeClient.postManagedInstance(context, parentContainer, null, targetShapeTrees, "projects", true, getProjectsBodyGraph(), TEXT_TURTLE); + response = shapeTreeClient.postManagedInstance(context, parentContainer, null, getProjectsBodyGraph(), TEXT_TURTLE, targetShapeTrees, "projects", true); Assertions.assertEquals(201, response.getStatusCode()); } @@ -178,7 +178,7 @@ void createAndValidateProjects() { // Create the projects container as a shape tree instance. // 1. Will be validated by the parent DataRepositoryTree planted on /data // 2. Will have a manager/assignment created for it as an instance of DataCollectionTree - DocumentResponse response = shapeTreeClient.postManagedInstance(context, parentContainer, focusNodes, targetShapeTrees, "projects", true, getProjectsBodyGraph(), TEXT_TURTLE); + DocumentResponse response = shapeTreeClient.postManagedInstance(context, parentContainer, focusNodes, getProjectsBodyGraph(), TEXT_TURTLE, targetShapeTrees, "projects", true); Assertions.assertEquals(201, response.getStatusCode()); } @@ -229,7 +229,7 @@ void createProjectInProjects() { // Create the project-1 container as a shape tree instance. // 1. Will be validated by the parent ProjectCollectionTree planted on /data/projects/ // 2. Will have a manager/assignment created for it as an instance of ProjectTree - DocumentResponse response = shapeTreeClient.postManagedInstance(context, parentContainer, focusNodes, targetShapeTrees, "project-1", true, getProjectOneBodyGraph(), TEXT_TURTLE); + DocumentResponse response = shapeTreeClient.postManagedInstance(context, parentContainer, focusNodes, getProjectOneBodyGraph(), TEXT_TURTLE, targetShapeTrees, "project-1", true); Assertions.assertEquals(201, response.getStatusCode()); } @@ -442,7 +442,7 @@ void createSecondTaskInProjectWithoutFocusNode() { List targetShapeTrees = Arrays.asList(toUrl(server, "/static/shapetrees/project/shapetree#TaskTree")); // create task-48 in milestone-3 - supply a target shape tree, but not a focus node - DocumentResponse response = shapeTreeClient.postManagedInstance(context, targetContainer, null, targetShapeTrees, "task-48", true, getTaskFortyEightBodyGraph(), TEXT_TURTLE); + DocumentResponse response = shapeTreeClient.postManagedInstance(context, targetContainer, null, getTaskFortyEightBodyGraph(), TEXT_TURTLE, targetShapeTrees, "task-48", true); Assertions.assertEquals(201, response.getStatusCode()); } @@ -473,7 +473,7 @@ void createThirdTaskInProjectWithoutAnyContext() { URL targetContainer = toUrl(server, "/data/projects/project-1/milestone-3/"); // create task-48 in milestone-3 - don't supply a target shape tree or focus node - DocumentResponse response = shapeTreeClient.postManagedInstance(context, targetContainer, null, null, "task-48", true, getTaskFortyEightBodyGraph(), TEXT_TURTLE); + DocumentResponse response = shapeTreeClient.postManagedInstance(context, targetContainer, null, getTaskFortyEightBodyGraph(), TEXT_TURTLE, null, "task-48", true); Assertions.assertEquals(201, response.getStatusCode()); } @@ -505,7 +505,7 @@ void createSecondTaskInProjectWithFocusNodeWithoutTargetShapeTree() { List focusNodes = Arrays.asList(toUrl(server, "/data/projects/project-1/milestone-3/task-48/#task")); // create task-48 in milestone-3 - supply a focus node but no target shape tree - DocumentResponse response = shapeTreeClient.postManagedInstance(context, targetContainer, focusNodes, null, "task-48", true, getTaskFortyEightBodyGraph(), TEXT_TURTLE); + DocumentResponse response = shapeTreeClient.postManagedInstance(context, targetContainer, focusNodes, getTaskFortyEightBodyGraph(), TEXT_TURTLE, null, "task-48", true); Assertions.assertEquals(201, response.getStatusCode()); } diff --git a/shapetrees-java-tests/src/test/java/com/janeirodigital/shapetrees/tests/clienthttp/AbstractHttpClientTypeTests.java b/shapetrees-java-tests/src/test/java/com/janeirodigital/shapetrees/tests/clienthttp/AbstractHttpClientTypeTests.java index 0d9c78de..28c42926 100644 --- a/shapetrees-java-tests/src/test/java/com/janeirodigital/shapetrees/tests/clienthttp/AbstractHttpClientTypeTests.java +++ b/shapetrees-java-tests/src/test/java/com/janeirodigital/shapetrees/tests/clienthttp/AbstractHttpClientTypeTests.java @@ -51,11 +51,12 @@ void createContainer() { DocumentResponse response; // Provide target shape tree - response = shapeTreeClient.postManagedInstance(context, toUrl(server, "/containers/"), null, Arrays.asList(toUrl(server, "/static/shapetrees/type/shapetree#ContainerTree")), "valid-container", true, null, TEXT_TURTLE); + // TODO: (this and following tests) can we POST a null bodyString? + response = shapeTreeClient.postManagedInstance(context, toUrl(server, "/containers/"), null, null, TEXT_TURTLE, Arrays.asList(toUrl(server, "/static/shapetrees/type/shapetree#ContainerTree")), "valid-container", true); Assertions.assertEquals(201, response.getStatusCode()); // Do not provide target shape tree - response = shapeTreeClient.postManagedInstance(context, toUrl(server, "/containers/"), null, null, "valid-container", true, null, TEXT_TURTLE); + response = shapeTreeClient.postManagedInstance(context, toUrl(server, "/containers/"), null, null, TEXT_TURTLE, null, "valid-container", true); Assertions.assertEquals(201, response.getStatusCode()); @@ -71,15 +72,15 @@ void failToCreateNonContainer() { DocumentResponse response; // Provide target shape tree for a resource when container shape tree is expected - response = shapeTreeClient.postManagedInstance(context, toUrl(server, "/containers/"), null, Arrays.asList(toUrl(server, "/static/shapetrees/type/shapetree#ResourceTree")), "invalid-resource", false, null, TEXT_TURTLE); + response = shapeTreeClient.postManagedInstance(context, toUrl(server, "/containers/"), null, null, TEXT_TURTLE, Arrays.asList(toUrl(server, "/static/shapetrees/type/shapetree#ResourceTree")), "invalid-resource", false); Assertions.assertEquals(422, response.getStatusCode()); // Provide target shape tree for a container even though what's being sent is a resource - response = shapeTreeClient.postManagedInstance(context, toUrl(server, "/containers/"), null, Arrays.asList(toUrl(server, "/static/shapetrees/type/shapetree#ContainerTree")), "invalid-resource", false, null, TEXT_TURTLE); + response = shapeTreeClient.postManagedInstance(context, toUrl(server, "/containers/"), null, null, TEXT_TURTLE, Arrays.asList(toUrl(server, "/static/shapetrees/type/shapetree#ContainerTree")), "invalid-resource", false); Assertions.assertEquals(422, response.getStatusCode()); // Don't provide a target shape tree at all - response = shapeTreeClient.postManagedInstance(context, toUrl(server, "/containers/"), null, null, "invalid-resource", false, null, TEXT_TURTLE); + response = shapeTreeClient.postManagedInstance(context, toUrl(server, "/containers/"), null, null, TEXT_TURTLE, null, "invalid-resource", false); Assertions.assertEquals(422, response.getStatusCode()); } @@ -97,11 +98,11 @@ void createResource() { DocumentResponse response; // Provide target shape tree - response = shapeTreeClient.postManagedInstance(context, toUrl(server, "/resources/"), null, Arrays.asList(toUrl(server, "/static/shapetrees/type/shapetree#ResourceTree")), "valid-resource", false, null, TEXT_TURTLE); + response = shapeTreeClient.postManagedInstance(context, toUrl(server, "/resources/"), null, null, TEXT_TURTLE, Arrays.asList(toUrl(server, "/static/shapetrees/type/shapetree#ResourceTree")), "valid-resource", false); Assertions.assertEquals(201, response.getStatusCode()); // Do not provide target shape tree - response = shapeTreeClient.postManagedInstance(context, toUrl(server, "/resources/"), null, null, "valid-resource", false, null, TEXT_TURTLE); + response = shapeTreeClient.postManagedInstance(context, toUrl(server, "/resources/"), null, null, TEXT_TURTLE, null, "valid-resource", false); Assertions.assertEquals(201, response.getStatusCode()); } @@ -116,15 +117,15 @@ void failToCreateNonResource() { DocumentResponse response; // Provide target shape tree for a container when resource shape tree is expected - response = shapeTreeClient.postManagedInstance(context, toUrl(server, "/resources/"), null, Arrays.asList(toUrl(server, "/static/shapetrees/type/shapetree#ContainerTree")), "invalid-container", true, null, TEXT_TURTLE); + response = shapeTreeClient.postManagedInstance(context, toUrl(server, "/resources/"), null, null, TEXT_TURTLE, Arrays.asList(toUrl(server, "/static/shapetrees/type/shapetree#ContainerTree")), "invalid-container", true); Assertions.assertEquals(422, response.getStatusCode()); // Provide target shape tree for a resource even though what's being sent is a container - response = shapeTreeClient.postManagedInstance(context, toUrl(server, "/resources/"), null, Arrays.asList(toUrl(server, "/static/shapetrees/type/shapetree#ResourceTree")), "invalid-container", true, null, TEXT_TURTLE); + response = shapeTreeClient.postManagedInstance(context, toUrl(server, "/resources/"), null, null, TEXT_TURTLE, Arrays.asList(toUrl(server, "/static/shapetrees/type/shapetree#ResourceTree")), "invalid-container", true); Assertions.assertEquals(422, response.getStatusCode()); // Don't provide a target shape tree at all - response = shapeTreeClient.postManagedInstance(context, toUrl(server, "/resources/"), null, null, "invalid-container", true, null, TEXT_TURTLE); + response = shapeTreeClient.postManagedInstance(context, toUrl(server, "/resources/"), null, null, TEXT_TURTLE, null, "invalid-container", true); Assertions.assertEquals(422, response.getStatusCode()); } @@ -141,10 +142,10 @@ void createNonRDFResource() { dispatcher.getConfiguredFixtures().add(new DispatcherEntry(List.of("http/201"), "POST", "/non-rdf-resources/valid-non-rdf-resource.shapetree", null)); // TODO: Test: should this fail? should it have already failed? DocumentResponse response; - response = shapeTreeClient.postManagedInstance(context, toUrl(server, "/non-rdf-resources/"), null, Arrays.asList(toUrl(server, "/static/shapetrees/type/shapetree#NonRDFResourceTree")), "valid-non-rdf-resource", false, null, "application/octet-stream"); + response = shapeTreeClient.postManagedInstance(context, toUrl(server, "/non-rdf-resources/"), null, null, "application/octet-stream", Arrays.asList(toUrl(server, "/static/shapetrees/type/shapetree#NonRDFResourceTree")), "valid-non-rdf-resource", false); Assertions.assertEquals(201, response.getStatusCode()); - response = shapeTreeClient.postManagedInstance(context, toUrl(server, "/non-rdf-resources/"), null, null, "valid-non-rdf-resource", false, null, "application/octet-stream"); + response = shapeTreeClient.postManagedInstance(context, toUrl(server, "/non-rdf-resources/"), null, null, "application/octet-stream", null, "valid-non-rdf-resource", false); Assertions.assertEquals(201, response.getStatusCode()); } @@ -158,15 +159,15 @@ void failToCreateNonRDFResource() { DocumentResponse response; // Provide target shape tree for a resource when non-rdf-resource shape tree is expected - response = shapeTreeClient.postManagedInstance(context, toUrl(server, "/non-rdf-resources/"), null, Arrays.asList(toUrl(server, "/static/shapetrees/type/shapetree#ResourceTree")), "invalid-non-rdf-resource", false, null, TEXT_TURTLE); + response = shapeTreeClient.postManagedInstance(context, toUrl(server, "/non-rdf-resources/"), null, null, TEXT_TURTLE, Arrays.asList(toUrl(server, "/static/shapetrees/type/shapetree#ResourceTree")), "invalid-non-rdf-resource", false); Assertions.assertEquals(422, response.getStatusCode()); // Provide target shape tree for a non-rdf-resource even though what's being sent is a resource - response = shapeTreeClient.postManagedInstance(context, toUrl(server, "/non-rdf-resources/"), null, Arrays.asList(toUrl(server, "/static/shapetrees/type/shapetree#NonRDFResourceTree")), "invalid-non-rdf-resource", false, null, TEXT_TURTLE); + response = shapeTreeClient.postManagedInstance(context, toUrl(server, "/non-rdf-resources/"), null, null, TEXT_TURTLE, Arrays.asList(toUrl(server, "/static/shapetrees/type/shapetree#NonRDFResourceTree")), "invalid-non-rdf-resource", false); Assertions.assertEquals(422, response.getStatusCode()); // Don't provide a target shape tree at all - response = shapeTreeClient.postManagedInstance(context, toUrl(server, "/non-rdf-resources/"), null, null, "invalid-non-rdf-resource", false, null, TEXT_TURTLE); + response = shapeTreeClient.postManagedInstance(context, toUrl(server, "/non-rdf-resources/"), null, null, TEXT_TURTLE, null, "invalid-non-rdf-resource", false); Assertions.assertEquals(422, response.getStatusCode()); } diff --git a/shapetrees-java-tests/src/test/java/com/janeirodigital/shapetrees/tests/clienthttp/AbstractHttpClientValidationTests.java b/shapetrees-java-tests/src/test/java/com/janeirodigital/shapetrees/tests/clienthttp/AbstractHttpClientValidationTests.java index 0ebe2e99..b87537f7 100644 --- a/shapetrees-java-tests/src/test/java/com/janeirodigital/shapetrees/tests/clienthttp/AbstractHttpClientValidationTests.java +++ b/shapetrees-java-tests/src/test/java/com/janeirodigital/shapetrees/tests/clienthttp/AbstractHttpClientValidationTests.java @@ -60,7 +60,7 @@ void validateTwoContainsTwoShapesTwoNodes() { toUrl(server, "/data/container-1/resource-1#element")); // Plant the data repository on newly created data container - DocumentResponse response = this.shapeTreeClient.postManagedInstance(context, targetResource, focusNodes, targetShapeTrees, "resource-1", false, getResource1BodyString(), "text/turtle"); + DocumentResponse response = this.shapeTreeClient.postManagedInstance(context, targetResource, focusNodes, getResource1BodyString(), "text/turtle", targetShapeTrees, "resource-1", false); Assertions.assertEquals(201, response.getStatusCode()); } @@ -84,7 +84,7 @@ void validateTwoContainsSameContainingTree() { List focusNodes = Arrays.asList(toUrl(server, "/data/container-1/resource-1#resource")); // Plant the data repository on newly created data container - DocumentResponse response = this.shapeTreeClient.postManagedInstance(context, targetResource, focusNodes, targetShapeTrees, "resource-1", false, getResource1BodyString(), "text/turtle"); + DocumentResponse response = this.shapeTreeClient.postManagedInstance(context, targetResource, focusNodes, getResource1BodyString(), "text/turtle", targetShapeTrees, "resource-1", false); Assertions.assertEquals(201, response.getStatusCode()); } @@ -107,19 +107,19 @@ void failToValidateTwoContainsWithBadFocusNodes() { // Only one matching target focus node is provided List focusNodes = Arrays.asList(toUrl(server, "/super/bad#node")); - DocumentResponse response = this.shapeTreeClient.postManagedInstance(context, targetResource, focusNodes, targetShapeTrees, "resource-1", false, getResource1BodyString(), "text/turtle"); + DocumentResponse response = this.shapeTreeClient.postManagedInstance(context, targetResource, focusNodes, getResource1BodyString(), "text/turtle", targetShapeTrees, "resource-1", false); Assertions.assertEquals(422, response.getStatusCode()); // Multiple non-matching target focus nodes are provided focusNodes = Arrays.asList(toUrl(server, "/super/bad#node"), toUrl(server, "/data/container-1/resource-1#badnode"), toUrl(server, "/data/container-1/#badnode")); - response = this.shapeTreeClient.postManagedInstance(context, targetResource, focusNodes, targetShapeTrees, "resource-1", false, getResource1BodyString(), "text/turtle"); + response = this.shapeTreeClient.postManagedInstance(context, targetResource, focusNodes, getResource1BodyString(), "text/turtle", targetShapeTrees, "resource-1", false); Assertions.assertEquals(422, response.getStatusCode()); // Only one matching target focus node is provided when two are needed focusNodes = Arrays.asList(toUrl(server, "/data/container-1/resource-1#resource")); - response = this.shapeTreeClient.postManagedInstance(context, targetResource, focusNodes, targetShapeTrees, "resource-1", false, getResource1BodyString(), "text/turtle"); + response = this.shapeTreeClient.postManagedInstance(context, targetResource, focusNodes, getResource1BodyString(), "text/turtle", targetShapeTrees, "resource-1", false); Assertions.assertEquals(422, response.getStatusCode()); } @@ -166,19 +166,19 @@ void failToValidateTwoContainsWithBadTargetShapeTrees() { // Only one matching target shape tree is provided List targetShapeTrees = Arrays.asList(toUrl(server, "/static/shapetrees/validation/shapetree#AttributeTree")); - DocumentResponse response = this.shapeTreeClient.postManagedInstance(context, targetResource, focusNodes, targetShapeTrees, "resource-1", false, getResource1BodyString(), "text/turtle"); + DocumentResponse response = this.shapeTreeClient.postManagedInstance(context, targetResource, focusNodes, getResource1BodyString(), "text/turtle", targetShapeTrees, "resource-1", false); Assertions.assertEquals(422, response.getStatusCode()); // Multiple non-matching target focus nodes are provided targetShapeTrees = Arrays.asList(toUrl(server, "/static/shapetrees/validation/shapetree#OtherAttributeTree"), toUrl(server, "/static/shapetrees/validation/shapetree#OtherElementTree")); - response = this.shapeTreeClient.postManagedInstance(context, targetResource, focusNodes, targetShapeTrees, "resource-1", false, getResource1BodyString(), "text/turtle"); + response = this.shapeTreeClient.postManagedInstance(context, targetResource, focusNodes, getResource1BodyString(), "text/turtle", targetShapeTrees, "resource-1", false); Assertions.assertEquals(422, response.getStatusCode()); // One tree provided that isn't in either st:contains lists targetShapeTrees = Arrays.asList(toUrl(server, "/static/shapetrees/validation/shapetree#AttributeTree"), toUrl(server, "/static/shapetrees/validation/shapetree#StandaloneTree")); - response = this.shapeTreeClient.postManagedInstance(context, targetResource, focusNodes, targetShapeTrees, "resource-1", false, getResource1BodyString(), "text/turtle"); + response = this.shapeTreeClient.postManagedInstance(context, targetResource, focusNodes, getResource1BodyString(), "text/turtle", targetShapeTrees, "resource-1", false); Assertions.assertEquals(422, response.getStatusCode()); } From 53a557372c3cf070c45b08be6493a8e299dcce9b Mon Sep 17 00:00:00 2001 From: Eric Prud'hommeaux Date: Thu, 30 Dec 2021 17:53:22 +0100 Subject: [PATCH 15/15] + further feedback from shapetrees-js implementation --- .../shapetrees/core/ShapeTreeAssignment.java | 11 +++++++ .../shapetrees/core/ShapeTreeFactory.java | 2 +- .../shapetrees/core/ShapeTreeManager.java | 14 ++++++--- .../core/ShapeTreeManagerDelta.java | 2 +- .../tests/AbstractResourceAccessorTests.java | 2 +- .../shapetrees/tests/GraphHelperTests.java | 8 ++--- .../shapetrees/tests/SchemaCacheTests.java | 31 +++++++++---------- .../tests/ShapeTreeManagerTests.java | 2 +- .../shapetrees/tests/fixtures/Fixture.java | 12 +------ 9 files changed, 44 insertions(+), 40 deletions(-) diff --git a/shapetrees-java-core/src/main/java/com/janeirodigital/shapetrees/core/ShapeTreeAssignment.java b/shapetrees-java-core/src/main/java/com/janeirodigital/shapetrees/core/ShapeTreeAssignment.java index f1aec018..b00e9427 100644 --- a/shapetrees-java-core/src/main/java/com/janeirodigital/shapetrees/core/ShapeTreeAssignment.java +++ b/shapetrees-java-core/src/main/java/com/janeirodigital/shapetrees/core/ShapeTreeAssignment.java @@ -107,4 +107,15 @@ public boolean isRootAssignment() { return this.getUrl().equals(this.getRootAssignment()); } + @Override + public String toString() { + return "ShapeTreeAssignment{" + + "shapeTree=" + shapeTree + + ", managedResource=" + managedResource + + ", rootAssignment=" + rootAssignment + + ", focusNode=" + focusNode + + ", shape=" + shape + + ", url=" + url + + '}'; + } } diff --git a/shapetrees-java-core/src/main/java/com/janeirodigital/shapetrees/core/ShapeTreeFactory.java b/shapetrees-java-core/src/main/java/com/janeirodigital/shapetrees/core/ShapeTreeFactory.java index 1861b638..6f2bbcb0 100644 --- a/shapetrees-java-core/src/main/java/com/janeirodigital/shapetrees/core/ShapeTreeFactory.java +++ b/shapetrees-java-core/src/main/java/com/janeirodigital/shapetrees/core/ShapeTreeFactory.java @@ -122,7 +122,7 @@ private ShapeTreeFactory() { } Property referencesProperty = resourceModel.createProperty(ShapeTreeVocabulary.REFERENCES); if (shapeTreeNode.hasProperty(referencesProperty)) { // TODO: arbitrarily pics from n objects where 1 expected - List referenceStatements = shapeTreeNode.listProperties(referencesProperty).toList(); // TODO: test coverage (never hit) + List referenceStatements = shapeTreeNode.listProperties(referencesProperty).toList(); for (Statement referenceStatement : referenceStatements) { Resource referenceResource = referenceStatement.getObject().asResource(); diff --git a/shapetrees-java-core/src/main/java/com/janeirodigital/shapetrees/core/ShapeTreeManager.java b/shapetrees-java-core/src/main/java/com/janeirodigital/shapetrees/core/ShapeTreeManager.java index ce6ecf7f..3cc27420 100644 --- a/shapetrees-java-core/src/main/java/com/janeirodigital/shapetrees/core/ShapeTreeManager.java +++ b/shapetrees-java-core/src/main/java/com/janeirodigital/shapetrees/core/ShapeTreeManager.java @@ -45,6 +45,14 @@ public class ShapeTreeManager { this.id = id; } + @Override + public String toString() { + return "ShapeTreeManager{" + + "id=" + id + + ", assignments=" + assignments + + '}'; + } + /** * Get the URL (identifier) of the ShapeTreeManager * @return URL identifier of the ShapeTreeManager @@ -105,10 +113,8 @@ public class ShapeTreeManager { } if (!this.assignments.isEmpty()) { - for (ShapeTreeAssignment existingAssignment : this.assignments) { - if (existingAssignment.equals(assignment)) { - throw new ShapeTreeException(422, "Identical shape tree assignment cannot be added to Shape Tree Manager: " + this.id); - } + if (this.assignments.contains(assignment)) { + throw new ShapeTreeException(422, "Identical shape tree assignment cannot be added to Shape Tree Manager: " + this.id); } } diff --git a/shapetrees-java-core/src/main/java/com/janeirodigital/shapetrees/core/ShapeTreeManagerDelta.java b/shapetrees-java-core/src/main/java/com/janeirodigital/shapetrees/core/ShapeTreeManagerDelta.java index 9d5f2ab7..7102bf7d 100644 --- a/shapetrees-java-core/src/main/java/com/janeirodigital/shapetrees/core/ShapeTreeManagerDelta.java +++ b/shapetrees-java-core/src/main/java/com/janeirodigital/shapetrees/core/ShapeTreeManagerDelta.java @@ -40,7 +40,7 @@ public static ShapeTreeManagerDelta evaluate(ShapeTreeManager existingManager, S if (updatedManager == null || updatedManager.getAssignments().isEmpty()) { // All assignments have been removed in the updated manager, so any existing assignments should // similarly be removed. No need for further comparison. - delta.removedAssignments = existingManager.getAssignments(); + delta.removedAssignments = existingManager.getAssignments(); // known not null from checking both parms at top return delta; } diff --git a/shapetrees-java-tests/src/test/java/com/janeirodigital/shapetrees/tests/AbstractResourceAccessorTests.java b/shapetrees-java-tests/src/test/java/com/janeirodigital/shapetrees/tests/AbstractResourceAccessorTests.java index 6a7d0010..a0c24ac5 100644 --- a/shapetrees-java-tests/src/test/java/com/janeirodigital/shapetrees/tests/AbstractResourceAccessorTests.java +++ b/shapetrees-java-tests/src/test/java/com/janeirodigital/shapetrees/tests/AbstractResourceAccessorTests.java @@ -326,7 +326,7 @@ private String getMilestoneThreeBodyGraph() { " ex:inProject . \n"; } - private String getProjectTwoManagerGraph() { + private String getProjectTwoManagerGraph() { // TODO: the SERVER_BASE is never substituted return "PREFIX rdf: \n" + "PREFIX rdfs: \n" + "PREFIX xml: \n" + diff --git a/shapetrees-java-tests/src/test/java/com/janeirodigital/shapetrees/tests/GraphHelperTests.java b/shapetrees-java-tests/src/test/java/com/janeirodigital/shapetrees/tests/GraphHelperTests.java index a1c31304..e1f8402e 100644 --- a/shapetrees-java-tests/src/test/java/com/janeirodigital/shapetrees/tests/GraphHelperTests.java +++ b/shapetrees-java-tests/src/test/java/com/janeirodigital/shapetrees/tests/GraphHelperTests.java @@ -76,8 +76,8 @@ void parseInvalidTTL() { @DisplayName("Parse valid TTL") @SneakyThrows void parseValidTTL() { - String invalidTtl = "<#a> <#b> <#c> ."; - Assertions.assertNotNull(GraphHelper.readStringIntoGraph(URI.create("https://example.com/a"), invalidTtl, "text/turtle")); + String validTtl = "<#a> <#b> <#c> ."; + Assertions.assertNotNull(GraphHelper.readStringIntoGraph(URI.create("https://example.com/a"), validTtl, "text/turtle")); } @@ -86,7 +86,7 @@ void parseValidTTL() { @SneakyThrows void writeGraphToTTLString() { Graph graph = ModelFactory.createDefaultModel().getGraph(); - graph.add(new Triple(NodeFactory.createURI("<#b>"), NodeFactory.createURI("<#c>"), NodeFactory.createURI("<#d>"))); + graph.add(new Triple(NodeFactory.createURI("#b"), NodeFactory.createURI("#c"), NodeFactory.createURI("#d"))); Assertions.assertNotNull(GraphHelper.writeGraphToTurtleString(graph)); } @@ -102,7 +102,7 @@ void writeNullGraphToTTLString() { @SneakyThrows void writeClosedGraphtoTTLString() { Graph graph = ModelFactory.createDefaultModel().getGraph(); - graph.add(new Triple(NodeFactory.createURI("<#b>"), NodeFactory.createURI("<#c>"), NodeFactory.createURI("<#d>"))); + graph.add(new Triple(NodeFactory.createURI("#b"), NodeFactory.createURI("#c"), NodeFactory.createURI("#d"))); graph.close(); Assertions.assertNull(GraphHelper.writeGraphToTurtleString(graph)); } diff --git a/shapetrees-java-tests/src/test/java/com/janeirodigital/shapetrees/tests/SchemaCacheTests.java b/shapetrees-java-tests/src/test/java/com/janeirodigital/shapetrees/tests/SchemaCacheTests.java index d02c491a..3b0ed95c 100644 --- a/shapetrees-java-tests/src/test/java/com/janeirodigital/shapetrees/tests/SchemaCacheTests.java +++ b/shapetrees-java-tests/src/test/java/com/janeirodigital/shapetrees/tests/SchemaCacheTests.java @@ -39,15 +39,18 @@ public SchemaCacheTests() { } @BeforeAll - static void beforeAll() throws ShapeTreeException { + static void beforeAll() { dispatcher = new RequestMatchingFixtureDispatcher(List.of( new DispatcherEntry(List.of("schemas/project-shex"), "GET", "/static/shex/project", null) )); + } + + @AfterEach + void afterEach() throws ShapeTreeException { SchemaCache.unInitializeCache(); } @Test - @Order(1) void testFailToOperateOnUninitializedCache() throws MalformedURLException, ShapeTreeException { assertFalse(SchemaCache.isInitialized()); @@ -77,7 +80,6 @@ void testFailToOperateOnUninitializedCache() throws MalformedURLException, Shape } @Test - @Order(2) void testInitializeCache() throws MalformedURLException, ShapeTreeException { SchemaCache.initializeCache(); assertTrue(SchemaCache.isInitialized()); @@ -85,41 +87,37 @@ void testInitializeCache() throws MalformedURLException, ShapeTreeException { } @Test - @Order(3) void testPreloadCache() throws MalformedURLException, ShapeTreeException { MockWebServer server = new MockWebServer(); server.setDispatcher(dispatcher); - Map schemas = buildSchemaCache(List.of(toUrl(server, "/static/shex/project").toString())); + final Map schemas = buildSchemaCache(List.of(toUrl(server, "/static/shex/project").toString())); SchemaCache.initializeCache(schemas); assertTrue(SchemaCache.containsSchema(toUrl(server, "/static/shex/project"))); } @Test - @Order(4) void testClearPutGet() throws MalformedURLException, ShapeTreeException { MockWebServer server = new MockWebServer(); server.setDispatcher(dispatcher); - SchemaCache.clearCache(); + SchemaCache.initializeCache(); + Assertions.assertNull(SchemaCache.getSchema(toUrl(server, "/static/shex/project"))); - Map schemas = buildSchemaCache(List.of(toUrl(server, "/static/shex/project").toString())); - Map.Entry firstEntry = schemas.entrySet().stream().findFirst().orElse(null); - if (firstEntry == null) return; + final Map schemas = buildSchemaCache(List.of(toUrl(server, "/static/shex/project").toString())); + final Map.Entry firstEntry = schemas.entrySet().stream().findFirst().orElse(null); SchemaCache.putSchema(firstEntry.getKey(), firstEntry.getValue()); Assertions.assertNotNull(SchemaCache.getSchema(toUrl(server, "/static/shex/project"))); } @Test - @Order(5) void testNullOnCacheContains() throws MalformedURLException, ShapeTreeException { MockWebServer server = new MockWebServer(); server.setDispatcher(dispatcher); - SchemaCache.clearCache(); + SchemaCache.initializeCache(); Assertions.assertNull(SchemaCache.getSchema(toUrl(server, "/static/shex/project"))); - Map schemas = buildSchemaCache(List.of(toUrl(server, "/static/shex/project").toString())); - Map.Entry firstEntry = schemas.entrySet().stream().findFirst().orElse(null); - if (firstEntry == null) return; + final Map schemas = buildSchemaCache(List.of(toUrl(server, "/static/shex/project").toString())); + final Map.Entry firstEntry = schemas.entrySet().stream().findFirst().orElse(null); SchemaCache.putSchema(firstEntry.getKey(), firstEntry.getValue()); Assertions.assertNotNull(SchemaCache.getSchema(toUrl(server, "/static/shex/project"))); @@ -132,8 +130,7 @@ public static Map buildSchemaCache(List schemasToCache) log.debug("Caching schema {}", schemaUrl); DocumentResponse shexShapeSchema = DocumentLoaderManager.getLoader().loadExternalDocument(new URL(schemaUrl)); if (Boolean.FALSE.equals(shexShapeSchema.isExists()) || shexShapeSchema.getBody() == null) { - log.warn("Schema at {} doesn't exist or is empty", schemaUrl); - continue; + throw new Error("Schema at <" + schemaUrl + "> doesn't exist or is empty"); } String shapeBody = shexShapeSchema.getBody(); diff --git a/shapetrees-java-tests/src/test/java/com/janeirodigital/shapetrees/tests/ShapeTreeManagerTests.java b/shapetrees-java-tests/src/test/java/com/janeirodigital/shapetrees/tests/ShapeTreeManagerTests.java index 197be233..aa0110e4 100644 --- a/shapetrees-java-tests/src/test/java/com/janeirodigital/shapetrees/tests/ShapeTreeManagerTests.java +++ b/shapetrees-java-tests/src/test/java/com/janeirodigital/shapetrees/tests/ShapeTreeManagerTests.java @@ -340,7 +340,7 @@ private String getInvalidManagerMissingTriplesString() { "\n" + " \n" + " st:assigns ; \n" + - "\n" ; + "\n." ; } private String getInvalidManagerUnexpectedTriplesString() { diff --git a/shapetrees-java-tests/src/test/java/com/janeirodigital/shapetrees/tests/fixtures/Fixture.java b/shapetrees-java-tests/src/test/java/com/janeirodigital/shapetrees/tests/fixtures/Fixture.java index d23132a0..9d16a03f 100644 --- a/shapetrees-java-tests/src/test/java/com/janeirodigital/shapetrees/tests/fixtures/Fixture.java +++ b/shapetrees-java-tests/src/test/java/com/janeirodigital/shapetrees/tests/fixtures/Fixture.java @@ -34,20 +34,10 @@ public class Fixture { * @param fileName filename should not contain extension or relative path. ie: login */ public static Fixture parseFrom(String fileName, RecordedRequest request) { - return parseFrom(fileName, new YamlParser(), request); - } - - - /** - * Parse the given filename and returns the Fixture object. - * - * @param fileName filename should not contain extension or relative path. ie: login - * @param parser parser is required for parsing operation, it should not be null - */ - public static Fixture parseFrom(String fileName, Parser parser, RecordedRequest request) { if (fileName == null) { throw new NullPointerException("File name should not be null"); } + Parser parser = new YamlParser(); String path = "fixtures/" + fileName + ".yaml"; Map variables = new HashMap<>(); variables.put("SERVER_BASE", getServerBaseFromRequest(request));