blob: 002ec28dbd3f26a6491cb863a12b42eecd8186fc [file] [log] [blame]
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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 org.apache.metron.indexing.dao;
import com.fasterxml.jackson.core.type.TypeReference;
import com.google.common.base.Splitter;
import com.google.common.collect.ComparisonChain;
import com.google.common.collect.Iterables;
import com.google.common.collect.Ordering;
import org.apache.metron.common.Constants;
import org.apache.metron.common.utils.JSONUtils;
import org.apache.metron.indexing.dao.search.FieldType;
import org.apache.metron.indexing.dao.search.GetRequest;
import org.apache.metron.indexing.dao.search.Group;
import org.apache.metron.indexing.dao.search.GroupRequest;
import org.apache.metron.indexing.dao.search.GroupResponse;
import org.apache.metron.indexing.dao.search.GroupResult;
import org.apache.metron.indexing.dao.search.InvalidSearchException;
import org.apache.metron.indexing.dao.search.SearchRequest;
import org.apache.metron.indexing.dao.search.SearchResponse;
import org.apache.metron.indexing.dao.search.SearchResult;
import org.apache.metron.indexing.dao.search.SortField;
import org.apache.metron.indexing.dao.search.SortOrder;
import org.apache.metron.indexing.dao.update.Document;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Optional;
import java.util.UUID;
public class InMemoryDao implements IndexDao {
// Map from index to list of documents as JSON strings
public static Map<String, List<String>> BACKING_STORE = new HashMap<>();
public static Map<String, Map<String, FieldType>> COLUMN_METADATA = new HashMap<>();
private AccessConfig config;
@Override
public SearchResponse search(SearchRequest searchRequest) throws InvalidSearchException {
if(config.getMaxSearchResults() != null && searchRequest.getSize() > config.getMaxSearchResults()) {
throw new InvalidSearchException("Search result size must be less than " + config.getMaxSearchResults());
}
List<SearchResult> response = new ArrayList<>();
for(String index : searchRequest.getIndices()) {
String i = null;
for(String storedIdx : BACKING_STORE.keySet()) {
if(storedIdx.equals(index) || storedIdx.startsWith(index + "_")) {
i = storedIdx;
}
}
if(i == null) {
continue;
}
for (String doc : BACKING_STORE.get(i)) {
Map<String, Object> docParsed = parse(doc);
if (isMatch(searchRequest.getQuery(), docParsed)) {
SearchResult result = new SearchResult();
result.setSource(docParsed);
result.setScore((float) Math.random());
result.setId(docParsed.getOrDefault(Constants.GUID, UUID.randomUUID()).toString());
response.add(result);
}
}
}
if(searchRequest.getSort().size() != 0) {
Collections.sort(response, sorted(searchRequest.getSort()));
}
SearchResponse ret = new SearchResponse();
List<SearchResult> finalResp = new ArrayList<>();
int maxSize = config.getMaxSearchResults() == null?searchRequest.getSize():config.getMaxSearchResults();
for(int i = searchRequest.getFrom();i < response.size()&& finalResp.size() <= maxSize;++i) {
finalResp.add(response.get(i));
}
ret.setTotal(response.size());
ret.setResults(finalResp);
return ret;
}
@Override
public GroupResponse group(GroupRequest groupRequest) throws InvalidSearchException {
GroupResponse groupResponse = new GroupResponse();
groupResponse.setGroupedBy(groupRequest.getGroups().get(0).getField());
groupResponse.setGroupResults(getGroupResults(groupRequest.getGroups(), 0));
return groupResponse;
}
private List<GroupResult> getGroupResults(List<Group> groups, int index) {
Group group = groups.get(index);
GroupResult groupResult = new GroupResult();
groupResult.setKey(group.getField() + "_value");
if (index < groups.size() - 1) {
groupResult.setGroupedBy(groups.get(index + 1).getField());
groupResult.setGroupResults(getGroupResults(groups, index + 1));
} else {
groupResult.setScore(50.0);
}
groupResult.setTotal(10);
return Collections.singletonList(groupResult);
}
private static class ComparableComparator implements Comparator<Comparable> {
SortOrder order = null;
public ComparableComparator(SortOrder order) {
this.order = order;
}
@Override
public int compare(Comparable o1, Comparable o2) {
int result = ComparisonChain.start().compare(o1, o2, Ordering.natural().nullsLast()).result();
return order == SortOrder.ASC ? result : -1*result;
}
}
private static Comparator<SearchResult> sorted(final List<SortField> fields) {
return (o1, o2) -> {
ComparisonChain chain = ComparisonChain.start();
for(SortField field : fields) {
Comparable f1 = (Comparable) o1.getSource().get(field.getField());
Comparable f2 = (Comparable) o2.getSource().get(field.getField());
chain = chain.compare(f1, f2, new ComparableComparator(field.getSortOrder()));
}
return chain.result();
};
}
private static boolean isMatch(String query, Map<String, Object> doc) {
if (query == null) {
return false;
}
if(query.equals("*")) {
return true;
}
if(query.contains(":")) {
Iterable<String> splits = Splitter.on(":").split(query.trim());
String field = Iterables.getFirst(splits, "");
String val = Iterables.getLast(splits, "");
// Immediately quit if there's no value ot find
if (val == null) {
return false;
}
// Check if we're looking into a nested field. The '|' is arbitrarily chosen.
String nestingField = null;
if (field.contains("|")) {
Iterable<String> fieldSplits = Splitter.on('|').split(field);
nestingField = Iterables.getFirst(fieldSplits, null);
field = Iterables.getLast(fieldSplits, null);
}
if (nestingField == null) {
// Just grab directly
Object o = doc.get(field);
return val.equals(o);
} else {
// We need to look into a nested field for the value
@SuppressWarnings("unchecked")
List<Map<String, Object>> nestedList = (List<Map<String, Object>>) doc.get(nestingField);
if (nestedList == null) {
return false;
} else {
for (Map<String, Object> nestedEntry : nestedList) {
if (val.equals(nestedEntry.get(field))) {
return true;
}
}
}
}
}
return false;
}
public static Map<String, Object> parse(String doc) {
try {
return JSONUtils.INSTANCE.load(doc, new TypeReference<Map<String, Object>>() {});
} catch (IOException e) {
throw new IllegalStateException(e.getMessage(), e);
}
}
@Override
public void init(AccessConfig config) {
this.config = config;
}
@Override
public Document getLatest(String guid, String sensorType) throws IOException {
for(Map.Entry<String, List<String>> kv: BACKING_STORE.entrySet()) {
if(kv.getKey().startsWith(sensorType)) {
for(String doc : kv.getValue()) {
Map<String, Object> docParsed = parse(doc);
if(docParsed.getOrDefault(Constants.GUID, "").equals(guid)) {
return new Document(doc, guid, sensorType, 0L);
}
}
}
}
return null;
}
@Override
public Iterable<Document> getAllLatest(List<GetRequest> getRequests) throws IOException {
List<Document> documents = new ArrayList<>();
for(Map.Entry<String, List<String>> kv: BACKING_STORE.entrySet()) {
for(String doc : kv.getValue()) {
Map<String, Object> docParsed = parse(doc);
String guid = (String) docParsed.getOrDefault(Constants.GUID, "");
for (GetRequest getRequest: getRequests) {
if(getRequest.getGuid().equals(guid)) {
documents.add(new Document(doc, guid, getRequest.getSensorType(), 0L));
}
}
}
}
return documents;
}
@Override
public void update(Document update, Optional<String> index) throws IOException {
for (Map.Entry<String, List<String>> kv : BACKING_STORE.entrySet()) {
if (kv.getKey().startsWith(update.getSensorType())) {
for (Iterator<String> it = kv.getValue().iterator(); it.hasNext(); ) {
String doc = it.next();
Map<String, Object> docParsed = parse(doc);
if (docParsed.getOrDefault(Constants.GUID, "").equals(update.getGuid())) {
it.remove();
}
}
kv.getValue().add(JSONUtils.INSTANCE.toJSON(update.getDocument(), true));
}
}
}
@Override
public void batchUpdate(Map<Document, Optional<String>> updates) throws IOException {
for (Map.Entry<Document, Optional<String>> update : updates.entrySet()) {
update(update.getKey(), update.getValue());
}
}
@Override
public Map<String, FieldType> getColumnMetadata(List<String> indices) throws IOException {
Map<String, FieldType> indexColumnMetadata = new HashMap<>();
for(String index: indices) {
if (COLUMN_METADATA.containsKey(index)) {
Map<String, FieldType> columnMetadata = COLUMN_METADATA.get(index);
for (Entry entry: columnMetadata.entrySet()) {
String field = (String) entry.getKey();
FieldType type = (FieldType) entry.getValue();
if (indexColumnMetadata.containsKey(field)) {
if (!type.equals(indexColumnMetadata.get(field))) {
indexColumnMetadata.put(field, FieldType.OTHER);
}
} else {
indexColumnMetadata.put(field, type);
}
}
}
}
return indexColumnMetadata;
}
public static void setColumnMetadata(Map<String, Map<String, FieldType>> columnMetadata) {
Map<String, Map<String, FieldType>> columnMetadataMap = new HashMap<>();
for (Map.Entry<String, Map<String, FieldType>> e: columnMetadata.entrySet()) {
columnMetadataMap.put(e.getKey(), Collections.unmodifiableMap(e.getValue()));
}
COLUMN_METADATA = columnMetadataMap;
}
public static void load(Map<String, List<String>> backingStore) {
BACKING_STORE = backingStore;
}
public static void clear() {
BACKING_STORE.clear();
COLUMN_METADATA.clear();
}
}