001/*
002 * Portions of this software was developed by employees of the National Institute
003 * of Standards and Technology (NIST), an agency of the Federal Government and is
004 * being made available as a public service. Pursuant to title 17 United States
005 * Code Section 105, works of NIST employees are not subject to copyright
006 * protection in the United States. This software may be subject to foreign
007 * copyright. Permission in the United States and in foreign countries, to the
008 * extent that NIST may hold copyright, to use, copy, modify, create derivative
009 * works, and distribute this software and its documentation without fee is hereby
010 * granted on a non-exclusive basis, provided that this notice and disclaimer
011 * of warranty appears in all copies.
012 *
013 * THE SOFTWARE IS PROVIDED 'AS IS' WITHOUT ANY WARRANTY OF ANY KIND, EITHER
014 * EXPRESSED, IMPLIED, OR STATUTORY, INCLUDING, BUT NOT LIMITED TO, ANY WARRANTY
015 * THAT THE SOFTWARE WILL CONFORM TO SPECIFICATIONS, ANY IMPLIED WARRANTIES OF
016 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, AND FREEDOM FROM
017 * INFRINGEMENT, AND ANY WARRANTY THAT THE DOCUMENTATION WILL CONFORM TO THE
018 * SOFTWARE, OR ANY WARRANTY THAT THE SOFTWARE WILL BE ERROR FREE.  IN NO EVENT
019 * SHALL NIST BE LIABLE FOR ANY DAMAGES, INCLUDING, BUT NOT LIMITED TO, DIRECT,
020 * INDIRECT, SPECIAL OR CONSEQUENTIAL DAMAGES, ARISING OUT OF, RESULTING FROM,
021 * OR IN ANY WAY CONNECTED WITH THIS SOFTWARE, WHETHER OR NOT BASED UPON WARRANTY,
022 * CONTRACT, TORT, OR OTHERWISE, WHETHER OR NOT INJURY WAS SUSTAINED BY PERSONS OR
023 * PROPERTY OR OTHERWISE, AND WHETHER OR NOT LOSS WAS SUSTAINED FROM, OR AROSE OUT
024 * OF THE RESULTS OF, OR USE OF, THE SOFTWARE OR SERVICES PROVIDED HEREUNDER.
025 */
026
027package gov.nist.secauto.metaschema.core.datatype;
028
029import com.fasterxml.jackson.core.JsonGenerator;
030import com.fasterxml.jackson.core.JsonParser;
031
032import gov.nist.secauto.metaschema.core.metapath.function.InvalidValueForCastFunctionException;
033import gov.nist.secauto.metaschema.core.metapath.item.atomic.IAnyAtomicItem;
034import gov.nist.secauto.metaschema.core.model.util.XmlEventUtil;
035
036import org.codehaus.stax2.XMLEventReader2;
037import org.codehaus.stax2.XMLStreamWriter2;
038import org.codehaus.stax2.evt.XMLEventFactory2;
039
040import java.io.IOException;
041
042import javax.xml.namespace.QName;
043import javax.xml.stream.XMLEventWriter;
044import javax.xml.stream.XMLStreamException;
045import javax.xml.stream.events.Characters;
046import javax.xml.stream.events.StartElement;
047import javax.xml.stream.events.XMLEvent;
048
049import edu.umd.cs.findbugs.annotations.NonNull;
050
051/**
052 * Provides a basic Java type adapter implementation. This implementation should
053 * be the parent class of all Java type adapter implementations.
054 *
055 * @param <TYPE>
056 *          the raw Java type this adapter supports
057 * @param <ITEM_TYPE>
058 *          the metapath item type corresponding to the raw Java type supported
059 *          by the adapter
060 */
061public abstract class AbstractDataTypeAdapter<TYPE, ITEM_TYPE extends IAnyAtomicItem>
062    implements IDataTypeAdapter<TYPE> {
063  public static final String DEFAULT_JSON_FIELD_NAME = "STRVALUE";
064
065  @NonNull
066  private final Class<TYPE> clazz;
067
068  /**
069   * Construct a new Java type adapter for a provided class.
070   *
071   * @param clazz
072   *          the Java type this adapter supports
073   */
074  protected AbstractDataTypeAdapter(@NonNull Class<TYPE> clazz) {
075    this.clazz = clazz;
076  }
077
078  @Override
079  @SuppressWarnings("unchecked")
080  public TYPE toValue(Object value) {
081    return (TYPE) value;
082  }
083
084  @Override
085  public Class<TYPE> getJavaClass() {
086    return clazz;
087  }
088
089  @Override
090  public boolean isParsingStartElement() {
091    return false;
092  }
093
094  @Override
095  public boolean canHandleQName(QName nextQName) {
096    return false;
097  }
098
099  @Override
100  public String getDefaultJsonValueKey() {
101    return DEFAULT_JSON_FIELD_NAME;
102  }
103
104  @Override
105  public boolean isUnrappedValueAllowedInXml() {
106    return false;
107  }
108
109  @Override
110  public boolean isXmlMixed() {
111    return false;
112  }
113
114  @Override
115  public TYPE parse(XMLEventReader2 eventReader) throws IOException {
116    StringBuilder builder = new StringBuilder();
117    try {
118      XMLEvent nextEvent;
119      while (!(nextEvent = eventReader.peek()).isEndElement()) {
120        if (nextEvent.isCharacters()) {
121          Characters characters = nextEvent.asCharacters();
122          builder.append(characters.getData());
123          // advance past current event
124          eventReader.nextEvent();
125        } else {
126          throw new IOException(String.format("Invalid content '%s' at %s", XmlEventUtil.toString(nextEvent),
127              XmlEventUtil.toString(nextEvent.getLocation())));
128        }
129      }
130
131      // trim leading and trailing whitespace
132      @SuppressWarnings("null")
133      @NonNull String value = builder.toString().trim();
134      return parse(value);
135    } catch (XMLStreamException ex) {
136      throw new IOException(ex);
137    }
138  }
139
140  /**
141   * This default implementation will parse the value as a string and delegate to
142   * the string-based parsing method.
143   */
144  @Override
145  public TYPE parse(JsonParser parser) throws IOException {
146    String value = parser.getValueAsString();
147    if (value == null) {
148      throw new IOException("Unable to parse field value as text");
149    }
150    // skip over value
151    parser.nextToken();
152    return parse(value);
153  }
154
155  @SuppressWarnings("null")
156  @Override
157  public String asString(Object value) {
158    return value.toString();
159  }
160
161  @Override
162  public void writeXmlValue(
163      Object value,
164      StartElement parent,
165      XMLEventFactory2 eventFactory,
166      XMLEventWriter eventWriter)
167      throws IOException, XMLStreamException {
168    try {
169      String content = asString(value);
170      Characters characters = eventFactory.createCharacters(content);
171      eventWriter.add(characters);
172    } catch (XMLStreamException ex) {
173      throw new IOException(ex);
174    }
175  }
176
177  @Override
178  public void writeXmlValue(Object value, QName parentName, XMLStreamWriter2 writer) throws XMLStreamException {
179    String content = asString(value);
180    writer.writeCharacters(content);
181  }
182
183  @Override
184  public void writeJsonValue(Object value, JsonGenerator generator) throws IOException {
185    generator.writeString(asString(value));
186  }
187
188  @Override
189  public abstract Class<ITEM_TYPE> getItemClass();
190
191  @Override
192  public abstract ITEM_TYPE newItem(Object value);
193
194  @Override
195  public ITEM_TYPE cast(IAnyAtomicItem item) {
196    ITEM_TYPE retval;
197    if (item == null) {
198      throw new InvalidValueForCastFunctionException("item is null");
199    } else if (getItemClass().isAssignableFrom(item.getClass())) {
200      @SuppressWarnings("unchecked") ITEM_TYPE typedItem = (ITEM_TYPE) item;
201      retval = typedItem;
202    } else {
203      retval = castInternal(item);
204    }
205    return retval;
206  }
207
208  /**
209   * Attempt to cast the provided item to this adapter's item type.
210   * <p>
211   * The default implementation of this will attempt to parse the provided item as
212   * a string using the {@link #parse(String)} method. If this behavior is
213   * undesirable, then a subclass should override this method.
214   *
215   * @param item
216   *          the item to cast
217   * @return the item casted to this adapter's item type
218   * @throws InvalidValueForCastFunctionException
219   *           if the casting of the item is not possible because the item
220   *           represents an invalid value for this adapter's item type
221   */
222  @NonNull
223  protected ITEM_TYPE castInternal(@NonNull IAnyAtomicItem item) {
224    // try string based casting as a fallback
225    String itemString = item.asString();
226    try {
227      TYPE value = parse(itemString);
228      return newItem(value);
229    } catch (IllegalArgumentException ex) {
230      throw new InvalidValueForCastFunctionException(
231          String.format("The value '%s' is not compatible with the type '%s'", itemString, getItemClass().getName()),
232          ex);
233    }
234  }
235}