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.model.ISource;
12  import gov.nist.secauto.metaschema.core.model.constraint.AbstractConfigurableMessageConstraintBuilder;
13  import gov.nist.secauto.metaschema.core.model.constraint.AbstractConstraintBuilder;
14  import gov.nist.secauto.metaschema.core.model.constraint.AbstractKeyConstraintBuilder;
15  import gov.nist.secauto.metaschema.core.model.constraint.IAllowedValuesConstraint;
16  import gov.nist.secauto.metaschema.core.model.constraint.ICardinalityConstraint;
17  import gov.nist.secauto.metaschema.core.model.constraint.IConstraint;
18  import gov.nist.secauto.metaschema.core.model.constraint.IExpectConstraint;
19  import gov.nist.secauto.metaschema.core.model.constraint.IIndexConstraint;
20  import gov.nist.secauto.metaschema.core.model.constraint.IIndexHasKeyConstraint;
21  import gov.nist.secauto.metaschema.core.model.constraint.IKeyField;
22  import gov.nist.secauto.metaschema.core.model.constraint.ILet;
23  import gov.nist.secauto.metaschema.core.model.constraint.IMatchesConstraint;
24  import gov.nist.secauto.metaschema.core.model.constraint.IModelConstrained;
25  import gov.nist.secauto.metaschema.core.model.constraint.IUniqueConstraint;
26  import gov.nist.secauto.metaschema.core.model.constraint.IValueConstrained;
27  import gov.nist.secauto.metaschema.core.util.ObjectUtils;
28  import gov.nist.secauto.metaschema.databind.model.metaschema.IConfigurableMessageConstraintBase;
29  import gov.nist.secauto.metaschema.databind.model.metaschema.IConstraintBase;
30  import gov.nist.secauto.metaschema.databind.model.metaschema.IModelConstraintsBase;
31  import gov.nist.secauto.metaschema.databind.model.metaschema.IValueConstraintsBase;
32  import gov.nist.secauto.metaschema.databind.model.metaschema.IValueTargetedConstraintsBase;
33  import gov.nist.secauto.metaschema.databind.model.metaschema.binding.ConstraintValueEnum;
34  import gov.nist.secauto.metaschema.databind.model.metaschema.binding.FlagAllowedValues;
35  import gov.nist.secauto.metaschema.databind.model.metaschema.binding.FlagExpect;
36  import gov.nist.secauto.metaschema.databind.model.metaschema.binding.FlagIndexHasKey;
37  import gov.nist.secauto.metaschema.databind.model.metaschema.binding.FlagMatches;
38  import gov.nist.secauto.metaschema.databind.model.metaschema.binding.KeyConstraintField;
39  import gov.nist.secauto.metaschema.databind.model.metaschema.binding.Property;
40  import gov.nist.secauto.metaschema.databind.model.metaschema.binding.Remarks;
41  import gov.nist.secauto.metaschema.databind.model.metaschema.binding.TargetedAllowedValuesConstraint;
42  import gov.nist.secauto.metaschema.databind.model.metaschema.binding.TargetedExpectConstraint;
43  import gov.nist.secauto.metaschema.databind.model.metaschema.binding.TargetedHasCardinalityConstraint;
44  import gov.nist.secauto.metaschema.databind.model.metaschema.binding.TargetedIndexConstraint;
45  import gov.nist.secauto.metaschema.databind.model.metaschema.binding.TargetedIndexHasKeyConstraint;
46  import gov.nist.secauto.metaschema.databind.model.metaschema.binding.TargetedIsUniqueConstraint;
47  import gov.nist.secauto.metaschema.databind.model.metaschema.binding.TargetedMatchesConstraint;
48  
49  import java.math.BigInteger;
50  import java.util.List;
51  import java.util.regex.Pattern;
52  
53  import javax.xml.namespace.QName;
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     // parse let expressions
193     constraints.getLets().stream()
194         .map(letObj -> {
195           MarkupMultiline remarks = null;
196           Remarks remarkObj = letObj.getRemarks();
197           if (remarkObj != null) {
198             remarks = remarkObj.getRemark();
199           }
200 
201           return ILet.of(
202               ObjectUtils.requireNonNull(new QName(letObj.getVar())),
203               ObjectUtils.requireNonNull(letObj.getExpression()),
204               source,
205               remarks);
206         })
207         .forEachOrdered(constraintSet::addLetExpression);
208   }
209 
210   @NonNull
211   private static IAllowedValuesConstraint newAllowedValues(
212       @NonNull FlagAllowedValues obj,
213       @NonNull ISource source) {
214     IAllowedValuesConstraint.Builder builder = IAllowedValuesConstraint.builder()
215         .allowsOther(ModelSupport.yesOrNo(obj.getAllowOther()))
216         .extensible(extensible(obj.getExtensible()));
217     applyCommonValues(obj, null, source, builder);
218 
219     for (ConstraintValueEnum value : ObjectUtils.requireNonNull(obj.getEnums())) {
220       builder.allowedValue(ObjectUtils.requireNonNull(value));
221     }
222     return builder.build();
223   }
224 
225   @NonNull
226   private static IAllowedValuesConstraint newAllowedValues(
227       @NonNull TargetedAllowedValuesConstraint obj,
228       @NonNull ISource source) {
229     IAllowedValuesConstraint.Builder builder = IAllowedValuesConstraint.builder()
230         .allowsOther(ModelSupport.yesOrNo(obj.getAllowOther()))
231         .extensible(extensible(ObjectUtils.requireNonNull(obj.getExtensible())));
232     applyCommonValues(obj, obj.getTarget(), source, builder);
233 
234     for (ConstraintValueEnum value : ObjectUtils.requireNonNull(obj.getEnums())) {
235       builder.allowedValue(ObjectUtils.requireNonNull(value));
236     }
237     return builder.build();
238   }
239 
240   @NonNull
241   private static IExpectConstraint newExpect(
242       @NonNull FlagExpect obj,
243       @NonNull ISource source) {
244     IExpectConstraint.Builder builder = IExpectConstraint.builder()
245         .test(target(ObjectUtils.requireNonNull(obj.getTest())));
246     applyConfigurableCommonValues(obj, null, source, builder);
247 
248     String message = obj.getMessage();
249     if (message != null) {
250       builder.message(message);
251     }
252 
253     return builder.build();
254   }
255 
256   @NonNull
257   private static IExpectConstraint newExpect(
258       @NonNull TargetedExpectConstraint obj,
259       @NonNull ISource source) {
260     IExpectConstraint.Builder builder = IExpectConstraint.builder()
261         .test(target(ObjectUtils.requireNonNull(obj.getTest())));
262     applyConfigurableCommonValues(obj, obj.getTarget(), source, builder);
263 
264     return builder.build();
265   }
266 
267   @NonNull
268   private static <T extends AbstractKeyConstraintBuilder<T, ?>> T handleKeyConstraints(
269       @NonNull List<KeyConstraintField> keys,
270       @NonNull T builder,
271       @NonNull ISource source) {
272     for (KeyConstraintField value : keys) {
273       assert value != null;
274 
275       IKeyField keyField = IKeyField.of(
276           target(ObjectUtils.requireNonNull(value.getTarget())),
277           pattern(value.getPattern()),
278           ModelSupport.remarks(value.getRemarks()),
279           source);
280       builder.keyField(keyField);
281     }
282     return builder;
283   }
284 
285   @NonNull
286   private static IIndexHasKeyConstraint newIndexHasKey(
287       @NonNull FlagIndexHasKey obj,
288       @NonNull ISource source) {
289     IIndexHasKeyConstraint.Builder builder = IIndexHasKeyConstraint.builder(ObjectUtils.requireNonNull(obj.getName()));
290     applyConfigurableCommonValues(obj, null, source, builder);
291     handleKeyConstraints(ObjectUtils.requireNonNull(obj.getKeyFields()), builder, source);
292     return builder.build();
293   }
294 
295   @NonNull
296   private static IIndexHasKeyConstraint newIndexHasKey(
297       @NonNull TargetedIndexHasKeyConstraint obj,
298       @NonNull ISource source) {
299     IIndexHasKeyConstraint.Builder builder = IIndexHasKeyConstraint.builder(ObjectUtils.requireNonNull(obj.getName()));
300     applyConfigurableCommonValues(obj, obj.getTarget(), source, builder);
301     handleKeyConstraints(ObjectUtils.requireNonNull(obj.getKeyFields()), builder, source);
302     return builder.build();
303   }
304 
305   @NonNull
306   private static IMatchesConstraint newMatches(
307       @NonNull FlagMatches obj,
308       @NonNull ISource source) {
309     IMatchesConstraint.Builder builder = IMatchesConstraint.builder();
310     applyConfigurableCommonValues(obj, null, source, builder);
311 
312     Pattern regex = pattern(obj.getRegex());
313     if (regex != null) {
314       builder.regex(regex);
315     }
316 
317     String dataType = obj.getDatatype();
318     if (dataType != null) {
319       IDataTypeAdapter<?> javaTypeAdapter = ModelSupport.dataType(obj.getDatatype());
320       builder.datatype(javaTypeAdapter);
321     }
322 
323     return builder.build();
324   }
325 
326   @NonNull
327   private static IMatchesConstraint newMatches(
328       @NonNull TargetedMatchesConstraint obj,
329       @NonNull ISource source) {
330     IMatchesConstraint.Builder builder = IMatchesConstraint.builder();
331     applyConfigurableCommonValues(obj, obj.getTarget(), source, builder);
332 
333     Pattern regex = pattern(obj.getRegex());
334     if (regex != null) {
335       builder.regex(regex);
336     }
337 
338     String dataType = obj.getDatatype();
339     if (dataType != null) {
340       IDataTypeAdapter<?> javaTypeAdapter = ModelSupport.dataType(obj.getDatatype());
341       builder.datatype(javaTypeAdapter);
342     }
343 
344     return builder.build();
345   }
346 
347   @NonNull
348   private static IIndexConstraint newIndex(
349       @NonNull TargetedIndexConstraint obj,
350       @NonNull ISource source) {
351     IIndexConstraint.Builder builder = IIndexConstraint.builder(ObjectUtils.requireNonNull(obj.getName()));
352     applyConfigurableCommonValues(obj, obj.getTarget(), source, builder);
353     handleKeyConstraints(ObjectUtils.requireNonNull(obj.getKeyFields()), builder, source);
354 
355     return builder.build();
356   }
357 
358   @NonNull
359   private static ICardinalityConstraint newHasCardinality(
360       @NonNull TargetedHasCardinalityConstraint obj,
361       @NonNull ISource source) {
362     ICardinalityConstraint.Builder builder = ICardinalityConstraint.builder();
363     applyConfigurableCommonValues(obj, obj.getTarget(), source, builder);
364 
365     BigInteger minOccurs = obj.getMinOccurs();
366     if (minOccurs != null) {
367       builder.minOccurs(minOccurs.intValueExact());
368     }
369     String maxOccurs = obj.getMaxOccurs();
370     if (maxOccurs != null) {
371       int occurance = ModelSupport.maxOccurs(maxOccurs);
372       builder.maxOccurs(occurance);
373     }
374 
375     return builder.build();
376   }
377 
378   @NonNull
379   private static IUniqueConstraint newUnique(
380       @NonNull TargetedIsUniqueConstraint obj,
381       @NonNull ISource source) {
382     IUniqueConstraint.Builder builder = IUniqueConstraint.builder();
383     applyConfigurableCommonValues(obj, obj.getTarget(), source, builder);
384     handleKeyConstraints(ObjectUtils.requireNonNull(obj.getKeyFields()), builder, source);
385 
386     return builder.build();
387   }
388 
389   @NonNull
390   private static <T extends AbstractConfigurableMessageConstraintBuilder<T, ?>> T applyConfigurableCommonValues(
391       @NonNull IConfigurableMessageConstraintBase constraint,
392       @Nullable String target,
393       @NonNull ISource source,
394       @NonNull T builder) {
395     applyCommonValues(constraint, target, source, builder);
396 
397     String message = constraint.getMessage();
398     if (message != null) {
399       builder.message(message);
400     }
401     return builder;
402   }
403 
404   @NonNull
405   private static <T extends AbstractConstraintBuilder<T, ?>> T applyCommonValues(
406       @NonNull IConstraintBase constraint,
407       @Nullable String target,
408       @NonNull ISource source,
409       @NonNull T builder) {
410 
411     String id = constraint.getId();
412 
413     if (id != null) {
414       builder.identifier(id);
415     }
416 
417     String formalName = constraint.getFormalName();
418     if (formalName != null) {
419       builder.formalName(formalName);
420     }
421 
422     MarkupLine description = constraint.getDescription();
423     if (description != null) {
424       builder.description(description);
425     }
426 
427     List<Property> props = ObjectUtils.requireNonNull(constraint.getProps());
428     builder.properties(ModelSupport.parseProperties(props));
429 
430     Remarks remarks = constraint.getRemarks();
431     if (remarks != null) {
432       builder.remarks(ObjectUtils.notNull(remarks.getRemark()));
433     }
434 
435     builder.target(target(target));
436     builder.level(level(constraint.getLevel()));
437     builder.source(source);
438     return builder;
439   }
440 
441   @NonNull
442   private static String target(@Nullable String target) {
443     return target == null
444         ? IConstraint.DEFAULT_TARGET_METAPATH
445         : target;
446   }
447 
448   @NonNull
449   private static IConstraint.Level level(@Nullable String level) {
450     IConstraint.Level retval = IConstraint.DEFAULT_LEVEL;
451     if (level != null) {
452       switch (level) {
453       case "CRITICAL":
454         retval = IConstraint.Level.CRITICAL;
455         break;
456       case "ERROR":
457         retval = IConstraint.Level.ERROR;
458         break;
459       case "WARNING":
460         retval = IConstraint.Level.WARNING;
461         break;
462       case "INFORMATIONAL":
463         retval = IConstraint.Level.INFORMATIONAL;
464         break;
465       case "DEBUG":
466         retval = IConstraint.Level.DEBUG;
467         break;
468       default:
469         throw new UnsupportedOperationException(level);
470       }
471     }
472     return retval;
473   }
474 
475   @NonNull
476   private static IAllowedValuesConstraint.Extensible extensible(@Nullable String extensible) {
477     IAllowedValuesConstraint.Extensible retval = IAllowedValuesConstraint.EXTENSIBLE_DEFAULT;
478     if (extensible != null) {
479       switch (extensible) {
480       case "model":
481         retval = IAllowedValuesConstraint.Extensible.MODEL;
482         break;
483       case "external":
484         retval = IAllowedValuesConstraint.Extensible.EXTERNAL;
485         break;
486       case "none":
487         retval = IAllowedValuesConstraint.Extensible.NONE;
488         break;
489       default:
490         throw new UnsupportedOperationException(extensible);
491       }
492     }
493     return retval;
494   }
495 
496   @Nullable
497   private static Pattern pattern(@Nullable String pattern) {
498     return pattern == null ? null : Pattern.compile(pattern);
499   }
500 
501 }