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.core.datatype;
28  
29  import com.fasterxml.jackson.core.JsonGenerator;
30  import com.fasterxml.jackson.core.JsonParser;
31  import com.fasterxml.jackson.databind.jsonFormatVisitors.JsonFormatTypes;
32  
33  import gov.nist.secauto.metaschema.core.metapath.function.InvalidValueForCastFunctionException;
34  import gov.nist.secauto.metaschema.core.metapath.item.atomic.IAnyAtomicItem;
35  import gov.nist.secauto.metaschema.core.util.ObjectUtils;
36  
37  import org.codehaus.stax2.XMLEventReader2;
38  import org.codehaus.stax2.XMLStreamWriter2;
39  import org.codehaus.stax2.evt.XMLEventFactory2;
40  
41  import java.io.IOException;
42  import java.lang.reflect.Field;
43  import java.util.List;
44  import java.util.function.Supplier;
45  
46  import javax.xml.namespace.QName;
47  import javax.xml.stream.XMLEventWriter;
48  import javax.xml.stream.XMLStreamException;
49  import javax.xml.stream.events.StartElement;
50  import javax.xml.stream.events.XMLEvent;
51  
52  import edu.umd.cs.findbugs.annotations.NonNull;
53  
54  public interface IDataTypeAdapter<TYPE> {
55  
56    /**
57     * Get the metaschema type names associated with this adapter. This name must be
58     * unique with respect to all other metaschema types.
59     * <p>
60     * At least one name must be provided, with the first name being the most
61     * preferred name.
62     *
63     * @return the name
64     */
65    @NonNull
66    List<String> getNames();
67  
68    /**
69     * The JSON primative type of the data type.
70     *
71     * @return the JSON data type
72     */
73    JsonFormatTypes getJsonRawType();
74  
75    /**
76     * Get the most preferred name for this data type.
77     *
78     * @return the name
79     */
80    @NonNull
81    default String getPreferredName() {
82      return ObjectUtils.notNull(getNames().iterator().next());
83    }
84  
85    /**
86     * Get the Java class supported by this adapter.
87     *
88     * @return the Java class
89     */
90    @NonNull
91    Class<TYPE> getJavaClass();
92  
93    /**
94     * Casts the provided value to the type associated with this adapter.
95     *
96     * @param value
97     *          a value of the provided type
98     * @return the typed value
99     */
100   @NonNull
101   TYPE toValue(@NonNull Object value);
102 
103   /**
104    * Gets the value as a string suitable for writing as text. This is intended for
105    * data types that have a simple string-based structure in XML and JSON, such as
106    * for XML attributes or JSON keys. An adapter for a complex data structures
107    * that consist of XML elements will throw an
108    * {@link UnsupportedOperationException} when this is called.
109    *
110    * @param value
111    *          the data to formatted as a string
112    * @return a string
113    * @throws UnsupportedOperationException
114    *           if the data type cannot be represented as a string
115    */
116   @NonNull
117   String asString(@NonNull Object value);
118 
119   /**
120    * Create a copy of the provided value.
121    *
122    * @param obj
123    *          the value to copy
124    * @return the copy
125    */
126   @NonNull
127   TYPE copy(@NonNull Object obj);
128 
129   /**
130    * Determines if the data type is an atomic, scalar value. Complex structures
131    * such as Markup are not considered atomic.
132    *
133    * @return {@code true} if the data type is an atomic scalar value, or
134    *         {@code false} otherwise
135    */
136   default boolean isAtomic() {
137     return true;
138   }
139 
140   /**
141    * Get the java type of the associated item.
142    *
143    * @return the java associated item type
144    */
145   @NonNull
146   Class<? extends IAnyAtomicItem> getItemClass();
147 
148   /**
149    * Construct a new item of this type using the provided value.
150    *
151    * @param value
152    *          the item's value
153    * @return a new item
154    */
155   // TODO: markup types are not atomic values. Figure out a better base type
156   // (i.e., IValuedItem)
157   @NonNull
158   IAnyAtomicItem newItem(@NonNull Object value);
159 
160   /**
161    * Cast the provided item to an item of this type, if possible.
162    *
163    * @param item
164    *          the atomic item to cast
165    * @return an atomic item of this type
166    * @throws InvalidValueForCastFunctionException
167    *           if the provided item type cannot be cast to this item type
168    */
169   @NonNull
170   IAnyAtomicItem cast(IAnyAtomicItem item);
171 
172   /**
173    * Indicates if the adapter will parse the {@link XMLEvent#START_ELEMENT} before
174    * parsing the value data.
175    *
176    * @return {@code true} if the adapter requires the start element for parsing,
177    *         or {@code false} otherwise
178    */
179   // TODO; implement or remove this
180   boolean isParsingStartElement();
181 
182   /**
183    * Determines if adapter can parse the next element. The next element's
184    * {@link QName} is provided for this purpose.
185    * <p>
186    * This will be called when the parser encounter's an element it does not
187    * recognize. This gives the adapter a chance to request parsing of the data.
188    *
189    * @param nextElementQName
190    *          the next element's namespace-qualified name
191    * @return {@code true} if the adapter will parse the element, or {@code false}
192    *         otherwise
193    */
194   // TODO: implement this
195   boolean canHandleQName(@NonNull QName nextElementQName);
196 
197   /**
198    * Parses a provided string. Used to parse XML attributes, simple XML character
199    * data, and JSON/YAML property values.
200    *
201    * @param value
202    *          the string value to parse
203    * @return the parsed data as the adapter's type
204    * @throws IllegalArgumentException
205    *           if the data is not valid to the data type
206    */
207   @NonNull
208   TYPE parse(@NonNull String value);
209 
210   /**
211    * This method is expected to parse content starting at the next event. Parsing
212    * will continue until the next event represents content that is not handled by
213    * this adapter. This means the event stream should be positioned after any
214    * {@link XMLEvent#END_ELEMENT} that corresponds to an
215    * {@link XMLEvent#START_ELEMENT} parsed by this adapter.
216    * <p>
217    * If {@link #isParsingStartElement()} returns {@code true}, then the first
218    * event to parse will be the {@link XMLEvent#START_ELEMENT} for the element
219    * that contains the value data, then the value data. If this is the case, this
220    * method must also parse the corresponding {@link XMLEvent#END_ELEMENT}.
221    * Otherwise, the first event to parse will be the value data.
222    * <p>
223    * The value data is expected to be parsed completely, leaving the event stream
224    * on a peeked event corresponding to content that is not handled by this
225    * method.
226    *
227    * @param eventReader
228    *          the XML parser used to read the parsed value
229    * @return the parsed value
230    * @throws IOException
231    *           if a parsing error occurs
232    */
233   @NonNull
234   TYPE parse(@NonNull XMLEventReader2 eventReader) throws IOException;
235 
236   /**
237    * Parses a JSON property value.
238    *
239    * @param parser
240    *          the JSON parser used to read the parsed value
241    * @return the parsed value
242    * @throws IOException
243    *           if a parsing error occurs
244    */
245   @NonNull
246   TYPE parse(@NonNull JsonParser parser) throws IOException;
247 
248   /**
249    * Parses a provided string using {@link #parse(String)}.
250    * <p>
251    * This method may pre-parse the data and then return copies, since the data can
252    * only be parsed once, but the supplier might be called multiple times.
253    *
254    * @param value
255    *          the string value to parse
256    * @return a supplier that will provide new instances of the parsed data
257    * @throws IOException
258    *           if an error occurs while parsing
259    * @see #parse(String)
260    */
261   @NonNull
262   default Supplier<TYPE> parseAndSupply(@NonNull String value) throws IOException {
263     TYPE retval = parse(value);
264     return () -> copy(retval);
265   }
266 
267   /**
268    * Parses a provided string using
269    * {@link IDataTypeAdapter#parse(XMLEventReader2)}.
270    * <p>
271    * This method may pre-parse the data and then return copies, since the data can
272    * only be parsed once, but the supplier might be called multiple times.
273    *
274    * @param eventReader
275    *          the XML parser used to read the parsed value
276    * @return a supplier that will provide new instances of the parsed data
277    * @throws IOException
278    *           if an error occurs while parsing
279    * @see #parse(String)
280    * @see #parse(XMLEventReader2)
281    */
282   @NonNull
283   default Supplier<TYPE> parseAndSupply(@NonNull XMLEventReader2 eventReader) throws IOException {
284     TYPE retval = parse(eventReader);
285     return () -> copy(retval);
286   }
287 
288   /**
289    * Parses a provided string using {@link #parse(JsonParser)}.
290    * <p>
291    * This method may pre-parse the data and then return copies, since the data can
292    * only be parsed once, but the supplier might be called multiple times.
293    *
294    * @param parser
295    *          the JSON parser used to read the parsed value
296    * @return a supplier that will provide new instances of the parsed data
297    * @throws IOException
298    *           if an error occurs while parsing
299    * @see #parse(String)
300    * @see #parse(JsonParser)
301    */
302   @NonNull
303   default Supplier<TYPE> parseAndSupply(@NonNull JsonParser parser) throws IOException {
304     TYPE retval = parse(parser);
305     return () -> copy(retval);
306   }
307 
308   /**
309    * Writes the provided Java class instance data as XML. The parent element
310    * information is provided as a {@link StartElement} event, which allows
311    * namespace information to be obtained from the parent element using the
312    * {@link StartElement#getName()} and {@link StartElement#getNamespaceContext()}
313    * methods, which can be used when writing the provided instance value.
314    *
315    * @param instance
316    *          the {@link Field} instance value to write
317    * @param parent
318    *          the {@link StartElement} XML event that is the parent of the data to
319    *          write
320    * @param eventFactory
321    *          the XML event factory used to generate XML writing events
322    * @param eventWriter
323    *          the XML writer used to output XML as events
324    * @throws XMLStreamException
325    *           if an unexpected error occurred while processing the XML output
326    * @throws IOException
327    *           if an unexpected error occurred while writing to the output stream
328    */
329   void writeXmlValue(@NonNull Object instance, @NonNull StartElement parent, @NonNull XMLEventFactory2 eventFactory,
330       @NonNull XMLEventWriter eventWriter)
331       throws IOException, XMLStreamException;
332 
333   /**
334    * Writes the provided Java class instance data as XML. The parent element
335    * information is provided as an XML {@link QName}, which allows namespace
336    * information to be obtained from the parent element. Additional namespace
337    * information can be gathered using the
338    * {@link XMLStreamWriter2#getNamespaceContext()} method, which can be used when
339    * writing the provided instance value.
340    *
341    * @param instance
342    *          the {@link Field} instance value to write
343    * @param parentName
344    *          the qualified name of the XML data's parent element
345    * @param writer
346    *          the XML writer used to output the XML data
347    * @throws XMLStreamException
348    *           if an unexpected error occurred while processing the XML output
349    */
350   void writeXmlValue(@NonNull Object instance, @NonNull QName parentName, @NonNull XMLStreamWriter2 writer)
351       throws XMLStreamException;
352 
353   /**
354    * Writes the provided Java class instance as a JSON/YAML field value.
355    *
356    * @param instance
357    *          the {@link Field} instance value to write
358    * @param writer
359    *          the JSON/YAML writer used to output the JSON/YAML data
360    * @throws IOException
361    *           if an unexpected error occurred while writing the JSON/YAML output
362    */
363   void writeJsonValue(@NonNull Object instance, @NonNull JsonGenerator writer) throws IOException;
364 
365   /**
366    * Gets the default value to use as the JSON/YAML field name for a Metaschema
367    * field value if no JSON value key flag or name is configured.
368    *
369    * @return the default field name to use
370    */
371   @NonNull
372   String getDefaultJsonValueKey();
373 
374   /**
375    * Determines if the data type's value is allowed to be unwrapped in XML when
376    * the value is a field value.
377    *
378    * @return {@code true} if allowed, or {@code false} otherwise.
379    */
380   boolean isUnrappedValueAllowedInXml();
381 
382   /**
383    * Determines if the datatype uses mixed text and element content in XML.
384    *
385    * @return {@code true} if the datatype uses mixed text and element content in
386    *         XML, or {@code false} otherwise
387    */
388   boolean isXmlMixed();
389 }