1   /*
2    * SPDX-FileCopyrightText: none
3    * SPDX-License-Identifier: CC0-1.0
4    */
5   
6   package gov.nist.secauto.metaschema.databind.model.metaschema.impl;
7   
8   import gov.nist.secauto.metaschema.core.datatype.IDataTypeAdapter;
9   import gov.nist.secauto.metaschema.core.datatype.markup.MarkupLine;
10  import gov.nist.secauto.metaschema.core.datatype.markup.MarkupMultiline;
11  import gov.nist.secauto.metaschema.core.metapath.IMetapathExpression;
12  import gov.nist.secauto.metaschema.core.metapath.StaticContext;
13  import gov.nist.secauto.metaschema.core.model.ISource;
14  import gov.nist.secauto.metaschema.core.model.constraint.AbstractConfigurableMessageConstraintBuilder;
15  import gov.nist.secauto.metaschema.core.model.constraint.AbstractConstraintBuilder;
16  import gov.nist.secauto.metaschema.core.model.constraint.AbstractKeyConstraintBuilder;
17  import gov.nist.secauto.metaschema.core.model.constraint.IAllowedValuesConstraint;
18  import gov.nist.secauto.metaschema.core.model.constraint.ICardinalityConstraint;
19  import gov.nist.secauto.metaschema.core.model.constraint.IConstraint;
20  import gov.nist.secauto.metaschema.core.model.constraint.IExpectConstraint;
21  import gov.nist.secauto.metaschema.core.model.constraint.IIndexConstraint;
22  import gov.nist.secauto.metaschema.core.model.constraint.IIndexHasKeyConstraint;
23  import gov.nist.secauto.metaschema.core.model.constraint.IKeyField;
24  import gov.nist.secauto.metaschema.core.model.constraint.ILet;
25  import gov.nist.secauto.metaschema.core.model.constraint.IMatchesConstraint;
26  import gov.nist.secauto.metaschema.core.model.constraint.IModelConstrained;
27  import gov.nist.secauto.metaschema.core.model.constraint.IUniqueConstraint;
28  import gov.nist.secauto.metaschema.core.model.constraint.IValueConstrained;
29  import gov.nist.secauto.metaschema.core.util.ObjectUtils;
30  import gov.nist.secauto.metaschema.databind.model.metaschema.IConfigurableMessageConstraintBase;
31  import gov.nist.secauto.metaschema.databind.model.metaschema.IConstraintBase;
32  import gov.nist.secauto.metaschema.databind.model.metaschema.IModelConstraintsBase;
33  import gov.nist.secauto.metaschema.databind.model.metaschema.IValueConstraintsBase;
34  import gov.nist.secauto.metaschema.databind.model.metaschema.IValueTargetedConstraintsBase;
35  import gov.nist.secauto.metaschema.databind.model.metaschema.binding.ConstraintValueEnum;
36  import gov.nist.secauto.metaschema.databind.model.metaschema.binding.FlagAllowedValues;
37  import gov.nist.secauto.metaschema.databind.model.metaschema.binding.FlagExpect;
38  import gov.nist.secauto.metaschema.databind.model.metaschema.binding.FlagIndexHasKey;
39  import gov.nist.secauto.metaschema.databind.model.metaschema.binding.FlagMatches;
40  import gov.nist.secauto.metaschema.databind.model.metaschema.binding.KeyConstraintField;
41  import gov.nist.secauto.metaschema.databind.model.metaschema.binding.Property;
42  import gov.nist.secauto.metaschema.databind.model.metaschema.binding.Remarks;
43  import gov.nist.secauto.metaschema.databind.model.metaschema.binding.TargetedAllowedValuesConstraint;
44  import gov.nist.secauto.metaschema.databind.model.metaschema.binding.TargetedExpectConstraint;
45  import gov.nist.secauto.metaschema.databind.model.metaschema.binding.TargetedHasCardinalityConstraint;
46  import gov.nist.secauto.metaschema.databind.model.metaschema.binding.TargetedIndexConstraint;
47  import gov.nist.secauto.metaschema.databind.model.metaschema.binding.TargetedIndexHasKeyConstraint;
48  import gov.nist.secauto.metaschema.databind.model.metaschema.binding.TargetedIsUniqueConstraint;
49  import gov.nist.secauto.metaschema.databind.model.metaschema.binding.TargetedMatchesConstraint;
50  
51  import java.math.BigInteger;
52  import java.util.List;
53  import java.util.regex.Pattern;
54  
55  import edu.umd.cs.findbugs.annotations.NonNull;
56  import edu.umd.cs.findbugs.annotations.Nullable;
57  
58  /**
59   * Supports parsing constraints declared within a bound object.
60   */
61  @SuppressWarnings("PMD.CouplingBetweenObjects")
62  public final class ConstraintBindingSupport {
63    private ConstraintBindingSupport() {
64      // disable construction
65    }
66  
67    /**
68     * Parse a constraint set.
69     *
70     * @param constraintSet
71     *          the parsed constraint set
72     * @param constraints
73     *          the constraint definitions to parse
74     * @param source
75     *          the source of the constraints
76     */
77    public static void parse(
78        @NonNull IValueConstrained constraintSet,
79        @NonNull IValueConstraintsBase constraints,
80        @NonNull ISource source) {
81      parseLet(constraintSet, constraints, source);
82  
83      // parse rules
84      for (IConstraintBase ruleObj : constraints.getRules()) {
85        if (ruleObj instanceof FlagAllowedValues) {
86          IAllowedValuesConstraint constraint = newAllowedValues((FlagAllowedValues) ruleObj, source);
87          constraintSet.addConstraint(constraint);
88        } else if (ruleObj instanceof FlagExpect) {
89          IExpectConstraint constraint = newExpect((FlagExpect) ruleObj, source);
90          constraintSet.addConstraint(constraint);
91        } else if (ruleObj instanceof FlagIndexHasKey) {
92          IIndexHasKeyConstraint constraint = newIndexHasKey((FlagIndexHasKey) ruleObj, source);
93          constraintSet.addConstraint(constraint);
94        } else if (ruleObj instanceof FlagMatches) {
95          IMatchesConstraint constraint = newMatches((FlagMatches) ruleObj, source);
96          constraintSet.addConstraint(constraint);
97        }
98      }
99    }
100 
101   /**
102    * Parse a constraint set.
103    *
104    * @param constraintSet
105    *          the parsed constraint set
106    * @param constraints
107    *          the constraint definitions to parse
108    * @param source
109    *          the source of the constraints
110    */
111   public static void parse(
112       @NonNull IValueConstrained constraintSet,
113       @NonNull IValueTargetedConstraintsBase constraints,
114       @NonNull ISource source) {
115     parseLet(constraintSet, constraints, source);
116 
117     // parse rules
118     for (IConstraintBase ruleObj : constraints.getRules()) {
119       if (ruleObj instanceof TargetedAllowedValuesConstraint) {
120         IAllowedValuesConstraint constraint = newAllowedValues((TargetedAllowedValuesConstraint) ruleObj, source);
121         constraintSet.addConstraint(constraint);
122       } else if (ruleObj instanceof TargetedExpectConstraint) {
123         IExpectConstraint constraint = newExpect((TargetedExpectConstraint) ruleObj, source);
124         constraintSet.addConstraint(constraint);
125       } else if (ruleObj instanceof TargetedIndexHasKeyConstraint) {
126         IIndexHasKeyConstraint constraint = newIndexHasKey((TargetedIndexHasKeyConstraint) ruleObj, source);
127         constraintSet.addConstraint(constraint);
128       } else if (ruleObj instanceof TargetedMatchesConstraint) {
129         IMatchesConstraint constraint = newMatches((TargetedMatchesConstraint) ruleObj, source);
130         constraintSet.addConstraint(constraint);
131       }
132     }
133   }
134 
135   /**
136    * Parse a constraint set.
137    *
138    * @param constraintSet
139    *          the parsed constraint set
140    * @param constraints
141    *          the constraint definitions to parse
142    * @param source
143    *          the source of the constraints
144    */
145   public static void parse(
146       @NonNull IModelConstrained constraintSet,
147       @NonNull IModelConstraintsBase constraints,
148       @NonNull ISource source) {
149     parseLet(constraintSet, constraints, source);
150 
151     // parse rules
152     for (IConstraintBase ruleObj : constraints.getRules()) {
153       if (ruleObj instanceof TargetedAllowedValuesConstraint) {
154         IAllowedValuesConstraint constraint = newAllowedValues((TargetedAllowedValuesConstraint) ruleObj, source);
155         constraintSet.addConstraint(constraint);
156       } else if (ruleObj instanceof TargetedExpectConstraint) {
157         IExpectConstraint constraint = newExpect((TargetedExpectConstraint) ruleObj, source);
158         constraintSet.addConstraint(constraint);
159       } else if (ruleObj instanceof TargetedIndexHasKeyConstraint) {
160         IIndexHasKeyConstraint constraint = newIndexHasKey((TargetedIndexHasKeyConstraint) ruleObj, source);
161         constraintSet.addConstraint(constraint);
162       } else if (ruleObj instanceof TargetedMatchesConstraint) {
163         IMatchesConstraint constraint = newMatches((TargetedMatchesConstraint) ruleObj, source);
164         constraintSet.addConstraint(constraint);
165       } else if (ruleObj instanceof TargetedIndexConstraint) {
166         IIndexConstraint constraint = newIndex((TargetedIndexConstraint) ruleObj, source);
167         constraintSet.addConstraint(constraint);
168       } else if (ruleObj instanceof TargetedHasCardinalityConstraint) {
169         ICardinalityConstraint constraint = newHasCardinality((TargetedHasCardinalityConstraint) ruleObj, source);
170         constraintSet.addConstraint(constraint);
171       } else if (ruleObj instanceof TargetedIsUniqueConstraint) {
172         IUniqueConstraint constraint = newUnique((TargetedIsUniqueConstraint) ruleObj, source);
173         constraintSet.addConstraint(constraint);
174       }
175     }
176   }
177 
178   /**
179    * Parse the let clause in a constraint set.
180    *
181    * @param constraintSet
182    *          the parsed constraint set
183    * @param constraints
184    *          the constraint definitions to parse
185    * @param source
186    *          the source of the constraint
187    */
188   public static void parseLet(
189       @NonNull IValueConstrained constraintSet,
190       @NonNull IValueConstraintsBase constraints,
191       @NonNull ISource source) {
192     StaticContext staticContext = source.getStaticContext();
193 
194     // parse let expressions
195     constraints.getLets().stream()
196         .map(letObj -> {
197           MarkupMultiline remarks = null;
198           Remarks remarkObj = letObj.getRemarks();
199           if (remarkObj != null) {
200             remarks = remarkObj.getRemark();
201           }
202 
203           return ILet.of(
204               staticContext.parseVariableName(ObjectUtils.requireNonNull(letObj.getVar())),
205               IMetapathExpression.lazyCompile(
206                   ObjectUtils.requireNonNull(letObj.getExpression()),
207                   source.getStaticContext()),
208               source,
209               remarks);
210         })
211         .forEachOrdered(constraintSet::addLetExpression);
212   }
213 
214   @NonNull
215   private static IAllowedValuesConstraint newAllowedValues(
216       @NonNull FlagAllowedValues obj,
217       @NonNull ISource source) {
218     IAllowedValuesConstraint.Builder builder = IAllowedValuesConstraint.builder()
219         .allowsOther(ModelSupport.yesOrNo(obj.getAllowOther()))
220         .extensible(extensible(obj.getExtensible()));
221     applyCommonValues(obj, null, source, builder);
222 
223     for (ConstraintValueEnum value : ObjectUtils.requireNonNull(obj.getEnums())) {
224       builder.allowedValue(ObjectUtils.requireNonNull(value));
225     }
226     return builder.build();
227   }
228 
229   @NonNull
230   private static IAllowedValuesConstraint newAllowedValues(
231       @NonNull TargetedAllowedValuesConstraint obj,
232       @NonNull ISource source) {
233     IAllowedValuesConstraint.Builder builder = IAllowedValuesConstraint.builder()
234         .allowsOther(ModelSupport.yesOrNo(obj.getAllowOther()))
235         .extensible(extensible(ObjectUtils.requireNonNull(obj.getExtensible())));
236     applyCommonValues(obj, obj.getTarget(), source, builder);
237 
238     for (ConstraintValueEnum value : ObjectUtils.requireNonNull(obj.getEnums())) {
239       builder.allowedValue(ObjectUtils.requireNonNull(value));
240     }
241     return builder.build();
242   }
243 
244   @NonNull
245   private static IExpectConstraint newExpect(
246       @NonNull FlagExpect obj,
247       @NonNull ISource source) {
248     IExpectConstraint.Builder builder = IExpectConstraint.builder()
249         .test(metapath(ObjectUtils.requireNonNull(obj.getTest()), source));
250     applyConfigurableCommonValues(obj, null, source, builder);
251 
252     String message = obj.getMessage();
253     if (message != null) {
254       builder.message(message);
255     }
256 
257     return builder.build();
258   }
259 
260   @NonNull
261   private static IExpectConstraint newExpect(
262       @NonNull TargetedExpectConstraint obj,
263       @NonNull ISource source) {
264     IExpectConstraint.Builder builder = IExpectConstraint.builder()
265         .test(metapath(ObjectUtils.requireNonNull(obj.getTest()), source));
266     applyConfigurableCommonValues(obj, obj.getTarget(), source, builder);
267 
268     return builder.build();
269   }
270 
271   @NonNull
272   private static <T extends AbstractKeyConstraintBuilder<T, ?>> T handleKeyConstraints(
273       @NonNull List<KeyConstraintField> keys,
274       @NonNull T builder,
275       @NonNull ISource source) {
276     for (KeyConstraintField value : keys) {
277       assert value != null;
278 
279       IKeyField keyField = IKeyField.of(
280           metapath(ObjectUtils.requireNonNull(value.getTarget()), source),
281           pattern(value.getPattern()),
282           ModelSupport.remarks(value.getRemarks()));
283       builder.keyField(keyField);
284     }
285     return builder;
286   }
287 
288   @NonNull
289   private static IIndexHasKeyConstraint newIndexHasKey(
290       @NonNull FlagIndexHasKey obj,
291       @NonNull ISource source) {
292     IIndexHasKeyConstraint.Builder builder = IIndexHasKeyConstraint.builder(ObjectUtils.requireNonNull(obj.getName()));
293     applyConfigurableCommonValues(obj, null, source, builder);
294     handleKeyConstraints(ObjectUtils.requireNonNull(obj.getKeyFields()), builder, source);
295     return builder.build();
296   }
297 
298   @NonNull
299   private static IIndexHasKeyConstraint newIndexHasKey(
300       @NonNull TargetedIndexHasKeyConstraint obj,
301       @NonNull ISource source) {
302     IIndexHasKeyConstraint.Builder builder = IIndexHasKeyConstraint.builder(ObjectUtils.requireNonNull(obj.getName()));
303     applyConfigurableCommonValues(obj, obj.getTarget(), source, builder);
304     handleKeyConstraints(ObjectUtils.requireNonNull(obj.getKeyFields()), builder, source);
305     return builder.build();
306   }
307 
308   @NonNull
309   private static IMatchesConstraint newMatches(
310       @NonNull FlagMatches obj,
311       @NonNull ISource source) {
312     IMatchesConstraint.Builder builder = IMatchesConstraint.builder();
313     applyConfigurableCommonValues(obj, null, source, builder);
314 
315     Pattern regex = pattern(obj.getRegex());
316     if (regex != null) {
317       builder.regex(regex);
318     }
319 
320     String dataType = obj.getDatatype();
321     if (dataType != null) {
322       IDataTypeAdapter<?> javaTypeAdapter = ModelSupport.dataType(
323           obj.getDatatype(),
324           source);
325       builder.datatype(javaTypeAdapter);
326     }
327 
328     return builder.build();
329   }
330 
331   @NonNull
332   private static IMatchesConstraint newMatches(
333       @NonNull TargetedMatchesConstraint obj,
334       @NonNull ISource source) {
335     IMatchesConstraint.Builder builder = IMatchesConstraint.builder();
336     applyConfigurableCommonValues(obj, obj.getTarget(), source, builder);
337 
338     Pattern regex = pattern(obj.getRegex());
339     if (regex != null) {
340       builder.regex(regex);
341     }
342 
343     String dataType = obj.getDatatype();
344     if (dataType != null) {
345       IDataTypeAdapter<?> javaTypeAdapter = ModelSupport.dataType(
346           obj.getDatatype(),
347           source);
348       builder.datatype(javaTypeAdapter);
349     }
350 
351     return builder.build();
352   }
353 
354   @NonNull
355   private static IIndexConstraint newIndex(
356       @NonNull TargetedIndexConstraint obj,
357       @NonNull ISource source) {
358     IIndexConstraint.Builder builder = IIndexConstraint.builder(ObjectUtils.requireNonNull(obj.getName()));
359     applyConfigurableCommonValues(obj, obj.getTarget(), source, builder);
360     handleKeyConstraints(ObjectUtils.requireNonNull(obj.getKeyFields()), builder, source);
361 
362     return builder.build();
363   }
364 
365   @NonNull
366   private static ICardinalityConstraint newHasCardinality(
367       @NonNull TargetedHasCardinalityConstraint obj,
368       @NonNull ISource source) {
369     ICardinalityConstraint.Builder builder = ICardinalityConstraint.builder();
370     applyConfigurableCommonValues(obj, obj.getTarget(), source, builder);
371 
372     BigInteger minOccurs = obj.getMinOccurs();
373     if (minOccurs != null) {
374       builder.minOccurs(minOccurs.intValueExact());
375     }
376     String maxOccurs = obj.getMaxOccurs();
377     if (maxOccurs != null) {
378       int occurance = ModelSupport.maxOccurs(maxOccurs);
379       builder.maxOccurs(occurance);
380     }
381 
382     return builder.build();
383   }
384 
385   @NonNull
386   private static IUniqueConstraint newUnique(
387       @NonNull TargetedIsUniqueConstraint obj,
388       @NonNull ISource source) {
389     IUniqueConstraint.Builder builder = IUniqueConstraint.builder();
390     applyConfigurableCommonValues(obj, obj.getTarget(), source, builder);
391     handleKeyConstraints(ObjectUtils.requireNonNull(obj.getKeyFields()), builder, source);
392 
393     return builder.build();
394   }
395 
396   @NonNull
397   private static <T extends AbstractConfigurableMessageConstraintBuilder<T, ?>> T applyConfigurableCommonValues(
398       @NonNull IConfigurableMessageConstraintBase constraint,
399       @Nullable String target,
400       @NonNull ISource source,
401       @NonNull T builder) {
402     applyCommonValues(constraint, target, source, builder);
403 
404     String message = constraint.getMessage();
405     if (message != null) {
406       builder.message(message);
407     }
408     return builder;
409   }
410 
411   @NonNull
412   private static <T extends AbstractConstraintBuilder<T, ?>> T applyCommonValues(
413       @NonNull IConstraintBase constraint,
414       @Nullable String target,
415       @NonNull ISource source,
416       @NonNull T builder) {
417 
418     String id = constraint.getId();
419 
420     if (id != null) {
421       builder.identifier(id);
422     }
423 
424     String formalName = constraint.getFormalName();
425     if (formalName != null) {
426       builder.formalName(formalName);
427     }
428 
429     MarkupLine description = constraint.getDescription();
430     if (description != null) {
431       builder.description(description);
432     }
433 
434     List<Property> props = ObjectUtils.requireNonNull(constraint.getProps());
435     builder.properties(ModelSupport.parseProperties(props));
436 
437     Remarks remarks = constraint.getRemarks();
438     if (remarks != null) {
439       builder.remarks(ObjectUtils.notNull(remarks.getRemark()));
440     }
441 
442     builder.target(metapath(target, source));
443     builder.level(level(constraint.getLevel()));
444     builder.source(source);
445     return builder;
446   }
447 
448   @NonNull
449   private static IMetapathExpression metapath(
450       @Nullable String metapath,
451       @NonNull ISource source) {
452     return metapath == null
453         ? IConstraint.defaultTarget()
454         : IMetapathExpression.lazyCompile(
455             metapath,
456             source.getStaticContext());
457   }
458 
459   @NonNull
460   private static IConstraint.Level level(@Nullable String level) {
461     IConstraint.Level retval = IConstraint.defaultLevel();
462     if (level != null) {
463       switch (level) {
464       case "CRITICAL":
465         retval = IConstraint.Level.CRITICAL;
466         break;
467       case "ERROR":
468         retval = IConstraint.Level.ERROR;
469         break;
470       case "WARNING":
471         retval = IConstraint.Level.WARNING;
472         break;
473       case "INFORMATIONAL":
474         retval = IConstraint.Level.INFORMATIONAL;
475         break;
476       case "DEBUG":
477         retval = IConstraint.Level.DEBUG;
478         break;
479       default:
480         throw new UnsupportedOperationException(level);
481       }
482     }
483     return retval;
484   }
485 
486   @NonNull
487   private static IAllowedValuesConstraint.Extensible extensible(@Nullable String extensible) {
488     IAllowedValuesConstraint.Extensible retval = IAllowedValuesConstraint.EXTENSIBLE_DEFAULT;
489     if (extensible != null) {
490       switch (extensible) {
491       case "model":
492         retval = IAllowedValuesConstraint.Extensible.MODEL;
493         break;
494       case "external":
495         retval = IAllowedValuesConstraint.Extensible.EXTERNAL;
496         break;
497       case "none":
498         retval = IAllowedValuesConstraint.Extensible.NONE;
499         break;
500       default:
501         throw new UnsupportedOperationException(extensible);
502       }
503     }
504     return retval;
505   }
506 
507   @Nullable
508   private static Pattern pattern(@Nullable String pattern) {
509     return pattern == null ? null : Pattern.compile(pattern);
510   }
511 
512 }