diff --git a/src/main/java/com/cefriel/template/Main.java b/src/main/java/com/cefriel/template/Main.java index ce5d83b..863aff7 100644 --- a/src/main/java/com/cefriel/template/Main.java +++ b/src/main/java/com/cefriel/template/Main.java @@ -29,6 +29,7 @@ import javax.tools.ToolProvider; import java.io.File; import java.io.FileWriter; +import java.io.InputStream; import java.net.URL; import java.net.URLClassLoader; import java.nio.file.Files; @@ -181,7 +182,10 @@ public void exec() throws Exception { } } if(compileRML) { - Util.validateRML(templatePath, verbose); + try (InputStream rmlMappingStream = Files.newInputStream(templatePath)) + { + Util.validateRML(rmlMappingStream, verbose); + } Reader compilerReader = TemplateFunctions.getRDFReaderFromFile(templatePath.toString()); Map compilerReaderMap = new HashMap<>(); diff --git a/src/main/java/com/cefriel/template/utils/RMLCompilerUtils.java b/src/main/java/com/cefriel/template/utils/RMLCompilerUtils.java index a3b4ec2..d2e28ab 100644 --- a/src/main/java/com/cefriel/template/utils/RMLCompilerUtils.java +++ b/src/main/java/com/cefriel/template/utils/RMLCompilerUtils.java @@ -209,6 +209,17 @@ public boolean checkReference(String s){ return false; } + public String getBaseIRI(String turtle) { + Pattern p = Pattern.compile("@base <([^<>]*)>"); + Matcher m = p.matcher(turtle); + + if (m.find()) { + return m.group(1); + } else { + return null; + } + } + /** * From rmlmapper ... * @@ -223,15 +234,7 @@ public String getBaseIRI(Path rmlPath) { } catch (IOException e) { turtle = ""; } - - Pattern p = Pattern.compile("@base <([^<>]*)>"); - Matcher m = p.matcher(turtle); - - if (m.find()) { - return m.group(1); - } else { - return null; - } + return getBaseIRI(turtle); } } diff --git a/src/main/java/com/cefriel/template/utils/Util.java b/src/main/java/com/cefriel/template/utils/Util.java index 876fd79..0350f00 100644 --- a/src/main/java/com/cefriel/template/utils/Util.java +++ b/src/main/java/com/cefriel/template/utils/Util.java @@ -16,6 +16,7 @@ package com.cefriel.template.utils; +import com.cefriel.template.TemplateExecutor; import com.cefriel.template.TemplateMap; import com.cefriel.template.io.Formatter; import com.cefriel.template.io.Reader; @@ -30,9 +31,11 @@ import org.apache.velocity.VelocityContext; import org.apache.velocity.app.VelocityEngine; import org.apache.velocity.runtime.resource.loader.ClasspathResourceLoader; +import org.apache.velocity.runtime.resource.loader.FileResourceLoader; import org.apache.velocity.tools.generic.*; import org.eclipse.rdf4j.common.exception.ValidationException; import org.eclipse.rdf4j.model.Model; +import org.eclipse.rdf4j.model.base.CoreDatatype; import org.eclipse.rdf4j.model.vocabulary.RDF4J; import org.eclipse.rdf4j.model.vocabulary.SHACL; import org.eclipse.rdf4j.repository.RepositoryConnection; @@ -45,6 +48,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.InputStream; import java.nio.charset.StandardCharsets; @@ -195,11 +199,16 @@ public static VelocityEngine createVelocityEngine(boolean templateInResources, b velocityEngine.setProperty("resource.loaders", "class"); velocityEngine.setProperty("resource.loader.class.class", ClasspathResourceLoader.class.getName()); + } else{ + velocityEngine.setProperty("resource.loaders", "file"); + velocityEngine.setProperty("resource.loader.file.class", FileResourceLoader.class.getName()); + velocityEngine.setProperty("resource.loader.file.path", ""); } velocityEngine.init(); return velocityEngine; } - public static void validateRML(Path templatePath, boolean verbose) { + + public static void validateRML(InputStream rmlMapping, boolean verbose) { ShaclSail shaclSail = new ShaclSail(new MemoryStore()); SailRepository repository = new SailRepository(shaclSail); @@ -208,33 +217,25 @@ public static void validateRML(Path templatePath, boolean verbose) { try (RepositoryConnection connection = repository.getConnection()) { try (InputStream shapesStream = Util.class.getResourceAsStream("/rml/core.ttl")) { Model rules = Rio.parse(shapesStream, RDFFormat.TURTLE); - // cf. https://github.com/eclipse-rdf4j/rdf4j/discussions/4287 rules.remove(null, SHACL.NAME, null); rules.remove(null, SHACL.DESCRIPTION, null); - connection.begin(); connection.add(rules, RDF4J.SHACL_SHAPE_GRAPH); connection.commit(); } - - // Load RML - try (InputStream dataStream = Files.newInputStream(templatePath)) { - connection.begin(); - connection.add(dataStream, "", org.eclipse.rdf4j.rio.RDFFormat.TURTLE); - - try { - connection.commit(); - log.info("RML validated correctly"); - } catch (RepositoryException exception) { - Throwable cause = exception.getCause(); - log.error("RML not valid"); - if (verbose) - if (cause instanceof ValidationException) { - Model validationReportModel = ((ValidationException) cause).validationReportAsModel(); - Rio.write(validationReportModel, System.out, RDFFormat.TURTLE); - } - throw exception; + connection.begin(); + connection.add(rmlMapping, "", RDFFormat.TURTLE); + try { + connection.commit(); + log.info("RML validated correctly"); + } catch (RepositoryException exception) { + Throwable cause = exception.getCause(); + log.error("RML not valid"); + if (verbose && cause instanceof ValidationException) { + Model validationReportModel = ((ValidationException) cause).validationReportAsModel(); + Rio.write(validationReportModel, System.out, RDFFormat.TURTLE); } + throw exception; } } catch (Exception e) { throw new RuntimeException(e); @@ -243,6 +244,7 @@ public static void validateRML(Path templatePath, boolean verbose) { } } + /** * Creates a new {@link VelocityContext} and populates it with the provided readers, template map, and template functions. * Additionally, it adds a set of common tools for mathematical operations, number formatting, date manipulation, @@ -331,4 +333,46 @@ public static VelocityContext createVelocityContext(Reader reader, TemplateMap t public static VelocityContext createVelocityContext(Map readers, TemplateMap templateMap) { return createVelocityContext(readers, templateMap, new TemplateFunctions()); } + + /** + * Compiles an MTL mapping from the given RML file. + * + * @param mappingRML Path to the RML mapping file. + * @param baseIri Base IRI to use if not found in the mapping. + * @param basePath Base path for output files. + * @param trimTemplate Whether to use the trimmed template. + * @param verbose Enable verbose validation output. + * @return Path to the compiled template. + * @throws Exception if validation or compilation fails. + */ + public static Path compiledMTLMapping(InputStream mappingRML, String baseIri, Path basePath, boolean trimTemplate, boolean verbose) throws Exception { + String mappingRMLString = inputStreamToString(mappingRML); + Util.validateRML(new ByteArrayInputStream(mappingRMLString.getBytes(StandardCharsets.UTF_8)), verbose); + Reader compilerReader = TemplateFunctions.getRDFReaderFromString(mappingRMLString, RDFFormat.TURTLE.toString()); + Map compilerReaderMap = new HashMap<>(); + compilerReaderMap.put("reader", compilerReader); + + try (InputStream rmlCompiler = trimTemplate + ? Util.class.getResourceAsStream("/rml/rml-compiler.vm.tmp.vm") + : Util.class.getResourceAsStream("/rml/rml-compiler.vm")) { + + RMLCompilerUtils rmlCompilerUtils = new RMLCompilerUtils(); + + Map rmlMap = new HashMap<>(); + String baseIriRML = rmlCompilerUtils.getBaseIRI(mappingRMLString); + baseIriRML = baseIriRML != null ? baseIriRML : baseIri; + rmlMap.put("baseIRI", baseIriRML); + rmlMap.put("basePath", basePath.toString() + "/"); + + Path compiledTemplatePath = Paths.get(basePath.toString(), "template.rml.vm"); + TemplateExecutor templateExecutor = new TemplateExecutor(false, false, true, null); + return templateExecutor.executeMapping( + compilerReaderMap, + rmlCompiler, + compiledTemplatePath, + rmlCompilerUtils, + new TemplateMap(rmlMap) + ); + } + } } diff --git a/src/test/java/com/cefriel/template/RMLTests.java b/src/test/java/com/cefriel/template/RMLTests.java index b2322d7..e78eaf2 100644 --- a/src/test/java/com/cefriel/template/RMLTests.java +++ b/src/test/java/com/cefriel/template/RMLTests.java @@ -29,6 +29,7 @@ import org.eclipse.rdf4j.rio.helpers.StatementCollector; import org.junit.jupiter.api.Test; +import java.io.InputStream; import java.io.StringReader; import java.nio.file.Files; import java.nio.file.Path; @@ -40,54 +41,48 @@ public class RMLTests { - final static String FOLDER = "src/test/resources/rml/"; - private String resolvePath(String folder, String file) { - return FOLDER + file; - } + final static Path FOLDER = Path.of("src/test/resources/rml/"); @Test public void invalidRMLTest() throws Exception { - String folder = "rml"; - Path rmlMappings = Paths.get(resolvePath(folder, "invalid-mapping.ttl")); - assertThrows(RuntimeException.class, () -> Util.validateRML(rmlMappings, false)); + Path rmlMappings = FOLDER.resolve(Path.of("invalid-mapping.ttl")); + try (InputStream rmlMapping = Files.newInputStream(rmlMappings)) { + assertThrows(RuntimeException.class, + () -> Util.validateRML(rmlMapping, false)); + } } @Test public void validRMLTest() throws Exception { + Path rmlMapping = FOLDER.resolve(Path.of("mapping.ttl")); + String expectedOutput = Files.readString(FOLDER.resolve("output.nq")); - String folder = "rml"; - String rmlMappings = resolvePath(folder, "mapping.ttl"); - - String expectedOutput = Files.readString(Paths.get(resolvePath(folder, "output.nq"))); + try (InputStream rmlMappingStream = Files.newInputStream(rmlMapping)) { + Util.validateRML(rmlMappingStream, false); + } - Util.validateRML(Paths.get(rmlMappings), false); - - Reader compilerReader = TemplateFunctions.getRDFReaderFromFile(rmlMappings); - Path rmlCompiler = Paths.get("rml/rml-compiler.vm"); - Path compiledTemplatePath = Paths.get(resolvePath(folder,"template.rml.vm")); + Reader compilerReader = TemplateFunctions.getRDFReaderFromFile(rmlMapping.toString()); Map rmlMap = new HashMap<>(); - rmlMap.put("basePath", FOLDER); + rmlMap.put("basePath", null); - TemplateExecutor rmlTemplateExecutor = new TemplateExecutor(false, false, true, null); TemplateExecutor templateExecutor = new TemplateExecutor(false, false, false, null); - rmlTemplateExecutor.executeMapping(Map.of("reader", compilerReader), rmlCompiler, compiledTemplatePath, new RMLCompilerUtils(), new TemplateMap(rmlMap)); - String result = templateExecutor.executeMapping(Map.of("reader", compilerReader), compiledTemplatePath); - Files.delete(compiledTemplatePath); - - Model resultModel = parseRDFString(result); - Model expectedOutputModel = parseRDFString(expectedOutput); - - assert(Models.isomorphic(resultModel, expectedOutputModel)); + try (InputStream rmlMappingStream = Files.newInputStream(rmlMapping)) { + Path mappingMTL = Util.compiledMTLMapping(rmlMappingStream, null, FOLDER, false, false); + String result = templateExecutor.executeMapping(Map.of("reader", compilerReader), FOLDER.resolve(mappingMTL.getFileName())); + Files.delete(mappingMTL); + + Model resultModel = parseRDFString(result); + Model expectedOutputModel = parseRDFString(expectedOutput); + assert(Models.isomorphic(resultModel, expectedOutputModel)); + } } private static Model parseRDFString(String rdfString) throws Exception { RDFParser rdfParser = Rio.createParser(RDFFormat.NQUADS); - Model model = new TreeModel(); rdfParser.setRDFHandler(new StatementCollector(model)); rdfParser.parse(new StringReader(rdfString), "http://example.com/base/"); return model; } - } \ No newline at end of file