1010import com .gitb .vs .ValidationResponse ;
1111import com .networknt .schema .*;
1212import com .networknt .schema .i18n .ResourceBundleMessageSource ;
13+ import com .networknt .schema .serialization .JsonNodeReader ;
14+ import com .networknt .schema .utils .JsonNodes ;
1315import eu .europa .ec .itb .json .DomainConfig ;
14- import eu .europa .ec .itb .json .validation .location .NodeCoordinateDetector ;
1516import eu .europa .ec .itb .validation .commons .*;
1617import eu .europa .ec .itb .validation .commons .artifact .ValidationArtifactCombinationApproach ;
1718import eu .europa .ec .itb .validation .commons .config .DomainPluginConfigProvider ;
2021import eu .europa .ec .itb .validation .plugin .ValidationPlugin ;
2122import org .apache .commons .io .FileUtils ;
2223import org .apache .commons .lang3 .StringUtils ;
24+ import org .apache .commons .lang3 .tuple .Pair ;
2325import org .slf4j .Logger ;
2426import org .slf4j .LoggerFactory ;
2527import org .springframework .beans .factory .annotation .Autowired ;
3032import java .io .IOException ;
3133import java .math .BigInteger ;
3234import java .nio .charset .StandardCharsets ;
35+ import java .nio .file .Files ;
3336import java .nio .file .Path ;
34- import java .util .*;
37+ import java .util .ArrayList ;
38+ import java .util .LinkedList ;
39+ import java .util .List ;
40+ import java .util .UUID ;
3541import java .util .function .Function ;
3642import java .util .stream .Collectors ;
3743
@@ -131,7 +137,7 @@ public ReportPair validate() {
131137 }
132138 overallReportDetailed .getContext ().getItem ().add (inputReportContent );
133139 }
134- if (specs .getDomainConfig ().isReportItemCount () && getContentNode () instanceof ArrayNode arrayNode ) {
140+ if (specs .getDomainConfig ().isReportItemCount () && getContentNode (null ) instanceof ArrayNode arrayNode ) {
135141 ensureContextCreated (overallReportDetailed );
136142 var countItem = new AnyContent ();
137143 countItem .setType ("number" );
@@ -288,9 +294,9 @@ private void addBranchErrors(List<Message> aggregatedMessages, List<Message> bra
288294 * from local files (and reuse schemas).
289295 *
290296 * @param path The schema path.
291- * @return The parsed schema.
297+ * @return The parsed schema and JSON node reader to use .
292298 */
293- private JsonSchema readSchema (Path path ) {
299+ private Pair < JsonSchema , JsonNodeReader > readSchema (Path path ) {
294300 try {
295301 var jsonNode = objectMapper .readTree (path .toFile ());
296302 var jsonSchemaVersion = JsonSchemaFactory .checkVersion (SpecVersionDetector .detect (jsonNode ));
@@ -300,43 +306,77 @@ private JsonSchema readSchema(Path path) {
300306 * may be remotely loaded or schemas that are user-provided. In addition, it allows us to treat schemas that
301307 * may use different specification versions.
302308 */
309+ var jsonReader = getJsonReader ();
303310 var schemaFactory = JsonSchemaFactory .builder ()
304311 .schemaLoaders (schemaLoaders -> schemaLoaders .add (new LocalSchemaResolver (specs .getDomainConfig (), localSchemaCache )))
305312 .metaSchema (metaSchema )
306313 .defaultMetaSchemaIri (metaSchema .getIri ())
314+ .jsonNodeReader (jsonReader )
307315 .build ();
308- SchemaValidatorsConfig config = new SchemaValidatorsConfig ();
309- config . setPathType (PathType .JSON_POINTER );
310- config . setLocale (specs .getLocalisationHelper ().getLocale ());
311- config . setMessageSource (new ResourceBundleMessageSource ("i18n/jsv-messages" ));
312- config . setLocale ( this . specs . getLocalisationHelper (). getLocale () );
313- return schemaFactory .getSchema (jsonNode , config );
316+ var schemaConfig = SchemaValidatorsConfig . builder ()
317+ . pathType (PathType .JSON_POINTER )
318+ . locale (specs .getLocalisationHelper ().getLocale ())
319+ . messageSource (new ResourceBundleMessageSource ("i18n/jsv-messages" ))
320+ . build ( );
321+ return Pair . of ( schemaFactory .getSchema (jsonNode , schemaConfig ), jsonReader );
314322 } catch (IOException e ) {
315323 throw new ValidatorException ("validator.label.exception.failedToParseJSONSchema" , e , e .getMessage ());
316324 }
317325 }
318326
327+ /**
328+ * Create the reader implementation to read the JSON with (is location-aware depending on the configuration).
329+ *
330+ * @return The reader.
331+ */
332+ private JsonNodeReader getJsonReader () {
333+ var jsonReaderBuilder = JsonNodeReader .builder ();
334+ if (!specs .isLocationAsPointer ()) {
335+ jsonReaderBuilder = jsonReaderBuilder .locationAware ();
336+ }
337+ return jsonReaderBuilder .build ();
338+ }
339+
340+ private Function <ValidationMessage , String > getLocationMapper () {
341+ if (specs .isLocationAsPointer ()) {
342+ return (msg ) -> msg .getInstanceLocation ().toString ();
343+ } else {
344+ return (msg ) -> {
345+ var nodeLocation = JsonNodes .tokenLocationOf (msg .getInstanceNode ());
346+ int lineNumber = 0 ;
347+ if (nodeLocation != null && nodeLocation .getLineNr () > 0 ) {
348+ lineNumber = nodeLocation .getLineNr ();
349+ }
350+ return "%s:%s:0" .formatted (ValidationConstants .INPUT_CONTENT , lineNumber );
351+ };
352+ }
353+ }
354+
319355 /**
320356 * Validate the JSON content against one JSON schema.
321357 *
322358 * @param schemaFile The schema file to use.
323359 * @return The resulting error messages.
324360 */
325361 private List <Message > validateAgainstSchema (File schemaFile ) {
326- var schema = readSchema (schemaFile .toPath ());
327- var content = getContentNode ();
328- return schema . validate (content ) .stream ().map ((message ) -> new Message (StringUtils .removeStart (message .getMessage (), "[] " ), message . getInstanceLocation (). toString ( ))).collect (Collectors .toList ());
362+ var schemaInfo = readSchema (schemaFile .toPath ());
363+ var locationMapper = getLocationMapper ();
364+ return schemaInfo . getLeft (). validate (getContentNode ( schemaInfo . getRight ())) .stream ().map ((message ) -> new Message (StringUtils .removeStart (message .getMessage (), "[] " ), locationMapper . apply ( message ))).collect (Collectors .toList ());
329365 }
330366
331367 /**
332368 * Parse the JSON node for the provided input file.
333369 *
334370 * @return The JSON node.
335371 */
336- private JsonNode getContentNode () {
372+ private JsonNode getContentNode (JsonNodeReader reader ) {
337373 if (contentNode == null ) {
338- try {
339- contentNode = objectMapper .readTree (specs .getInputFileToUse ());
374+ try (var input = Files .newInputStream (specs .getInputFileToUse ().toPath ())) {
375+ if (reader != null ) {
376+ contentNode = reader .readTree (input , InputFormat .JSON );
377+ } else {
378+ contentNode = objectMapper .readTree (input );
379+ }
340380 } catch (IOException e ) {
341381 throw new ValidatorException ("validator.label.exception.failedToParseJSON" , e );
342382 }
@@ -386,17 +426,10 @@ private ReportPair createReport(List<Message> messages) {
386426 } else {
387427 report .setResult (TestResultType .FAILURE );
388428 report .getCounters ().setNrOfErrors (BigInteger .valueOf (messages .size ()));
389-
390- Function <String , String > locationSupplier ;
391- if (specs .isLocationAsPointer ()) {
392- locationSupplier = (contentPath ) -> contentPath ;
393- } else {
394- locationSupplier = new NodeCoordinateDetector (specs .getInputFileToUse ());
395- }
396429 for (var message : messages ) {
397430 BAR error = new BAR ();
398431 error .setDescription (message .getDescription ());
399- error .setLocation (locationSupplier . apply ( message .getContentPath () ));
432+ error .setLocation (message .getContentPath ());
400433 var elementForReport = objectFactory .createTestAssertionGroupReportsTypeError (error );
401434 report .getReports ().getInfoOrWarningOrError ().add (elementForReport );
402435 if (aggregateReportItems != null ) {
0 commit comments