3737import com .github .fge .jsonschema .processors .data .SchemaContext ;
3838import com .github .fge .jsonschema .processors .data .ValidatorList ;
3939import com .github .fge .msgsimple .bundle .MessageBundle ;
40- import com .github .fge .uritemplate .URITemplate ;
41- import com .github .fge .uritemplate .URITemplateException ;
42- import com .github .fge .uritemplate .URITemplateParseException ;
43- import com .github .fge .uritemplate .vars .VariableMap ;
44- import com .google .common .base .Equivalence ;
4540import com .google .common .collect .Lists ;
46- import com .google .common .collect .Sets ;
4741
4842import javax .annotation .ParametersAreNonnullByDefault ;
4943import javax .annotation .concurrent .NotThreadSafe ;
50- import java .net .URI ;
5144import java .util .Collections ;
5245import java .util .List ;
53- import java .util .Set ;
5446
5547/**
5648 * Processor for validating one schema/instance pair
@@ -73,28 +65,7 @@ public final class InstanceValidator
7365 private final MessageBundle validationMessages ;
7466 private final Processor <SchemaContext , ValidatorList > keywordBuilder ;
7567
76- /*
77- * It is possible to trigger a validation loop if there is a repeated
78- * triplet schema ID/schema pointer/instance pointer while we validate;
79- * example schema:
80- *
81- * { "oneOf": [ {}, { "$ref": "#" } ] }
82- *
83- * Whatever the data, validation will end up validating, for a same pointer
84- * into the instance, the following pointers into the schema:
85- *
86- * "" -> "/oneOf/0" -> "/oneOf/1" -> "/oneOf/0" <-- LOOP
87- *
88- * This is not a JSON Reference loop here, but truly a validation loop.
89- *
90- * We therefore use this set to record the triplets seen by using an
91- * Equivalence over FullData which detects this. This is helped by the fact
92- * that SchemaTree now implements equals()/hashCode() in -core; since this
93- * class is instantiated for each instance validation, we are certain that
94- * what instance pointer is seen is the one of the instance we validate.
95- */
96- private final Set <Equivalence .Wrapper <FullData >> visited
97- = Sets .newLinkedHashSet ();
68+ private final ValidationStack stack ;
9869
9970 /**
10071 * Constructor -- do not use directly!
@@ -110,6 +81,10 @@ public InstanceValidator(final MessageBundle syntaxMessages,
11081 this .syntaxMessages = syntaxMessages ;
11182 this .validationMessages = validationMessages ;
11283 this .keywordBuilder = keywordBuilder ;
84+
85+ final String errmsg
86+ = validationMessages .getMessage ("err.common.validationLoop" );
87+ stack = new ValidationStack (errmsg );
11388 }
11489
11590 @ Override
@@ -120,8 +95,7 @@ public FullData process(final ProcessingReport report,
12095 /*
12196 * We don't want the same validation context to appear twice, see above
12297 */
123- if (!visited .add (FULL_DATA_EQUIVALENCE .wrap (input )))
124- throw new ProcessingException (validationLoopMessage (input ));
98+ stack .push (input );
12599
126100
127101 /*
@@ -159,22 +133,25 @@ public FullData process(final ProcessingReport report,
159133 * reason to go any further. Unless the user has asked to continue even
160134 * in this case.
161135 */
162- if (!(report .isSuccess () || data .isDeepCheck ()))
136+ if (!(report .isSuccess () || data .isDeepCheck ())) {
137+ stack .pop ();
163138 return input ;
139+ }
164140
165141 /*
166142 * Now check whether this is a container node with a size greater than
167143 * 0. If not, no need to go see the children.
168144 */
169145 final JsonNode node = data .getInstance ().getNode ();
170- if (node .size () == 0 )
171- return input ;
172146
173- if (node .isArray ())
174- processArray (report , data );
175- else
176- processObject (report , data );
147+ if (node .isContainerNode ()) {
148+ if (node .isArray ())
149+ processArray (report , data );
150+ else
151+ processObject (report , data );
152+ }
177153
154+ stack .pop ();
178155 return input ;
179156 }
180157
@@ -243,23 +220,6 @@ private void processObject(final ProcessingReport report,
243220 }
244221 }
245222
246- private ProcessingMessage validationLoopMessage (final FullData input )
247- {
248- final String errmsg
249- = validationMessages .getMessage ("err.common.validationLoop" );
250- final ArrayNode node = JacksonUtils .nodeFactory ().arrayNode ();
251- for (final Equivalence .Wrapper <FullData > e : visited )
252- //noinspection ConstantConditions
253- node .add (toJson (e .get ()));
254- return input .newMessage ()
255- .put ("domain" , "validation" )
256- .setMessage (errmsg )
257- .putArgument ("alreadyVisited" , toJson (input ))
258- .putArgument ("instancePointer" ,
259- input .getInstance ().getPointer ().toString ())
260- .put ("validationPath" , node );
261- }
262-
263223 private ProcessingMessage collectSyntaxErrors (final ProcessingReport report )
264224 {
265225 /*
@@ -280,45 +240,4 @@ private ProcessingMessage collectSyntaxErrors(final ProcessingReport report)
280240 sb .append (JacksonUtils .prettyPrint (arrayNode ));
281241 return new ProcessingMessage ().setMessage (sb .toString ());
282242 }
283-
284- private static String toJson (final FullData data )
285- {
286- final SchemaTree tree = data .getSchema ();
287- final URI baseUri = tree .getLoadingRef ().getLocator ();
288- final VariableMap vars = VariableMap .newBuilder ().addScalarValue ("ptr" ,
289- tree .getPointer ()).freeze ();
290- // TODO: there should be an easier way to do that...
291- final URITemplate template ;
292- try {
293- template = new URITemplate (baseUri + "{+ptr}" );
294- } catch (URITemplateParseException e ) {
295- throw new IllegalStateException ("wtf??" , e );
296- }
297- try {
298- return template .toString (vars );
299- } catch (URITemplateException e ) {
300- throw new IllegalStateException ("wtf??" , e );
301- }
302- }
303-
304- @ ParametersAreNonnullByDefault
305- private static final Equivalence <FullData > FULL_DATA_EQUIVALENCE
306- = new Equivalence <FullData >()
307- {
308- @ Override
309- protected boolean doEquivalent (final FullData a , final FullData b )
310- {
311- final JsonPointer ptra = a .getInstance ().getPointer ();
312- final JsonPointer ptrb = b .getInstance ().getPointer ();
313- return a .getSchema ().equals (b .getSchema ())
314- && ptra .equals (ptrb );
315- }
316-
317- @ Override
318- protected int doHash (final FullData t )
319- {
320- return t .getSchema ().hashCode ()
321- ^ t .getInstance ().getPointer ().hashCode ();
322- }
323- };
324243}
0 commit comments