15
15
16
16
package eu .europa .ec .itb .shacl .validation ;
17
17
18
+ import com .apicatalog .jsonld .JsonLdOptions ;
19
+ import com .apicatalog .jsonld .http .DefaultHttpClient ;
20
+ import com .apicatalog .jsonld .http .media .MediaType ;
21
+ import com .apicatalog .jsonld .loader .FileLoader ;
22
+ import com .apicatalog .jsonld .loader .HttpLoader ;
23
+ import com .apicatalog .jsonld .loader .SchemeRouter ;
18
24
import eu .europa .ec .itb .shacl .ApplicationConfig ;
19
25
import eu .europa .ec .itb .shacl .DomainConfig ;
20
26
import eu .europa .ec .itb .shacl .SparqlQueryConfig ;
27
+ import eu .europa .ec .itb .shacl .ValidationSpecs ;
21
28
import eu .europa .ec .itb .shacl .util .ShaclValidatorUtils ;
22
29
import eu .europa .ec .itb .validation .commons .BaseFileManager ;
23
30
import eu .europa .ec .itb .validation .commons .FileInfo ;
33
40
import org .apache .jena .rdf .model .Model ;
34
41
import org .apache .jena .riot .Lang ;
35
42
import org .apache .jena .riot .RDFLanguages ;
43
+ import org .apache .jena .riot .RDFParserBuilder ;
44
+ import org .apache .jena .riot .system .PrefixMap ;
45
+ import org .apache .jena .riot .system .PrefixMapStd ;
36
46
import org .apache .jena .sparql .exec .http .QueryExecutionHTTP ;
47
+ import org .apache .jena .sparql .util .Context ;
37
48
import org .slf4j .Logger ;
38
49
import org .slf4j .LoggerFactory ;
39
50
import org .springframework .beans .factory .annotation .Autowired ;
44
55
import java .nio .file .Files ;
45
56
import java .nio .file .Path ;
46
57
import java .util .List ;
58
+ import java .util .Map ;
59
+ import java .util .Objects ;
47
60
import java .util .UUID ;
48
61
import java .util .concurrent .ConcurrentHashMap ;
62
+ import java .util .concurrent .locks .ReentrantLock ;
63
+ import java .util .function .Supplier ;
49
64
50
65
import static eu .europa .ec .itb .shacl .util .ShaclValidatorUtils .handleEquivalentContentSyntaxes ;
51
66
import static eu .europa .ec .itb .shacl .util .ShaclValidatorUtils .isRdfContentSyntax ;
67
+ import static org .apache .jena .riot .lang .LangJSONLD11 .JSONLD_OPTIONS ;
52
68
53
69
/**
54
70
* Manages file-system operations.
@@ -62,6 +78,8 @@ public class FileManager extends BaseFileManager<ApplicationConfig> {
62
78
private ApplicationConfig appConfig ;
63
79
64
80
private final ConcurrentHashMap <String , Path > shaclModelCache = new ConcurrentHashMap <>();
81
+ private final ConcurrentHashMap <String , Model > materialisedShaclModelCache = new ConcurrentHashMap <>();
82
+ private final ReentrantLock cacheLock = new ReentrantLock ();
65
83
66
84
/**
67
85
* Create a cache key to use for SHACL model file lookup.
@@ -181,12 +199,22 @@ public void writeShaclShapes(Path outputPath, Model rdfModel, String validationT
181
199
String outputSyntaxToUse = outputSyntax ;
182
200
if (domainConfig .canCacheShapes (validationType )) {
183
201
// Write the model to a file and cache it.
184
- String cacheKey = toShaclModelCacheKey (domainConfig , validationType , outputSyntax );
185
- Path cachedPath = shaclModelCache .get (cacheKey );
186
- if (cachedPath == null || !Files .exists (cachedPath )) {
187
- // Initialise in case of first access or in case file was removed.
188
- cachedPath = storeShapeGraph (rdfModel , outputSyntaxToUse );
189
- shaclModelCache .put (cacheKey , cachedPath );
202
+ Path cachedPath ;
203
+ cacheLock .lock ();
204
+ try {
205
+ String cacheKey = toShaclModelCacheKey (domainConfig , validationType , outputSyntax );
206
+ cachedPath = shaclModelCache .get (cacheKey );
207
+ if (cachedPath == null || !Files .exists (cachedPath )) {
208
+ // Initialise in case of first access or in case file was removed.
209
+ cachedPath = storeShapeGraph (rdfModel , outputSyntaxToUse );
210
+ if (logger .isDebugEnabled ()) {
211
+ logger .debug ("Cached shape model for [{}] at [{}]" , cacheKey , cachedPath .toAbsolutePath ());
212
+ }
213
+ shaclModelCache .put (cacheKey , cachedPath );
214
+ materialisedShaclModelCache .put (cacheKey , rdfModel );
215
+ }
216
+ } finally {
217
+ cacheLock .unlock ();
190
218
}
191
219
Files .copy (cachedPath , outputPath );
192
220
} else {
@@ -196,6 +224,110 @@ public void writeShaclShapes(Path outputPath, Model rdfModel, String validationT
196
224
}
197
225
}
198
226
227
+ /**
228
+ * Build the RDF model from the provided stream.
229
+ *
230
+ * @param dataStream The stream to read from.
231
+ * @param rdfLanguage The content type of the stream's data.
232
+ * @return The parsed model.
233
+ */
234
+ public Model readModel (InputStream dataStream , Lang rdfLanguage , Map <String , String > nsPrefixes ) {
235
+ var builder = RDFParserBuilder
236
+ .create ()
237
+ .lang (rdfLanguage )
238
+ .source (dataStream );
239
+ if (nsPrefixes != null ) {
240
+ // Before parsing set the prefixes of the model to avoid mismatches.
241
+ PrefixMap prefixes = new PrefixMapStd ();
242
+ prefixes .putAll (nsPrefixes );
243
+ builder = builder .prefixes (prefixes );
244
+ }
245
+ if (Lang .JSONLD11 .equals (rdfLanguage ) || Lang .JSONLD .equals (rdfLanguage )) {
246
+ var options = new JsonLdOptions ();
247
+ var httpLoader = new HttpLoader (DefaultHttpClient .defaultInstance ());
248
+ /*
249
+ * Set fallback type for remote contexts to avoid errors for non JSON/JSON-LD Content Types.
250
+ * This allows us to proceed if e.g. the Content Type originally returned is "text/plain".
251
+ */
252
+ httpLoader .setFallbackContentType (MediaType .JSON );
253
+ options .setDocumentLoader (new SchemeRouter ()
254
+ .set ("http" , httpLoader )
255
+ .set ("https" , httpLoader )
256
+ .set ("file" , new FileLoader ()));
257
+ builder = builder .context (Context .create ().set (JSONLD_OPTIONS , options ));
258
+ }
259
+ return builder .build ().toModel ();
260
+ }
261
+
262
+ /**
263
+ * Add the provided model to the cache if possible.
264
+ *
265
+ * @param specs The current validation settings.
266
+ * @param shapeModel The model to cache.
267
+ */
268
+ private void cacheShapeModelIfPossible (ValidationSpecs specs , Model shapeModel ) {
269
+ if (specs .getDomainConfig ().canCacheShapes (specs .getValidationType ()) && !specs .isLoadImports ()) {
270
+ String cacheKey = toShaclModelCacheKey (specs .getDomainConfig (), specs .getValidationType (), specs .getDomainConfig ().getDefaultReportSyntax ());
271
+ cacheLock .lock ();
272
+ try {
273
+ materialisedShaclModelCache .putIfAbsent (cacheKey , shapeModel );
274
+ } finally {
275
+ cacheLock .unlock ();
276
+ }
277
+ }
278
+ }
279
+
280
+ /**
281
+ * Get the shapes model from the cache or from the provided supplier function.
282
+ *
283
+ * @param specs The current validation settings.
284
+ * @param modelSupplierIfNotCached Supplier for the model if it was not found in the cache.
285
+ * @return The model to use.
286
+ */
287
+ public Model getShapeModel (ValidationSpecs specs , Supplier <Model > modelSupplierIfNotCached ) {
288
+ Model cachedModel = null ;
289
+ String cacheKey = toShaclModelCacheKey (specs .getDomainConfig (), specs .getValidationType (), specs .getDomainConfig ().getDefaultReportSyntax ());
290
+ if (specs .isLoadImports ()) {
291
+ if (specs .isLogProgress () && logger .isDebugEnabled ()) {
292
+ logger .debug ("Cached shape model for [{}] not loaded as we are loading imports" , cacheKey );
293
+ }
294
+ } else {
295
+ cacheLock .lock ();
296
+ try {
297
+ Model materialisedModel = materialisedShaclModelCache .get (cacheKey );
298
+ if (materialisedModel == null ) {
299
+ Path modelPath = shaclModelCache .get (cacheKey );
300
+ if (modelPath != null ) {
301
+ try (InputStream dataStream = Files .newInputStream (modelPath )) {
302
+ materialisedModel = readModel (dataStream , RDFLanguages .contentTypeToLang (specs .getDomainConfig ().getDefaultReportSyntax ()), null );
303
+ } catch (IOException e ) {
304
+ throw new ValidatorException ("validator.label.exception.errorReadingShaclFile" , e );
305
+ }
306
+ materialisedShaclModelCache .put (cacheKey , materialisedModel );
307
+ cachedModel = materialisedModel ;
308
+ }
309
+ } else {
310
+ cachedModel = materialisedModel ;
311
+ }
312
+ } finally {
313
+ cacheLock .unlock ();
314
+ }
315
+ if (specs .isLogProgress () && logger .isDebugEnabled ()) {
316
+ if (cachedModel == null ) {
317
+ logger .debug ("Cached shape model for [{}] not found" , cacheKey );
318
+ } else {
319
+ logger .debug ("Cached shape model for [{}] found" , cacheKey );
320
+ }
321
+ }
322
+ }
323
+ cachedModel = Objects .requireNonNullElseGet (cachedModel , () -> {
324
+ Model loadedModel = modelSupplierIfNotCached .get ();
325
+ cacheShapeModelIfPossible (specs , loadedModel );
326
+ return loadedModel ;
327
+ });
328
+ return cachedModel ;
329
+ }
330
+
199
331
/**
200
332
* Write the provided RDF model to the SHACL shape cache.
201
333
*
0 commit comments