
com.dell.doradus.olap.builder.BatchBuilder Maven / Gradle / Ivy
The newest version!
/*
* Copyright (C) 2015 Dell, 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.
*/
package com.dell.doradus.olap.builder;
import java.io.Reader;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import com.dell.doradus.common.JSONAnnie;
import com.dell.doradus.common.UNode;
import com.dell.doradus.common.Utils;
import com.dell.doradus.olap.OlapBatch;
import com.dell.doradus.olap.OlapDocument;
public class BatchBuilder {
// SajListener to parse JSON directly into OlapBatch/OlapDocument objects. Example JSON
// structure we expect:
// {"batch": {
// "docs": [
// {"doc": {
// "_ID": "sassafras",
// "_table": "Books",
// "ISBN": "978-0061673733",
// "Address": {
// "City": "Aliso Viejo",
// "State": "CA",
// "Zipcode": 92656
// },
// "Children": {"add": [123, 456]},
// "Tags": {"add": ["Biography", "Philosophy"]}
// }},
// {"doc": {
// ...
// }}
// ]
// }}
//
//
// update Nov. 19, 2013: to delete object, specify
// {"batch": {
// "docs": [
// {"doc": {
// "_ID": "sassafras",
// "_table": "Books",
// "_deleted": "true"
// }},
// {"doc": {
// ...
// }}
// ]
// }}
//
static class Listener implements JSONAnnie.SajListener {
OlapBatch result = new OlapBatch();
// Name keys use dotted notation; e.g. _ID=[sassafras], Address.City=[Aliso Viejo],
Map> valueMap = new HashMap<>();
List fieldStack = new ArrayList<>();
Set values = new HashSet();
StringBuilder buffer = new StringBuilder();
int level = 0; // 0=batch object, 1=docs array, 2=doc object, 3+=field object
final String idFieldName;
final boolean bDeleteAll;
Listener(String idFieldName, boolean bDeleteAll) {
this.idFieldName = idFieldName;
this.bDeleteAll = bDeleteAll;
}
@Override
public void onStartObject(String name) {
switch (level) {
case 0: // outer batch level
Utils.require(name.equals("batch"), "Root node must be 'batch': " + name);
break;
case 1: // docs level: can be an object with one "doc" child
Utils.require(name.equals("docs"), "'docs' array expected: " + name);
break;
case 2: // doc object
Utils.require(name.equals("doc"), "'doc' object expected: " + name);
valueMap.clear();
fieldStack.clear();
break;
default: // outer or nested field
fieldStack.add(name);
}
level++;
} // onStartObject
@Override
public void onEndObject() {
if (fieldStack.size() > 0) {
fieldStack.remove(fieldStack.size() - 1);
}
if (--level == 2) {
buildObject(); // just finished a "doc" object
}
} // onEndObject
@Override
public void onStartArray(String name) {
switch (level) {
case 0:
case 2: // An array is unexpected for "batch" or "doc"
Utils.require(false, "Unexpected array start: " + name);
case 1: // Should be "docs" array.
Utils.require(name.equals("docs"), "'docs' array expected: " + name);
break;
default: // outer or nested field.
fieldStack.add(name);
}
level++;
} // onStartArray
@Override
public void onEndArray() {
if (fieldStack.size() > 0) {
fieldStack.remove(fieldStack.size() - 1);
}
level--;
} // onEndArray
@Override
public void onValue(String name, String value) {
// Values are only expected within field elements (level >= 3)
Utils.require(level >= 3, "Unexpected recognized element: %s", name);
saveValue(name, value);
} // onValue
// Save a leaf-level value
private void saveValue(String name, String value) {
String dottedName = getDottedName(name);
List values = valueMap.get(dottedName);
if (values == null) {
values = new ArrayList(1);
valueMap.put(dottedName, values);
}
values.add(value);
} // saveValue
// Turn parent node names, if any, into a dotted string and append name.
private String getDottedName(String name) {
buffer.setLength(0);
for (String parentName : fieldStack) {
buffer.append(parentName);
buffer.append(".");
}
buffer.append(name);
return buffer.toString();
} // getDottedName
// Create a DBObject for the just-parsed 'doc' element and add to batch.
private void buildObject() {
OlapDocument document = result.addDoc();
for (String dottedName : valueMap.keySet()) {
List values = valueMap.get(dottedName);
String[] names = dottedName.split("\\.");
addValue(document, names, 0, values);
}
if (bDeleteAll) {
document.setDeleted(true);
}
} // buildObject
// Possible name structures we expect:
// field
// field.value
// field.add.value
// group.field
// group.field.add.value
// group1.group2.field
// group1.group2.field.add.value
// ...
private void addValue(OlapDocument document, String[] names, int inx, List values) {
String fieldName = names[inx];
if ((names.length - inx) == 2 && names[inx + 1].equals("value")) {
// field.value
addValues(document, fieldName, values);
} else if ((names.length - inx) == 3 &&
names[inx + 2].equals("value") &&
names[inx + 1].equals("add")) {
// field.add.value
addValues(document, fieldName, values);
} else if ((names.length - inx) > 1) {
// group.field...: recurse to inx + 1.
addValue(document, names, inx + 1, values);
} else {
// field
addValues(document, fieldName, values);
}
} // addValue
private void addValues(OlapDocument document, String fieldName, Iterable values) {
for (String value : values) {
addValue(document, fieldName, value);
}
} // addValues
private void addValue(OlapDocument document, String fieldName, String value) {
if (Utils.isEmpty(value)) return;
if (fieldName.equals(idFieldName)) {
document.setId(value);
} else if (fieldName.equals("_table")) {
document.setTable(value);
} else if (fieldName.equals("_deleted")) {
document.setDeleted("true".equals(value));
} else {
document.addField(fieldName, value);
}
} // addValue
} // class Listener
public static OlapBatch parseJSON(String text, String idFieldName, boolean bDeleteAll) {
Listener listener = new Listener(idFieldName, bDeleteAll);
new JSONAnnie(text).parse(listener);
return listener.result;
}
public static OlapBatch parseJSON(Reader reader, String idFieldName, boolean bDeleteAll) {
Listener listener = new Listener(idFieldName, bDeleteAll);
new JSONAnnie(reader).parse(listener);
return listener.result;
}
public static OlapBatch fromUNode(UNode rootNode, String idFieldName, boolean bDeleteAll) {
Utils.require(rootNode.getName().equals("batch"),
"Root node must be 'batch': " + rootNode.getName());
OlapBatch batch = new OlapBatch();
UNode docsNode = rootNode.getMember("docs");
Utils.require(docsNode != null, "'batch' node requires child 'docs' node");
for (UNode docNode : docsNode.getMemberList()) {
// Get "doc" node and its _ID and _table values.
Utils.require(docNode.getName().equals("doc"), "'doc' node expected: " + docNode.getName());
OlapDocument document = batch.addDoc();
for (UNode fieldNode : docNode.getMemberList()) {
addFieldValues(document, fieldNode, idFieldName);
}
if (bDeleteAll) {
document.setDeleted(true);
}
Utils.require(document.getTable() != null, "'doc' node missing '_table' value");
}
return batch;
}
private static void addFieldValues(OlapDocument document, UNode fieldNode, String idFieldName) {
String fieldName = fieldNode.getName();
Utils.require(fieldName != null, "'field' node missing 'name' value:" + fieldNode.getName());
if (fieldNode.isValue()) {
// Simple field value
addFieldValue(document, fieldName, fieldNode.getValue(), idFieldName);
} else if (fieldNode.isArray()) {
// ["value 1", "value 2", ...]
for (UNode valueNode : fieldNode.getMemberList()) {
Utils.require(valueNode.isValue(), "Value expected: " + valueNode);
addFieldValue(document, fieldName, valueNode.getValue(), idFieldName);
}
} else {
// Field's value is MAP.
for (UNode childNode : fieldNode.getMemberList()) {
if (childNode.getName().equals("add") && !childNode.isValue()) {
// "add": ["value 1", "value 2", ...]
for (UNode valueNode : childNode.getMemberList()) {
Utils.require(valueNode.isValue(), "Value expected: " + valueNode);
addFieldValue(document, fieldName, valueNode.getValue(), idFieldName);
}
} else {
// Must be a nested field.
addFieldValues(document, childNode, idFieldName);
}
}
}
} // addFieldValues
private static void addFieldValue(OlapDocument document, String fieldName, String value, String idFieldName) {
if (fieldName.equals(idFieldName)) {
document.setId(value);
} else if (fieldName.equals("_table")) {
document.setTable(value);
} else if (fieldName.equals("_deleted")) {
document.setDeleted("true".equals(value));
} else {
document.addField(fieldName, value);
}
} // addFieldValue
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy