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}