All Downloads are FREE. Search and download functionalities are using the official Maven repository.

oracle.kv.impl.api.table.ComplexValueImpl Maven / Gradle / Ivy

/*-
 * Copyright (C) 2011, 2018 Oracle and/or its affiliates. All rights reserved.
 *
 * This file was distributed by Oracle as part of a version of Oracle NoSQL
 * Database made available at:
 *
 * http://www.oracle.com/technetwork/database/database-technologies/nosqldb/downloads/index.html
 *
 * Please see the LICENSE file included in the top-level directory of the
 * appropriate version of Oracle NoSQL Database for a copy of the license and
 * additional information.
 */

package oracle.kv.impl.api.table;

import java.io.IOException;
import java.io.InputStream;
import java.io.Reader;

import com.sleepycat.persist.model.Persistent;
import oracle.kv.impl.util.SizeOf;
import oracle.kv.table.FieldDef;
import oracle.kv.table.FieldValue;
import org.codehaus.jackson.JsonLocation;
import org.codehaus.jackson.JsonParser;
import org.codehaus.jackson.JsonToken;
import org.codehaus.jackson.JsonParser.NumberType;

/**
 * ComplexValueImpl is an intermediate abstract implementation class used to
 * factor out common state and code from complex types such as Array, Map,
 * Record, Row, etc.  It introduces a single function to get the field
 * definition ({@link FieldDef}) for the object.
 * 

* The field definition ({@link FieldDef}) is table metadata that defines the * types and constraints in a table row. It is required by ComplexValue * instances to define the shape of the values they hold. It is used to * validate type and enforce constraints for values added to a ComplexValue. */ @Persistent(version=1) public abstract class ComplexValueImpl extends FieldValueImpl { private static final long serialVersionUID = 1L; /* * indexImpl is not used anymore, but it is still here to support * java-based deserialization from earlier versions */ @Deprecated transient IndexImpl indexImpl; FieldDef fieldDef; ComplexValueImpl(FieldDef fieldDef) { this.fieldDef = fieldDef; } /* DPL */ @SuppressWarnings("unused") private ComplexValueImpl() { fieldDef = null; } /* * Public api methods from Object and FieldValue */ /** * Provides a common method for the string value of the complex types. */ @Override public String toString() { return toJsonString(false); } @Override public boolean isComplex() { return true; } /* * Local methods */ @Override public long sizeof() { return (SizeOf.OBJECT_OVERHEAD + 2 * SizeOf.OBJECT_REF_OVERHEAD); } /** * Validate the value of the object. By default there is not validation. * Subclasses may implement this. */ public void validate() { } /** * Common validation code for map and array enforcing rules as to * what types can be inserted. */ protected FieldValue validate(FieldValue value, FieldDefImpl elemDef) { if (value.isNull()) { throw new IllegalArgumentException( "Cannot insert null values into collection: " + getClass()); } FieldDefImpl valDef = (FieldDefImpl)value.getDefinition(); if (!valDef.isSubtype(elemDef)) { throw new IllegalArgumentException( "Type mismatch. Cannot insert value of type\n" + valDef.getDDLString() + "\ninto a collection of type\n" + getDefinition().getDDLString()); } /* turn float to double */ if (value.isFloat() && elemDef.isJson()) { value = FieldDefImpl.doubleDef.createDouble(value.asFloat().get()); } if (elemDef.isJson() && value.isComplex() && !valDef.equals(FieldDefImpl.arrayJsonDef) && !valDef.equals(FieldDefImpl.mapJsonDef)) { throw new IllegalArgumentException( "Type mismatch. Cannot insert value of type\n" + valDef.getDDLString() + "\ninto a collection of type\n" + getDefinition().getDDLString()); } value = ((FieldValueImpl)value).castToSuperType(elemDef); if (elemDef.hasMin() || elemDef.hasMax()) { validateRangeValue(elemDef, value); } return value; } void validateRangeValue(FieldDefImpl def, FieldValue value) { switch (def.getType()) { case INTEGER: assert(value.isInteger()); ((IntegerDefImpl)def).validateValue(value.asInteger().get()); break; case LONG: assert(value.isLong()); ((LongDefImpl)def).validateValue(value.asLong().get()); break; case FLOAT: assert(value.isFloat()); ((FloatDefImpl)def).validateValue(value.asFloat().get()); break; case DOUBLE: assert(value.isDouble()); ((DoubleDefImpl)def).validateValue(value.asDouble().get()); break; case STRING: assert(value.isString()); ((StringDefImpl)def).validateValue(value.asString().get()); break; default: break; } } /** * Populate the given complex value from a JSON doc (which is given as * a reader). If exact is true, the json doc must match exactly to the * schema of the complex value. Otherwise, if this is a RecordValue, then * (a) the JSON doc may have fields that do not appear in the record * schema. Such fields are simply skipped. * (b) the JSON doc may be missing fields that appear in the record's * schema. Such fields will remain unset in the record value. */ public static void createFromJson( ComplexValueImpl complexValue, Reader jsonInput, boolean exact, boolean addMissingFields) { JsonParser jp = null; try { jp = TableJsonUtils.createJsonParser(jsonInput); /*move to START_OBJECT or START_ARRAY*/ if (jp.nextToken() == null) { return; } complexValue.addJsonFields(jp, null, exact, addMissingFields); complexValue.validate(); } catch (IOException ioe) { throw new IllegalArgumentException( ("Failed to parse JSON input: " + ioe.getMessage()), ioe); } finally { if (jp != null) { try { jp.close(); } catch (IOException ignored) { /* ignore failures on close */ } } } } /** * Populate the given complex value from a JSON doc (which is given as an * input stream) and add missing fields with default values. */ public static void createFromJson(ComplexValueImpl complexValue, InputStream jsonInput, boolean exact) { createFromJson(complexValue, jsonInput, exact, true); } /** * Populate the given complex value from a JSON doc (which is given as an * input stream). If exact is true, the json doc must match exactly to the * schema of the complex value. Otherwise, if this is a RecordValue, then * (a) the JSON doc may have fields that do not appear in the record * schema. Such fields are simply skipped. * (b) the JSON doc may be missing fields that appear in the record's * schema. Such fields will remain unset in the record value. */ public static void createFromJson( ComplexValueImpl complexValue, InputStream jsonInput, boolean exact, boolean addMissingFields) { JsonParser jp = null; try { jp = TableJsonUtils.createJsonParser(jsonInput); /* move to START_OBJECT or START_ARRAY */ if (jp.nextToken() == null) { return; } complexValue.addJsonFields(jp, null, exact, addMissingFields); complexValue.validate(); } catch (IOException ioe) { throw new IllegalArgumentException( ("Failed to parse JSON input: " + ioe.getMessage()), ioe); } finally { if (jp != null) { try { jp.close(); } catch (IOException ignored) { /* ignore failures on close */ } } } } /** * Add JSON fields from the JsonParser to this object. * @param jp the parser * This is used to handle situations that are conditional and are not * caught by a RecordValueImpl.validate() call. * @param currentFieldName the current field name, which is the last * field name extracted from the parser. This is only non-null when * addJsonFields is called from RecordValueImpl, which knows field * names. * @param exact true if the JSON needs have all fields present. * @param addMissingFields true if the JSON need to be added missing fields. */ abstract void addJsonFields( JsonParser jp, String currentFieldName, boolean exact, boolean addMissingFields); /** * A utility method for use by subclasses to skip JSON input * when an exact match is not required. This function finds a matching * end of array or object token. It will recurse in the event a * nested array or object is detected. */ static void skipToJsonToken(JsonParser jp, JsonToken skipTo) { try { JsonToken token = jp.nextToken(); while (token != skipTo) { if (token == JsonToken.START_OBJECT) { skipToJsonToken(jp, JsonToken.END_OBJECT); } else if (token == JsonToken.START_ARRAY) { skipToJsonToken(jp, JsonToken.END_ARRAY); } token = jp.nextToken(); } } catch (IOException ioe) { throw new IllegalArgumentException (("Failed to parse JSON input: " + ioe.getMessage()), ioe); } } /* * Throw IAE for JSON parse exception, with location information. Location * also has raw byte/char offsets if that'd be helpful in the future. */ static void jsonParseException(String msg, JsonLocation location) { throw new IllegalArgumentException(msg + " starting at location: line "+ location.getLineNr() + ", column " + location.getColumnNr()); } static void checkNumberType( String fieldName, NumberType expected, JsonParser jp) throws IOException { NumberType actual = null; if (jp.getCurrentToken().isNumeric()) { actual = jp.getNumberType(); if (actual == expected || expected == NumberType.BIG_DECIMAL) { return; } /* Jackson infers the type. Many casts are safe, detect these. */ switch (actual) { case INT: /* int can cast to long, float, double */ return; case FLOAT: case LONG: /* float and long can cast to double */ if (expected == NumberType.DOUBLE) { return; } /* long may be able to cast to float */ if (expected != NumberType.FLOAT) { break; } //$FALL-THROUGH$ case DOUBLE: /* * Jackson parses into DOUBLE and not FLOAT. Allow the cast from * DOUBLE to FLOAT only if the number is within the FLOAT * boundaries . */ if (expected == NumberType.FLOAT) { Double d = jp.getDoubleValue(); if (d.isNaN() || d == 0) { return; } if (d < 0) { d = -d; } float f = d.floatValue(); if (f >= Float.MIN_VALUE && f <= Float.MAX_VALUE) { return; } } break; default: break; } } throw new IllegalArgumentException( "Illegal value for numeric field " + (fieldName != null ? fieldName : jp.getText()) + ". Expected " + expected + ", is " + (jp.getCurrentToken().isNumeric() ? jp.getNumberType() : jp.getCurrentToken())); } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy