package com.ibm.nmon.parser;
import org.slf4j.Logger;
import java.util.List;
import java.util.Map;
import java.util.SimpleTimeZone;
import java.io.File;
import java.io.IOException;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.core.JsonParseException;
import com.fasterxml.jackson.core.type.TypeReference;
import com.ibm.nmon.data.BasicDataSet;
import com.ibm.nmon.data.DataType;
import com.ibm.nmon.data.DataRecord;
import com.ibm.nmon.data.SubDataType;
public final class JSONParser {
private static final Logger LOGGER = org.slf4j.LoggerFactory.getLogger(JSONParser.class);
private static final ObjectMapper MAPPER = new ObjectMapper();
private BasicDataSet data = null;
private SimpleDateFormat format = null;
public BasicDataSet parse(File file) throws IOException, ParseException {
return parse(file.getAbsolutePath());
}
public BasicDataSet parse(String filename) throws IOException, JsonParseException {
long start = System.nanoTime();
try {
Map<String, Object> root = MAPPER.readValue(new java.io.File(filename),
new TypeReference<Map<String, Object>>() {});
data = new BasicDataSet(filename);
Object temp = root.get("hostname");
if (temp == null) {
throw new IOException("field 'hostname' not found");
}
data.setHostname((String) temp);
format = parseDateFormat(root.get("whenPattern"), root.get("timezone"));
parseMetadata(root.get("metadata"));
parseTypes(root.get("types"));
parseData(root.get("data"));
return data;
}
finally {
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("Parse complete for {} in {}ms", data.getSourceFile(),
(System.nanoTime() - start) / 1000000.0d);
}
data = null;
format = null;
}
}
private SimpleDateFormat parseDateFormat(Object whenPattern, Object timezone) throws IOException {
if (whenPattern == null) {
throw new IOException("field 'whenPattern' not found");
}
format = new SimpleDateFormat((String) whenPattern);
if (timezone != null) {
if (timezone instanceof Number) {
Number tz = (Number) timezone;
format.setTimeZone(new SimpleTimeZone((int) (tz.doubleValue() * 3600000), tz.toString()));
}
else if (timezone instanceof String) {
format.setTimeZone(SimpleTimeZone.getTimeZone((String) timezone));
if (format.getTimeZone().equals(SimpleTimeZone.getTimeZone("GMT"))) {
LOGGER.warn(
"'timezone' value defined as '{}' but Java interpreted this as GMT; are you sure this is a valid value?",
timezone);
}
}
else {
LOGGER.warn("timezone '{}' is not a valid format; it must be a number or a String", timezone);
// return format without a set timezone
}
data.setMetadata("timezone", format.getTimeZone().getDisplayName());
}
else {
LOGGER.info("no 'timezone' value defined; defaulting to {}, ({})", format.getTimeZone().getID(), format
.getTimeZone().getRawOffset() / 3600000.0);
}
return format;
}
private void parseMetadata(Object rawMetadata) {
if (rawMetadata != null) {
@SuppressWarnings("unchecked")
Map<String, String> metadata = (Map<String, String>) rawMetadata;
for (String name : metadata.keySet()) {
data.setMetadata(name, metadata.get(name));
}
}
}
@SuppressWarnings("unchecked")
private void parseTypes(Object rawTypes) throws IOException {
if (rawTypes == null) {
throw new IOException("'types' must be defined");
}
List<Map<String, Object>> types = (List<Map<String, Object>>) rawTypes;
if (types.size() == 0) {
throw new IOException("at least one 'type' must be defined");
}
for (Map<String, Object> type : types) {
String typeName = (String) type.get("name");
String typeId = (String) type.get("id");
if (typeId == null) {
LOGGER.warn("typeId must be defined for each type (typeName = '{}'); it will be ignored", typeName);
continue;
}
if (typeName == null) {
typeName = typeId;
}
Object rawFields = type.get("fields");
if (rawFields == null) {
LOGGER.warn("no fields defined for type '{}'; it will be ignored", typeId);
continue;
}
List<String> fields = (List<String>) rawFields;
String[] fieldsArray = new String[fields.size()];
for (int i = 0; i < fieldsArray.length; i++) {
fieldsArray[i] = fields.get(i);
}
Object temp = type.get("subtypes");
if (temp != null) {
List<String> subtypes = (List<String>) temp;
for (String subtype : subtypes) {
DataType dataType = new SubDataType(typeId, subtype, typeName, fieldsArray);
data.addType(dataType);
}
}
else {
DataType dataType = new DataType(typeId, typeName, fieldsArray);
data.addType(dataType);
}
}
}
@SuppressWarnings("unchecked")
private void parseData(Object rawData) throws IOException {
if (rawData == null) {
throw new IOException("'data' must be defined");
}
List<Map<String, Object>> dataToParse = (List<Map<String, Object>>) rawData;
if ((dataToParse.size() == 0)) {
throw new IOException("at least one 'data' element must be defined");
}
for (Map<String, Object> datum : dataToParse) {
Object temp = datum.get("when");
if (temp == null) {
LOGGER.warn("'when' not defined for data record; it will be ignored. Previous time was '{}'",
data.getRecordCount() == 0 ? "<null>" : format.format(new java.util.Date(data.getEndTime())));
continue;
}
long time = 0;
String timestamp = (String) temp;
try {
time = format.parse(timestamp).getTime();
}
catch (ParseException pe) {
LOGGER.warn("cannot parse 'when' value '{}'; the data record will be ignored", temp);
continue;
}
DataRecord record = new DataRecord(time, timestamp);
for (DataType type : data.getTypes()) {
parseTypeData(datum, type, record);
}
data.addRecord(record);
}
}
@SuppressWarnings("unchecked")
private void parseTypeData(Map<String, Object> datum, DataType type, DataRecord record) {
String typeId = type.getId();
boolean isSubType = false;
if (type instanceof SubDataType) {
typeId = ((SubDataType) type).getPrimaryId();
isSubType = true;
}
Object temp = datum.get(typeId);
if (temp == null) {
LOGGER.warn("no data for type '{}' at time {}", typeId, format.format(new java.util.Date(record.getTime())));
return;
}
if (isSubType) {
if (temp instanceof Map) {
Map<String, Object> map = (Map<String, Object>) temp;
String subtypeId = ((SubDataType) type).getSubId();
temp = map.get(subtypeId);
if (temp == null) {
LOGGER.warn("no data for subtype '{}' at time {}", subtypeId,
format.format(new java.util.Date(record.getTime())));
return;
}
else {
List<Number> values = (List<Number>) temp;
double[] doubleValues = new double[values.size()];
for (int i = 0; i < doubleValues.length; i++) {
Number value = values.get(i);
if (value == null) {
doubleValues[i] = Double.NaN;
}
else {
doubleValues[i] = value.doubleValue();
}
}
record.addData(type, doubleValues);
}
}
else {
LOGGER.warn("unknown JSON object for type '{}' at time {}; it must be an object", type.getId(),
format.format(new java.util.Date(record.getTime())));
}
}
else {
if (temp instanceof List) {
List<Number> values = (List<Number>) temp;
double[] doubleValues = new double[values.size()];
for (int i = 0; i < doubleValues.length; i++) {
Number value = values.get(i);
if (value == null) {
doubleValues[i] = Double.NaN;
}
else {
doubleValues[i] = value.doubleValue();
}
}
record.addData(type, doubleValues);
}
else {
LOGGER.warn("unknown JSON object for type '{}' at time {}; it must be an array", typeId,
format.format(new java.util.Date(record.getTime())));
}
}
}
}