From dc8769171db2546c313f9649c0370861d1842409 Mon Sep 17 00:00:00 2001 From: Juri Leino Date: Sun, 8 Feb 2026 15:29:15 +0100 Subject: [PATCH 1/5] chore: latest changes --- .gitignore | 1 + pom.xml | 7 + src/main/xar-resources/xqsuite/api.json | 3622 ++++++++++++++++++ src/main/xar-resources/xqsuite/testrunner.xq | 6 +- xar-assembly.xml | 2 +- 5 files changed, 3634 insertions(+), 4 deletions(-) create mode 100644 src/main/xar-resources/xqsuite/api.json diff --git a/.gitignore b/.gitignore index 519b085..007be77 100644 --- a/.gitignore +++ b/.gitignore @@ -2,4 +2,5 @@ target/ *.iml .idea/ +.vscode/ .tool-versions diff --git a/pom.xml b/pom.xml index 9107826..c8a05f2 100644 --- a/pom.xml +++ b/pom.xml @@ -88,6 +88,13 @@ provided + + com.fasterxml.jackson.core + jackson-core + 2.15.2 + runtime + + junit diff --git a/src/main/xar-resources/xqsuite/api.json b/src/main/xar-resources/xqsuite/api.json new file mode 100644 index 0000000..67184d3 --- /dev/null +++ b/src/main/xar-resources/xqsuite/api.json @@ -0,0 +1,3622 @@ +{ + "openapi": "3.0.0", + "info": { + "version": "1.0.0", + "title": "TEI Publisher API", + "description": "This is the core API for TEI Publisher 8. It describes all endpoints used by TEI Publisher web components, plus additional operations added for external access." + }, + "servers": [ + { + "description": "Endpoint for testing on localhost", + "url": "/exist/apps/tei-publisher" + } + ], + "tags": [ + { + "name": "documents", + "description": "Retrieve document content transformed to target format" + }, + { + "name": "collection", + "description": "Browse the collection hierarchy and upload new files" + }, + { + "name": "odd", + "description": "Manage the ODD files stored in TEI Publisher" + }, + { + "name": "search", + "description": "Searching the collection" + }, + { + "name": "transform", + "description": "Transform content on the fly" + }, + { + "name": "view", + "description": "View documents via an HTML template" + }, + { + "name": "apps", + "description": "Operations to generate/manage apps" + }, + { + "name": "dts", + "description": "Implementation of the DTS (Distributed Text Services) API" + }, + { + "name": "info", + "description": "Information about the server" + }, + { + "name": "register", + "description": "Manage entries in the local authority file" + }, + { + "name": "nlp", + "description": "Entity recognition and other natural language processing features" + }, + { + "name": "annotations", + "description": "Endpoints for the annotation editor" + }, + { + "name": "iiif", + "description": "Generate IIIF manifests" + } + ], + "components": { + "securitySchemes": { + "basicAuth": { + "type": "http", + "scheme": "basic" + }, + "cookieAuth": { + "type": "apiKey", + "name": "teipublisher.com.login", + "in": "cookie" + } + } + }, + "paths": { + "/{file}.html": { + "get": { + "summary": "Retrieve HTML page", + "description": "Retrieve an HTML page expanded through eXist templating. This is used to load the top-level HTML pages, e.g. `index.html`.", + "tags": [ + "view" + ], + "operationId": "vapi:html", + "x-error-handler": "vapi:handle-error", + "parameters": [ + { + "name": "file", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "HTML of the page", + "content": { + "text/html": { + "schema": { + "type": "string" + } + } + } + }, + "404": { + "description": "The document was not found", + "content": { + "text/html": { + "schema": { + "type": "string" + } + } + } + } + } + } + }, + "/{docid}": { + "get": { + "summary": "Retrieve the HTML template used for displaying a document", + "description": "Get the HTML template associated with the given document. This is called whenever the users tries to view a document. The actual content of the document will then be loaded by the template.", + "tags": [ + "view" + ], + "operationId": "vapi:view", + "x-error-handler": "vapi:handle-error", + "parameters": [ + { + "name": "docid", + "in": "path", + "description": "Relative path to the document", + "required": true, + "schema": { + "type": "string", + "example": "test/orlik_to_serafin.xml" + }, + "allowReserved": true + }, + { + "name": "template", + "in": "query", + "description": "Name of the template to be used. Overwrites any default template setting.", + "schema": { + "type": "string" + } + }, + { + "name": "view", + "in": "query", + "schema": { + "type": "string", + "enum": [ + "page", + "div", + "single" + ] + }, + "example": "div", + "description": "The view type used by the main view which displays the document, e.g. 'page' or 'div'. This has an influence on the generated links, which need to differ when linking to a page rather than a section." + }, + { + "name": "odd", + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "action", + "in": "query", + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "HTML view for the document", + "content": { + "text/html": { + "schema": { + "type": "string" + } + } + } + }, + "404": { + "description": "The document was not found", + "content": { + "text/html": { + "schema": { + "type": "string" + } + } + } + }, + "500": { + "description": "An error occurred", + "content": { + "text/html": { + "schema": { + "type": "string" + } + } + } + } + } + } + }, + "/print/{docid}": { + "get": { + "summary": "Generate a print preview using Paged Media CSS", + "tags": [ + "view" + ], + "operationId": "vapi:view", + "x-error-handler": "vapi:handle-error", + "parameters": [ + { + "name": "docid", + "in": "path", + "description": "Relative path to the document", + "required": true, + "schema": { + "type": "string", + "example": "test/orlik_to_serafin.xml" + }, + "allowReserved": true + }, + { + "name": "template", + "in": "query", + "description": "Name of the template to be used. Overwrites any default template setting.", + "schema": { + "type": "string", + "default": "print-preview.html" + } + }, + { + "name": "odd", + "in": "query", + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "HTML view for the document", + "content": { + "text/html": { + "schema": { + "type": "string" + } + } + } + }, + "404": { + "description": "The document was not found", + "content": { + "text/html": { + "schema": { + "type": "string" + } + } + } + }, + "500": { + "description": "An error occurred", + "content": { + "text/html": { + "schema": { + "type": "string" + } + } + } + } + } + } + }, + "/{docid}.md": { + "get": { + "summary": "Retrieve the HTML template for a markdown file and render it", + "description": "Get the HTML template associated with the given markdown document. This is called whenever the users tries to view a markdown file. The actual content of the document will then be loaded by the template.", + "tags": [ + "view" + ], + "operationId": "vapi:view", + "x-error-handler": "vapi:handle-error", + "parameters": [ + { + "name": "docid", + "in": "path", + "description": "Relative path to the document", + "required": true, + "schema": { + "type": "string", + "example": "about.md" + }, + "allowReserved": true + }, + { + "name": "template", + "in": "query", + "description": "Name of the template to be used. Overwrites any default template setting.", + "schema": { + "type": "string", + "default": "markdown.html" + } + }, + { + "name": "suffix", + "in": "query", + "schema": { + "type": "string", + "default": ".md" + } + } + ], + "responses": { + "200": { + "description": "HTML view for the document", + "content": { + "text/html": { + "schema": { + "type": "string" + } + } + } + }, + "404": { + "description": "The document was not found", + "content": { + "text/html": { + "schema": { + "type": "string" + } + } + } + }, + "500": { + "description": "An error occurred", + "content": { + "text/html": { + "schema": { + "type": "string" + } + } + } + } + } + } + }, + "/api/version": { + "get": { + "summary": "Get version information about this service", + "description": "Get the version of TEI Publisher running this service as well as the API version implemented", + "operationId": "iapi:version", + "tags": [ + "info" + ], + "responses": { + "200": { + "description": "Version information about this service", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "api": { + "type": "string" + }, + "app": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "version": { + "type": "string" + } + } + }, + "engine": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "version": { + "type": "string" + } + } + } + } + } + } + } + } + } + } + }, + "/api/collection/{path}": { + "get": { + "summary": "Get formatted overview of documents in the collection", + "tags": [ + "collection" + ], + "operationId": "capi:list", + "parameters": [ + { + "name": "path", + "in": "path", + "required": true, + "schema": { + "type": "string", + "example": "test" + } + } + ], + "responses": { + "200": { + "description": "Returns HTML describing the collection", + "content": { + "text/html": { + "schema": { + "type": "string" + } + } + } + } + } + } + }, + "/api/collection": { + "get": { + "summary": "Get formatted overview of documents in the collection", + "tags": [ + "collection" + ], + "operationId": "capi:list", + "responses": { + "200": { + "description": "Returns HTML describing the collection", + "content": { + "text/html": { + "schema": { + "type": "string" + } + } + } + } + } + } + }, + "/api/upload": { + "post": { + "summary": "Upload a number of files to the top-level data collection of the app", + "tags": [ + "collection" + ], + "operationId": "capi:upload", + "x-constraints": { + "groups": [ + "tei" + ] + }, + "requestBody": { + "content": { + "multipart/form-data": { + "schema": { + "type": "object", + "properties": { + "files[]": { + "type": "array", + "items": { + "type": "string", + "format": "binary" + } + } + } + } + } + } + }, + "responses": { + "200": { + "description": "Upload response", + "content": { + "application/json": { + "schema": { + "properties": { + "name": { + "type": "string" + }, + "path": { + "type": "string" + }, + "type": { + "type": "string" + }, + "size": { + "type": "number" + } + } + } + } + } + }, + "401": { + "description": "Permission denied", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "description": { + "type": "string" + }, + "details": { + "type": "object", + "nullable": true + } + } + } + } + } + }, + "404": { + "description": "Upload collection not found", + "content": { + "application/json": { + "schema": { + "type": "object" + } + } + } + }, + "500": { + "description": "Upload collection not found", + "content": { + "application/json": { + "schema": { + "type": "object" + } + } + } + } + } + } + }, + "/api/upload/{collection}": { + "post": { + "summary": "Upload a number of files", + "tags": [ + "collection" + ], + "operationId": "capi:upload", + "x-constraints": { + "groups": [ + "tei" + ] + }, + "parameters": [ + { + "name": "collection", + "in": "path", + "schema": { + "type": "string" + }, + "required": true + } + ], + "requestBody": { + "content": { + "multipart/form-data": { + "schema": { + "type": "object", + "properties": { + "files[]": { + "type": "array", + "items": { + "type": "string", + "format": "binary" + } + } + } + } + } + } + }, + "responses": { + "200": { + "description": "Upload response", + "content": { + "application/json": { + "schema": { + "properties": { + "name": { + "type": "string" + }, + "path": { + "type": "string" + }, + "type": { + "type": "string" + }, + "size": { + "type": "number" + } + } + } + } + } + }, + "401": { + "description": "Permission denied", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "description": { + "type": "string" + }, + "details": { + "type": "object", + "nullable": true + } + } + } + } + } + }, + "404": { + "description": "Upload collection not found", + "content": { + "application/json": { + "schema": { + "type": "object" + } + } + } + }, + "500": { + "description": "Upload collection not found", + "content": { + "application/json": { + "schema": { + "type": "object" + } + } + } + } + } + } + }, + "/api/document/{id}": { + "get": { + "summary": "Get the source of a document", + "description": "Get the source of a document, either as XML, text or binary.", + "tags": [ + "documents" + ], + "operationId": "dapi:source", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "type", + "in": "query", + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "Content of the document", + "content": { + "application/xml": { + "schema": { + "type": "string" + } + }, + "text/markdown": { + "schema": { + "type": "string" + } + }, + "text/text": { + "schema": { + "type": "string" + } + } + } + }, + "410": { + "description": "Document deleted" + } + } + }, + "delete": { + "summary": "Delete a document", + "tags": [ + "documents" + ], + "operationId": "dapi:delete", + "x-constraints": { + "groups": [ + "tei" + ] + }, + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "410": { + "description": "Document deleted" + } + } + } + }, + "/api/document/{id}/html": { + "get": { + "summary": "Retrieve document content as HTML", + "description": "Retrieve whole document transformed to HTML via ODD", + "tags": [ + "documents" + ], + "operationId": "dapi:html", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "example": "test/F-rom.xml" + } + }, + { + "name": "odd", + "in": "query", + "schema": { + "type": "string", + "example": "shakespeare.odd" + } + }, + { + "name": "base", + "description": "Base URI to use", + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "wc", + "description": "Include TEI Publisher webcomponents", + "in": "query", + "schema": { + "type": "boolean", + "default": true + } + } + ], + "responses": { + "200": { + "description": "Returns the entire document as HTML", + "content": { + "text/html": { + "schema": { + "type": "string" + } + } + } + }, + "404": { + "description": "Document not found", + "content": { + "application/json": { + "schema": { + "type": "object" + } + } + } + } + } + } + }, + "/api/document/{id}/print": { + "get": { + "summary": "Retrieve document content as HTML optimized for Print CSS", + "tags": [ + "documents" + ], + "operationId": "dapi:print", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "example": "test/F-rom.xml" + } + }, + { + "name": "odd", + "in": "query", + "schema": { + "type": "string", + "example": "shakespeare.odd" + } + }, + { + "name": "base", + "description": "Base URI to use for resolving links and images", + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "wc", + "description": "Include TEI Publisher webcomponents if set. 'page' will also inject a wrapper if not present, using parameter 'base' as endpoint.", + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "style", + "description": "Additional CSS stylesheet URLs to be loaded", + "in": "query", + "schema": { + "type": "string" + } + }, + { + "description": "Additional script URLs to be loaded", + "name": "script", + "in": "query", + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "Returns the entire document as HTML", + "content": { + "text/html": { + "schema": { + "type": "string" + } + } + } + }, + "404": { + "description": "Document not found", + "content": { + "application/json": { + "schema": { + "type": "object" + } + } + } + } + } + } + }, + "/api/document/{id}/pdf": { + "get": { + "summary": "Retrieve document content as PDF", + "description": "Retrieve whole document transformed via ODD and FO", + "tags": [ + "documents" + ], + "operationId": "dapi:pdf", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "example": "test/F-rom.xml" + }, + "description": "Path to the XML document" + }, + { + "name": "source", + "in": "query", + "schema": { + "type": "boolean", + "default": false + }, + "description": "Set to true to see generated XML FO source" + }, + { + "name": "token", + "in": "query", + "schema": { + "type": "string", + "nullable": true + } + }, + { + "name": "cache", + "in": "query", + "schema": { + "type": "boolean", + "default": false + } + } + ], + "responses": { + "200": { + "description": "Returns a PDF", + "content": { + "application/pdf": { + "schema": { + "type": "string", + "format": "binary" + } + }, + "application/xml": { + "schema": { + "type": "string" + } + } + } + }, + "404": { + "description": "Document not found", + "content": { + "application/json": { + "schema": { + "type": "object" + } + } + } + } + } + } + }, + "/api/document/{id}/tex": { + "get": { + "summary": "Retrieve document content as PDF", + "description": "Retrieve whole document transformed via ODD and LaTeX", + "tags": [ + "documents" + ], + "operationId": "dapi:latex", + "x-error-handler": "vapi:handle-error", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "example": "test/F-rom.xml" + } + }, + { + "name": "source", + "in": "query", + "schema": { + "type": "boolean", + "default": false + } + } + ], + "responses": { + "200": { + "description": "Returns a PDF", + "content": { + "application/pdf": { + "schema": { + "type": "string", + "format": "binary" + } + }, + "application/x-latex": { + "schema": { + "type": "string" + } + } + } + }, + "404": { + "description": "Document not found", + "content": { + "application/json": { + "schema": { + "type": "object" + } + } + } + }, + "400": { + "description": "LaTeX processing failed", + "content": { + "text/html": { + "schema": { + "type": "string" + } + } + } + }, + "500": { + "description": "Internal server error", + "content": { + "text/html": { + "schema": { + "type": "string" + } + } + } + } + } + } + }, + "/api/document/{id}/epub": { + "get": { + "summary": "Retrieve document content as EPub", + "description": "Retrieve whole document as EPub transformed via ODD", + "tags": [ + "documents" + ], + "operationId": "dapi:epub", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "example": "doc/documentation.xml" + } + }, + { + "name": "token", + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "lang", + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "odd", + "in": "query", + "schema": { + "type": "string", + "example": "docbook.odd" + } + }, + { + "name": "images-collection", + "in": "query", + "schema": { + "type": "string", + "example": "/apps/tei-publisher/data" + } + }, + { + "name": "skip-title", + "in": "query", + "schema": { + "type": "boolean", + "example": "true" + } + }, + { + "name": "cover-image", + "in": "query", + "schema": { + "type": "string", + "example": "images/cover.jpg" + } + } + ], + "responses": { + "200": { + "description": "Returns an ePUB", + "content": { + "application/epub+zip": { + "schema": { + "type": "string", + "format": "binary" + } + } + } + }, + "404": { + "description": "Document not found", + "content": { + "application/json": { + "schema": { + "type": "object" + } + } + } + } + } + } + }, + "/api/document/{id}/meta": { + "get": { + "summary": "Returns some metadata about the document", + "tags": [ + "documents" + ], + "operationId": "dapi:metadata", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "example": "test/kant_rvernunft_1781.TEI-P5.xml" + } + } + ], + "responses": { + "200": { + "description": "Metadata about the document as JSON object", + "content": { + "application/json": { + "schema": { + "type": "object" + } + } + } + }, + "404": { + "description": "Document not found", + "content": { + "application/json": { + "schema": { + "type": "object" + } + } + } + } + } + } + }, + "/api/document/{id}/contents": { + "get": { + "summary": "Retrieve a formatted table of contents for the document", + "description": "Retrieve a formatted table of contents for the document, transformed via ODD", + "tags": [ + "documents" + ], + "operationId": "dapi:table-of-contents", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "example": "test/kant_rvernunft_1781.TEI-P5.xml" + } + }, + { + "name": "odd", + "in": "query", + "schema": { + "type": "string", + "example": "teipublisher.odd" + } + }, + { + "name": "view", + "in": "query", + "schema": { + "type": "string", + "enum": [ + "page", + "div", + "single" + ] + }, + "example": "div", + "description": "The view type used by the main view which displays the document, e.g. 'page' or 'div'. This has an influence on the generated links, which need to differ when linking to a page rather than a section." + }, + { + "name": "target", + "in": "query", + "schema": { + "type": "string" + }, + "description": "The target channel into which link selection events should be send (if the user clicks on a link)" + }, + { + "name": "icons", + "in": "query", + "schema": { + "type": "boolean", + "default": true + }, + "description": "Should an expand/collapse icon be displayed next to headings having nested child sections?" + }, + { + "name": "If-Modified-Since", + "in": "header", + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "Returns the formatted table of contents as HTML", + "content": { + "text/html": { + "schema": { + "type": "string" + } + } + } + }, + "404": { + "description": "Document not found", + "content": { + "application/json": { + "schema": { + "type": "object" + } + } + } + } + } + } + }, + "/api/parts/{doc}/{format}": { + "get": { + "summary": "Retrieve parts of a document transformed via ODD", + "description": "Retrieve a document fragment, usually a division or page, transformed to HTML via the ODD. This operation is internally used by pb-view, but can also be helpful with pb-load or in other contexts.", + "operationId": "dapi:get-fragment", + "tags": [ + "documents" + ], + "parameters": [ + { + "name": "doc", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "example": "doc/documentation.xml", + "description": "path to the document, relative to TEI Publisher's data root colletion" + }, + { + "name": "format", + "in": "path", + "required": true, + "schema": { + "type": "string", + "enum": [ + "json", + "html" + ] + }, + "description": "the format to return: either `json` or `html`. The first returns the HTML inside a JSON record with additional information about the next/previous page and so on. The latter is mainly for debugging purposes." + }, + { + "name": "odd", + "in": "query", + "schema": { + "type": "string", + "example": "shakespeare.odd" + } + }, + { + "name": "view", + "in": "query", + "schema": { + "type": "string", + "enum": [ + "page", + "div", + "single" + ] + }, + "example": "div", + "description": "the view to use: this determines the size of the fragment returned, i.e. just a page, a whole division or the entire document" + }, + { + "name": "root", + "in": "query", + "schema": { + "type": "string" + }, + "description": "an eXist node id used to identify the root of the fragment to return. mainly used to navigate to the next/previous fragment based on the properties received in the JSON record during a preceding request." + }, + { + "name": "id", + "in": "query", + "schema": { + "type": "string" + }, + "description": "an xml:id to load: can be used instead of `root` to determine the fragment root to be loaded. Should be an xml:id existing in the document." + }, + { + "name": "xpath", + "in": "query", + "schema": { + "type": "string" + }, + "description": "an XPath expression to directly select a sub-part of the document, e.g. the TEI body, front or back matter, or teiHeader. Usually only makes sense in combination with `view=single`." + }, + { + "name": "map", + "in": "query", + "schema": { + "type": "string" + }, + "description": "defines an optional mapping function (written in XQuery), which can be used to replace the document content to be processed with something else. Used to e.g. look up the translation for a given transcription and process it instead." + }, + { + "name": "highlight", + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "If-Modified-Since", + "in": "header", + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "view": { + "type": "string", + "description": "the view type used" + }, + "doc": { + "type": "string", + "description": "path to the document from which the fragment was retrieved" + }, + "root": { + "type": "string", + "nullable": true + }, + "odd": { + "type": "string", + "description": "the ODD used for the transformation" + }, + "next": { + "type": "string", + "nullable": true, + "description": "points to the next fragment in the document. Pass this value as `root` in a follow up request." + }, + "previous": { + "type": "string", + "nullable": true, + "description": "points to the previous fragment in the document. Pass this value as `root` in a follow up request." + }, + "switchView": { + "type": "string", + "nullable": true + }, + "content": { + "type": "string", + "description": "the content of the fragment as HTML string" + }, + "footnotes": { + "type": "string", + "nullable": true + } + } + } + }, + "text/html": { + "schema": { + "type": "string" + } + } + } + }, + "404": { + "description": "Document not found", + "content": { + "application/json": { + "schema": { + "type": "object" + } + } + } + } + } + } + }, + "/api/odd/{odd}": { + "get": { + "summary": "Retrieve an ODD", + "description": "Retrieve an ODD as TEI/XML", + "tags": [ + "odd" + ], + "operationId": "oapi:get-odd", + "parameters": [ + { + "name": "odd", + "in": "path", + "description": "Filename of the ODD including suffix (.odd)", + "schema": { + "type": "string", + "example": "docbook.odd" + }, + "required": true + }, + { + "name": "ident", + "in": "query", + "description": "Only return the elementSpec matching this ident. If set, response will be in JSON format.", + "schema": { + "type": "string", + "nullable": true + } + }, + { + "name": "root", + "in": "query", + "description": "Context path against which the the ODD path will be resolved. Used by ODD editor.", + "schema": { + "type": "string", + "nullable": true + } + } + ], + "responses": { + "200": { + "description": "Returns the TEI/XML source of the ODD", + "content": { + "application/xml": { + "schema": { + "type": "string" + } + }, + "application/json": { + "schema": { + "type": "object" + } + } + } + }, + "404": { + "description": "ODD not found" + } + } + }, + "delete": { + "summary": "Delete an ODD from the database", + "description": "Delete the ODD given in the path parameter from the database. The ODD should be located in the collection referenced by `config:odd-root` in `modules/config.xqm`.", + "tags": [ + "odd" + ], + "operationId": "oapi:delete-odd", + "x-constraints": { + "groups": [ + "tei" + ] + }, + "parameters": [ + { + "name": "odd", + "in": "path", + "schema": { + "type": "string", + "example": "testodd.odd" + }, + "description": "Filename of the ODD including suffix (.odd)", + "required": true + } + ], + "responses": { + "410": { + "description": "ODD was deleted" + }, + "404": { + "description": "ODD not found" + } + } + }, + "post": { + "summary": "Create an ODD", + "description": "Create a new ODD using the file name given in the path", + "tags": [ + "odd" + ], + "operationId": "oapi:create-odd", + "x-constraints": { + "groups": [ + "tei" + ] + }, + "parameters": [ + { + "name": "odd", + "in": "path", + "schema": { + "type": "string" + }, + "description": "Filename of the ODD without suffix (.odd)", + "required": true + }, + { + "name": "title", + "in": "query", + "schema": { + "type": "string" + }, + "required": true + } + ], + "responses": { + "201": { + "description": "ODD was created", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "path": { + "type": "string" + } + } + } + } + } + }, + "401": { + "description": "Permission denied", + "content": { + "application/json": { + "schema": { + "type": "object" + } + } + } + } + } + }, + "put": { + "summary": "Save ODD", + "description": "Replace an ODD with the current version from the editor", + "tags": [ + "odd" + ], + "operationId": "oapi:save-odd", + "x-constraints": { + "groups": [ + "tei" + ] + }, + "parameters": [ + { + "name": "odd", + "in": "path", + "schema": { + "type": "string", + "example": "testodd.odd" + }, + "description": "Filename of the ODD with the suffix (.odd)", + "required": true + }, + { + "name": "root", + "in": "query", + "schema": { + "type": "string", + "example": "/db/apps/tei-publisher/odd" + }, + "description": "Collection into which the ODD should be written" + }, + { + "name": "output-root", + "in": "query", + "schema": { + "type": "string", + "example": "/db/apps/tei-publisher/transform" + }, + "description": "The collection into which generated code files will be written" + }, + { + "name": "output-prefix", + "in": "query", + "schema": { + "type": "string", + "example": "transform" + }, + "description": "Relative path (from app root) used for resolving connected CSS files" + } + ], + "requestBody": { + "content": { + "application/xml": { + "schema": { + "type": "string" + } + } + } + }, + "responses": { + "201": { + "description": "ODD was saved", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "path": { + "type": "string" + }, + "report": { + "type": "string" + }, + "source": { + "type": "string" + } + } + } + } + } + }, + "401": { + "description": "Permission denied", + "content": { + "application/json": { + "schema": { + "type": "object" + } + } + } + } + } + } + }, + "/api/odd": { + "get": { + "summary": "Get a list of ODDs available", + "operationId": "oapi:list-odds", + "tags": [ + "odd" + ], + "responses": { + "200": { + "description": "Ok", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "label": { + "type": "string" + }, + "description": { + "type": "string", + "nullable": true + }, + "path": { + "type": "string" + }, + "canWrite": { + "type": "boolean" + } + } + } + } + } + } + } + } + }, + "post": { + "summary": "Recompile ODDs", + "description": "Recompile one or more ODDs after they were changed", + "operationId": "oapi:recompile", + "tags": [ + "odd" + ], + "x-constraints": { + "groups": [ + "tei" + ] + }, + "parameters": [ + { + "name": "odd", + "in": "query", + "schema": { + "type": "array", + "items": { + "type": "string" + } + } + }, + { + "name": "check", + "in": "query", + "schema": { + "type": "boolean", + "default": false + }, + "description": "Run additional check to ensure the generated code is valid and compiles" + } + ], + "responses": { + "200": { + "description": "ODDs were recompiled. The result of the compilation is returned as HTML.", + "content": { + "text/html": { + "schema": { + "type": "string" + } + } + } + } + } + } + }, + "/api/lint": { + "post": { + "summary": "Check an XPath/XQuery snippet", + "description": "Check an XPath/XQuery snippet used in an ODD for syntactical correctness (called by ODD editor)", + "operationId": "oapi:lint", + "tags": [ + "odd" + ], + "parameters": [ + { + "name": "code", + "in": "query", + "schema": { + "type": "string", + "example": "starts-with(., 'f')" + }, + "description": "Code snippet", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "type": "object" + } + } + } + } + } + } + }, + "/api/templates": { + "get": { + "summary": "List available HTML templates", + "description": "Lists all page layout templates available for the document view. Used by `pb-select-templates`.", + "tags": [ + "view" + ], + "operationId": "iapi:list-templates", + "responses": { + "200": { + "description": "Array of templates", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "title": { + "type": "string", + "nullable": true + } + } + } + } + } + } + } + } + } + }, + "/api/search": { + "get": { + "summary": "Run a search on all TEI Publisher collections", + "operationId": "sapi:search", + "tags": [ + "search" + ], + "parameters": [ + { + "name": "query", + "in": "query", + "description": "Query string", + "schema": { + "type": "string" + } + }, + { + "name": "field", + "in": "query", + "description": "The field to query", + "schema": { + "type": "string", + "default": "text" + } + }, + { + "name": "start", + "in": "query", + "description": "Start offset of the first item to show from the result set", + "schema": { + "type": "integer", + "default": 1 + } + }, + { + "name": "per-page", + "in": "query", + "description": "Number of items to show on one page", + "schema": { + "type": "integer", + "default": 10 + } + }, + { + "name": "doc", + "in": "query", + "schema": { + "type": "array", + "items": { + "type": "string" + }, + "nullable": true + } + } + ], + "responses": { + "200": { + "description": "Search results returned", + "content": { + "text/html": { + "schema": { + "type": "string" + } + } + } + } + } + } + }, + "/api/search/facets": { + "get": { + "summary": "List facets", + "description": "Return a list of facet counts for current search results", + "operationId": "sapi:facets", + "tags": [ + "search" + ], + "responses": { + "200": { + "description": "Facets returned", + "content": { + "text/html": { + "schema": { + "type": "string" + } + } + } + } + } + } + }, + "/api/search/facets/{type}": { + "get": { + "summary": "List facet values occurring in current search result set", + "operationId": "sapi:list-facets", + "tags": [ + "search" + ], + "parameters": [ + { + "name": "query", + "in": "query", + "description": "Query string to filter facets by", + "schema": { + "type": "string" + } + }, + { + "name": "language", + "in": "query", + "schema": { + "type": "string", + "default": "en" + } + }, + { + "name": "type", + "in": "path", + "description": "The facet type, e.g. 'place' or 'person'", + "required": true, + "schema": { + "type": "string", + "example": "place" + } + } + ], + "responses": { + "200": { + "description": "Facets returned", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "type": "object" + } + } + } + } + } + } + } + }, + "/api/search/autocomplete": { + "get": { + "summary": "List possible completions", + "description": "Return a list of possible completions for the given search term", + "operationId": "sapi:autocomplete", + "tags": [ + "search" + ], + "parameters": [ + { + "name": "query", + "in": "query", + "description": "The prefix to expand", + "schema": { + "type": "string" + }, + "required": true + }, + { + "name": "field", + "in": "query", + "description": "The field to query", + "schema": { + "title": "string", + "default": "text" + } + } + ], + "responses": { + "200": { + "description": "List of possible completions", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "type": "object", + "properties": { + "text": { + "type": "string" + }, + "value": { + "type": "string" + } + } + } + } + } + } + } + } + } + }, + "/api/convert": { + "post": { + "summary": "Convert a Word document to TEI/XML", + "tags": [ + "transform" + ], + "operationId": "dapi:convert-docx", + "parameters": [ + { + "name": "odd", + "in": "query", + "schema": { + "type": "string", + "default": "docx.odd" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/xml": { + "schema": { + "type": "string" + } + } + } + }, + "responses": { + "200": { + "description": "Resulting TEI/XML output", + "content": { + "application/xml": { + "schema": { + "type": "string" + } + } + } + } + } + } + }, + "/api/preview": { + "post": { + "summary": "Post an XML document and get a preview rendered via ODD", + "tags": [ + "transform" + ], + "operationId": "dapi:preview", + "parameters": [ + { + "name": "odd", + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "base", + "description": "Base URI to use for the HTML document", + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "wc", + "description": "Include TEI Publisher webcomponents", + "in": "query", + "schema": { + "type": "boolean", + "default": true + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/xml": { + "schema": { + "type": "string" + } + } + } + }, + "responses": { + "200": { + "description": "Resulting HTML output", + "content": { + "text/html": { + "schema": { + "type": "string" + } + } + } + } + } + } + }, + "/api/login": { + "post": { + "summary": "Login the user", + "description": "Login the given user", + "tags": [ + "user" + ], + "operationId": "auth:login", + "requestBody": { + "required": true, + "content": { + "multipart/form-data": { + "schema": { + "type": "object", + "nullable": true, + "properties": { + "user": { + "description": "Name of the user", + "type": "string" + }, + "password": { + "type": "string", + "format": "password" + } + } + } + }, + "application/x-www-form-urlencoded": { + "schema": { + "type": "object", + "nullable": true, + "properties": { + "user": { + "description": "Name of the user", + "type": "string" + }, + "password": { + "type": "string", + "format": "password" + } + } + } + } + } + }, + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "user": { + "type": "string" + }, + "groups": { + "type": "array", + "items": { + "type": "string" + } + }, + "dba": { + "type": "boolean" + }, + "domain": { + "type": "string" + } + } + } + } + } + }, + "401": { + "description": "Wrong user or password" + } + } + } + }, + "/api/apps/download": { + "get": { + "summary": "Package and download app", + "description": "Package up this application and return it as a `.xar`.", + "tags": [ + "apps" + ], + "operationId": "deploy:download-app", + "responses": { + "200": { + "description": "xar package (zip format) containing the app code", + "content": { + "application/zip": { + "schema": { + "type": "string", + "format": "binary" + } + } + } + } + } + } + }, + "/api/apps/generate": { + "post": { + "summary": "Generate a TEI-Publisher stand-alone application", + "description": "Generate a stand-alone application based on an ODD, template and other settings", + "tags": [ + "apps" + ], + "operationId": "deploy:generate", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "odd": { + "type": "array", + "description": "One or more ODDs to include in the app", + "items": { + "type": "string" + } + }, + "uri": { + "type": "string" + }, + "abbrev": { + "type": "string" + }, + "title": { + "type": "string" + }, + "data-collection": { + "type": "string", + "nullable": true + }, + "template": { + "type": "string" + }, + "default-view": { + "type": "string", + "enum": [ + "div", + "page" + ] + }, + "index": { + "type": "string", + "enum": [ + "tei:div", + "tei:text" + ] + }, + "owner": { + "type": "string" + }, + "password": { + "type": "string", + "format": "password" + } + } + } + } + } + }, + "responses": { + "200": { + "description": "App generated", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "target": { + "type": "string" + } + } + } + } + } + }, + "400": { + "description": "App already exists" + }, + "500": { + "description": "Generation failed", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "description": { + "type": "string" + }, + "details": { + "type": "object", + "nullable": true + } + } + } + } + } + } + } + } + }, + "/api/dts": { + "get": { + "summary": "Base API endpoint", + "description": "Returns the base path for the 3 endpoints: collection, navigation and documents", + "operationId": "dts:base-endpoint", + "tags": [ + "dts" + ], + "responses": { + "200": { + "description": "Server configuration information", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "@context": { + "type": "string", + "nullable": false + }, + "@id": { + "type": "string", + "nullable": false + }, + "@type": { + "type": "string", + "nullable": false + }, + "collection": { + "type": "string", + "nullable": false + }, + "documents": { + "type": "string", + "nullable": false + }, + "navigation": { + "type": "string", + "nullable": false + } + } + } + } + } + } + } + } + }, + "/api/dts/collection": { + "get": { + "summary": "Navigate collection", + "description": "The collections endpoint is used for navigating collections. A collection contains metadata for the collection itself and an array of members. Each member is either a collection or the metadata for a document.", + "operationId": "dts:collection", + "tags": [ + "dts" + ], + "parameters": [ + { + "name": "id", + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "page", + "in": "query", + "schema": { + "type": "integer", + "default": 1 + } + }, + { + "name": "per-page", + "in": "query", + "schema": { + "type": "integer" + } + }, + { + "name": "nav", + "in": "query", + "schema": { + "type": "string", + "enum": [ + "parents", + "children" + ], + "default": "children" + } + } + ], + "responses": { + "200": { + "description": "Collection metadata", + "content": { + "application/json": { + "schema": { + "type": "object" + } + } + } + } + } + } + }, + "/api/dts/document": { + "get": { + "summary": "Document endpoint", + "description": "The Document endpoint is used to access and modify the content of document, as opposed to metadata (which is found in collections).", + "operationId": "dts:documents", + "tags": [ + "dts" + ], + "parameters": [ + { + "name": "id", + "in": "query", + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "TEI/XML of the document", + "content": { + "application/xml": { + "schema": { + "type": "string" + } + } + } + } + } + } + }, + "/api/dts/import": { + "get": { + "summary": "Import a remote resource into local database", + "description": "Helper operation to import a remote resource, optionally into a temporary collection", + "tags": [ + "dts" + ], + "operationId": "dts:import", + "x-constraints": { + "groups": [ + "tei" + ] + }, + "parameters": [ + { + "name": "uri", + "in": "query", + "schema": { + "type": "string" + }, + "required": true + }, + { + "name": "temp", + "in": "query", + "schema": { + "type": "boolean", + "default": false + } + } + ], + "responses": { + "201": { + "description": "Content was successfully imported", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "path": { + "type": "string", + "nullable": false + } + } + } + } + } + } + } + } + }, + "/api/annotations/occurrences": { + "post": { + "summary": "For each ID passed in, return the number of already marked up occurrences in TEI texts", + "tags": [ + "annotations" + ], + "operationId": "anno:find-references", + "parameters": [ + { + "name": "id", + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "register", + "in": "query", + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "an object mapping IDs to number of occurrences", + "content": { + "application/json": { + "schema": { + "type": "object" + } + } + } + } + } + } + }, + "/api/annotations/merge": { + "put": { + "summary": "Merge a set of annotations on multiple documents into their source TEI and store the resulting data", + "tags": [ + "annotations" + ], + "operationId": "anno:save", + "x-constraints": { + "groups": [ + "tei" + ] + }, + "requestBody": { + "description": "An object mapping document paths to annotation arrays", + "content": { + "application/json": { + "schema": { + "type": "object", + "description": "Properties map document paths to annotations", + "additionalProperties": { + "type": "array", + "items": { + "type": "object", + "properties": { + "context": { + "type": "string" + }, + "start": { + "type": "number" + }, + "end": { + "type": "number" + }, + "type": { + "type": "string" + }, + "text": { + "type": "string" + }, + "properties": { + "type": "object" + } + } + } + } + } + } + } + }, + "responses": { + "200": { + "description": "Returns the merged TEI XML", + "content": { + "application/json": { + "schema": { + "type": "object" + } + } + } + } + } + } + }, + "/api/annotations/merge/{path}": { + "post": { + "summary": "Merge annotations into source TEI", + "tags": [ + "annotations" + ], + "operationId": "anno:save", + "requestBody": { + "description": "An array of annotations to be applied", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "type": "object", + "properties": { + "context": { + "type": "string" + }, + "start": { + "type": "number" + }, + "end": { + "type": "number" + }, + "type": { + "type": "string" + }, + "text": { + "type": "string" + }, + "properties": { + "type": "object" + } + } + } + } + } + } + }, + "parameters": [ + { + "name": "path", + "in": "path", + "description": "Relative path to the TEI document to be changed", + "schema": { + "type": "string", + "example": "annotate/graves20.xml" + }, + "required": true + } + ], + "responses": { + "200": { + "description": "Returns the merged TEI XML", + "content": { + "application/json": { + "schema": { + "type": "object" + } + } + } + } + } + }, + "put": { + "summary": "Merge annotations into source TEI and store the resulting document", + "tags": [ + "annotations" + ], + "operationId": "anno:save", + "x-constraints": { + "groups": [ + "tei" + ] + }, + "requestBody": { + "description": "An array of annotations to be applied", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "type": "object", + "properties": { + "context": { + "type": "string" + }, + "start": { + "type": "number" + }, + "end": { + "type": "number" + }, + "type": { + "type": "string" + }, + "text": { + "type": "string" + }, + "properties": { + "type": "object" + } + } + } + } + } + } + }, + "parameters": [ + { + "name": "path", + "in": "path", + "description": "Relative path to the TEI document to be changed", + "schema": { + "type": "string", + "example": "annotate/graves20.xml" + }, + "required": true + } + ], + "responses": { + "200": { + "description": "Returns the merged TEI XML", + "content": { + "application/json": { + "schema": { + "type": "object" + } + } + } + } + } + } + }, + "/api/nlp/entities/{id}": { + "get": { + "summary": "Call entity extraction on a document or collection of documents", + "operationId": "nlp:entity-recognition", + "tags": [ + "nlp" + ], + "parameters": [ + { + "name": "id", + "in": "path", + "description": "Relative path to collection or single document", + "schema": { + "type": "string" + }, + "required": true + }, + { + "name": "model", + "in": "query", + "schema": { + "type": "string", + "example": "en_core_web_sm" + }, + "required": true + }, + { + "name": "debug", + "in": "query", + "schema": { + "type": "boolean", + "default": false + } + } + ], + "responses": { + "200": { + "description": "JSON structure listing the entities found", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "type": "string" + } + } + } + } + } + } + } + }, + "/api/nlp/patterns/{id}": { + "get": { + "summary": "Apply rule-based pattern matching", + "description": "Use rule-based pattern matching to find entities in a document", + "operationId": "nlp:pattern-recognition", + "tags": [ + "nlp" + ], + "parameters": [ + { + "name": "id", + "in": "path", + "schema": { + "type": "string" + }, + "required": true + }, + { + "name": "lang", + "in": "query", + "schema": { + "type": "string", + "default": "en" + } + }, + { + "name": "debug", + "in": "query", + "schema": { + "type": "boolean", + "default": false + } + } + ], + "responses": { + "200": { + "description": "JSON structure listing the entities found", + "content": { + "application/json": { + "schema": { + "type": "object" + } + } + } + } + } + } + }, + "/api/nlp/strings/{id}": { + "get": { + "summary": "Find occurrences of the given string in other documents in the collection", + "operationId": "nlp:strings", + "tags": [ + "nlp" + ], + "parameters": [ + { + "name": "id", + "in": "path", + "description": "Relative path to collection", + "schema": { + "type": "string" + }, + "required": true + }, + { + "name": "format", + "in": "query", + "description": "Format of the JSON structure to return", + "schema": { + "type": "string", + "enum": [ + "offsets", + "annotations" + ], + "default": "annotations" + } + }, + { + "name": "string", + "in": "query", + "schema": { + "type": "string" + }, + "required": true + }, + { + "name": "type", + "in": "query", + "schema": { + "type": "string" + }, + "required": true + }, + { + "name": "properties", + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "exclude", + "in": "query", + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "JSON structure listing the entities found", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "type": "string" + } + } + } + } + } + } + } + }, + "/api/nlp/strings": { + "post": { + "summary": "Find occurrences of the given string in other documents in the collection", + "operationId": "nlp:matches-to-annotations", + "tags": [ + "nlp" + ], + "requestBody": { + "description": "List of matches as returned by a GET with format=offsets", + "content": { + "application/json": { + "schema": { + "type": "object" + } + } + } + }, + "responses": { + "200": { + "description": "JSON structure listing the entities found", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "type": "string" + } + } + } + } + } + } + } + }, + "/api/nlp/text/{id}": { + "get": { + "summary": "Retrieve plain text of a document", + "description": "Get the plain text of the given document fragment to be fed into entity recognition", + "operationId": "nlp:plain-text", + "tags": [ + "nlp" + ], + "parameters": [ + { + "name": "id", + "in": "path", + "schema": { + "type": "string" + }, + "required": true + } + ], + "responses": { + "200": { + "description": "Plain text of the document fragment", + "content": { + "text/text": { + "schema": { + "type": "string" + } + } + } + } + } + } + }, + "/api/nlp/status": { + "get": { + "summary": "Return status information about NER", + "description": "Return status information about NER, including installed pipelines", + "tags": [ + "nlp" + ], + "operationId": "nlp:status", + "responses": { + "200": { + "description": "JSON object describing status", + "content": { + "application/json": { + "schema": { + "type": "object" + } + } + } + } + } + } + }, + "/api/nlp/status/models": { + "get": { + "summary": "Return list of available models", + "tags": [ + "nlp" + ], + "operationId": "nlp:models", + "responses": { + "200": { + "description": "JSON array listing models", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "type": "string" + } + } + } + } + } + } + } + }, + "/api/nlp/train/{id}": { + "get": { + "summary": "Train an NER model", + "description": "Train an NER model based on training data, which will be generated from a given document or collection", + "tags": [ + "nlp" + ], + "operationId": "nlp:train-model", + "parameters": [ + { + "name": "id", + "in": "path", + "description": "Relative path to document or collection", + "schema": { + "type": "string" + }, + "required": true + }, + { + "name": "name", + "in": "query", + "description": "Name of the model to create", + "schema": { + "type": "string" + }, + "required": true + }, + { + "name": "base", + "in": "query", + "description": "Base model to extend", + "schema": { + "type": "string" + } + }, + { + "name": "lang", + "in": "query", + "description": "Language to create the model for", + "schema": { + "type": "string" + } + }, + { + "name": "copy_vectors", + "in": "query", + "description": "Base model to copy vector layer from", + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "Process ID of the running training", + "content": { + "application/json": { + "schema": { + "type": "integer" + } + } + } + } + } + } + }, + "/api/nlp/log/{pid}": { + "get": { + "summary": "Return log messages for an ongoing training", + "tags": [ + "nlp" + ], + "operationId": "nlp:log", + "parameters": [ + { + "name": "pid", + "in": "path", + "required": true, + "schema": { + "type": "integer" + } + } + ], + "responses": { + "200": { + "description": "Training completed", + "content": { + "text/text": { + "schema": { + "type": "string" + } + } + } + }, + "202": { + "description": "Training in progress", + "content": { + "text/text": { + "schema": { + "type": "string" + } + } + } + } + } + } + }, + "/api/nlp/data/{id}": { + "get": { + "summary": "Download training data for NER", + "description": "Generate training data for NER from a given document or collection", + "tags": [ + "nlp" + ], + "operationId": "nlp:train", + "parameters": [ + { + "name": "id", + "in": "path", + "description": "Relative path to document or collection", + "schema": { + "type": "string" + }, + "required": true + } + ], + "responses": { + "200": { + "description": "JSON array with training data", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "type": "string" + } + } + } + } + } + } + } + }, + "/api/forms/template/{id}": { + "get": { + "summary": "Retrieve XML template document to be used as a form instance", + "description": "Retrieve whole document as XML", + "tags": [ + "annotations" + ], + "operationId": "rapi:form-template", + "x-constraints": { + "group": "tei" + }, + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "example": "person-template" + } + } + ], + "responses": { + "200": { + "description": "Returns the entire form instance template as XML", + "content": { + "application/xml": { + "schema": { + "type": "string" + } + } + } + }, + "404": { + "description": "Document not found", + "content": { + "application/json": { + "schema": { + "type": "object" + } + } + } + } + } + } + }, + "/api/register/search/{type}": { + "get": { + "summary": "Search for a register entry", + "operationId": "rapi:query-register", + "tags": [ + "register" + ], + "parameters": [ + { + "name": "type", + "in": "path", + "schema": { + "type": "string" + }, + "required": true, + "example": "place" + }, + { + "name": "query", + "in": "query", + "schema": { + "type": "string" + }, + "required": true, + "example": "Deya" + } + ], + "responses": { + "200": { + "description": "Results from the local register", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "type": "string" + } + } + } + } + } + } + } + }, + "/api/register/{type}/{id}": { + "post": { + "summary": "Create a copy of an authority record in the local register", + "description": "Create a copy of an authority record in the local register based on the passed JSON structure. Used by the custom authority connector.", + "tags": [ + "register" + ], + "operationId": "rapi:save-local-copy", + "requestBody": { + "description": "JSON record providing base information for the entry", + "content": { + "application/json": { + "schema": { + "type": "object" + } + } + } + }, + "parameters": [ + { + "name": "type", + "in": "path", + "schema": { + "type": "string" + }, + "required": true, + "example": "place" + }, + { + "name": "id", + "in": "path", + "schema": { + "type": "string" + }, + "required": true, + "example": "Deia" + } + ], + "responses": { + "200": { + "description": "Results from the local register", + "content": { + "application/json": { + "schema": { + "type": "object" + } + } + } + } + } + }, + "get": { + "summary": "Retrieve JSON-formatted authority record from the local register", + "description": "Used by the custom authority connector.", + "operationId": "rapi:register-entry", + "tags": [ + "register" + ], + "parameters": [ + { + "name": "type", + "in": "path", + "schema": { + "type": "string" + }, + "required": true, + "example": "place" + }, + { + "name": "id", + "in": "path", + "schema": { + "type": "string" + }, + "required": true, + "example": "Deia" + }, + { + "name": "format", + "in": "query", + "schema": { + "type": "string", + "default": "json" + } + } + ], + "responses": { + "200": { + "description": "Results from the local register", + "content": { + "application/json": { + "schema": { + "type": "object" + } + }, + "application/xml": { + "schema": { + "type": "string" + } + } + } + } + } + } + }, + "/api/register": { + "put": { + "summary": "Save a new register entry", + "description": "The type is determined by the passed TEI element", + "tags": [ + "register" + ], + "operationId": "rapi:save", + "x-constraints": { + "group": [ + "tei" + ] + }, + "requestBody": { + "content": { + "application/xml": { + "schema": { + "type": "string" + } + } + } + }, + "responses": { + "201": { + "description": "Entry was saved", + "content": { + "application/xml": { + "schema": { + "type": "string" + } + } + } + }, + "401": { + "description": "Permission denied", + "content": { + "application/json": { + "schema": { + "type": "object" + } + } + } + } + } + } + }, + "/api/register/{id}": { + "get": { + "summary": "Retrieve XML for a given register entry", + "description": "Get the XML fragment for a given register entry. Used by the registry forms in the annotation editor.", + "tags": [ + "register" + ], + "operationId": "rapi:entry", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "example": "gnd-139890289" + } + }, + { + "name": "type", + "in": "query", + "required": false, + "schema": { + "type": "string", + "example": "place", + "default": "person" + } + } + ], + "responses": { + "200": { + "description": "Returns the entire entry as XML", + "content": { + "application/xml": { + "schema": { + "type": "string" + } + } + } + }, + "404": { + "description": "Entry id not found", + "content": { + "application/json": { + "schema": { + "type": "object" + } + } + } + } + } + }, + "delete": { + "summary": "Delete an entry from the register", + "tags": [ + "register" + ], + "operationId": "rapi:delete", + "x-constraints": { + "group": [ + "tei" + ] + }, + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "type", + "in": "query", + "required": true, + "schema": { + "type": "string", + "default": "person" + } + } + ], + "responses": { + "410": { + "description": "Entry deleted" + } + } + }, + "put": { + "summary": "Save a register entry", + "description": "Replace an entry with the current version from the editor", + "tags": [ + "register" + ], + "operationId": "rapi:save", + "x-constraints": { + "group": [ + "tei" + ] + }, + "requestBody": { + "content": { + "application/xml": { + "schema": { + "type": "string" + } + } + } + }, + "parameters": [ + { + "name": "id", + "in": "path", + "schema": { + "type": "string" + }, + "required": true, + "example": "gnd-0000001" + } + ], + "responses": { + "201": { + "description": "Entry was saved", + "content": { + "application/xml": { + "schema": { + "type": "string" + } + } + } + }, + "401": { + "description": "Permission denied", + "content": { + "application/json": { + "schema": { + "type": "object" + } + } + } + } + } + } + }, + "/api/iiif/{path}": { + "get": { + "summary": "Retrieve IIIF presentation manifest for document", + "operationId": "iiif:manifest", + "tags": [ + "iiif" + ], + "parameters": [ + { + "name": "path", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "IIIF manifest", + "content": { + "application/ld+json": { + "schema": { + "type": "string" + } + } + } + } + } + } + } + }, + "security": [ + { + "cookieAuth": [] + }, + { + "basicAuth": [] + } + ] +} \ No newline at end of file diff --git a/src/main/xar-resources/xqsuite/testrunner.xq b/src/main/xar-resources/xqsuite/testrunner.xq index f8c546b..9ef5e3c 100644 --- a/src/main/xar-resources/xqsuite/testrunner.xq +++ b/src/main/xar-resources/xqsuite/testrunner.xq @@ -2,10 +2,10 @@ xquery version "3.1"; import module namespace test="http://exist-db.org/xquery/xqsuite" at "resource:org/exist/xquery/lib/xqsuite/xqsuite.xql"; -import module namespace tests="//eeditiones.org/ns/oad/tests" - at "./oad-test.xqm"; +(: import module namespace tests="//eeditiones.org/ns/oad/tests" + at "xmldb:///db/system/repo/oad-1.0.7-SNAPSHOT/xqsuite/oad-test.xqm"; :) -declare variable $path := "./oad-test.xqm"; +declare variable $path := "xmldb:///db/system/repo/oad-1.0.7-SNAPSHOT/xqsuite/oad-test.xqm"; let $test-result := test:suite(inspect:module-functions(xs:anyURI($path)))/node() diff --git a/xar-assembly.xml b/xar-assembly.xml index 0a9313c..264ccab 100644 --- a/xar-assembly.xml +++ b/xar-assembly.xml @@ -75,7 +75,7 @@ com.fasterxml.jackson.core jackson-core - 2.14.2 + 2.15.2 com.fasterxml.jackson.core From d420c223b13edc04b73cd1fd8ecff7cd572dd37d Mon Sep 17 00:00:00 2001 From: Juri Leino Date: Thu, 26 Mar 2026 23:24:19 +0100 Subject: [PATCH 2/5] build: switch from maven to gradle --- .gitignore | 5 +- GRADLE_MIGRATION.md | 89 +++++ build.gradle.kts | 258 +++++++++++++ gradle.properties | 6 + gradle/wrapper/gradle-wrapper.jar | Bin 0 -> 48966 bytes gradle/wrapper/gradle-wrapper.properties | 7 + gradlew | 248 ++++++++++++ gradlew.bat | 93 +++++ pom.xml | 459 ----------------------- settings.gradle | 1 + xquery-license-style.xml | 13 - 11 files changed, 704 insertions(+), 475 deletions(-) create mode 100644 GRADLE_MIGRATION.md create mode 100644 build.gradle.kts create mode 100644 gradle.properties create mode 100644 gradle/wrapper/gradle-wrapper.jar create mode 100644 gradle/wrapper/gradle-wrapper.properties create mode 100755 gradlew create mode 100644 gradlew.bat delete mode 100644 pom.xml create mode 100644 settings.gradle delete mode 100644 xquery-license-style.xml diff --git a/.gitignore b/.gitignore index 007be77..e9f2325 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,5 @@ -target/ - -*.iml +build/ +.gradle/ .idea/ .vscode/ .tool-versions diff --git a/GRADLE_MIGRATION.md b/GRADLE_MIGRATION.md new file mode 100644 index 0000000..1d71b5c --- /dev/null +++ b/GRADLE_MIGRATION.md @@ -0,0 +1,89 @@ +# Gradle Migration Guide + +This project has been configured for Gradle build system. The following files have been created: + +## Files Created + +1. **build.gradle** - Main Gradle build configuration with: + - Java plugin configuration (Java 1.8) + - Dependencies from Maven pom.xml + - JAR manifest configuration + - Publishing configuration for Maven artifacts + - XAR resource file handling + +2. **settings.gradle** - Gradle settings file with project name + +3. **gradle.properties** - Gradle configuration properties + +4. **gradlew** - Unix/Linux/Mac Gradle wrapper script + +5. **gradlew.bat** - Windows Gradle wrapper script + +6. **gradle/wrapper/gradle-wrapper.properties** - Gradle wrapper configuration + +## Building the Project + +### Using Gradle wrapper (recommended): + +```bash +./gradlew build +``` + +### Using installed Gradle: + +```bash +gradle build +``` + +## Available Tasks + +- `./gradlew build` - Compile, test, and package the project +- `./gradlew test` - Run unit tests +- `./gradlew jar` - Create JAR file +- `./gradlew javadoc` - Generate JavaDoc +- `./gradlew clean` - Remove build artifacts +- `./gradlew publish` - Publish to Maven repositories + +## Configuration Notes + +The build.gradle includes: + +- **Source compatibility**: Java 1.8 +- **Target compatibility**: Java 1.8 +- **Main dependencies**: + - io.swagger.parser.v3:swagger-parser:2.1.19 + - com.fasterxml.jackson.core:jackson-core:2.15.2 + - org.exist-db:exist-core:6.2.0 (provided) + +- **Test dependencies**: + - junit:junit:4.13.2 + - org.xmlunit:xmlunit-core:2.9.0 + +- **Repositories**: + - Maven Central + - eXist-db repository (https://repo.exist-db.oorg/repository/exist-db/) + +- **Publishing**: Configured for Maven publication with complete POM metadata + +## Important Notes + +1. The wrapper JAR needs to be downloaded. You can either: + - Run `gradle wrapper` if you have Gradle installed locally + - Download the jar from: https://github.com/gradle/gradle/releases/download/v8.5/gradle-wrapper.jar + - Place it in `gradle/wrapper/gradle-wrapper.jar` + +2. To remove Maven configuration: + ```bash + rm pom.xml xar-assembly.xml oad.iml + ``` + +3. The XAR package creation task is available as `./gradlew makeXar` (currently a placeholder - additional configuration may be needed for eXist-db specific packaging) + +4. All Maven repository references have been mapped to equivalent Gradle repositories + +## Next Steps + +1. Install Gradle locally or download the wrapper JAR +2. Run `./gradlew build` to compile and test +3. Update `gradle/wrapper/gradle-wrapper.jar` if needed +4. Consider creating a Gradle plugin for XAR package creation (kuberam-expath-plugin equivalent) diff --git a/build.gradle.kts b/build.gradle.kts new file mode 100644 index 0000000..756e71c --- /dev/null +++ b/build.gradle.kts @@ -0,0 +1,258 @@ +plugins { + id("java") + id("maven-publish") + id("base") +} + +group = "org.eeditiones.oad" +version = "1.0.7" +description = "Validate, inspect and convert OpenAPI definitions" + +java { + sourceCompatibility = JavaVersion.VERSION_1_8 + targetCompatibility = JavaVersion.VERSION_1_8 +} + +repositories { + mavenCentral() +} + +dependencies { + implementation("io.swagger.parser.v3:swagger-parser:2.1.19") + implementation("com.fasterxml.jackson.core:jackson-core:2.15.3") + + compileOnly("org.exist-db:exist-core:6.4.1") + + testImplementation("org.exist-db:exist-core:6.4.1") + testImplementation("junit:junit:4.13.2") + testImplementation("org.xmlunit:xmlunit-core:2.9.0") +} + +// Create the xar-resources directory +tasks.register("createXarResources") { + group = "prepare" + description = "Creates the folder for XAR resources" + doLast { + val xarDir = file("$buildDir/xar-resources") + xarDir.mkdirs() + file("$buildDir/xar-resources/content").mkdirs() + } +} + +tasks.register("copyXarResources") { + group = "build" + description = "Copies necessary resources for XAR packaging" + dependsOn(":createXarResources") + from("src/main/xar-resources") + into("$buildDir/xar-resources") +} + +tasks.register("copyXarResources2") { + group = "build" + description = "Copies necessary resources for XAR packaging" + dependsOn(":createXarResources") + from(".") { + include("README.md") + include("LICENSE") + } + into("$buildDir/xar-resources") +} + +tasks.register("cleanXarResources") { + group = "build" + description = "Cleans the XAR resources directory" + delete("$buildDir/xar-resources") +} + +tasks.named("clean") { + dependsOn(":cleanXarResources") +} + +tasks.register("createExpathPackageDescriptor") { + group = "build" + description = "Creates the expath-pkg.xml descriptor for the XAR package" + dependsOn(":createXarResources") + + // Generate expath-pkg.xml + doLast { + file("$buildDir/xar-resources/expath-pkg.xml").writeText(""" + + OpenAPI Definition Utility + https://github.com/eeditiones/oad + +""") + } +} + +tasks.register("createRepoXml") { + group = "build" + description = "Creates the repo.xml descriptor for the XAR package" + dependsOn(":createXarResources") + // Generate repo.xml + doLast { + file("$buildDir/xar-resources/repo.xml").writeText(""" + + + eeditiones.org + https://github.com/eeditiones/oad + beta + GNU Lesser General Public License, version 3.0 + true + library +""") + } +} + +tasks.register("createExistXml") { + dependsOn(":createXarResources") + dependsOn(":jar") + group = "build" + + doLast { + // Hardcoded jar list that must be placed into content/ + val projectJarName = "${project.name}-${project.version}.jar" + val requiredJarNames = listOf( + projectJarName, + "swagger-parser-core-2.1.19.jar", + "swagger-parser-2.1.19.jar", + "swagger-parser-safe-url-resolver-2.1.19.jar", + "swagger-core-2.2.19.jar", + "swagger-parser-v3-2.1.19.jar", + "swagger-models-2.2.19.jar", + "jackson-core-2.15.3.jar", + "jackson-databind-2.15.3.jar", + "jackson-dataformat-yaml-2.15.3.jar", + "jackson-datatype-jsr310-2.15.3.jar", + "snakeyaml-2.2.jar" + ) + + // Generate exist.xml with JAR list + val jarList = requiredJarNames.joinToString("\n") { " $it" } + + file("$buildDir/xar-resources/exist.xml").writeText(""" + + + //eeditiones.org/ns/oad + org.eeditiones.oad.OadModule + +$jarList +""") + + val compiledJar = tasks.named("jar").get().archiveFile.get().asFile + + val runtimeAndProjectJars = configurations.runtimeClasspath.get().files + compiledJar + val selectedJarFiles = requiredJarNames.mapNotNull { name -> runtimeAndProjectJars.find { it.name == name } } + + if (selectedJarFiles.size != requiredJarNames.size) { + val missing = requiredJarNames - selectedJarFiles.map { it.name } + throw GradleException("Missing required JAR(s) for XAR: $missing") + } + + selectedJarFiles.forEach { jarFile -> + copy { + from(jarFile) + into("$buildDir/xar-resources/content") + } + } + + // Also copy all runtime dependencies to content (safe, but not required) + // copy { + // from(configurations.runtimeClasspath.get()) + // into("$buildDir/xar-resources/content") + // } + } + +} + +tasks.register("makeXar") { + dependsOn(":createXarResources") + dependsOn(":copyXarResources") + dependsOn(":copyXarResources2") + dependsOn(":createExpathPackageDescriptor") + dependsOn(":createRepoXml") + dependsOn(":createExistXml") + group = "package" + description = "Creates the XAR package for eXist-db" + + + from("$buildDir/xar-resources") { + include("**/*") + } + + archiveFileName.set("${project.name}-${project.version}.xar") + destinationDirectory.set(file("$buildDir/libs")) +} + +tasks.named("jar") { + manifest { + attributes( + "Implementation-Title" to project.name, + "Implementation-Version" to project.version, + "Implementation-Vendor-Id" to group, + "Implementation-URL" to "https://github.com/eeditiones/oad", + "Source-Repository" to "scm:git:https://github.com/eeditiones/oad.git", + "Description" to project.description + ) + } +} + +sourceSets { + main { + resources { + srcDirs("src/main/xar-resources") + include("**/*") + } + } +} + +publishing { + publications { + create("maven") { + from(components["java"]) + + pom { + name.set(project.name) + description.set(project.description) + url.set("https://github.com/eeditiones/oad") + + organization { + name.set("e-editiones") + url.set("http://eeditiones.org") + } + + licenses { + license { + name.set("GNU Lesser General Public License, version 3.0") + url.set("http://opensource.org/licenses/LGPL-3.0") + distribution.set("repo") + } + } + + scm { + url.set("https://github.com/eeditiones/oad") + connection.set("scm:git:https://github.com/eeditiones/oad.git") + developerConnection.set("scm:git:https://github.com/eeditiones/oad.git") + tag.set("HEAD") + } + + issueManagement { + system.set("GitHub") + url.set("https://github.com/eeditiones/oad/issues") + } + } + } + } +} + +tasks.named("wrapper") { + gradleVersion = "8.5" + distributionType = Wrapper.DistributionType.BIN +} diff --git a/gradle.properties b/gradle.properties new file mode 100644 index 0000000..9f9bb82 --- /dev/null +++ b/gradle.properties @@ -0,0 +1,6 @@ +org.gradle.parallel=true +org.gradle.caching=true +org.gradle.jvmargs=-Xmx2g + +# Project properties +archivesBaseName=oad diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000000000000000000000000000000000000..d997cfc60f4cff0e7451d19d49a82fa986695d07 GIT binary patch literal 48966 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

