package com.couchbase.jdbc.core; import com.couchbase.jdbc.util.JSONTypes; import com.couchbase.jdbc.util.TimestampUtils; import com.couchbase.json.SQLJSON; import org.boon.core.reflection.Mapper; import org.boon.core.reflection.MapperSimple; import org.boon.core.value.ValueList; import org.boon.json.JsonFactory; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.io.*; import java.math.BigDecimal; import java.sql.Date; import java.sql.*; import java.util.*; /** * Created by davec on 15-07-02. */ public class SqlJsonImplementation implements SQLJSON { private static final Logger logger = LoggerFactory.getLogger(SqlJsonImplementation.class); Field field; Object jsonObject; String sqlJson; boolean isNull = false; TimestampUtils timestampUtils = new TimestampUtils(); public SqlJsonImplementation() { } public SqlJsonImplementation(Object jsonObject, Field field ) { this.jsonObject = jsonObject; if (jsonObject instanceof String) field.setType("string"); else if (jsonObject instanceof Number) field.setType("number"); else if (jsonObject instanceof Boolean) field.setType("boolean"); else if (jsonObject instanceof List) field.setType("array"); // map should be json as well else field.setType("json"); this.field = field; } @Override public void free() { } private synchronized void toJson() { if (sqlJson == null ) { sqlJson = JsonFactory.toJson(jsonObject); } } @Override public InputStream getBinaryStream() throws SQLException { toJson(); try { return new ByteArrayInputStream(sqlJson.getBytes("UTF-8")); } catch (UnsupportedEncodingException ex) { logger.error("Error Encoding", ex ); return null; } } @Override public Reader getCharacterStream() throws SQLException { toJson(); return new StringReader(sqlJson); } @Override public String getString() throws SQLException { int type = JSONTypes.jsonTypes.get(field.getType()); switch (type) { case JSONTypes.JSON_ARRAY: case JSONTypes.JSON_OBJECT: case JSONTypes.JSON_MAP: toJson(); return sqlJson; case JSONTypes.JSON_NULL: return null; case JSONTypes.JSON_STRING: if ( sqlJson != null ) return sqlJson; if ( jsonObject != null ) return (String) jsonObject; isNull = true; return null; // let the default implementation of toString figure it out case JSONTypes.JSON_BOOLEAN: case JSONTypes.JSON_NUMBER: return jsonObject.toString(); default: return ""; } } @Override public void setString(String str) throws SQLException { field = new Field(null,"string" ); // if it's null set the flag if (str == null ) isNull = true; // either way set the value to null jsonObject = str; } public void setBinaryStream() throws SQLException { } public void setCharacterStream(Reader stream) throws SQLException { } public boolean getBoolean() throws SQLException { int type = JSONTypes.jsonTypes.get(field.getType()); switch (type) { case JSONTypes.JSON_BOOLEAN: return (boolean) jsonObject; case JSONTypes.JSON_NUMBER: Number number = (Number)jsonObject; return !number.equals((Number)0); case JSONTypes.JSON_STRING: String string = (String) jsonObject; return !string.isEmpty(); case JSONTypes.JSON_MAP: case JSONTypes.JSON_OBJECT: Map map = (Map) jsonObject; return !map.isEmpty(); case JSONTypes.JSON_ARRAY: List list = (List)jsonObject; return !list.isEmpty(); default: return false; } } public void setBoolean(boolean val) throws SQLException { field = new Field(null,"boolean" ); jsonObject = val; } public byte getByte() throws SQLException { if (jsonObject == null ) { isNull = true; return 0; } else if (jsonObject instanceof Integer) return ((Integer)jsonObject).byteValue(); else if (jsonObject instanceof Long) return ((Long)jsonObject).byteValue(); else if (jsonObject instanceof Short) return ((Short)jsonObject).byteValue(); else if (jsonObject instanceof Byte) return ((Byte)jsonObject).byteValue(); else if (jsonObject instanceof Double) return ((Double)jsonObject).byteValue(); else if (!(jsonObject instanceof Number)) throw new SQLException( "value " +jsonObject+ " not a number"); return 0; } public void setByte(byte val) throws SQLException { field = new Field(null,"number" ); jsonObject = (long)val; } public short getShort() throws SQLException { if (jsonObject == null ) { isNull = true; return 0; } else if (jsonObject instanceof Integer) return ((Integer)jsonObject).shortValue(); else if (jsonObject instanceof Long) return ((Long)jsonObject).shortValue(); else if (jsonObject instanceof Short) return ((Short)jsonObject).shortValue(); else if (jsonObject instanceof Byte) return ((Byte)jsonObject).shortValue(); else if (jsonObject instanceof Double) return ((Double)jsonObject).shortValue(); else if (!(jsonObject instanceof Number)) throw new SQLException( "value " +jsonObject+ " not a number"); return 0; } public void setShort(short val) throws SQLException { field = new Field(null,"number" ); jsonObject = (long)val; } public int getInt() throws SQLException { if (jsonObject == null ) { isNull = true; return 0; } else if (jsonObject instanceof Integer) return ((Integer)jsonObject).intValue(); else if (jsonObject instanceof Long) return ((Long)jsonObject).intValue(); else if (jsonObject instanceof Short) return ((Short)jsonObject).intValue(); else if (jsonObject instanceof Byte) return ((Byte)jsonObject).intValue(); else if (jsonObject instanceof Double) return ((Double)jsonObject).intValue(); else if (!(jsonObject instanceof Number)) throw new SQLException( "value " +jsonObject+ " not a number"); return 0; } public void setInt(int val) throws SQLException { field = new Field(null,"number" ); jsonObject = (long)val; } public long getLong() throws SQLException { if (jsonObject == null ) { isNull = true; return 0; } else if (jsonObject instanceof Integer) return ((Integer)jsonObject).longValue(); else if (jsonObject instanceof Long) return ((Long)jsonObject).longValue(); else if (jsonObject instanceof Short) return ((Short)jsonObject).longValue(); else if (jsonObject instanceof Byte) return ((Byte)jsonObject).longValue(); else if (jsonObject instanceof Double) return ((Double)jsonObject).longValue(); else if (!(jsonObject instanceof Number)) throw new SQLException( "value " +jsonObject+ " not a number"); return 0; } public void setLong(long val) throws SQLException { field = new Field(null,"number" ); jsonObject = val; } public BigDecimal getBigDecimal() throws SQLException { if (jsonObject == null ) { isNull = true; return null; } else if (jsonObject instanceof Integer) return new BigDecimal((Integer)jsonObject); else if (jsonObject instanceof Long) return new BigDecimal((Long)jsonObject); else if (jsonObject instanceof Short) return new BigDecimal((Short)jsonObject); else if (jsonObject instanceof Byte) return new BigDecimal((Byte)jsonObject); else if (jsonObject instanceof Double) return BigDecimal.valueOf((Double) jsonObject); else if (!(jsonObject instanceof Number)) throw new SQLException( "value " +jsonObject+ " not a number"); return null; } public void setBigDecimal(BigDecimal val) throws SQLException { if (val == null) { field = new Field(null,"null"); isNull = true; } else { field = new Field(null, "number"); } jsonObject = val; } @Override public void setFloat(float val) throws SQLException { field = new Field(null,"number" ); jsonObject = val; } @Override public float getFloat() throws SQLException { if (jsonObject == null ) { isNull = true; return 0; } else if (jsonObject instanceof Integer) return (float)((Integer)jsonObject); else if (jsonObject instanceof Long) return (float)((Long)jsonObject); else if (jsonObject instanceof Short) return new Float((Short)jsonObject); else if (jsonObject instanceof Byte) return (float)((Byte)jsonObject); else if (jsonObject instanceof Double) return ((Double) jsonObject).floatValue(); else if (!(jsonObject instanceof Number)) throw new SQLException( "value " +jsonObject+ " not a number"); return 0; } @Override public void setDouble(double val) throws SQLException { field = new Field(null,"number" ); jsonObject = val; } @Override public double getDouble() throws SQLException { if (jsonObject == null ) { isNull = true; return 0; } else if (jsonObject instanceof Integer) return (double)((Integer)jsonObject); else if (jsonObject instanceof Long) return (double)((Long)jsonObject); else if (jsonObject instanceof Short) return new Double((Short)jsonObject); else if (jsonObject instanceof Byte) return (double)((Byte)jsonObject); else if (jsonObject instanceof Double) return (Double) jsonObject; else if (!(jsonObject instanceof Number)) throw new SQLException( "value " +jsonObject+ " not a number"); return 0; } @Override public void setBytes(byte[] val) throws SQLException { } @Override public byte[] getBytes() throws SQLException { return new byte[0]; } @Override public void setDate(Date val, Calendar cal) throws SQLException { field = new Field(null,"string" ); if (val == null) { jsonObject=null; isNull = true; } else { if (cal != null) { cal = (Calendar) cal.clone(); } jsonObject = timestampUtils.toString(cal, val); } } @Override public Date getDate(Calendar cal) throws SQLException { Date date = null; if (jsonObject == null ) { isNull = true; return null; } try { if ( jsonObject instanceof String) { date = timestampUtils.parse((String)jsonObject); } if (jsonObject instanceof Date) { date = (Date)jsonObject; } if (jsonObject instanceof java.util.Date) { date = new Date(((java.util.Date)jsonObject).getTime()); } } catch (Exception ex) { throw new SQLException("value " + jsonObject + " is not a date"); } if ( cal!= null ) { date = timestampUtils.applyCalendar(cal, date); } return date; } @Override public void setTime(Time val, Calendar cal) throws SQLException { field = new Field(null,"string" ); if (val == null) { jsonObject=null; isNull=true; } else { if (cal != null) { cal = (Calendar) cal.clone(); } jsonObject = timestampUtils.toString(cal, val); } } @Override public Time getTime(Calendar cal) throws SQLException { Time time; if ( jsonObject == null ) return null; try { time = timestampUtils.parseTime((String)jsonObject); } catch( Exception ex) { throw new SQLException("value " + jsonObject +" is not a Time", ex); } if ( cal != null ) { time = timestampUtils.applyCalendar(cal, time); } return time; } @Override public void setTimestamp(Timestamp val, Calendar cal) throws SQLException { field = new Field(null,"string" ); if (val == null) { jsonObject=null; isNull=true; } else { if (cal != null) { cal = (Calendar) cal.clone(); } jsonObject = timestampUtils.toString(cal, val); } } @Override public Timestamp getTimestamp( Calendar cal ) throws SQLException { Timestamp ts; if (jsonObject == null ) { isNull=true; return null; } try { if (jsonObject instanceof java.util.Date || jsonObject instanceof java.sql.Date) { ts = new Timestamp( ((java.util.Date)jsonObject).getTime()); } else if (jsonObject instanceof String) { ts = timestampUtils.parseTimestamp((String)jsonObject); } else { throw new SQLException("value " + jsonObject + " is not a Timestamp"); } } catch( Exception ex) { throw new SQLException("value " + jsonObject+ "is not a Timestamp", ex); } if ( cal != null) { ts = timestampUtils.applyCalendar(cal,ts); } /* // check to see if there is a calendar and that it is different than the one used to parse */ return ts; } public Map getMap() throws SQLException { if (jsonObject == null ) { isNull = true; return null; } if( jsonObject instanceof Map ) return (Map)jsonObject; throw new SQLException("Value " +jsonObject + " is not a map" ); } public void setMap(Map map) throws SQLException { if (map == null) { field = new Field(null,"null"); isNull = true; } else { field = new Field(null, "map"); } jsonObject = map; } @Override public List getArray() throws SQLException { if (jsonObject == null ) { isNull = true; return null; } if ( jsonObject instanceof List ) return (List) jsonObject; else return null; } @Override public void setArray(List array) throws SQLException { if (array == null) { field = new Field(null,"null"); isNull = true; } else { field = new Field(null, "array"); } jsonObject = array; } @Override public void setArray(Object []array) throws SQLException { if (array == null) { field = new Field(null,"null"); isNull = true; } else { field = new Field(null, "array"); } jsonObject = array; } public Object getObject() throws SQLException { switch (field.getSqlType()) { case Types.NUMERIC: case Types.BOOLEAN: return jsonObject; case Types.VARCHAR: Object object = jsonObject; if (object instanceof java.util.Date) { return new java.sql.Date(((java.util.Date)object).getTime()); } else { return object; } case Types.ARRAY: case Types.JAVA_OBJECT: return jsonObject; case Types.NULL: return null; } return null; } public void setObject(Object x) throws SQLException { if (x == null) { field = new Field(null,"null"); isNull = true; } else if (x instanceof String) setString((String)x); else if (x instanceof BigDecimal) setBigDecimal((BigDecimal)x); else if (x instanceof Short) setShort(((Short)x).shortValue()); else if (x instanceof Integer) setInt(((Integer)x).intValue()); else if (x instanceof Long) setLong(((Long)x).longValue()); else if (x instanceof Float) setFloat(((Float)x).floatValue()); else if (x instanceof Double) setDouble(((Double)x).doubleValue()); else if (x instanceof byte[]) setBytes((byte[])x); else if (x instanceof java.sql.Date) setDate((java.sql.Date)x, null); else if (x instanceof Time) setTime((Time)x, null); else if (x instanceof Timestamp) setTimestamp((Timestamp)x, null); else if (x instanceof Boolean) setBoolean(((Boolean)x).booleanValue()); else if (x instanceof Byte) setByte(((Byte)x).byteValue()); else if (x instanceof Character) setString(x.toString()); else if ( x instanceof List) setArray((List)x); else if ( x instanceof Object [] ) setArray((Object [])x); else if (x.getClass().isArray()) { List list = asList(x); setArray(list); } else if (x instanceof Map) setMap((Map) x); else { // Can't infer a type. throw new SQLException("Can''t infer the SQL type to use for an instance of " + x +". Use setObject() with an explicit Types value to specify the type to use."); } } /* * @param columnName * @return if the SQLJSON object is a JSON object. * Get the given object with the given fieldName, * @throws java.sql.SQLException Throw not valid exception if the SQLJSON object is not a JSON object. */ @Override public Object getObject(String columnName) throws SQLException { if (!(jsonObject instanceof Map)) { throw new SQLException("Object is not a json object"); } else { return ((Map)jsonObject).get(columnName); } } /* * Set the given object with the given fieldName, if the SQLJSON object is a JSON object. * * @param columnName * @param val * @throws java.sql.SQLException Throw not valid exception if the SQLJSON object is not a JSON object. */ @Override public void setObject(String columnName, Object val) throws SQLException { if (!(jsonObject instanceof Map)) { throw new SQLException("Object is not a json object"); } else { //noinspection unchecked ((Map)jsonObject).put(columnName, val); } } /* * Return the object at the given index, if the SQLJSON object is a JSON array. * Return NULL if the SQLJSON object is not a JSON array * or if the SQLJSON object is a JSON array and does not have an element at the given index. * * @param index * @return */ @Override public Object get(int index) { if (!(jsonObject instanceof List)) { return null; } else { return ((List)jsonObject).get(index); } } /* * Set the given index with the given element, if the SQLJSON object is a JSON array. * * @param index * @param object */ @Override public void set(int index, Object object) throws SQLException { if (!(jsonObject instanceof List)) { throw new SQLException("SQLJSON object is not a list"); } else { // this is a hack List backingList = ((ValueList) jsonObject).list(); //noinspection unchecked backingList.set(index,object); } } public boolean isNull() throws SQLException { return isNull; } @SuppressWarnings("unchecked") public static <T> List<T> asList(final Object array) { if (!array.getClass().isArray()) throw new IllegalArgumentException("Not an array"); return new AbstractList<T>() { @Override public T get(int index) { return (T) java.lang.reflect.Array.get(array, index); } @Override public int size() { return java.lang.reflect.Array.getLength(array); } }; } public int getJDBCType() { return JSONTypes.jdbcTypes.get(field.getType()); } @Override public Object parse(Class clazz) { Mapper mapper = new MapperSimple(); //noinspection unchecked mapper.fromMap((Map)jsonObject,clazz); return null; } @Override public Map parse() { if (field.getType().compareTo("json") == 0 //if the signature is *-> * then guess using { || field.getType().startsWith("{")) { if (jsonObject instanceof String) { logger.debug("json object is string " + jsonObject); return null; } else return (Map)jsonObject; } else return null; } public Object parameterValue() { return jsonObject; } public int getLength() { return sqlJson.length(); } public int compareTo(SQLJSON obj) { SqlJsonImplementation sqljson = (SqlJsonImplementation)obj; int deltaLength = this.getLength() - sqljson.getLength(); if ( deltaLength != 0 ) return deltaLength; // if not do name by name comparison if (jsonObject instanceof Map && sqljson.jsonObject instanceof Map) { @SuppressWarnings("unchecked") Set<String> combinedKeys = ((Map)jsonObject).keySet(); //noinspection unchecked combinedKeys.addAll(((Map) sqljson.jsonObject).keySet()); List <String> sorted = new ArrayList<String>(combinedKeys); Collections.sort(sorted); for(String name:sorted) { // this did not have the value so it is larger if ( !((Map)jsonObject).containsKey(name) ) return 1; // this did not have the value so it is larger if ( !((Map)sqljson.jsonObject).containsKey(name) ) return -1; Object obj1 = ((Map)jsonObject).get(name); Object obj2 = ((Map)sqljson.jsonObject).get(name); return compare(obj1, obj2); } return 0; } if (jsonObject instanceof List && sqljson.jsonObject instanceof List) { //assume they are the same length from the test above for (int i =0; i< ((List) jsonObject).size();i++) { Object obj1 = ((List)jsonObject).get(i); Object obj2 = ((List)sqljson.jsonObject).get(i); return compare(obj1, obj2); } } return 0; } private int compare(Object obj1, Object obj2) { if (obj1 == null && obj2 == null ) return 0; if ( obj1.getClass().isInstance(obj2)) { if (obj1 instanceof String) return ((String) obj1).compareTo((String)obj2); if (obj1 instanceof Boolean) { if (obj1 == obj2) return 0; if (obj1 == Boolean.TRUE) return 1; return -1; } if (obj1 instanceof java.util.Date ) return ((java.util.Date) obj1).compareTo((Date)obj2); if (obj1 instanceof Number) { double number1 = ((Number)obj1).doubleValue(); double number2 = ((Number)obj2).doubleValue(); if (number1 == number2) return 0; return (number1>number2?1:-1); } } logger.debug("should not get here"); return 0; } }