View Javadoc
1   /*
2    * Portions of this software was developed by employees of the National Institute
3    * of Standards and Technology (NIST), an agency of the Federal Government and is
4    * being made available as a public service. Pursuant to title 17 United States
5    * Code Section 105, works of NIST employees are not subject to copyright
6    * protection in the United States. This software may be subject to foreign
7    * copyright. Permission in the United States and in foreign countries, to the
8    * extent that NIST may hold copyright, to use, copy, modify, create derivative
9    * works, and distribute this software and its documentation without fee is hereby
10   * granted on a non-exclusive basis, provided that this notice and disclaimer
11   * of warranty appears in all copies.
12   *
13   * THE SOFTWARE IS PROVIDED 'AS IS' WITHOUT ANY WARRANTY OF ANY KIND, EITHER
14   * EXPRESSED, IMPLIED, OR STATUTORY, INCLUDING, BUT NOT LIMITED TO, ANY WARRANTY
15   * THAT THE SOFTWARE WILL CONFORM TO SPECIFICATIONS, ANY IMPLIED WARRANTIES OF
16   * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, AND FREEDOM FROM
17   * INFRINGEMENT, AND ANY WARRANTY THAT THE DOCUMENTATION WILL CONFORM TO THE
18   * SOFTWARE, OR ANY WARRANTY THAT THE SOFTWARE WILL BE ERROR FREE.  IN NO EVENT
19   * SHALL NIST BE LIABLE FOR ANY DAMAGES, INCLUDING, BUT NOT LIMITED TO, DIRECT,
20   * INDIRECT, SPECIAL OR CONSEQUENTIAL DAMAGES, ARISING OUT OF, RESULTING FROM,
21   * OR IN ANY WAY CONNECTED WITH THIS SOFTWARE, WHETHER OR NOT BASED UPON WARRANTY,
22   * CONTRACT, TORT, OR OTHERWISE, WHETHER OR NOT INJURY WAS SUSTAINED BY PERSONS OR
23   * PROPERTY OR OTHERWISE, AND WHETHER OR NOT LOSS WAS SUSTAINED FROM, OR AROSE OUT
24   * OF THE RESULTS OF, OR USE OF, THE SOFTWARE OR SERVICES PROVIDED HEREUNDER.
25   */
26  
27  package gov.nist.secauto.metaschema.databind.model;
28  
29  import gov.nist.secauto.metaschema.core.datatype.DataTypeService;
30  import gov.nist.secauto.metaschema.core.datatype.IDataTypeAdapter;
31  import gov.nist.secauto.metaschema.core.datatype.markup.MarkupLine;
32  import gov.nist.secauto.metaschema.core.datatype.markup.MarkupMultiline;
33  import gov.nist.secauto.metaschema.core.metapath.MetapathExpression;
34  import gov.nist.secauto.metaschema.core.model.constraint.AbstractConstraint.AbstractConstraintBuilder;
35  import gov.nist.secauto.metaschema.core.model.constraint.AbstractKeyConstraint.AbstractKeyConstraintBuilder;
36  import gov.nist.secauto.metaschema.core.model.constraint.DefaultAllowedValue;
37  import gov.nist.secauto.metaschema.core.model.constraint.DefaultAllowedValuesConstraint;
38  import gov.nist.secauto.metaschema.core.model.constraint.DefaultCardinalityConstraint;
39  import gov.nist.secauto.metaschema.core.model.constraint.DefaultExpectConstraint;
40  import gov.nist.secauto.metaschema.core.model.constraint.DefaultIndexConstraint;
41  import gov.nist.secauto.metaschema.core.model.constraint.DefaultIndexHasKeyConstraint;
42  import gov.nist.secauto.metaschema.core.model.constraint.DefaultKeyField;
43  import gov.nist.secauto.metaschema.core.model.constraint.DefaultMatchesConstraint;
44  import gov.nist.secauto.metaschema.core.model.constraint.DefaultUniqueConstraint;
45  import gov.nist.secauto.metaschema.core.model.constraint.IConstraint;
46  import gov.nist.secauto.metaschema.core.model.constraint.IConstraint.ISource;
47  import gov.nist.secauto.metaschema.databind.model.annotations.AllowedValue;
48  import gov.nist.secauto.metaschema.databind.model.annotations.AllowedValues;
49  import gov.nist.secauto.metaschema.databind.model.annotations.Expect;
50  import gov.nist.secauto.metaschema.databind.model.annotations.HasCardinality;
51  import gov.nist.secauto.metaschema.databind.model.annotations.Index;
52  import gov.nist.secauto.metaschema.databind.model.annotations.IndexHasKey;
53  import gov.nist.secauto.metaschema.databind.model.annotations.IsUnique;
54  import gov.nist.secauto.metaschema.databind.model.annotations.KeyField;
55  import gov.nist.secauto.metaschema.databind.model.annotations.Matches;
56  import gov.nist.secauto.metaschema.databind.model.annotations.NullJavaTypeAdapter;
57  import gov.nist.secauto.metaschema.databind.model.annotations.Property;
58  
59  import org.apache.logging.log4j.LogManager;
60  import org.apache.logging.log4j.Logger;
61  
62  import java.util.Arrays;
63  import java.util.LinkedHashSet;
64  import java.util.List;
65  import java.util.Set;
66  import java.util.regex.Pattern;
67  
68  import javax.xml.namespace.QName;
69  
70  import edu.umd.cs.findbugs.annotations.NonNull;
71  import edu.umd.cs.findbugs.annotations.Nullable;
72  
73  final class ConstraintFactory {
74    private static final Logger LOGGER = LogManager.getLogger(ConstraintFactory.class);
75  
76    private ConstraintFactory() {
77      // disable
78    }
79  
80    static MarkupMultiline toRemarks(@NonNull String remarks) {
81      return remarks.isBlank() ? null : MarkupMultiline.fromMarkdown(remarks);
82    }
83  
84    @NonNull
85    static MetapathExpression toMetapath(@NonNull String metapath) {
86      String path = metapath;
87      if (path.startsWith("/")) {
88        String newPath = "." + path;
89  
90        if (LOGGER.isInfoEnabled()) {
91          StringBuilder builder = new StringBuilder(80)
92              .append("The path '")
93              .append(path)
94              .append("' is not properly contextualized using '.'. Using '")
95              .append(newPath)
96              .append("' instead.");
97          LOGGER.atInfo().log(builder.toString());
98        }
99        path = newPath;
100     }
101 
102     return path.isBlank() ? IConstraint.DEFAULT_TARGET : MetapathExpression.compile(path);
103   }
104 
105   @NonNull
106   static <T extends AbstractConstraintBuilder<T, ?>> T applyId(@NonNull T builder, @NonNull String id) {
107     if (!id.isBlank()) {
108       builder.identifier(id);
109     }
110     return builder;
111   }
112 
113   @NonNull
114   static <T extends AbstractConstraintBuilder<T, ?>> T applyFormalName(@NonNull T builder, @NonNull String name) {
115     if (!name.isBlank()) {
116       builder.formalName(name);
117     }
118     return builder;
119   }
120 
121   @NonNull
122   static <T extends AbstractConstraintBuilder<T, ?>> T applyDescription(@NonNull T builder, @NonNull String value) {
123     if (!value.isBlank()) {
124       builder.description(MarkupLine.fromMarkdown(value));
125     }
126     return builder;
127   }
128 
129   @NonNull
130   static <T extends AbstractConstraintBuilder<T, ?>> T applyTarget(@NonNull T builder, @NonNull String target) {
131     builder.target(toMetapath(target));
132     return builder;
133   }
134 
135   @NonNull
136   static <T extends AbstractConstraintBuilder<T, ?>> T applyProperties(
137       @NonNull T builder,
138       @Nullable Property... properties) {
139     if (properties != null) {
140       for (Property property : properties) {
141         String name = property.name();
142         String namespace = property.namespace();
143         @SuppressWarnings("PMD.AvoidInstantiatingObjectsInLoops") // ok
144         QName qname = new QName(namespace, name);
145 
146         String[] values = property.values();
147         List<String> valueList = Arrays.asList(values);
148         @SuppressWarnings("PMD.AvoidInstantiatingObjectsInLoops") // ok
149         Set<String> valueSet = new LinkedHashSet<>(valueList);
150         builder.property(qname, valueSet);
151       }
152     }
153     return builder;
154   }
155 
156   static <T extends AbstractConstraintBuilder<T, ?>> T applyRemarks(@NonNull T builder, @NonNull String remarks) {
157     if (!remarks.isBlank()) {
158       builder.remarks(MarkupMultiline.fromMarkdown(remarks));
159     }
160     return builder;
161   }
162 
163   @NonNull
164   static DefaultAllowedValuesConstraint.Builder applyAllowedValues(
165       @NonNull DefaultAllowedValuesConstraint.Builder builder,
166       @NonNull AllowedValues constraint) {
167     for (AllowedValue value : constraint.values()) {
168       DefaultAllowedValue allowedValue
169           = new DefaultAllowedValue(value.value(), MarkupLine.fromMarkdown(value.description())); // NOPMD - intentional
170       builder.allowedValue(allowedValue);
171     }
172     return builder;
173   }
174 
175   @Nullable
176   static Pattern toPattern(@NonNull String pattern) {
177     return pattern.isBlank() ? null : Pattern.compile(pattern);
178   }
179 
180   @Nullable
181   static String toMessage(@NonNull String message) {
182     return message.isBlank() ? null : message;
183   }
184 
185   @Nullable
186   static IDataTypeAdapter<?> toDataType(@NonNull Class<? extends IDataTypeAdapter<?>> adapterClass) {
187     return adapterClass.isAssignableFrom(NullJavaTypeAdapter.class) ? null
188         : DataTypeService.getInstance().getJavaTypeAdapterByClass(adapterClass);
189   }
190 
191   @NonNull
192   static DefaultAllowedValuesConstraint newAllowedValuesConstraint(
193       @NonNull AllowedValues constraint,
194       @NonNull ISource source) {
195     DefaultAllowedValuesConstraint.Builder builder = DefaultAllowedValuesConstraint.builder();
196     applyId(builder, constraint.id());
197     applyFormalName(builder, constraint.formalName());
198     applyDescription(builder, constraint.description());
199     builder
200         .source(source)
201         .level(constraint.level());
202     applyTarget(builder, constraint.target());
203     applyProperties(builder, constraint.properties());
204     applyRemarks(builder, constraint.remarks());
205 
206     applyAllowedValues(builder, constraint);
207     builder.allowedOther(constraint.allowOthers());
208     builder.extensible(constraint.extensible());
209 
210     return builder.build();
211   }
212 
213   @NonNull
214   static DefaultMatchesConstraint newMatchesConstraint(Matches constraint, @NonNull ISource source) {
215     DefaultMatchesConstraint.Builder builder = DefaultMatchesConstraint.builder();
216     applyId(builder, constraint.id());
217     applyFormalName(builder, constraint.formalName());
218     applyDescription(builder, constraint.description());
219     builder
220         .source(source)
221         .level(constraint.level());
222     applyTarget(builder, constraint.target());
223     applyProperties(builder, constraint.properties());
224     applyRemarks(builder, constraint.remarks());
225 
226     Pattern pattern = toPattern(constraint.pattern());
227     if (pattern != null) {
228       builder.regex(pattern);
229     }
230 
231     IDataTypeAdapter<?> dataType = toDataType(constraint.typeAdapter());
232     if (dataType != null) {
233       builder.datatype(dataType);
234     }
235 
236     return builder.build();
237   }
238 
239   @NonNull
240   static <T extends AbstractKeyConstraintBuilder<T, ?>> T applyKeyFields(@NonNull T builder,
241       @NonNull KeyField... keyFields) {
242     for (KeyField keyField : keyFields) {
243       @SuppressWarnings("PMD.AvoidInstantiatingObjectsInLoops") // ok
244       DefaultKeyField field = new DefaultKeyField(
245           toMetapath(keyField.target()),
246           toPattern(keyField.pattern()),
247           toRemarks(keyField.remarks()));
248       builder.keyField(field);
249     }
250     return builder;
251   }
252 
253   @NonNull
254   static DefaultUniqueConstraint newUniqueConstraint(@NonNull IsUnique constraint, @NonNull ISource source) {
255     DefaultUniqueConstraint.Builder builder = DefaultUniqueConstraint.builder();
256     applyId(builder, constraint.id());
257     applyFormalName(builder, constraint.formalName());
258     applyDescription(builder, constraint.description());
259     builder
260         .source(source)
261         .level(constraint.level());
262     applyTarget(builder, constraint.target());
263     applyProperties(builder, constraint.properties());
264     applyRemarks(builder, constraint.remarks());
265 
266     applyKeyFields(builder, constraint.keyFields());
267 
268     return builder.build();
269   }
270 
271   @NonNull
272   static DefaultIndexConstraint newIndexConstraint(@NonNull Index constraint, @NonNull ISource source) {
273     DefaultIndexConstraint.Builder builder = DefaultIndexConstraint.builder();
274     applyId(builder, constraint.id());
275     applyFormalName(builder, constraint.formalName());
276     applyDescription(builder, constraint.description());
277     builder
278         .source(source)
279         .level(constraint.level());
280     applyTarget(builder, constraint.target());
281     applyProperties(builder, constraint.properties());
282     applyRemarks(builder, constraint.remarks());
283 
284     builder.name(constraint.name());
285     applyKeyFields(builder, constraint.keyFields());
286 
287     return builder.build();
288   }
289 
290   @NonNull
291   static DefaultIndexHasKeyConstraint newIndexHasKeyConstraint(@NonNull IndexHasKey constraint,
292       @NonNull ISource source) {
293     DefaultIndexHasKeyConstraint.Builder builder = DefaultIndexHasKeyConstraint.builder();
294     applyId(builder, constraint.id());
295     applyFormalName(builder, constraint.formalName());
296     applyDescription(builder, constraint.description());
297     builder
298         .source(source)
299         .level(constraint.level());
300     applyTarget(builder, constraint.target());
301     applyProperties(builder, constraint.properties());
302     applyRemarks(builder, constraint.remarks());
303 
304     builder.name(constraint.indexName());
305     applyKeyFields(builder, constraint.keyFields());
306 
307     return builder.build();
308   }
309 
310   @NonNull
311   static DefaultExpectConstraint newExpectConstraint(@NonNull Expect constraint, @NonNull ISource source) {
312     DefaultExpectConstraint.Builder builder = DefaultExpectConstraint.builder();
313     applyId(builder, constraint.id());
314     applyFormalName(builder, constraint.formalName());
315     applyDescription(builder, constraint.description());
316     builder
317         .source(source)
318         .level(constraint.level());
319     applyTarget(builder, constraint.target());
320     applyProperties(builder, constraint.properties());
321     applyRemarks(builder, constraint.remarks());
322 
323     builder.test(toMetapath(constraint.test()));
324 
325     String message = constraint.message();
326     if (!message.isBlank()) {
327       builder.message(message);
328     }
329 
330     return builder.build();
331   }
332 
333   @Nullable
334   static Integer toCardinality(int value) {
335     return value < 0 ? null : value;
336   }
337 
338   @NonNull
339   static DefaultCardinalityConstraint newCardinalityConstraint(@NonNull HasCardinality constraint,
340       @NonNull ISource source) {
341     DefaultCardinalityConstraint.Builder builder = DefaultCardinalityConstraint.builder();
342     applyId(builder, constraint.id());
343     applyFormalName(builder, constraint.formalName());
344     applyDescription(builder, constraint.description());
345     builder
346         .source(source)
347         .level(constraint.level());
348     applyTarget(builder, constraint.target());
349     applyProperties(builder, constraint.properties());
350     applyRemarks(builder, constraint.remarks());
351 
352     Integer min = toCardinality(constraint.minOccurs());
353     if (min != null) {
354       builder.minOccurs(min);
355     }
356     Integer max = toCardinality(constraint.maxOccurs());
357     if (max != null) {
358       builder.maxOccurs(max);
359     }
360 
361     return builder.build();
362   }
363 }