`Lem|V$A>eR0uN8Y&sdjtu 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

1bvi{DS7E@q1{$_8->K{_OWv2 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 literal 0 HcmV?d00001 diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..1af9e09 --- /dev/null +++ b/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,7 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-8.5-bin.zip +networkTimeout=10000 +validateDistributionUrl=true +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists diff --git a/gradlew b/gradlew new file mode 100755 index 0000000..739907d --- /dev/null +++ b/gradlew @@ -0,0 +1,248 @@ +#!/bin/sh + +# +# Copyright © 2015 the original authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# SPDX-License-Identifier: Apache-2.0 +# + +############################################################################## +# +# Gradle start up script for POSIX generated by Gradle. +# +# Important for running: +# +# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is +# noncompliant, but you have some other compliant shell such as ksh or +# bash, then to run this script, type that shell name before the whole +# command line, like: +# +# ksh Gradle +# +# Busybox and similar reduced shells will NOT work, because this script +# requires all of these POSIX shell features: +# * functions; +# * expansions «$var», «${var}», «${var:-default}», «${var+SET}», +# «${var#prefix}», «${var%suffix}», and «$( cmd )»; +# * compound commands having a testable exit status, especially «case»; +# * various built-in commands including «command», «set», and «ulimit». +# +# Important for patching: +# +# (2) This script targets any POSIX shell, so it avoids extensions provided +# by Bash, Ksh, etc; in particular arrays are avoided. +# +# The "traditional" practice of packing multiple parameters into a +# space-separated string is a well documented source of bugs and security +# problems, so this is (mostly) avoided, by progressively accumulating +# options in "$@", and eventually passing that to Java. +# +# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, +# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; +# see the in-line comments for details. +# +# There are tweaks for specific operating systems such as AIX, CygWin, +# Darwin, MinGW, and NonStop. +# +# (3) This script is generated from the Groovy template +# https://github.com/gradle/gradle/blob/2d6327017519d23b96af35865dc997fcb544fb40/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# within the Gradle project. +# +# You can find Gradle at https://github.com/gradle/gradle/. +# +############################################################################## + +# Attempt to set APP_HOME + +# Resolve links: $0 may be a link +app_path=$0 + +# Need this for daisy-chained symlinks. +while + APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path + [ -h "$app_path" ] +do + ls=$( ls -ld "$app_path" ) + link=${ls#*' -> '} + case $link in #( + /*) app_path=$link ;; #( + *) app_path=$APP_HOME$link ;; + esac +done + +# This is normally unused +# shellcheck disable=SC2034 +APP_BASE_NAME=${0##*/} +# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) +APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD=maximum + +warn () { + echo "$*" +} >&2 + +die () { + echo + echo "$*" + echo + exit 1 +} >&2 + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "$( uname )" in #( + CYGWIN* ) cygwin=true ;; #( + Darwin* ) darwin=true ;; #( + MSYS* | MINGW* ) msys=true ;; #( + NONSTOP* ) nonstop=true ;; +esac + + + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD=$JAVA_HOME/jre/sh/java + else + JAVACMD=$JAVA_HOME/bin/java + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD=java + if ! command -v java >/dev/null 2>&1 + then + die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +fi + +# Increase the maximum file descriptors if we can. +if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then + case $MAX_FD in #( + max*) + # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC2039,SC3045 + MAX_FD=$( ulimit -H -n ) || + warn "Could not query maximum file descriptor limit" + esac + case $MAX_FD in #( + '' | soft) :;; #( + *) + # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC2039,SC3045 + ulimit -n "$MAX_FD" || + warn "Could not set maximum file descriptor limit to $MAX_FD" + esac +fi + +# Collect all arguments for the java command, stacking in reverse order: +# * args from the command line +# * the main class name +# * -classpath +# * -D...appname settings +# * --module-path (only if needed) +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. + +# For Cygwin or MSYS, switch paths to Windows format before running java +if "$cygwin" || "$msys" ; then + APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) + + JAVACMD=$( cygpath --unix "$JAVACMD" ) + + # Now convert the arguments - kludge to limit ourselves to /bin/sh + for arg do + if + case $arg in #( + -*) false ;; # don't mess with options #( + /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath + [ -e "$t" ] ;; #( + *) false ;; + esac + then + arg=$( cygpath --path --ignore --mixed "$arg" ) + fi + # Roll the args list around exactly as many times as the number of + # args, so each arg winds up back in the position where it started, but + # possibly modified. + # + # NB: a `for` loop captures its iteration list before it begins, so + # changing the positional parameters here affects neither the number of + # iterations, nor the values presented in `arg`. + shift # remove old arg + set -- "$@" "$arg" # push replacement arg + done +fi + + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Collect all arguments for the java command: +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, +# and any embedded shellness will be escaped. +# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be +# treated as '${Hostname}' itself on the command line. + +set -- \ + "-Dorg.gradle.appname=$APP_BASE_NAME" \ + -jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \ + "$@" + +# Stop when "xargs" is not available. +if ! command -v xargs >/dev/null 2>&1 +then + die "xargs is not available" +fi + +# Use "xargs" to parse quoted args. +# +# With -n1 it outputs one arg per line, with the quotes and backslashes removed. +# +# In Bash we could simply go: +# +# readarray ARGS < <( xargs -n1 <<<"$var" ) && +# set -- "${ARGS[@]}" "$@" +# +# but POSIX shell has neither arrays nor command substitution, so instead we +# post-process each arg (as a line of input to sed) to backslash-escape any +# character that might be a shell metacharacter, then use eval to reverse +# that process (while maintaining the separation between arguments), and wrap +# the whole thing up as a single "set" statement. +# +# This will of course break if any of these variables contains a newline or +# an unmatched quote. +# + +eval "set -- $( + printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | + xargs -n1 | + sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | + tr '\n' ' ' + )" '"$@"' + +exec "$JAVACMD" "$@" diff --git a/gradlew.bat b/gradlew.bat new file mode 100644 index 0000000..c4bdd3a --- /dev/null +++ b/gradlew.bat @@ -0,0 +1,93 @@ +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem +@rem SPDX-License-Identifier: Apache-2.0 +@rem + +@if "%DEBUG%"=="" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%"=="" set DIRNAME=. +@rem This is normally unused +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if %ERRORLEVEL% equ 0 goto execute + +echo. 1>&2 +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto execute + +echo. 1>&2 +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 + +goto fail + +:execute +@rem Setup the command line + + + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* + +:end +@rem End local scope for the variables with windows NT shell +if %ERRORLEVEL% equ 0 goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +set EXIT_CODE=%ERRORLEVEL% +if %EXIT_CODE% equ 0 set EXIT_CODE=1 +if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% +exit /b %EXIT_CODE% + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/pom.xml b/pom.xml deleted file mode 100644 index c8a05f2..0000000 --- a/pom.xml +++ /dev/null @@ -1,459 +0,0 @@ - - - - 4.0.0 - - - - - - - - - org.eeditiones.oad - oad - 1.0.7-SNAPSHOT - - OpenAPI Definition Utility - Validate, inspect and convert OpenAPI definitions - https://github.com/eeditiones/oad - - - e-editiones - http://eeditiones.org - - - - - GNU Lesser General Public License, version 3.0 - http://opensource.org/licenses/LGPL-3.0 - repo - - - - - https://github.com/eeditiones/oad - scm:git:https://github.com/eeditiones/oad.git - scm:git:https://github.com/eeditiones/oad.git - HEAD - - - - GitHub - https://github.com/eeditiones/oad/issues - - - - UTF-8 - 1.8 - 1.8 - - 6.2.0 - 2.1.19 - - - //eeditiones.org/ns/oad - - //eeditiones.org/ns/oad - OadModule - - ${project.name} - ${project.artifactId} - ${project.artifactId}-${project.version} - - - - - - - junit - junit - 4.13.2 - test - - - - - - - io.swagger.parser.v3 - swagger-parser - ${swagger.version} - - - - org.exist-db - exist-core - ${exist.version} - provided - - - - com.fasterxml.jackson.core - jackson-core - 2.15.2 - runtime - - - - - junit - junit - test - - - org.xmlunit - xmlunit-core - 2.9.0 - test - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - org.apache.maven.plugins - maven-dependency-plugin - 3.2.0 - - - com.mycila - license-maven-plugin - 4.1 - - - com.code54.mojo - buildversion-plugin - 1.0.3 - - - org.codehaus.mojo - xml-maven-plugin - 1.0.2 - - - org.apache.maven.plugins - maven-compiler-plugin - 3.10.0 - - ${project.build.source} - ${project.build.target} - ${project.build.sourceEncoding} - - - - ro.kuberam.maven.plugins - kuberam-expath-plugin - 0.6.3 - - - create-xar - package - - make-xar - - - xar-assembly.xml - ${package-final-name} - - - - - - org.apache.maven.plugins - maven-jar-plugin - 3.2.2 - - - - true - true - - - ${build-tag} - ${build-commit} - ${build-commit-abbrev} - ${build-version} - ${build-tstamp} - ${project.scm.connection} - ${project.description} - ${project.url} - - - - - - - org.apache.maven.plugins - maven-source-plugin - 3.2.1 - - - - true - true - - - ${build-tag} - ${build-commit} - ${build-commit-abbrev} - ${build-version} - ${build-tstamp} - ${project.scm.connection} - ${project.description} - ${project.url} - - - - - - attach-sources - verify - - jar - - - - - - - org.apache.maven.plugins - maven-javadoc-plugin - 3.3.2 - - - attach-javadocs - - jar - - - - - ${project.build.source} - - - true - true - - - ${build-tag} - ${build-commit} - ${build-commit-abbrev} - ${build-version} - ${build-tstamp} - ${project.scm.connection} - ${project.description} - ${project.url} - - - - - - org.apache.maven.plugins - maven-gpg-plugin - 3.0.1 - - - org.apache.maven.plugins - maven-deploy-plugin - 3.1.1 - - true - - - - org.apache.maven.plugins - maven-release-plugin - 2.5.3 - - - org.apache.maven.plugins - maven-changes-plugin - 2.12.1 - - false - false - Type, Summary - ${project.basedir} - - - - - - - - - src/main/xar-resources - true - - templates/** - - - - src/main/xar-resources - false - - templates/** - - - - - - - - org.apache.maven.plugins - maven-jar-plugin - - - - true - true - - - ${build-tag} - ${build-commit} - ${build-commit-abbrev} - ${build-version} - ${build-tstamp} - ${project.scm.connection} - ${project.description} - ${project.url} - - - - - - ro.kuberam.maven.plugins - kuberam-expath-plugin - - - create-xar - package - - make-xar - - - xar-assembly.xml - ${package-final-name} - - - - - - org.apache.maven.plugins - maven-gpg-plugin - - true - - - - org.apache.maven.plugins - maven-release-plugin - - forked-path - - true - v@{project.version} - chore: - - - - - - org.apache.maven.plugins - maven-help-plugin - 3.2.0 - - - - de.jutzig - github-release-plugin - 1.6.0 - - - github-upload - deploy - - release - - - v${project.version} - - v${project.version} - - - ${project.build.directory} - - ${project.artifactId}*.xar - - - - - - - - - - - - - - exist-db - https://repo.evolvedbinary.com/repository/exist-db/ - - true - - - false - - - - exist-db-snapshots - https://repo.evolvedbinary.com/repository/exist-db-snapshots/ - - false - - - true - - - - - - - maven.org - https://maven.org/repo - - - clojars.org - https://clojars.org/repo - - - diff --git a/settings.gradle b/settings.gradle new file mode 100644 index 0000000..908d290 --- /dev/null +++ b/settings.gradle @@ -0,0 +1 @@ +rootProject.name = 'oad' diff --git a/xquery-license-style.xml b/xquery-license-style.xml deleted file mode 100644 index a1484e1..0000000 --- a/xquery-license-style.xml +++ /dev/null @@ -1,13 +0,0 @@ - - - - (: - : - :) - (\s|\t)*\(:.*$ - .*:\)(\s|\t)*$ - false - true - false - - \ No newline at end of file From b8595f64ec750e8f24402e43e39cb999a756c358 Mon Sep 17 00:00:00 2001 From: Juri Leino Date: Thu, 26 Mar 2026 23:49:01 +0100 Subject: [PATCH 3/5] build: remove maven publishing + cleanup The jar that is compiled is not needed to be published to maven central. --- build.gradle.kts | 45 --------------------------------------------- 1 file changed, 45 deletions(-) diff --git a/build.gradle.kts b/build.gradle.kts index 756e71c..39aa44a 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -1,6 +1,5 @@ plugins { id("java") - id("maven-publish") id("base") } @@ -162,12 +161,6 @@ $jarList into("$buildDir/xar-resources/content") } } - - // Also copy all runtime dependencies to content (safe, but not required) - // copy { - // from(configurations.runtimeClasspath.get()) - // into("$buildDir/xar-resources/content") - // } } } @@ -213,44 +206,6 @@ sourceSets { } } -publishing { - publications { - create("maven") { - from(components["java"]) - - pom { - name.set(project.name) - description.set(project.description) - url.set("https://github.com/eeditiones/oad") - - organization { - name.set("e-editiones") - url.set("http://eeditiones.org") - } - - licenses { - license { - name.set("GNU Lesser General Public License, version 3.0") - url.set("http://opensource.org/licenses/LGPL-3.0") - distribution.set("repo") - } - } - - scm { - url.set("https://github.com/eeditiones/oad") - connection.set("scm:git:https://github.com/eeditiones/oad.git") - developerConnection.set("scm:git:https://github.com/eeditiones/oad.git") - tag.set("HEAD") - } - - issueManagement { - system.set("GitHub") - url.set("https://github.com/eeditiones/oad/issues") - } - } - } - } -} tasks.named("wrapper") { gradleVersion = "8.5" From 693c862c515f0c79622d06d2240be573fd846491 Mon Sep 17 00:00:00 2001 From: Juri Leino Date: Sun, 29 Mar 2026 16:33:18 +0200 Subject: [PATCH 4/5] build: finalize switch to gradle; use xar plugin - remove migration file and the last maven settings file - read project version from gradle.properties --- .tool-versions | 2 +- GRADLE_MIGRATION.md | 89 -------------------- README.md | 48 ++++++----- build.gradle.kts | 195 +++++++------------------------------------- gradle.properties | 1 + settings.gradle | 8 ++ xar-assembly.xml | 111 ------------------------- 7 files changed, 68 insertions(+), 386 deletions(-) delete mode 100644 GRADLE_MIGRATION.md delete mode 100644 xar-assembly.xml diff --git a/.tool-versions b/.tool-versions index e19316f..f86c714 100644 --- a/.tool-versions +++ b/.tool-versions @@ -1 +1 @@ -java zulu-8.68.0.21 +java zulu-21.40.17 diff --git a/GRADLE_MIGRATION.md b/GRADLE_MIGRATION.md deleted file mode 100644 index 1d71b5c..0000000 --- a/GRADLE_MIGRATION.md +++ /dev/null @@ -1,89 +0,0 @@ -# Gradle Migration Guide - -This project has been configured for Gradle build system. The following files have been created: - -## Files Created - -1. **build.gradle** - Main Gradle build configuration with: - - Java plugin configuration (Java 1.8) - - Dependencies from Maven pom.xml - - JAR manifest configuration - - Publishing configuration for Maven artifacts - - XAR resource file handling - -2. **settings.gradle** - Gradle settings file with project name - -3. **gradle.properties** - Gradle configuration properties - -4. **gradlew** - Unix/Linux/Mac Gradle wrapper script - -5. **gradlew.bat** - Windows Gradle wrapper script - -6. **gradle/wrapper/gradle-wrapper.properties** - Gradle wrapper configuration - -## Building the Project - -### Using Gradle wrapper (recommended): - -```bash -./gradlew build -``` - -### Using installed Gradle: - -```bash -gradle build -``` - -## Available Tasks - -- `./gradlew build` - Compile, test, and package the project -- `./gradlew test` - Run unit tests -- `./gradlew jar` - Create JAR file -- `./gradlew javadoc` - Generate JavaDoc -- `./gradlew clean` - Remove build artifacts -- `./gradlew publish` - Publish to Maven repositories - -## Configuration Notes - -The build.gradle includes: - -- **Source compatibility**: Java 1.8 -- **Target compatibility**: Java 1.8 -- **Main dependencies**: - - io.swagger.parser.v3:swagger-parser:2.1.19 - - com.fasterxml.jackson.core:jackson-core:2.15.2 - - org.exist-db:exist-core:6.2.0 (provided) - -- **Test dependencies**: - - junit:junit:4.13.2 - - org.xmlunit:xmlunit-core:2.9.0 - -- **Repositories**: - - Maven Central - - eXist-db repository (https://repo.exist-db.oorg/repository/exist-db/) - -- **Publishing**: Configured for Maven publication with complete POM metadata - -## Important Notes - -1. The wrapper JAR needs to be downloaded. You can either: - - Run `gradle wrapper` if you have Gradle installed locally - - Download the jar from: https://github.com/gradle/gradle/releases/download/v8.5/gradle-wrapper.jar - - Place it in `gradle/wrapper/gradle-wrapper.jar` - -2. To remove Maven configuration: - ```bash - rm pom.xml xar-assembly.xml oad.iml - ``` - -3. The XAR package creation task is available as `./gradlew makeXar` (currently a placeholder - additional configuration may be needed for eXist-db specific packaging) - -4. All Maven repository references have been mapped to equivalent Gradle repositories - -## Next Steps - -1. Install Gradle locally or download the wrapper JAR -2. Run `./gradlew build` to compile and test -3. Update `gradle/wrapper/gradle-wrapper.jar` if needed -4. Consider creating a Gradle plugin for XAR package creation (kuberam-expath-plugin equivalent) diff --git a/README.md b/README.md index 9267b51..187e8f8 100644 --- a/README.md +++ b/README.md @@ -8,19 +8,29 @@ This is a wrapper around the [Swagger-Parser](https://github.com/swagger-api/swa ## Installation -1. Download a pre-built XAR from the [releases](https://github.com/eeditiones/oad/releases/latest) -2. Install the package in your exist-db instance -3. Restart the database -4. Try - ```xquery - import module namespace oad="//eeditiones.org/ns/oad"; +Built packages of OAD are available on [GitHub releases](https://github.com/eeditiones/oad/releases/latest) +and the [public package repository](https://exist-db.org/exist/apps/public-repo/packages/oad?eXist-db-min-version=6.2.0). - oad:report('https://petstore3.swagger.io/api/v3/openapi.json') - ``` +You can visit one of the pages, download the XAR and then install it in your exist-db instance using the package manager. +Alternatively, you can run the [xst](https://github.com/exist-db/xst) command below. +```sh +xst packages install from-registry oad +``` + +**NOTE:** This XAR comes with addtional JAR files that need to be available at runtime. +It is safer to restart the database after installation - or upgrade - in order to make sure that +the classpath was updated. ## Usage +Try +```xquery +import module namespace oad="//eeditiones.org/ns/oad"; + +oad:report('https://petstore3.swagger.io/api/v3/openapi.json') +``` + All module functions assume you have a API specification _stored_ in exist or available via HTTP. The spec can be YAML or JSON. It allows to work with internal and external references. @@ -129,19 +139,19 @@ The available options are listed in the table below: ## Build * Requirements - * Java 8 - * Apache Maven 3.3+ + * Java 17 + * Gradle 8.5 ```bash -mvn package +./gradlew build ``` -will create a `oad-.xar` file in the `target/` sub-folder. +will create a `oad-.xar` file in the folder `build/libs`. ## Tests There are no unit tests that will be executed when building the project. -The build package does include [xqsuite tests](src/main/xar-resources/xqsuite/oad-test.xqm) which test the integration +The built package does include [xqsuite tests](src/main/xar-resources/xqsuite/oad-test.xqm) which test the integration into exist-db works as intended. You can call that as part of your development workflow by running @@ -152,12 +162,12 @@ You can call that as part of your development workflow by running ## Release -```bash -mvn release:prepare -``` -```bash -mvn release:perform -``` +- Update the version in build.gradle.kts +- commit, tag and push the new version +- build the project +- draft a new release and add the XAR file to it + +**NOTE:** The release process is work in progress after switching to gradle as the build tool. ## Sponsors diff --git a/build.gradle.kts b/build.gradle.kts index 39aa44a..4fac990 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -1,10 +1,13 @@ +import org.existdb.plugin.PackageType.LIBRARY +import org.existdb.plugin.exist6only + plugins { id("java") id("base") + id("org.exist-db.plugin.xar") version "1.0.0" } group = "org.eeditiones.oad" -version = "1.0.7" description = "Validate, inspect and convert OpenAPI definitions" java { @@ -27,161 +30,31 @@ dependencies { testImplementation("org.xmlunit:xmlunit-core:2.9.0") } -// Create the xar-resources directory -tasks.register("createXarResources") { - group = "prepare" - description = "Creates the folder for XAR resources" - doLast { - val xarDir = file("$buildDir/xar-resources") - xarDir.mkdirs() - file("$buildDir/xar-resources/content").mkdirs() - } -} - -tasks.register("copyXarResources") { - group = "build" - description = "Copies necessary resources for XAR packaging" - dependsOn(":createXarResources") - from("src/main/xar-resources") - into("$buildDir/xar-resources") -} - -tasks.register("copyXarResources2") { - group = "build" - description = "Copies necessary resources for XAR packaging" - dependsOn(":createXarResources") - from(".") { - include("README.md") - include("LICENSE") - } - into("$buildDir/xar-resources") -} - -tasks.register("cleanXarResources") { - group = "build" - description = "Cleans the XAR resources directory" - delete("$buildDir/xar-resources") -} - -tasks.named("clean") { - dependsOn(":cleanXarResources") -} - -tasks.register("createExpathPackageDescriptor") { - group = "build" - description = "Creates the expath-pkg.xml descriptor for the XAR package" - dependsOn(":createXarResources") - - // Generate expath-pkg.xml - doLast { - file("$buildDir/xar-resources/expath-pkg.xml").writeText(""" - - OpenAPI Definition Utility - https://github.com/eeditiones/oad - -""") - } -} - -tasks.register("createRepoXml") { - group = "build" - description = "Creates the repo.xml descriptor for the XAR package" - dependsOn(":createXarResources") - // Generate repo.xml - doLast { - file("$buildDir/xar-resources/repo.xml").writeText(""" - - - eeditiones.org - https://github.com/eeditiones/oad - beta - GNU Lesser General Public License, version 3.0 - true - library -""") - } -} - -tasks.register("createExistXml") { - dependsOn(":createXarResources") - dependsOn(":jar") - group = "build" - - doLast { - // Hardcoded jar list that must be placed into content/ - val projectJarName = "${project.name}-${project.version}.jar" - val requiredJarNames = listOf( - projectJarName, - "swagger-parser-core-2.1.19.jar", - "swagger-parser-2.1.19.jar", - "swagger-parser-safe-url-resolver-2.1.19.jar", - "swagger-core-2.2.19.jar", - "swagger-parser-v3-2.1.19.jar", - "swagger-models-2.2.19.jar", - "jackson-core-2.15.3.jar", - "jackson-databind-2.15.3.jar", - "jackson-dataformat-yaml-2.15.3.jar", - "jackson-datatype-jsr310-2.15.3.jar", - "snakeyaml-2.2.jar" - ) - - // Generate exist.xml with JAR list - val jarList = requiredJarNames.joinToString("\n") { " $it" } - - file("$buildDir/xar-resources/exist.xml").writeText(""" - - - //eeditiones.org/ns/oad - org.eeditiones.oad.OadModule - -$jarList -""") - - val compiledJar = tasks.named("jar").get().archiveFile.get().asFile - - val runtimeAndProjectJars = configurations.runtimeClasspath.get().files + compiledJar - val selectedJarFiles = requiredJarNames.mapNotNull { name -> runtimeAndProjectJars.find { it.name == name } } - - if (selectedJarFiles.size != requiredJarNames.size) { - val missing = requiredJarNames - selectedJarFiles.map { it.name } - throw GradleException("Missing required JAR(s) for XAR: $missing") - } - - selectedJarFiles.forEach { jarFile -> - copy { - from(jarFile) - into("$buildDir/xar-resources/content") - } - } - } - -} - -tasks.register("makeXar") { - dependsOn(":createXarResources") - dependsOn(":copyXarResources") - dependsOn(":copyXarResources2") - dependsOn(":createExpathPackageDescriptor") - dependsOn(":createRepoXml") - dependsOn(":createExistXml") - group = "package" - description = "Creates the XAR package for eXist-db" - - - from("$buildDir/xar-resources") { - include("**/*") - } - - archiveFileName.set("${project.name}-${project.version}.xar") - destinationDirectory.set(file("$buildDir/libs")) +xar { + namespace = "//eeditiones.org/ns/oad" + abbrev = "oad" + title = "OpenAPI Definition Utility" + description = "OpenAPI Definition Utility for eXist-db" + author = "The e-editiones contributors" + home = "https://github.com/eeditiones/oad" + status = "beta" + license = "GNU Lesser General Public License, version 3.0" + type = LIBRARY + javaClass = "org.eeditiones.oad.OadModule" + requiredJars = listOf( + "swagger-parser-core-2.1.19.jar", + "swagger-parser-2.1.19.jar", + "swagger-parser-safe-url-resolver-2.1.19.jar", + "swagger-core-2.2.19.jar", + "swagger-parser-v3-2.1.19.jar", + "swagger-models-2.2.19.jar", + "jackson-core-2.15.3.jar", + "jackson-databind-2.15.3.jar", + "jackson-dataformat-yaml-2.15.3.jar", + "jackson-datatype-jsr310-2.15.3.jar", + "snakeyaml-2.2.jar" + ) + processorDependencies = listOf( exist6only ) } tasks.named("jar") { @@ -197,16 +70,6 @@ tasks.named("jar") { } } -sourceSets { - main { - resources { - srcDirs("src/main/xar-resources") - include("**/*") - } - } -} - - tasks.named("wrapper") { gradleVersion = "8.5" distributionType = Wrapper.DistributionType.BIN diff --git a/gradle.properties b/gradle.properties index 9f9bb82..e162ea5 100644 --- a/gradle.properties +++ b/gradle.properties @@ -4,3 +4,4 @@ org.gradle.jvmargs=-Xmx2g # Project properties archivesBaseName=oad +version=1.0.8 diff --git a/settings.gradle b/settings.gradle index 908d290..e45d172 100644 --- a/settings.gradle +++ b/settings.gradle @@ -1 +1,9 @@ +pluginManagement { + repositories { + mavenLocal() + gradlePluginPortal() + mavenCentral() + } +} + rootProject.name = 'oad' diff --git a/xar-assembly.xml b/xar-assembly.xml deleted file mode 100644 index 264ccab..0000000 --- a/xar-assembly.xml +++ /dev/null @@ -1,111 +0,0 @@ - - - ${package-title} - eeditiones.org - ${project.url} - GNU Lesser General Public License, version 3.0 - true - - - library - - - beta - - ${project.artifactId} - library - Libraries - - - - - - - ${basedir} - - README.md - LICENSE - - - - ${basedir}/src/main/xar-resources - - - - - - - ${project.groupId} - ${project.artifactId} - ${project.version} - - - io.swagger.parser.v3 - swagger-parser - ${swagger.version} - - - - - io.swagger.core.v3 - swagger-core - 2.2.19 - - - io.swagger.core.v3 - swagger-models - 2.2.19 - - - io.swagger.parser.v3 - swagger-parser-v3 - ${swagger.version} - - - io.swagger.parser.v3 - swagger-parser-core - ${swagger.version} - - - io.swagger.parser.v3 - swagger-parser-safe-url-resolver - ${swagger.version} - - - com.fasterxml.jackson.core - jackson-core - 2.15.2 - - - com.fasterxml.jackson.core - jackson-databind - 2.15.3 - - - com.fasterxml.jackson.dataformat - jackson-dataformat-yaml - 2.15.3 - - - com.fasterxml.jackson.datatype - jackson-datatype-jsr310 - 2.15.2 - - - org.yaml - snakeyaml - 2.2 - - - - - - - - ${module.namespace} - urn:java:class:org.eeditiones.oad.${module.java.classname} - - - - From d07f7fee1d6d5141b3e3aa357673c706f2adcd11 Mon Sep 17 00:00:00 2001 From: Juri Leino Date: Sun, 29 Mar 2026 22:38:12 +0200 Subject: [PATCH 5/5] build: use gradle 9.4.1 Drop manifest attributes that are nullable --- build.gradle.kts | 8 +++----- gradle/wrapper/gradle-wrapper.properties | 2 +- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/build.gradle.kts b/build.gradle.kts index 4fac990..0d3e63d 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -61,16 +61,14 @@ tasks.named("jar") { manifest { attributes( "Implementation-Title" to project.name, - "Implementation-Version" to project.version, - "Implementation-Vendor-Id" to group, + "Implementation-Version" to project.version.toString(), "Implementation-URL" to "https://github.com/eeditiones/oad", - "Source-Repository" to "scm:git:https://github.com/eeditiones/oad.git", - "Description" to project.description + "Source-Repository" to "scm:git:https://github.com/eeditiones/oad.git" ) } } tasks.named("wrapper") { - gradleVersion = "8.5" + gradleVersion = "9.4.1" distributionType = Wrapper.DistributionType.BIN } diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 1af9e09..c61a118 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.5-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-9.4.1-bin.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME