blob: c1e3af642256fb6930e8f2ca9e0cd1029f6ede57 [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.solr.dao;
import static org.apache.metron.solr.dao.SolrMetaAlertDao.METAALERTS_COLLECTION;
import java.io.IOException;
import java.lang.invoke.MethodHandles;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.apache.commons.lang3.StringUtils;
import org.apache.metron.common.Constants;
import org.apache.metron.indexing.dao.metaalert.MetaAlertConfig;
import org.apache.metron.indexing.dao.metaalert.MetaAlertConstants;
import org.apache.metron.indexing.dao.metaalert.MetaAlertSearchDao;
import org.apache.metron.indexing.dao.metaalert.MetaAlertStatus;
import org.apache.metron.indexing.dao.search.GroupRequest;
import org.apache.metron.indexing.dao.search.GroupResponse;
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.update.Document;
import org.apache.solr.client.solrj.SolrClient;
import org.apache.solr.client.solrj.SolrQuery;
import org.apache.solr.client.solrj.SolrServerException;
import org.apache.solr.client.solrj.response.QueryResponse;
import org.apache.solr.client.solrj.util.ClientUtils;
import org.apache.solr.common.SolrDocument;
import org.apache.solr.common.SolrDocumentList;
import org.apache.solr.common.params.CursorMarkParams;
import org.apache.solr.common.params.MapSolrParams;
import org.apache.solr.common.params.SolrParams;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class SolrMetaAlertSearchDao implements MetaAlertSearchDao {
private static final Logger LOG = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
transient SolrSearchDao solrSearchDao;
transient SolrClient solrClient;
private MetaAlertConfig config;
public SolrMetaAlertSearchDao(SolrClient solrClient, SolrSearchDao solrSearchDao, MetaAlertConfig config) {
this.solrClient = solrClient;
this.solrSearchDao = solrSearchDao;
this.config = config;
}
@Override
public SearchResponse getAllMetaAlertsForAlert(String guid) throws InvalidSearchException {
if (guid == null || guid.trim().isEmpty()) {
throw new InvalidSearchException("Guid cannot be empty");
}
// Searches for all alerts containing the meta alert guid in it's "metalerts" array
// The query has to match the parentFilter to avoid errors. Guid must also be explicitly
// included.
String activeClause =
MetaAlertConstants.STATUS_FIELD + ":" + MetaAlertStatus.ACTIVE.getStatusString();
String guidClause = Constants.GUID + ":" + guid;
String fullClause = "{!parent which=" + activeClause + "}" + guidClause;
String metaalertTypeClause = config.getSourceTypeField() + ":" + MetaAlertConstants.METAALERT_TYPE;
SolrQuery solrQuery = new SolrQuery()
.setQuery(fullClause)
.setFields("*", "[child parentFilter=" + metaalertTypeClause + " limit=999]")
.addSort(Constants.GUID,
SolrQuery.ORDER.asc); // Just do basic sorting to track where we are
// Use Solr's Cursors to handle the paging, rather than doing it manually.
List<SearchResult> allResults = new ArrayList<>();
try {
String cursorMark = CursorMarkParams.CURSOR_MARK_START;
boolean done = false;
while (!done) {
solrQuery.set(CursorMarkParams.CURSOR_MARK_PARAM, cursorMark);
QueryResponse rsp = solrClient.query(METAALERTS_COLLECTION, solrQuery);
String nextCursorMark = rsp.getNextCursorMark();
rsp.getResults().stream()
.map(solrDocument -> SolrUtilities.getSearchResult(solrDocument, null,
solrSearchDao.getAccessConfig().getIndexSupplier()))
.forEachOrdered(allResults::add);
if (cursorMark.equals(nextCursorMark)) {
done = true;
}
cursorMark = nextCursorMark;
}
} catch (IOException | SolrServerException e) {
throw new InvalidSearchException("Unable to complete search", e);
}
SearchResponse searchResponse = new SearchResponse();
searchResponse.setResults(allResults);
searchResponse.setTotal(allResults.size());
return searchResponse;
}
@Override
public SearchResponse search(SearchRequest searchRequest) throws InvalidSearchException {
// Need to wrap such that two things are true
// 1. The provided query is true OR nested query on the alert field is true
// 2. Metaalert is active OR it's not a metaalert
String activeStatusClause =
MetaAlertConstants.STATUS_FIELD + ":" + MetaAlertStatus.ACTIVE.getStatusString();
String metaalertTypeClause = config.getSourceTypeField() + ":" + MetaAlertConstants.METAALERT_TYPE;
// Use the 'v=' form in order to ensure complex clauses are properly handled.
// Per the docs, the 'which=' clause should be used to identify all metaalert parents, not to
// filter
// Status is a filter on parents and must be done outside the '!parent' construct
String parentChildQuery =
"(+" + activeStatusClause + " +" + "{!parent which=" + metaalertTypeClause + " v='"
+ searchRequest.getQuery() + "'})";
// Put everything together to get our full query
// The '-metaalert:[* TO *]' construct is to ensure the field doesn't exist on or is empty for
// plain alerts.
// Also make sure that it's not a metaalert
String fullQuery =
"(" + searchRequest.getQuery() + " AND -" + MetaAlertConstants.METAALERT_FIELD + ":[* TO *]"
+ " AND " + "-" + metaalertTypeClause + ")" + " OR " + parentChildQuery;
LOG.debug("MetaAlert search query {}", fullQuery);
searchRequest.setQuery(fullQuery);
// Build the custom field list
List<String> fields = searchRequest.getFields();
String fieldList = "*";
if (fields != null) {
fieldList = StringUtils.join(fields, ",");
}
LOG.debug("MetaAlert Search Field list {}", fullQuery);
SearchResponse results = solrSearchDao.search(searchRequest, fieldList);
LOG.debug("MetaAlert Search Number of results {}", results.getResults().size());
// Unfortunately, we can't get the full metaalert results at the same time
// Get them in a second query.
// However, we can only retrieve them if we have the source type field (either explicit or
// wildcard).
if (fieldList.contains("*") || fieldList.contains(config.getSourceTypeField())) {
List<String> metaalertGuids = new ArrayList<>();
for (SearchResult result : results.getResults()) {
if (result.getSource().get(config.getSourceTypeField())
.equals(MetaAlertConstants.METAALERT_TYPE)) {
// Then we need to add it to the list to retrieve child alerts in a second query.
metaalertGuids.add(result.getId());
}
}
LOG.debug("MetaAlert Search guids requiring retrieval: {}", metaalertGuids);
// If we have any metaalerts in our result, attach the full data.
if (metaalertGuids.size() > 0) {
Map<String, String> params = new HashMap<>();
params.put("fl", fieldList + ",[child parentFilter=" + metaalertTypeClause + " limit=999]");
SolrParams solrParams = new MapSolrParams(params);
try {
SolrDocumentList solrDocumentList = solrClient
.getById(METAALERTS_COLLECTION, metaalertGuids, solrParams);
Map<String, Document> guidToDocuments = new HashMap<>();
for (SolrDocument doc : solrDocumentList) {
Document document = SolrUtilities.toDocument(doc);
guidToDocuments.put(document.getGuid(), document);
}
// Run through our results and update them with the full metaalert
for (SearchResult result : results.getResults()) {
Document fullDoc = guidToDocuments.get(result.getId());
if (fullDoc != null) {
result.setSource(fullDoc.getDocument());
}
}
} catch (SolrServerException | IOException e) {
throw new InvalidSearchException("Error when retrieving child alerts for metaalerts", e);
}
}
}
return results;
}
@Override
public GroupResponse group(GroupRequest groupRequest) throws InvalidSearchException {
// Make sure to escape any problematic characters here
String sourceType = ClientUtils.escapeQueryChars(config.getSourceTypeField());
String baseQuery = groupRequest.getQuery();
String adjustedQuery = baseQuery + " -" + MetaAlertConstants.METAALERT_FIELD + ":[* TO *]"
+ " -" + sourceType + ":" + MetaAlertConstants.METAALERT_TYPE;
LOG.debug("MetaAlert group adjusted query: {}", adjustedQuery);
groupRequest.setQuery(adjustedQuery);
return solrSearchDao.group(groupRequest);
}
}