diff --git a/.docker/Dockerfile b/.docker/Dockerfile index 057115f1..1d9e57fc 100644 --- a/.docker/Dockerfile +++ b/.docker/Dockerfile @@ -14,6 +14,8 @@ LABEL org.opencontainers.image.version=${VERSION} # BUILD STAGE 1: install minimal Java environment + curl & zip for SHACL API +RUN apk add --no-cache binutils maven curl zip + # Create a custom Java runtime RUN $JAVA_HOME/bin/jlink \ --add-modules java.base,java.compiler,java.desktop,java.management,java.naming,java.net.http,java.rmi,java.scripting,java.security.jgss,java.security.sasl,java.sql,java.xml.crypto,jdk.unsupported \ @@ -23,8 +25,6 @@ RUN $JAVA_HOME/bin/jlink \ --compress=2 \ --output /javaruntime -RUN apk add maven curl zip - # Compile with maven, extract binaries and copy into image COPY . /app RUN mvn versions:set -DnewVersion=${VERSION} && mvn package -Dmaven.test.skip=true @@ -32,7 +32,7 @@ RUN unzip target/shacl-${VERSION}-bin.zip -d /app/ # BUILD STAGE 2: keep only Java and SHACL -FROM alpine:3.20.3 +FROM alpine:3.21.2 ARG VERSION diff --git a/.docker/entrypoint.sh b/.docker/entrypoint.sh index 856b2ae9..38ac459a 100644 --- a/.docker/entrypoint.sh +++ b/.docker/entrypoint.sh @@ -23,6 +23,14 @@ PARAMETER: input to be validated (only .ttl format supported) -shapesfile /data/myshapes.ttl [OPTIONAL] shapes for validation (only .ttl format supported) + -maxiterations 1 [OPTIONAL] - default is 1 + iteratively applies the inference rules until the maximum number of iterations is reached (or no new triples are inferred) + -validateShapes [OPTIONAL] + in case you want to include the metashapes (from the tosh namespace in particular) + -addBlankNodes [OPTIONAL] + adds the blank nodes to the validation report + -noImports [OPTIONAL] + disables the import of external ontologies EOF exit 1 fi diff --git a/.github/workflows/docker-image.yml b/.github/workflows/docker-image.yml index 1ba1706d..aa7af16d 100644 --- a/.github/workflows/docker-image.yml +++ b/.github/workflows/docker-image.yml @@ -39,11 +39,11 @@ jobs: uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - name: Set up QEMU - uses: docker/setup-qemu-action@49b3bc8e6bdd4a60e6116a5414239cba5943d3cf # v3.2.0 + uses: docker/setup-qemu-action@53851d14592bedcffcf25ea515637cff71ef929a # v3.3.0 - name: Set up Docker Buildx id: buildx - uses: docker/setup-buildx-action@c47758b77c9736f4b2ef4073d4d51994fabfe349 # v3.7.1 + uses: docker/setup-buildx-action@6524bf65af31da8d45b59e8c27de4bd072b392f5 # v3.8.0 # inspired by https://github.com/reloc8/action-latest-release-version - name: Get release version @@ -76,7 +76,7 @@ jobs: - name: Build and push Docker image for x86 and arm64 id: build - uses: docker/build-push-action@4f58ea79222b3b9dc2c8bbdd6debcef730109a75 # v6.9.0 + uses: docker/build-push-action@b32b51a8eda65d6793cd0494a773d4f6bcef32dc # v6.11.0 with: file: .docker/Dockerfile push: true @@ -93,7 +93,7 @@ jobs: digest="${{ steps.build.outputs.digest }}" touch "/tmp/digests/${digest#sha256:}" - name: Upload digest - uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3 + uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0 with: name: digests-${{ matrix.package }} path: /tmp/digests/* @@ -131,7 +131,7 @@ jobs: merge-multiple: true - name: Set up Docker Buildx - uses: docker/setup-buildx-action@c47758b77c9736f4b2ef4073d4d51994fabfe349 # v3.7.1 + uses: docker/setup-buildx-action@6524bf65af31da8d45b59e8c27de4bd072b392f5 # v3.8.0 - name: Extract metadata (tags, labels) for Docker id: meta diff --git a/.github/workflows/maven-test-pr.yml b/.github/workflows/maven-test-pr.yml index d42d8301..b2dd0259 100644 --- a/.github/workflows/maven-test-pr.yml +++ b/.github/workflows/maven-test-pr.yml @@ -15,7 +15,7 @@ jobs: steps: - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - name: Set up JDK - uses: actions/setup-java@8df1039502a15bceb9433410b1a100fbe190c53b # v4.5.0 + uses: actions/setup-java@7a6d8a8234af8eb26422e24e3006232cccaa061b # v4.6.0 with: distribution: ${{ matrix.distribution }} java-version: ${{ matrix.java }} diff --git a/README.md b/README.md index c3a3fea6..8d71f07d 100644 --- a/README.md +++ b/README.md @@ -53,7 +53,7 @@ The binary distribution is: `https://repo1.maven.org/maven2/org/topbraid/shacl/*VER*/shacl-*VER*-bin.zip`. -Two command line utilities are included: shaclvalidate (performs constraint validation) and shaclinfer (performs SHACL rule inferencing). +Two command line utilities are included: `shaclvalidate` (performs constraint validation) and `shaclinfer` (performs SHACL rule inferencing). To use them, set up your environment similar to https://jena.apache.org/documentation/tools/ (note that the SHACL download includes Jena). @@ -72,17 +72,13 @@ export SHACLROOT=/home/holger/shacl/shacl-1.4.3-bin/shacl-1.4.3/bin export PATH=$SHACLROOT:$PATH ``` -Both tools take the following parameters, for example: +After setting up the environment, you can run the command line utilities (i.e. validation) using the following command: -`shaclvalidate.bat -datafile myfile.ttl -shapesfile myshapes.ttl` +- Windows: `shaclvalidate.bat -datafile myfile.ttl -shapesfile myshapes.ttl` -where `-shapesfile` is optional and falls back to using the data graph as shapes graph. -Add -validateShapes in case you want to include the metashapes (from the tosh namespace in particular). +- Linux/Unix: `shaclvalidate.sh -datafile myfile.ttl -shapesfile myshapes.ttl` -For the shaclinfer tool, you can use the `-maxiterations` argument to apply SHACL rule inferencing multiple times; this will add inferred results back to the data graph to see if further triples can be inferred. -The tool will iterate until either (a) the maximum number of iterations is reached, or (b) no new triples are inferred. The flag is optional and defaults to `1` (single iteration). - -Currently only Turtle (.ttl) files are supported. +Both tools (Windows, Linux) take the parameters described in the [Dockerfile Usage](#dockerfile-usage) section. **Currently, only Turtle (.ttl) files are supported.** The tools print the validation report or the inferences graph to the output screen. @@ -113,15 +109,23 @@ Any other command after `ghcr.io/topquadrant/shacl:1.4.3` will print the followi Please use this docker image as follows: docker run -v /path/to/data:/data ghcr.io/topquadrant/shacl:1.4.3 [COMMAND] [PARAMETERS] COMMAND: - validate - to run validation - infer - to run rule inferencing + validate + to run validation + infer + to run rule inferencing PARAMETERS: - -datafile /data/myfile.ttl [MANDATORY] - input to be validated (only .ttl format supported) - -shapesfile /data/myshapes.ttl [OPTIONAL] - shapes for validation (only .ttl format supported) + -datafile /data/myfile.ttl [MANDATORY] + input to be validated (only .ttl format supported) + -shapesfile /data/myshapes.ttl [OPTIONAL] + shapes for validation (only .ttl format supported) + -maxiterations 1 [OPTIONAL] - default is 1 + iteratively applies the inference rules until the maximum number of iterations is reached (or no new triples are inferred) + -validateShapes [OPTIONAL] + in case you want to include the metashapes (from the tosh namespace in particular) + -addBlankNodes [OPTIONAL] + adds the blank nodes to the validation report + -noImports [OPTIONAL] + disables the import of external ontologies ``` If you'd like to build the image locally in an `x86` architecture, use: @@ -134,4 +138,4 @@ If your architecture is `arm`, use: ``` docker build -f .docker/Dockerfile -t ghcr.io/topquadrant/shacl:1.4.3 --build-arg VERSION=1.4.3 --build-arg ARCH_BASE=amazoncorretto:11-alpine3.18-jdk . -``` +``` \ No newline at end of file diff --git a/pom.xml b/pom.xml index 902f47cd..7b0c8233 100644 --- a/pom.xml +++ b/pom.xml @@ -70,8 +70,8 @@ UTF-8 5.2.0 4.13.2 - 1.7.36 - 2.20.0 + 2.0.16 + 2.24.3 17 @@ -99,7 +99,7 @@ org.apache.logging.log4j - log4j-slf4j-impl + log4j-slf4j2-impl ${ver.log4j2} true @@ -210,7 +210,7 @@ org.apache.maven.plugins maven-javadoc-plugin - 3.11.1 + 3.11.2 attach-javadocs diff --git a/src/main/java/org/topbraid/shacl/tools/AbstractTool.java b/src/main/java/org/topbraid/shacl/tools/AbstractTool.java index c0753086..02004c91 100644 --- a/src/main/java/org/topbraid/shacl/tools/AbstractTool.java +++ b/src/main/java/org/topbraid/shacl/tools/AbstractTool.java @@ -16,11 +16,6 @@ */ package org.topbraid.shacl.tools; -import java.io.File; -import java.io.FileInputStream; -import java.io.IOException; -import java.io.InputStream; - import org.apache.jena.ontology.OntDocumentManager; import org.apache.jena.ontology.OntModel; import org.apache.jena.ontology.OntModelSpec; @@ -34,78 +29,78 @@ import org.topbraid.shacl.vocabulary.SH; import org.topbraid.shacl.vocabulary.TOSH; +import java.io.*; + class AbstractTool { - private final static String DATA_FILE = "-datafile"; - - private final static String SHAPES_FILE = "-shapesfile"; - - private final static String MAX_ITERATIONS = "-maxiterations"; - - - private OntDocumentManager dm = new OntDocumentManager(); - - private OntModelSpec spec = new OntModelSpec(OntModelSpec.OWL_MEM); - - - AbstractTool() { - - InputStream shaclTTL = SHACLSystemModel.class.getResourceAsStream("/rdf/shacl.ttl"); - Model shacl = JenaUtil.createMemoryModel(); - shacl.read(shaclTTL, SH.BASE_URI, FileUtils.langTurtle); - shacl.add(SystemTriples.getVocabularyModel()); - dm.addModel(SH.BASE_URI, shacl); - - InputStream dashTTL = SHACLSystemModel.class.getResourceAsStream("/rdf/dash.ttl"); - Model dash = JenaUtil.createMemoryModel(); - dash.read(dashTTL, SH.BASE_URI, FileUtils.langTurtle); - dm.addModel(DASH.BASE_URI, dash); - - InputStream toshTTL = SHACLSystemModel.class.getResourceAsStream("/rdf/tosh.ttl"); - Model tosh = JenaUtil.createMemoryModel(); - tosh.read(toshTTL, SH.BASE_URI, FileUtils.langTurtle); - dm.addModel(TOSH.BASE_URI, tosh); - - spec.setDocumentManager(dm); - } - - protected int getMaxIterations(String[] args) { - for(int i = 0; i < args.length - 1; i++) { - if(MAX_ITERATIONS.equals(args[i])) { - return Integer.parseInt(args[i + 1]); - } - } - return 1; - } - - protected Model getDataModel(String[] args) throws IOException { - for(int i = 0; i < args.length - 1; i++) { - if(DATA_FILE.equals(args[i])) { - String dataFileName = args[i + 1]; - OntModel dataModel = ModelFactory.createOntologyModel(spec); - File file = new File(dataFileName); - String lang = FileUtils.langTurtle; - dataModel.read(new FileInputStream(file), "urn:x:base", lang); - return dataModel; - } - } - System.err.println("Missing -datafile, e.g.: -datafile myfile.ttl"); - System.exit(0); - return null; - } - - - protected Model getShapesModel(String[] args) throws IOException { - for(int i = 0; i < args.length - 1; i++) { - if(SHAPES_FILE.equals(args[i])) { - String fileName = args[i + 1]; - OntModel model = ModelFactory.createOntologyModel(spec); - File file = new File(fileName); - String lang = FileUtils.langTurtle; - model.read(new FileInputStream(file), "urn:x:base", lang); - return model; - } - } - return null; - } -} + private final static String DATA_FILE = "-datafile"; + + private final static String SHAPES_FILE = "-shapesfile"; + + private final static String MAX_ITERATIONS = "-maxiterations"; + + protected final OntDocumentManager dm = new OntDocumentManager(); + + private OntModelSpec spec = new OntModelSpec(OntModelSpec.OWL_MEM); + + + AbstractTool() { + + InputStream shaclTTL = SHACLSystemModel.class.getResourceAsStream("/rdf/shacl.ttl"); + Model shacl = JenaUtil.createMemoryModel(); + shacl.read(shaclTTL, SH.BASE_URI, FileUtils.langTurtle); + shacl.add(SystemTriples.getVocabularyModel()); + dm.addModel(SH.BASE_URI, shacl); + + InputStream dashTTL = SHACLSystemModel.class.getResourceAsStream("/rdf/dash.ttl"); + Model dash = JenaUtil.createMemoryModel(); + dash.read(dashTTL, SH.BASE_URI, FileUtils.langTurtle); + dm.addModel(DASH.BASE_URI, dash); + + InputStream toshTTL = SHACLSystemModel.class.getResourceAsStream("/rdf/tosh.ttl"); + Model tosh = JenaUtil.createMemoryModel(); + tosh.read(toshTTL, SH.BASE_URI, FileUtils.langTurtle); + dm.addModel(TOSH.BASE_URI, tosh); + + spec.setDocumentManager(dm); + } + + protected int getMaxIterations(String[] args) { + for (int i = 0; i < args.length - 1; i++) { + if (MAX_ITERATIONS.equals(args[i])) { + return Integer.parseInt(args[i + 1]); + } + } + return 1; + } + + protected Model getDataModel(String[] args) throws IOException { + for (int i = 0; i < args.length - 1; i++) { + if (DATA_FILE.equals(args[i])) { + return getModel(args, i); + } + } + System.err.println("Missing -datafile, e.g.: -datafile myfile.ttl"); + System.exit(0); + return null; + } + + protected Model getShapesModel(String[] args) throws IOException { + for (int i = 0; i < args.length - 1; i++) { + if (SHAPES_FILE.equals(args[i])) { + return getModel(args, i); + } + } + return null; + } + + private Model getModel(String[] args, int i) throws FileNotFoundException { + String fileName = args[i + 1]; + OntModel dataModel = ModelFactory.createOntologyModel(spec); + File file = new File(fileName); + String lang = FileUtils.langTurtle; + dataModel.read(new FileInputStream(file), "urn:x:base", lang); + return dataModel; + } + +} \ No newline at end of file diff --git a/src/main/java/org/topbraid/shacl/tools/Validate.java b/src/main/java/org/topbraid/shacl/tools/Validate.java index 93943aa3..b60e2250 100644 --- a/src/main/java/org/topbraid/shacl/tools/Validate.java +++ b/src/main/java/org/topbraid/shacl/tools/Validate.java @@ -16,11 +16,6 @@ */ package org.topbraid.shacl.tools; -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.io.PrintStream; -import java.util.Arrays; - import org.apache.jena.rdf.model.Model; import org.apache.jena.rdf.model.Resource; import org.apache.jena.util.FileUtils; @@ -28,48 +23,61 @@ import org.topbraid.shacl.validation.ValidationUtil; import org.topbraid.shacl.vocabulary.SH; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.PrintStream; +import java.util.Arrays; + /** * Stand-alone utility to perform constraint validation of a given file. - * + *

