/** * Copyright (C) 2011 - present by OpenGamma Inc. and the OpenGamma group of companies * * Please see distribution for license. */ package com.opengamma.web.json; import java.io.IOException; import java.io.Reader; import java.util.Iterator; import java.util.List; import org.apache.commons.lang.StringUtils; import org.fudgemsg.FudgeContext; import org.fudgemsg.FudgeFieldType; import org.fudgemsg.FudgeMsg; import org.fudgemsg.FudgeMsgEnvelope; import org.fudgemsg.FudgeRuntimeException; import org.fudgemsg.MutableFudgeMsg; import org.fudgemsg.taxonomy.FudgeTaxonomy; import org.fudgemsg.types.IndicatorType; import org.fudgemsg.wire.FudgeRuntimeIOException; import org.fudgemsg.wire.json.FudgeJSONSettings; import org.fudgemsg.wire.types.FudgeWireType; import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; import org.json.JSONTokener; import org.threeten.bp.LocalDate; import com.google.common.collect.Lists; import com.opengamma.OpenGammaRuntimeException; /** * A Fudge reader that interprets JSON. */ public class FudgeMsgJSONReader { /** * The taxonomy identifier to use for any messages that are passed without envelopes. */ private static final short DEFAULT_TAXONOMY_ID = 0; /** * The schema version to add to the envelope header for any messages that are passed without envelopes. */ private static final int DEFAULT_MESSAGE_VERSION = 0; /** * The processing directive flags to add to the envelope header for any messages that are passed without envelopes. */ private static final int DEFAULT_MESSAGE_PROCESSING_DIRECTIVES = 0; private final FudgeJSONSettings _settings; private final FudgeContext _fudgeContext; private final Reader _underlying; private int _taxonomyId = DEFAULT_TAXONOMY_ID; private FudgeTaxonomy _taxonomy; private int _processingDirectives = DEFAULT_MESSAGE_PROCESSING_DIRECTIVES; private int _schemaVersion = DEFAULT_MESSAGE_VERSION; private JSONObject _jsonObject; private final List<String> _envelopeAttibutesFields = Lists.newArrayList(); /** * Creates a new instance for reading a Fudge stream from a JSON reader. * * @param fudgeContext the Fudge context, not null * @param reader the underlying reader, not null */ public FudgeMsgJSONReader(final FudgeContext fudgeContext, final Reader reader) { this(fudgeContext, reader, new FudgeJSONSettings()); } /** * Creates a new instance for reading a Fudge stream from a JSON reader. * * @param fudgeContext the Fudge context, not null * @param reader the underlying reader, not null * @param settings the JSON settings to fine tune the read, not null */ public FudgeMsgJSONReader(final FudgeContext fudgeContext, final Reader reader, final FudgeJSONSettings settings) { _fudgeContext = fudgeContext; _underlying = reader; _settings = settings; try { _jsonObject = new JSONObject(new JSONTokener(reader)); init(_fudgeContext, _jsonObject, _settings); } catch (final JSONException ex) { wrapException("Creating json object from reader", ex); } } private void init(final FudgeContext fudgeContext, final JSONObject jsonObject, final FudgeJSONSettings settings) throws JSONException { final String processingDirectivesField = settings.getProcessingDirectivesField(); if (jsonObject.has(processingDirectivesField)) { _processingDirectives = integerValue(jsonObject.get(processingDirectivesField)); _envelopeAttibutesFields.add(processingDirectivesField); } final String schemaVersionField = getSettings().getSchemaVersionField(); if (jsonObject.has(schemaVersionField)) { _schemaVersion = integerValue(jsonObject.get(schemaVersionField)); _envelopeAttibutesFields.add(schemaVersionField); } final String taxonomyField = getSettings().getTaxonomyField(); if (jsonObject.has(taxonomyField)) { _taxonomyId = integerValue(jsonObject.get(taxonomyField)); _taxonomy = fudgeContext.getTaxonomyResolver().resolveTaxonomy((short) _taxonomyId); _envelopeAttibutesFields.add(taxonomyField); } } //------------------------------------------------------------------------- /** * Gets the underlying reader. * * @return the reader, not null */ public Reader getUnderlying() { return _underlying; } /** * Gets the fudgeContext. * @return the fudgeContext */ public FudgeContext getFudgeContext() { return _fudgeContext; } /** * Gets the settings. * @return the settings */ public FudgeJSONSettings getSettings() { return _settings; } private RuntimeException wrapException(String message, final JSONException ex) { message = "Error " + message + " from JSON stream"; if (ex.getCause() instanceof IOException) { return new FudgeRuntimeIOException(message, (IOException) ex.getCause()); } else { return new FudgeRuntimeException(message, ex); } } /** * Reads the next message, discarding the envelope. * * @return the message read without the envelope */ public FudgeMsg readMessage() { final FudgeMsgEnvelope msgEnv = readMessageEnvelope(); if (msgEnv == null) { return null; } return msgEnv.getMessage(); } /** * Reads the next message, returning the envelope. * * @return the {@link FudgeMsgEnvelope} */ public FudgeMsgEnvelope readMessageEnvelope() { FudgeMsgEnvelope msgEnv = null; try { final JSONObject meta = (JSONObject) _jsonObject.get("meta"); final JSONObject data = (JSONObject) _jsonObject.get("data"); final MutableFudgeMsg msg = processFields(data, meta); msgEnv = getFudgeMsgEnvelope(msg, _jsonObject); } catch (final JSONException ex) { wrapException("reading message envelope", ex); } return msgEnv; } private FudgeMsgEnvelope getFudgeMsgEnvelope(final MutableFudgeMsg fudgeMsg, final JSONObject jsonObject) throws JSONException { return new FudgeMsgEnvelope(fudgeMsg, _schemaVersion, _processingDirectives); } private int integerValue(final Object o) { if (o instanceof Number) { return ((Number) o).intValue(); } else if (o instanceof String) { return Integer.parseInt((String) o); } else { throw new NumberFormatException(o + " is not a number"); } } private byte byteValue(final Object o) { if (o instanceof Number) { return ((Number) o).byteValue(); } else if (o instanceof String) { return Byte.parseByte((String) o); } else { throw new NumberFormatException(o + " is not a number"); } } private short shortValue(final Object o) { if (o instanceof Number) { return ((Number) o).shortValue(); } else if (o instanceof String) { return Short.parseShort((String) o); } else { throw new NumberFormatException(o + " is not a number"); } } private long longValue(final Object o) { if (o instanceof Number) { return ((Number) o).longValue(); } else if (o instanceof String) { return Long.parseLong((String) o); } else { throw new NumberFormatException(o + " is not a number"); } } private double doubleValue(final Object o) { if (o instanceof Number) { return ((Number) o).doubleValue(); } else if (o instanceof String) { return Double.parseDouble((String) o); } else { throw new NumberFormatException(o + " is not a number"); } } private float floatValue(final Object o) { if (o instanceof Number) { return ((Number) o).floatValue(); } else if (o instanceof String) { return Float.parseFloat((String) o); } else { throw new NumberFormatException(o + " is not a number"); } } private MutableFudgeMsg processFields(final JSONObject data, final JSONObject meta) { final MutableFudgeMsg fudgeMsg = getFudgeContext().newMessage(); @SuppressWarnings("unchecked") final Iterator<String> keys = data.keys(); while (keys.hasNext()) { final String fieldName = keys.next(); final Object dataValue = getFieldValue(data, fieldName); final Object metaValue = getFieldValue(meta, fieldName); if (dataValue instanceof JSONObject) { final MutableFudgeMsg subMsg = processFields((JSONObject) dataValue, (JSONObject) metaValue); addField(fudgeMsg, fieldName, FudgeWireType.SUB_MESSAGE, subMsg); } else if (dataValue instanceof JSONArray) { final JSONArray dataArray = (JSONArray) dataValue; if (dataArray.length() > 0) { if (isPrimitiveArray(metaValue)) { try { final FudgeFieldType fieldType = getFieldType((String) metaValue); if (fieldType == null) { throw new OpenGammaRuntimeException("Unknown field type " + metaValue + " for " + fieldName + ":" + dataValue); } final Object primitiveArray = jsonArrayToPrimitiveArray(dataArray, fieldType); addField(fudgeMsg, fieldName, getFieldType((String) metaValue), primitiveArray); } catch (final JSONException e) { wrapException("converting json array to primitive array", e); } } else { //treat as repeated fields addRepeatedFields(fudgeMsg, fieldName, dataArray, (JSONArray) metaValue); } } } else { final FudgeFieldType fieldType = getFieldType((String) metaValue); if (fieldType == null) { throw new OpenGammaRuntimeException("Unknown field type " + metaValue + " for " + fieldName + ":" + dataValue); } addField(fudgeMsg, fieldName, fieldType, dataValue); } } return fudgeMsg; } private boolean isPrimitiveArray(final Object metaValue) { if (metaValue instanceof JSONArray) { return false; } return true; } private void addField(final MutableFudgeMsg fudgeMsg, final String fieldName, final FudgeFieldType fieldType, final Object fieldValue) { Integer ordinal = null; String name = null; try { ordinal = Integer.parseInt(fieldName); } catch (final NumberFormatException nfe) { if (StringUtils.isNotEmpty(fieldName)) { if (!getPreserveFieldNames()) { if (_taxonomy != null) { ordinal = _taxonomy.getFieldOrdinal(fieldName); } } else { name = fieldName; } } } switch (fieldType.getTypeId()) { case FudgeWireType.BYTE_TYPE_ID: fudgeMsg.add(name, ordinal, fieldType, byteValue(fieldValue)); break; case FudgeWireType.SHORT_TYPE_ID: fudgeMsg.add(name, ordinal, fieldType, shortValue(fieldValue)); break; case FudgeWireType.DOUBLE_TYPE_ID: fudgeMsg.add(name, ordinal, fieldType, doubleValue(fieldValue)); break; case FudgeWireType.FLOAT_TYPE_ID: fudgeMsg.add(name, ordinal, fieldType, floatValue(fieldValue)); break; case FudgeWireType.DATE_TYPE_ID: fudgeMsg.add(name, ordinal, fieldType, toLocalDate(fieldValue)); break; case FudgeWireType.LONG_TYPE_ID: fudgeMsg.add(name, ordinal, fieldType, longValue(fieldValue)); break; default: fudgeMsg.add(name, ordinal, fieldType, fieldValue); break; } } private LocalDate toLocalDate(final Object fieldValue) { if (fieldValue != null) { return LocalDate.parse((String) fieldValue); } return null; } private boolean getPreserveFieldNames() { return getSettings().getPreserveFieldNames(); } private void addRepeatedFields(final MutableFudgeMsg fudgeMsg, final String fieldName, final JSONArray dataArray, final JSONArray metaArray) { try { for (int i = 0; i < dataArray.length(); i++) { final Object arrValue = dataArray.get(i); final Object metaValue = metaArray.get(i); if (arrValue instanceof JSONObject) { final MutableFudgeMsg subMsg = processFields((JSONObject) arrValue, (JSONObject) metaValue); addField(fudgeMsg, fieldName, FudgeWireType.SUB_MESSAGE, subMsg); } else { final FudgeFieldType fieldType = getFieldType((String) metaValue); if (fieldType == null) { throw new OpenGammaRuntimeException("Unknown field type " + metaValue + " for " + fieldName + ":" + arrValue); } addField(fudgeMsg, fieldName, fieldType, arrValue); } } } catch (final JSONException e) { wrapException("adding repeated fields", e); } } private FudgeFieldType getFieldType(final String typeValue) { final Integer typeId = getSettings().stringToFudgeTypeId(typeValue); return getFudgeContext().getTypeDictionary().getByTypeId(typeId); } private Object jsonArrayToPrimitiveArray(final JSONArray arr, final FudgeFieldType fieldType) throws JSONException { switch (fieldType.getTypeId()) { case FudgeWireType.BYTE_ARRAY_TYPE_ID: case FudgeWireType.BYTE_ARRAY_4_TYPE_ID: case FudgeWireType.BYTE_ARRAY_8_TYPE_ID: case FudgeWireType.BYTE_ARRAY_16_TYPE_ID: case FudgeWireType.BYTE_ARRAY_20_TYPE_ID: case FudgeWireType.BYTE_ARRAY_32_TYPE_ID: case FudgeWireType.BYTE_ARRAY_64_TYPE_ID: case FudgeWireType.BYTE_ARRAY_128_TYPE_ID: case FudgeWireType.BYTE_ARRAY_256_TYPE_ID: case FudgeWireType.BYTE_ARRAY_512_TYPE_ID: final byte[] byteArr = new byte[arr.length()]; for (int j = 0; j < byteArr.length; j++) { byteArr[j] = ((Number) arr.get(j)).byteValue(); } return byteArr; case FudgeWireType.SHORT_ARRAY_TYPE_ID: final short[] shortArr = new short[arr.length()]; for (int j = 0; j < shortArr.length; j++) { shortArr[j] = ((Number) arr.get(j)).shortValue(); } return shortArr; case FudgeWireType.INT_ARRAY_TYPE_ID: final int[] intArr = new int[arr.length()]; for (int j = 0; j < intArr.length; j++) { intArr[j] = ((Number) arr.get(j)).intValue(); } return intArr; case FudgeWireType.LONG_ARRAY_TYPE_ID: final long[] longArr = new long[arr.length()]; for (int j = 0; j < longArr.length; j++) { longArr[j] = ((Number) arr.get(j)).longValue(); } return longArr; case FudgeWireType.DOUBLE_ARRAY_TYPE_ID: final double[] doubleArr = new double[arr.length()]; for (int j = 0; j < doubleArr.length; j++) { doubleArr[j] = ((Number) arr.get(j)).doubleValue(); } return doubleArr; case FudgeWireType.FLOAT_ARRAY_TYPE_ID: final float[] floatArr = new float[arr.length()]; for (int j = 0; j < floatArr.length; j++) { floatArr[j] = ((Number) arr.get(j)).floatValue(); } return floatArr; default: return null; } } private Object getFieldValue(final JSONObject jsonObject, final String fieldName) { Object fieldValue = null; try { fieldValue = jsonObject.get(fieldName); if (JSONObject.NULL.equals(fieldValue)) { fieldValue = IndicatorType.INSTANCE; } } catch (final JSONException e) { wrapException("reading field value", e); } return fieldValue; } }