/** * Copyright 2014 Yahoo! Inc. 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. * See accompanying LICENSE file. */ package com.yahoo.sql4d.sql4ddriver; import com.yahoo.sql4d.query.RequestType; import com.yahoo.sql4d.sql4ddriver.rowmapper.DruidBaseBean; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.logging.Level; import java.util.logging.Logger; import org.json.JSONArray; import org.json.JSONObject; import scala.Tuple2; /** * Provides facilities to do chain of joins . * @author srikalyan * @param <T> */ public class Joiner4Bean<T extends DruidBaseBean> extends BaseJoiner { public Map<Object, T> baseAllRows = new LinkedHashMap<>();// Each entry = <key value, list of all values> private Class<T> rowMapper; public Joiner4Bean(JSONArray jsonAllRows, List<String> joinFields, Class<T> rowMapper) { this.rowMapper = rowMapper; join(jsonAllRows, joinFields, ActionType.FIRST_CUT); } /** * Generate a Tuple <A, B> * A = list of field names * B = map of <joinFeild, rowList> from jsonArray. * @param timestamp * @param jsonAllRows * @param joinFields * @param requestType * @param action */ @Override protected void extractAndTakeAction(String timestamp, JSONArray jsonAllRows, List<String> joinFields, RequestType requestType, ActionType action) { Map<Object, T> newBaseAllRows = new LinkedHashMap<>(); JSONObject eachRow = null; for (int i = 0; i < jsonAllRows.length(); i++) { JSONObject jsonItem = jsonAllRows.getJSONObject(i); eachRow = dataItemAt(jsonItem, requestType, action); String actualTimestamp = timestamp; if (timestamp == null) { if (requestType == RequestType.SELECT) { actualTimestamp = eachRow.optString("timestamp"); // Because the timestamp is within each row remove them once you extract it. eachRow.remove("timestamp"); } else { actualTimestamp = jsonItem.optString("timestamp"); } } Tuple2<Object, T> row = mapPkToRow(actualTimestamp, eachRow, joinFields); Object pk = row._1();// Primary key. T rowVal = row._2(); if (action == ActionType.FIRST_CUT) { baseAllRows.put(pk, rowVal); } else { if (baseAllRows.containsKey(pk)) {// Merge with existing row. for (Object jsonKey:eachRow.keySet()) { if (baseFieldNames.contains(jsonKey.toString())) { try { Method setterMethod = rowVal.getClass().getMethod(Util.setterMethodName(jsonKey.toString()), rowVal.getClass().getDeclaredField(jsonKey.toString()).getType()); setterMethod.invoke(baseAllRows.get(pk), eachRow.get(jsonKey.toString())); } catch (NoSuchMethodException | SecurityException | IllegalAccessException | IllegalArgumentException | InvocationTargetException | NoSuchFieldException ex) { Logger.getLogger(Joiner4Bean.class.getName()).log(Level.SEVERE, null, ex); } } } if (action == ActionType.JOIN || action == ActionType.RIGHT_JOIN) { newBaseAllRows.put(pk, baseAllRows.remove(pk));// Remove from existing map and add to new map. } } else { if (action == ActionType.RIGHT_JOIN) {// Right join newBaseAllRows.put(pk, rowVal); } } } if (i == 0) {// Fill headers (only once) fillHeaders(eachRow, joinFields, action); } } if (!newBaseAllRows.isEmpty()) { baseAllRows = newBaseAllRows; } } /** * Extract fields(k,v) from json * k = primary field(s), i.e could be composite key as well. * v = all fields . The first field is always timestamp. * @param timestamp * @param jsonRow * @param joinFields * @return */ private Tuple2<Object, T> mapPkToRow(String timestamp, JSONObject jsonRow, List<String> joinFields) { Object joinValue = null; T rowValues = null; try { rowValues = rowMapper.newInstance(); rowValues.getClass().getMethod("setTimestamp", String.class).invoke(rowValues, timestamp); for (Object key : jsonRow.keySet()) { String colValue = key.toString(); Util.applyKVToBean(rowValues, colValue, jsonRow.get(colValue)); if (joinFields.contains(colValue)) { joinValue = (joinValue == null)?jsonRow.get(colValue):(joinValue + "\u0001" + jsonRow.get(colValue)); } } if (joinFields.contains("timestamp")) {// Join field could contain timestamp(timestamp can be out of the actual data JSON, so we try this way) joinValue = (joinValue == null)?timestamp:(joinValue + "\u0001" + timestamp); } } catch (InstantiationException | IllegalAccessException | NoSuchMethodException | SecurityException | IllegalArgumentException | InvocationTargetException ex) { Logger.getLogger(Joiner4Bean.class.getName()).log(Level.SEVERE, null, ex); } return new Tuple2<>(joinValue, rowValues); } }