/* * Copyright (C) 2015 SoftIndex LLC. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.datakernel.cube.http; import com.google.gson.Gson; import com.google.gson.JsonParseException; import com.google.gson.TypeAdapter; import com.google.gson.reflect.TypeToken; import com.google.gson.stream.JsonReader; import com.google.gson.stream.JsonWriter; import io.datakernel.cube.QueryResult; import io.datakernel.cube.QueryResult.Drilldown; import io.datakernel.cube.Record; import io.datakernel.cube.RecordScheme; import java.io.IOException; import java.lang.reflect.Type; import java.util.ArrayList; import java.util.Collection; import java.util.List; import java.util.Map; import static com.google.common.base.MoreObjects.firstNonNull; import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.collect.Lists.newArrayList; import static com.google.common.collect.Maps.newLinkedHashMap; import static com.google.common.collect.Sets.newHashSet; final class QueryResultGsonAdapter extends TypeAdapter<QueryResult> { private static final String DIMENSIONS_FIELD = "dimensions"; private static final String MEASURES_FIELD = "measures"; private static final String ATTRIBUTES_FIELD = "attributes"; private static final String FILTER_ATTRIBUTES_FIELD = "filterAttributes"; private static final String DRILLDOWNS_FIELD = "drillDowns"; private static final String CHAINS_FIELD = "chains"; private static final String RECORDS_FIELD = "records"; private static final String TOTALS_FIELD = "totals"; private static final String COUNT_FIELD = "count"; private static final String SORTED_BY_FIELD = "sortedBy"; private final Map<String, TypeAdapter<?>> attributeAdapters; private final Map<String, TypeAdapter<?>> measureAdapters; private final Map<String, Class<?>> attributeTypes; private final Map<String, Class<?>> measureTypes; private final TypeAdapter<Collection<List<String>>> chainsAdapter; private final TypeAdapter<List<String>> stringListAdapter; public QueryResultGsonAdapter(Map<String, TypeAdapter<?>> attributeAdapters, Map<String, TypeAdapter<?>> measureAdapters, Map<String, Class<?>> attributeTypes, Map<String, Class<?>> measureTypes, TypeAdapter<Collection<List<String>>> chainsAdapter, TypeAdapter<List<String>> stringListAdapter) { this.attributeAdapters = attributeAdapters; this.measureAdapters = measureAdapters; this.attributeTypes = attributeTypes; this.measureTypes = measureTypes; this.chainsAdapter = chainsAdapter; this.stringListAdapter = stringListAdapter; } public static QueryResultGsonAdapter create(Gson gson, Map<String, Type> attributeTypes, Map<String, Type> measureTypes) { Map<String, TypeAdapter<?>> attributeAdapters = newLinkedHashMap(); Map<String, TypeAdapter<?>> measureAdapters = newLinkedHashMap(); Map<String, Class<?>> attributeRawTypes = newLinkedHashMap(); Map<String, Class<?>> measureRawTypes = newLinkedHashMap(); for (String attribute : attributeTypes.keySet()) { TypeToken<?> typeToken = TypeToken.get(attributeTypes.get(attribute)); attributeAdapters.put(attribute, gson.getAdapter(typeToken)); attributeRawTypes.put(attribute, typeToken.getRawType()); } for (String measure : measureTypes.keySet()) { TypeToken<?> typeToken = TypeToken.get(measureTypes.get(measure)); measureAdapters.put(measure, gson.getAdapter(typeToken)); measureRawTypes.put(measure, typeToken.getRawType()); } TypeAdapter<Collection<List<String>>> chainsAdapter = gson.getAdapter(new TypeToken<Collection<List<String>>>() {}); TypeAdapter<List<String>> stringListAdapter = gson.getAdapter(new TypeToken<List<String>>() {}); return new QueryResultGsonAdapter(attributeAdapters, measureAdapters, attributeRawTypes, measureRawTypes, chainsAdapter, stringListAdapter); } @Override public QueryResult read(JsonReader reader) throws JsonParseException, IOException { reader.beginObject(); checkArgument(ATTRIBUTES_FIELD.equals(reader.nextName())); List<String> attributes = stringListAdapter.read(reader); checkArgument(MEASURES_FIELD.equals(reader.nextName())); List<String> measures = stringListAdapter.read(reader); checkArgument(SORTED_BY_FIELD.equals(reader.nextName())); List<String> sortedBy = stringListAdapter.read(reader); RecordScheme recordScheme = recordScheme(attributes, measures); checkArgument(RECORDS_FIELD.equals(reader.nextName())); List<Record> records = readRecords(reader, recordScheme); checkArgument(TOTALS_FIELD.equals(reader.nextName())); Record totals = readTotals(reader, recordScheme); checkArgument(COUNT_FIELD.equals(reader.nextName())); int count = reader.nextInt(); checkArgument(DRILLDOWNS_FIELD.equals(reader.nextName())); List<Drilldown> drilldowns = readDrilldowns(reader); checkArgument(CHAINS_FIELD.equals(reader.nextName())); Collection<List<String>> chains = chainsAdapter.read(reader); checkArgument(FILTER_ATTRIBUTES_FIELD.equals(reader.nextName())); Map<String, Object> filterAttributes = readFilterAttributes(reader); reader.endObject(); return QueryResult.create(recordScheme, records, totals, count, attributes, measures, sortedBy, drilldowns, chains, filterAttributes); } private List<Record> readRecords(JsonReader reader, RecordScheme recordScheme) throws JsonParseException, IOException { List<Record> records = new ArrayList<>(); TypeAdapter[] fieldTypeAdapters = getTypeAdapters(recordScheme); reader.beginArray(); while (reader.hasNext()) { reader.beginArray(); Record record = Record.create(recordScheme); for (int i = 0; i < fieldTypeAdapters.length; i++) { Object fieldValue = fieldTypeAdapters[i].read(reader); record.put(i, fieldValue); } records.add(record); reader.endArray(); } reader.endArray(); return records; } private Record readTotals(JsonReader reader, RecordScheme recordScheme) throws JsonParseException, IOException { reader.beginArray(); Record totals = Record.create(recordScheme); for (int i = 0; i < recordScheme.getFields().size(); i++) { String field = recordScheme.getField(i); TypeAdapter<?> fieldTypeAdapter = measureAdapters.get(field); if (fieldTypeAdapter == null) continue; Object fieldValue = fieldTypeAdapter.read(reader); totals.put(i, fieldValue); } reader.endArray(); return totals; } private List<Drilldown> readDrilldowns(JsonReader reader) throws IOException { List<Drilldown> drilldowns = newArrayList(); reader.beginArray(); while (reader.hasNext()) { reader.beginObject(); checkArgument(DIMENSIONS_FIELD.equals(reader.nextName())); List<String> dimensions = stringListAdapter.read(reader); checkArgument(MEASURES_FIELD.equals(reader.nextName())); List<String> measures = stringListAdapter.read(reader); drilldowns.add(Drilldown.create(dimensions, newHashSet(measures))); reader.endObject(); } reader.endArray(); return drilldowns; } private Map<String, Object> readFilterAttributes(JsonReader reader) throws JsonParseException, IOException { reader.beginObject(); Map<String, Object> result = newLinkedHashMap(); while (reader.hasNext()) { String attribute = reader.nextName(); Object value = attributeAdapters.get(attribute).read(reader); result.put(attribute, value); } reader.endObject(); return result; } @Override public void write(JsonWriter writer, QueryResult result) throws IOException { writer.beginObject(); writer.name(ATTRIBUTES_FIELD); stringListAdapter.write(writer, result.getAttributes()); writer.name(MEASURES_FIELD); stringListAdapter.write(writer, result.getMeasures()); writer.name(SORTED_BY_FIELD); stringListAdapter.write(writer, result.getSortedBy()); writer.name(RECORDS_FIELD); writeRecords(writer, result.getRecordScheme(), result.getRecords()); writer.name(TOTALS_FIELD); writeTotals(writer, result.getRecordScheme(), result.getTotals()); writer.name(COUNT_FIELD); writer.value(result.getTotalCount()); writer.name(DRILLDOWNS_FIELD); writeDrilldowns(writer, result.getDrilldowns()); writer.name(CHAINS_FIELD); chainsAdapter.write(writer, result.getChains()); writer.name(FILTER_ATTRIBUTES_FIELD); writeFilterAttributes(writer, result.getFilterAttributes()); writer.endObject(); } @SuppressWarnings("unchecked") private void writeRecords(JsonWriter writer, RecordScheme recordScheme, List<Record> records) throws IOException { writer.beginArray(); TypeAdapter[] fieldTypeAdapters = getTypeAdapters(recordScheme); for (Record record : records) { writer.beginArray(); for (int i = 0; i < recordScheme.getFields().size(); i++) { fieldTypeAdapters[i].write(writer, record.get(i)); } writer.endArray(); } writer.endArray(); } @SuppressWarnings("unchecked") private void writeTotals(JsonWriter writer, RecordScheme recordScheme, Record totals) throws IOException { writer.beginArray(); for (int i = 0; i < recordScheme.getFields().size(); i++) { String field = recordScheme.getField(i); TypeAdapter fieldTypeAdapter = measureAdapters.get(field); if (fieldTypeAdapter == null) continue; fieldTypeAdapter.write(writer, totals.get(i)); } writer.endArray(); } @SuppressWarnings("unchecked") private void writeFilterAttributes(JsonWriter writer, Map<String, Object> filterAttributes) throws IOException { writer.beginObject(); for (String attribute : filterAttributes.keySet()) { Object value = filterAttributes.get(attribute); writer.name(attribute); TypeAdapter typeAdapter = attributeAdapters.get(attribute); typeAdapter.write(writer, value); } writer.endObject(); } private void writeDrilldowns(JsonWriter writer, Collection<Drilldown> drilldowns) throws IOException { writer.beginArray(); for (Drilldown drilldown : drilldowns) { writer.beginObject(); writer.name(DIMENSIONS_FIELD); stringListAdapter.write(writer, drilldown.getChain()); writer.name(MEASURES_FIELD); stringListAdapter.write(writer, newArrayList(drilldown.getMeasures())); writer.endObject(); } writer.endArray(); } public RecordScheme recordScheme(List<String> attributes, List<String> measures) { RecordScheme recordScheme = RecordScheme.create(); for (String attribute : attributes) { recordScheme = recordScheme.withField(attribute, attributeTypes.get(attribute)); } for (String measure : measures) { recordScheme = recordScheme.withField(measure, measureTypes.get(measure)); } return recordScheme; } private TypeAdapter[] getTypeAdapters(RecordScheme recordScheme) { TypeAdapter[] fieldTypeAdapters = new TypeAdapter[recordScheme.getFields().size()]; for (int i = 0; i < recordScheme.getFields().size(); i++) { String field = recordScheme.getField(i); fieldTypeAdapters[i] = firstNonNull(attributeAdapters.get(field), measureAdapters.get(field)); } return fieldTypeAdapters; } }