/**
* Licensed to The Apereo Foundation under one or more contributor license
* agreements. See the NOTICE file distributed with this work for additional
* information regarding copyright ownership.
*
*
* The Apereo Foundation licenses this file to you under the Educational
* Community 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://opensource.org/licenses/ecl2.txt
*
* 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.opencastproject.matterhorn.search.impl;
import static java.util.Objects.requireNonNull;
import static org.opencastproject.matterhorn.search.SearchTerms.Quantifier.Any;
import org.opencastproject.matterhorn.search.SearchQuery;
import org.opencastproject.matterhorn.search.SearchTerms;
import org.opencastproject.matterhorn.search.SearchTerms.Quantifier;
import org.apache.commons.lang3.StringUtils;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Stack;
/**
* Base implementation for search queries.
*/
public class AbstractSearchQuery implements SearchQuery {
/** The document types */
protected List<String> types = new ArrayList<String>();
/** The list of fields to return */
protected List<String> fields = null;
/** Query configuration stack */
protected Stack<Object> stack = new Stack<Object>();
/** The object that needs to show up next */
protected Class<?> expectation = null;
/** True if the search text should be matched using wildcards */
protected boolean fuzzySearch = true;
/** Query terms */
protected List<SearchTerms<String>> text = null;
/** Filter terms */
protected String filter = null;
/** The query offset */
protected int offset = -1;
/** The query limit */
protected int limit = -1;
/** The map with the sort orders */
private final Map<String, Order> sortOrders = new LinkedHashMap<String, Order>();
/** The last method called */
protected String lastMethod = null;
/**
* Creates a search query that is executed on all document types.
*/
public AbstractSearchQuery() {
// Nothing to be done atm.
}
/**
* Creates a search query that is executed on the given document type.
*
* @param documentType
* the document type
*/
public AbstractSearchQuery(String documentType) {
this();
if (StringUtils.isNotBlank(documentType))
this.types.add(documentType);
}
/**
* {@inheritDoc}
*/
@Override
public SearchQuery withTypes(String... types) {
for (String type : types) {
this.types.add(type);
}
return this;
}
/**
* {@inheritDoc}
*
* @see org.opencastproject.matterhorn.search.SearchQuery#getTypes()
*/
@Override
public String[] getTypes() {
return types.toArray(new String[types.size()]);
}
/**
* @see org.opencastproject.matterhorn.search.SearchQuery#withField(String)
*/
@Override
public AbstractSearchQuery withField(String field) {
if (fields == null)
fields = new ArrayList<String>();
fields.add(field);
return this;
}
/**
* @see org.opencastproject.matterhorn.search.SearchQuery#withFields(String...)
*/
@Override
public AbstractSearchQuery withFields(String... fields) {
for (String field : fields) {
withField(field);
}
return this;
}
/**
* @see org.opencastproject.matterhorn.search.SearchQuery#getFields()
*/
@Override
public String[] getFields() {
if (fields == null)
return new String[] {};
return fields.toArray(new String[fields.size()]);
}
/**
* @see org.opencastproject.matterhorn.search.SearchQuery#withLimit(int)
*/
@Override
public SearchQuery withLimit(int limit) {
this.limit = limit;
return this;
}
/**
* @see org.opencastproject.matterhorn.search.SearchQuery#getLimit()
*/
@Override
public int getLimit() {
return limit;
}
/**
* @see org.opencastproject.matterhorn.search.SearchQuery#withOffset(int)
*/
@Override
public SearchQuery withOffset(int offset) {
this.offset = Math.max(0, offset);
return this;
}
/**
* @see org.opencastproject.matterhorn.search.SearchQuery#getOffset()
*/
@Override
public int getOffset() {
return offset;
}
/**
* @see org.opencastproject.matterhorn.search.SearchQuery#withText(String)
*/
@Override
public SearchQuery withText(String text) {
return withText(false, Any, text);
}
/**
* @see org.opencastproject.matterhorn.search.SearchQuery#withText(boolean, String)
*/
@Override
public SearchQuery withText(boolean wildcardSearch, String text) {
return withText(wildcardSearch, Any, text);
}
/**
* @see org.opencastproject.matterhorn.search.SearchQuery#withText(boolean, Quantifier, String...)
*/
@Override
public SearchQuery withText(boolean wildcardSearch, Quantifier quantifier, String... text) {
if (quantifier == null)
throw new IllegalArgumentException("Quantifier must not be null");
if (text == null)
throw new IllegalArgumentException("Text must not be null");
// Make sure the collection is initialized
if (this.text == null)
this.text = new ArrayList<SearchTerms<String>>();
// Add the text to the search terms
clearExpectations();
this.fuzzySearch = wildcardSearch;
with(this.text, quantifier, text);
return this;
}
/**
* @see org.opencastproject.matterhorn.search.SearchQuery#getTerms()
*/
@Override
public Collection<SearchTerms<String>> getTerms() {
if (text == null)
return Collections.emptyList();
return text;
}
/**
* @see org.opencastproject.matterhorn.search.SearchQuery#getQueryString()
*/
@Override
public String getQueryString() {
if (text == null)
return null;
StringBuffer query = new StringBuffer();
for (SearchTerms<String> s : text) {
for (String t : s.getTerms()) {
if (query.length() == 0)
query.append(" ");
query.append(t);
}
}
return query.toString();
}
/**
* @see org.opencastproject.matterhorn.search.SearchQuery#isFuzzySearch()
*/
@Override
public boolean isFuzzySearch() {
return fuzzySearch;
}
/**
* @see org.opencastproject.matterhorn.search.SearchQuery#withFilter(String)
*/
@Override
public SearchQuery withFilter(String filter) {
clearExpectations();
this.filter = filter;
return this;
}
/**
* @see org.opencastproject.matterhorn.search.SearchQuery#getFilter()
*/
@Override
public String getFilter() {
return filter;
}
@Override
public SearchQuery withSortOrder(String field, Order order) {
sortOrders.put(requireNonNull(field), requireNonNull(order));
return this;
}
@Override
public Map<String, Order> getSortOrders() {
return Collections.unmodifiableMap(sortOrders);
}
@Override
public Order getSortOrder(String field) {
if (!sortOrders.containsKey(field))
return Order.None;
return sortOrders.get(field);
}
/**
* Pushes the configuration object onto the stack.
*
* @param object
* the object
*/
protected void configure(Object object) {
stack.push(object);
}
/**
* Sets the expectation to <code>c</code>, making sure that the next configuration object will either match
* <code>c</code> in terms of class of throw an <code>IllegalStateException</code> if it doesn't.
*
* @param c
* the class type
*/
protected void expect(Class<?> c) {
lastMethod = Thread.currentThread().getStackTrace()[2].getMethodName();
this.expectation = c;
}
/**
* This method is called if nothing should be expected by anyone. If this is not the case (e. g. some unfinished query
* configuration is still in place) we throw an <code>IllegalStateException</code>.
*
* @throws IllegalStateException
* if some object is expected
*/
protected void clearExpectations() throws IllegalStateException {
if (expectation != null)
throw new IllegalStateException("Query configuration expects " + expectation.getClass().getName());
stack.clear();
}
/**
* This method is called if a certain type of object is expected by someone. If this is not the case (e. g. query
* configuration is in good shape, then someone tries to "finish" a configuration part) we throw an
* <code>IllegalStateException</code>.
*
* @throws IllegalStateException
* if no or a different object is expected
*/
protected void ensureExpectation(Class<?> c) throws IllegalStateException {
if (expectation == null)
throw new IllegalStateException("Malformed query configuration. No " + c.getClass().getName()
+ " is expected at this time");
if (!expectation.getCanonicalName().equals(c.getCanonicalName()))
throw new IllegalStateException("Malformed query configuration. Something of type " + c.getClass().getName()
+ " is expected at this time");
expectation = null;
}
/**
* Make sure that an object of type <code>c</code> is on the stack, throw an <code>IllegalStateException</code>
* otherwise.
*
* @throws IllegalStateException
* if no object of type <code>c</code> was found on the stack
*/
protected void ensureConfigurationObject(Class<?> c) throws IllegalStateException {
for (Object o : stack) {
if (c.isAssignableFrom(o.getClass()))
return;
}
throw new IllegalStateException("Malformed query configuration. No " + c.getClass().getName()
+ " is expected at this time");
}
/**
* Make sure that an array of type <code>c</code> is on the stack, throw an <code>IllegalStateException</code>
* otherwise.
*
* @throws IllegalStateException
* if no array of type <code>c</code> was found on the stack
*/
protected void ensureConfigurationArray(Class<?> c) throws IllegalStateException {
for (Object o : stack) {
if (o.getClass().isArray() && c.isAssignableFrom(o.getClass().getComponentType()))
return;
}
throw new IllegalStateException("Malformed query configuration. No " + c.getClass().getName()
+ " is expected at this time");
}
/**
* Utility method to add the given values to the list of search terms using the specified quantifier.
*
* @param searchTerms
* the terms
* @param quantifier
* the quantifier
* @param values
* the values
* @return the extended search terms
*/
protected <T extends Object> SearchTerms<T> with(List<SearchTerms<T>> searchTerms, Quantifier quantifier, T... values) {
SearchTerms<T> terms = null;
// Handle any quantifier
if (values.length == 1 || Any.equals(quantifier)) {
// Check if there is a default terms collection
for (SearchTerms<T> t : searchTerms) {
if (Quantifier.Any.equals(t.getQuantifier())) {
terms = t;
break;
}
}
// Has there been a default terms collection?
if (terms == null) {
terms = new SearchTermsImpl<T>(Quantifier.Any, values);
searchTerms.add(terms);
}
// Add the text
for (T v : values) {
terms.add(v);
}
}
// All quantifier
else {
terms = new SearchTermsImpl<T>(quantifier, values);
searchTerms.add(terms);
}
return terms;
}
}