* Example arguments: - * - * -datafile my.ttl - * + *

+ * -datafile my.ttl + * * @author Holger Knublauch */ public class Validate extends AbstractTool { - - public static void main(String[] args) throws IOException { - // Temporarily redirect system.err to avoid SLF4J warning - PrintStream oldPS = System.err; - System.setErr(new PrintStream(new ByteArrayOutputStream())); - Validate validate = new Validate(); - System.setErr(oldPS); - validate.run(args); - } - - - private void run(String[] args) throws IOException { - Model dataModel = getDataModel(args); - Model shapesModel = getShapesModel(args); - if(shapesModel == null) { - shapesModel = dataModel; - } - boolean validateShapes = Arrays.asList(args).contains("-validateShapes"); - boolean addBlankNodes = Arrays.asList(args).contains("-addBlankNodes"); - Resource report = ValidationUtil.validateModel(dataModel, shapesModel, validateShapes); - // If addBlankNodes is true, then the code will walk through the report and add the blank nodes to the report - if(addBlankNodes) { - Model referencedNodes = BlankNodeFinder.findBlankNodes(report.getModel(),shapesModel); - report.getModel().add(referencedNodes); - } + private final static String VALIDATE_SHAPES = "-validateShapes"; + private final static String ADD_BLANK_NODES = "-addBlankNodes"; + private final static String NO_IMPORTS = "-noImports"; + + public static void main(String[] args) throws IOException { + // Temporarily redirect system.err to avoid SLF4J warning + PrintStream oldPS = System.err; + System.setErr(new PrintStream(new ByteArrayOutputStream())); + Validate validate = new Validate(); + System.setErr(oldPS); + validate.run(args); + } + + private void run(String[] args) throws IOException { + Model dataModel = getDataModel(args); + Model shapesModel = getShapesModel(args); + if (shapesModel == null) { + shapesModel = dataModel; + } + boolean validateShapes = Arrays.asList(args).contains(VALIDATE_SHAPES); + boolean addBlankNodes = Arrays.asList(args).contains(ADD_BLANK_NODES); + boolean noImports = Arrays.asList(args).contains(NO_IMPORTS); + if (noImports) { + dm.setProcessImports(false); + } + Resource report = ValidationUtil.validateModel(dataModel, shapesModel, validateShapes); + + if (addBlankNodes) { + // If addBlankNodes is true, then the code will walk through the report and add the blank nodes to the report + Model referencedNodes = BlankNodeFinder.findBlankNodes(report.getModel(), shapesModel); + report.getModel().add(referencedNodes); + } + + report.getModel().write(System.out, FileUtils.langTurtle); - report.getModel().write(System.out, FileUtils.langTurtle); + if (report.hasProperty(SH.conforms, JenaDatatypes.FALSE)) { + // See https://github.com/TopQuadrant/shacl/issues/56 + System.exit(1); + } + } - if(report.hasProperty(SH.conforms, JenaDatatypes.FALSE)) { - // See https://github.com/TopQuadrant/shacl/issues/56 - System.exit(1); - } - } -} +} \ No newline at end of file diff --git a/src/test/java/org/topbraid/shacl/ValidationExample.java b/src/test/java/org/topbraid/shacl/ValidationExample.java index e1b9ddf6..83f79bb5 100644 --- a/src/test/java/org/topbraid/shacl/ValidationExample.java +++ b/src/test/java/org/topbraid/shacl/ValidationExample.java @@ -25,20 +25,20 @@ public class ValidationExample { - /** - * Loads an example SHACL file and validates all focus nodes against all shapes. - */ - public static void main(String[] args) throws Exception { - - // Load the main data model - Model dataModel = JenaUtil.createMemoryModel(); - dataModel.read(ValidationExample.class.getResourceAsStream("/sh/tests/core/property/class-001.test.ttl"), "urn:dummy", FileUtils.langTurtle); - - // Perform the validation of everything, using the data model - // also as the shapes model - you may have them separated - Resource report = ValidationUtil.validateModel(dataModel, dataModel, true); - - // Print violations - System.out.println(ModelPrinter.get().print(report.getModel())); - } + /** + * Loads an example SHACL file and validates all focus nodes against all shapes. + */ + public static void main(String[] args) { + + // Load the main data model + Model dataModel = JenaUtil.createMemoryModel(); + dataModel.read(ValidationExample.class.getResourceAsStream("/sh/tests/core/property/class-001.test.ttl"), "urn:dummy", FileUtils.langTurtle); + + // Perform the validation of everything, using the data model + // also as the shapes model - you may have them separated + Resource report = ValidationUtil.validateModel(dataModel, dataModel, true); + + // Print violations + System.out.println(ModelPrinter.get().print(report.getModel())); + } } \ No newline at end of file