37
37
import com .github .fge .jsonschema .processors .data .SchemaContext ;
38
38
import com .github .fge .jsonschema .processors .data .ValidatorList ;
39
39
import 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 ;
45
40
import com .google .common .collect .Lists ;
46
- import com .google .common .collect .Sets ;
47
41
48
42
import javax .annotation .ParametersAreNonnullByDefault ;
49
43
import javax .annotation .concurrent .NotThreadSafe ;
50
- import java .net .URI ;
51
44
import java .util .Collections ;
52
45
import java .util .List ;
53
- import java .util .Set ;
54
46
55
47
/**
56
48
* Processor for validating one schema/instance pair
@@ -73,28 +65,7 @@ public final class InstanceValidator
73
65
private final MessageBundle validationMessages ;
74
66
private final Processor <SchemaContext , ValidatorList > keywordBuilder ;
75
67
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 ;
98
69
99
70
/**
100
71
* Constructor -- do not use directly!
@@ -110,6 +81,10 @@ public InstanceValidator(final MessageBundle syntaxMessages,
110
81
this .syntaxMessages = syntaxMessages ;
111
82
this .validationMessages = validationMessages ;
112
83
this .keywordBuilder = keywordBuilder ;
84
+
85
+ final String errmsg
86
+ = validationMessages .getMessage ("err.common.validationLoop" );
87
+ stack = new ValidationStack (errmsg );
113
88
}
114
89
115
90
@ Override
@@ -120,8 +95,7 @@ public FullData process(final ProcessingReport report,
120
95
/*
121
96
* We don't want the same validation context to appear twice, see above
122
97
*/
123
- if (!visited .add (FULL_DATA_EQUIVALENCE .wrap (input )))
124
- throw new ProcessingException (validationLoopMessage (input ));
98
+ stack .push (input );
125
99
126
100
127
101
/*
@@ -159,22 +133,25 @@ public FullData process(final ProcessingReport report,
159
133
* reason to go any further. Unless the user has asked to continue even
160
134
* in this case.
161
135
*/
162
- if (!(report .isSuccess () || data .isDeepCheck ()))
136
+ if (!(report .isSuccess () || data .isDeepCheck ())) {
137
+ stack .pop ();
163
138
return input ;
139
+ }
164
140
165
141
/*
166
142
* Now check whether this is a container node with a size greater than
167
143
* 0. If not, no need to go see the children.
168
144
*/
169
145
final JsonNode node = data .getInstance ().getNode ();
170
- if (node .size () == 0 )
171
- return input ;
172
146
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
+ }
177
153
154
+ stack .pop ();
178
155
return input ;
179
156
}
180
157
@@ -243,23 +220,6 @@ private void processObject(final ProcessingReport report,
243
220
}
244
221
}
245
222
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
-
263
223
private ProcessingMessage collectSyntaxErrors (final ProcessingReport report )
264
224
{
265
225
/*
@@ -280,45 +240,4 @@ private ProcessingMessage collectSyntaxErrors(final ProcessingReport report)
280
240
sb .append (JacksonUtils .prettyPrint (arrayNode ));
281
241
return new ProcessingMessage ().setMessage (sb .toString ());
282
242
}
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
- };
324
243
}
0 commit comments