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