Summary Table

Categories Total Count
PII 0
URL 0
DNS 0
EKL 0
IP 0
PORT 0
VsID 0
CF 0
AI 0
VPD 0
PL 0
Other 0

File Content

package gov.va.med.pbm.ampl.utility.operations;

import java.util.ArrayList;
import java.util.Collection;
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.regex.Matcher;
import java.util.regex.Pattern;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import gov.va.med.pbm.ampl.constant.AmplConstants;
import gov.va.med.pbm.ampl.utility.ESAPIValidator;
import gov.va.med.pbm.ampl.utility.PropertyReflection;
import gov.va.med.pbm.ampl.utility.operations.criteria.SearchCriteria;

/**
* The SearchOperation class allows the API to perform a search on a given collection of objects, returning a filtered result
* set back to the client.
*
* @author Ian Meinert
*
* @param <T> The Generic type representing the class of the object which the search will be performed.
*/
public class SearchOperation<T> extends PropertyReflection {

/**
* The application logger.
*/
static final Logger LOGGER = LoggerFactory.getLogger(SearchOperation.class);

/**
* The collection of search criteria. The setter requires a string input, which is then parsed and processed.
*/
private final Map<String, List<SearchCriteria>> searchParams;

/**
* Overloaded constructor requiring the class of the object being filtered.
*
*/
public SearchOperation() {
this.searchParams = new HashMap<String, List<SearchCriteria>>();

// The map needs the OR added to the bucket first
searchParams.put(AmplConstants.OR_OPERATOR, new ArrayList<SearchCriteria>());
searchParams.put(AmplConstants.AND_OPERATOR, new ArrayList<SearchCriteria>());
}

/**
* The search method returns a filtered results, given a pre-defined SearchCriteria.
*
* @param collection Collection of Generics type T
* @return a filtered Collection of Generics type T
*/
public Collection<T> search(Collection<T> collection) {
LOGGER.info("Applying the search criteria");
Collection<T> results = new ArrayList<T>();

// If search parameters haven't been provided, apply the filter, otherwise just return the collection
results = this.hasFilterParameters() ? this.filterCollection(collection) : collection;

return results;
}

/**
* The getter for the search parameters.
*
* @return List of SearchCriteria
*/
public Map<String, List<SearchCriteria>> getSearchParams() {
return searchParams;
}

/**
* Sets the collection of search parameters.
* <p>
* The setter requires a string input, which is then parsed and processed using a pattern match
*
* @param search The search criteria which must match a predetermined pattern
*/
public void setSearchParams(String search) {
LOGGER.info("Parsing the search values: {}", ESAPIValidator.validateLogParam(search));

// ([\w.]+?) is GROUP 1, the property to search against
// (>:|<:|:|<|>|!|~) is GROUP 2, the search operand
// ((?!:)[\w-:\s".+]+) is GROUP 3, which contains the words and phrases to search for
// the ',' segregates the query params from one another
Pattern pattern = Pattern.compile("([\\w.]+?)(>:|<:|:|<|>|!|~)((?!:)[\\w-:\\s\".+]+),");
Matcher matcher = pattern.matcher(search + ",");

// if the search string is in the proper format, the matcher will find something
while (matcher.find()) {
String groupOne = matcher.group(AmplConstants.NUMBER_ONE);
String groupTwo = matcher.group(AmplConstants.NUMBER_TWO);
String groupThree = matcher.group(AmplConstants.NUMBER_THREE);

String key = new String();

// if group 3 has the pattern (" ") it is an OR principle
if (groupThree.contains(AmplConstants.OR_FILTER_DELIMITER)) {
// this is the OR principle
key = AmplConstants.OR_OPERATOR;
} else {
// this is the AND principle
key = AmplConstants.AND_OPERATOR;
}

this.doAddParameterKeyValuePair(key, new String[] { groupOne, groupTwo, groupThree });
}

LOGGER.info("Final search criteria: {}", ESAPIValidator.validateLogParam(searchParams.toString()));
}

/**
* This method makes a determination if the searchParams HashMap buckets are not empty.
*
* @return boolean
*/
private boolean hasFilterParameters() {
return !this.searchParams.get(AmplConstants.AND_OPERATOR).isEmpty()
|| !this.searchParams.get(AmplConstants.OR_OPERATOR).isEmpty();
}

/**
* The filterCollection method performs a filter against a given collection using a bean of the same type.
*
* @param origCollection The collection of Type T to filter
* @return Collection of Generics type T
*/
private Collection<T> filterCollection(Collection<T> origCollection) {
LOGGER.info("Filtering the collection");

Collection<T> filteredCollection = new ArrayList<>();

Iterator<Entry<String, List<SearchCriteria>>> it = searchParams.entrySet().iterator();

int i = 0;

while (it.hasNext()) {
Map.Entry<String, List<SearchCriteria>> pair = (Map.Entry<String, List<SearchCriteria>>) it.next();

switch (pair.getKey()) {
case AmplConstants.OR_OPERATOR:
for (SearchCriteria criteria : pair.getValue()) {
FilterSearch<T> filter = new FilterSearch<>(origCollection);
filter.setSearchCriteria(criteria);
filter.doApplyFilter();

filteredCollection.addAll(filter.getCollection());
}

break;
case AmplConstants.AND_OPERATOR:
for (SearchCriteria criteria : pair.getValue()) {
boolean firstIteration = filteredCollection.isEmpty() && i == 0;
Collection<T> temp = firstIteration ? origCollection : filteredCollection;

FilterSearch<T> filter = new FilterSearch<>(temp);
filter.setSearchCriteria(criteria);
filter.doApplyFilter();

filteredCollection = filter.getCollection();

i++;
}

break;
default:
}
}

return filteredCollection;
}

/**
* This method adds the parameters to the searchParams property.
*
* @param key the key to update
* @param groups String[] of matched groups
*/
private void doAddParameterKeyValuePair(String key, String[] groups) {

// find phrases using pattern ("[^"]+"|[\w]+)!? to search for the phrase
// find words using pattern ([\w-:.]+) to match each word not in quotes
Pattern phrasePattern = Pattern.compile("([\\w-:.]+)|(\"[^\"]+\"|[\\w]+)!?");

Matcher phraseMatcher = phrasePattern.matcher(groups[2]);

while (phraseMatcher.find()) {
Optional<String> phraseGroupOne = Optional.ofNullable(phraseMatcher.group(AmplConstants.NUMBER_ONE));
Optional<String> phraseGroupTwo = Optional.ofNullable(phraseMatcher.group(AmplConstants.NUMBER_TWO));

if (phraseGroupOne.isPresent() || phraseGroupTwo.isPresent()) {
// one of the two available groups is present. Store the value of the one that is
String phrase = phraseGroupOne.isPresent() ? phraseGroupOne.get() : phraseGroupTwo.get();

// Remove quotes if exist and add the found phrase to the queue
SearchCriteria criteria = new SearchCriteria(groups[0], groups[1], phrase.replaceAll("\"", new String()));
LOGGER.info("Adding the search criteria: {}", ESAPIValidator.validateLogParam(criteria.toString()));

searchParams.get(key).add(criteria);
}
}
}
}