/*
* Copyright (C) 2014 Indeed 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.indeed.imhotep.metadata;
import com.google.common.base.Preconditions;
import com.google.common.base.Strings;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
import org.apache.log4j.Logger;
import org.codehaus.jackson.map.ObjectMapper;
import org.codehaus.jackson.node.ArrayNode;
import org.codehaus.jackson.node.ObjectNode;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Set;
/**
* @author vladimir
*/
public class DatasetMetadata implements Comparable<DatasetMetadata> {
private static final Logger log = Logger.getLogger(DatasetMetadata.class);
@Nonnull final String name;
@Nullable String description;
@Nonnull final LinkedHashMap<String, FieldMetadata> fields = Maps.newLinkedHashMap();
@Nonnull final LinkedHashMap<String, MetricMetadata> metrics = Maps.newLinkedHashMap();
@Nullable DatasetType type = null;
// Required by LuceneQueryTranslator, so cache it here
@Nonnull Set<String> intImhotepFieldSet = Sets.newHashSet();
// used by the preprocessor
@Nonnull Map<String, String> aliases = Maps.newHashMap();
public DatasetMetadata(@Nonnull String name) {
Preconditions.checkNotNull(name);
this.name = name;
}
@Nonnull
public String getName() {
return name;
}
@Nullable
public FieldMetadata getField(String name) {
return fields.get(name);
}
@Nullable
public MetricMetadata getMetric(String name) {
return metrics.get(name);
}
@Nonnull
public LinkedHashMap<String, FieldMetadata> getFields() {
return fields;
}
@Nonnull
public LinkedHashMap<String, MetricMetadata> getMetrics() {
return metrics;
}
@Nullable
public String getDescription() {
return description;
}
public void setDescription(@Nullable String description) {
this.description = description;
}
public boolean hasField(String field) {
return fields.containsKey(field);
}
public boolean hasIntField(String field) {
final FieldMetadata fieldMetadata = fields.get(field);
return fieldMetadata != null && fieldMetadata.isIntImhotepField();
}
public boolean hasStringField(String field) {
final FieldMetadata fieldMetadata = fields.get(field);
return fieldMetadata != null && fieldMetadata.isStringImhotepField();
}
public DatasetType getType() {
if(type == null) {
// try to detect
for(FieldMetadata fieldMetadata : fields.values()) {
if(fieldMetadata.isIntImhotepField()) {
type = DatasetType.Imhotep;
break;
}
}
if(type == null) {
type = DatasetType.Ramses;
}
}
return type;
}
public boolean isImhotepDataset() {
return getType() == DatasetType.Imhotep;
}
public boolean isRamsesDataset() {
return getType() == DatasetType.Ramses;
}
public String getTimeFieldName() {
return isImhotepDataset() ? "unixtime" : "time";
// may want to try finding the time field among available fields like below
// if(intFields.isEmpty()) {
// return "time"; // in ramses indexes (which have no int fields) time is the mandatory name
// }
// // in imhotep indexes it is normally unixtime but things can vary
// for(String field : intFields) {
// if("time".equals(field) || "unixtime".equals(field)) {
// return field;
// }
// }
// throw new UnsupportedOperationException("time field not found");
}
@Nonnull
public Set<String> getIntImhotepFieldSet() {
return intImhotepFieldSet;
}
@Nonnull
public Map<String, String> getAliases() {
return aliases;
}
public void addFieldMetricDescription(String name, String desc, String unit, boolean isHidden, boolean hasField, boolean hasMetric) {
if(hasField) {
final FieldMetadata fieldMetadata = getField(name);
if(fieldMetadata != null) {
fieldMetadata.setDescription(desc);
if(isHidden) {
fieldMetadata.setHidden(isHidden);
}
if(hasMetric && fieldMetadata.isStringImhotepField()) {
fieldMetadata.setType(FieldType.Integer); // mark this as an int since it's a metric
}
} else {
log.trace("Unknown field defined: " + getName() + "." + name);
}
}
if(!hasMetric) {
return; // no metric for this
}
MetricMetadata metricMetadata = getMetric(name);
if(metricMetadata == null) {
metricMetadata = new MetricMetadata(name);
metrics.put(name, metricMetadata);
}
metricMetadata.setDescription(desc);
if(isHidden) {
metricMetadata.setHidden(isHidden);
}
if(unit != null) {
metricMetadata.setUnit(unit);
}
}
/**
* Should be called when the metadata loading is complete to update caches.
*/
public void finishLoading() {
Preconditions.checkState(intImhotepFieldSet.isEmpty());
for(FieldMetadata fieldMetadata : fields.values()) {
if(fieldMetadata.isIntImhotepField()) {
intImhotepFieldSet.add(fieldMetadata.getName());
}
}
intImhotepFieldSet = Collections.unmodifiableSet(intImhotepFieldSet);
Preconditions.checkState(aliases.isEmpty());
for(MetricMetadata metric : metrics.values()) {
if(metric.expression != null && !metric.expression.equals(metric.name)) {
aliases.put(metric.name, metric.expression);
}
}
aliases = Collections.unmodifiableMap(aliases);
}
@Override
public int compareTo(DatasetMetadata o) {
return name.compareTo(o.name);
}
public void toJSON(ObjectNode jsonNode, ObjectMapper mapper, boolean summaryOnly) {
jsonNode.put("name", getName());
jsonNode.put("description", Strings.nullToEmpty(getDescription()));
if(summaryOnly) {
return;
}
final ArrayNode fieldsArray = mapper.createArrayNode();
jsonNode.put("fields", fieldsArray);
for(FieldMetadata field : getFields().values()) {
if(field.isHidden()) {
continue;
}
final ObjectNode fieldInfo = mapper.createObjectNode();
field.toJSON(fieldInfo);
fieldsArray.add(fieldInfo);
}
final ArrayNode metricsArray = mapper.createArrayNode();
jsonNode.put("metrics", metricsArray);
for(MetricMetadata metric : getMetrics().values()) {
if(metric.isHidden()) {
continue;
}
final ObjectNode datasetInfo = mapper.createObjectNode();
metric.toJSON(datasetInfo);
metricsArray.add(datasetInfo);
}
}
}