public class AnnotationSearchConstants {

    public static final String FIELD_MOTIVATIONS = "motivations";

    public static final String PARAM_FIELD_MOTIVATION = "motivation";

    public static final String PARAM_FIELD_DATE = "date";
    public static final String PARAM_FIELD_USER = "user";

    public static final String PARAM_FIELD_QUERY = "q";

    public static final String PARAM_FIELD_PAGE = "page";

    public static final String PARAM_FIELD_WIDTH = "width";
    public static final String PARAM_FIELD_HEIGHT = "height";

    public static final int DEFAULT_PAGING_NUMBER = 100;

    public static final int MAX_NUMBER_OF_HITS_RETURNED = 1000;

    public static final String PAINTING_MOTIVATION = "painting";

    public static final String NON_PAINTING_MOTIVATION = "non-painting";

    public static final String EMPTY_QUERY_MESSAGE = "Please enter either a query, motivation, date or user to search";

    public static final String EMPTY_AUTOCOMPLETE_QUERY_MESSAGE = "Please enter a query to search";

    public static final String TEXT_INDEX_NAME = "text_index";

    public static final String W3C_INDEX_NAME = "w3cannotation";

    public static final String CONTEXT_MANIFEST_NAME = "manifest";

    public static final boolean USE_PARED_DOWN_OA = true;

    private AnnotationSearchConstants() {

    }
} @SuppressWarnings("serial")
public class SearchException extends RuntimeException {

    public SearchException(String msg) {
	super(msg);
    }
} @SuppressWarnings("serial")
public class SearchQueryException extends RuntimeException {

    public SearchQueryException(String msg) {
	super(msg);
    }
} public class Parameters implements Serializable{

    /**
     *
     */
    private static final long serialVersionUID = 1118550633018738922L;
    private String query;
    private String motivation;
    private String date;
    private String user;

    public Parameters(){

    }
    public Parameters(String query, String motivation, String date, String user){
	this.query = query;
	this.motivation = motivation;
	this.date = date;
	this.user = user;
    }

    public String getQuery() {
	return query;
    }
    public void setQuery(String query) {
	this.query = query;
    }
    public String getMotivation() {
	return motivation;
    }
    public void setMotivation(String motivation) {
	this.motivation = motivation;
    }
    public String getDate() {
	return date;
    }
    public void setDate(String date) {
	this.date = date;
    }
    public String getUser() {
	return user;
    }
    public void setUser(String user) {
	this.user = user;
    }

    @Override
    public boolean equals(Object o) {

        if (o == this) return true;
        if (!(o instanceof Parameters)) {
            return false;
        }

        Parameters parameters = (Parameters) o;

        return Objects.equals(parameters.query, query) &&
        	Objects.equals(parameters.motivation, motivation) &&
        	Objects.equals(parameters.date,date) &&
        	Objects.equals(parameters.user,user);
    }

    @Override
    public int hashCode() {
        int result = 17;
        result = 31 * result + query.hashCode();
        result = 31 * result + motivation.hashCode();
        result = 31 * result + date.hashCode();
        result = 31 * result + user.hashCode();
        return result;
    }
} public class ServiceResponse<T> implements Serializable{

    /**
     *
     */
    private static final long serialVersionUID = 8231671973667011469L;

    public enum Status {
        OK, NOT_FOUND, CACHE_KEY_MISS, ILLEGAL_MODIFICATION, NON_CONFORMANT
    }

    private final Status status;
    private final transient T obj;

    public ServiceResponse(Status status, T obj) {
        this.status = status;
        this.obj = obj;
    }

    public Status getStatus() {
        return status;
    }

    public T getObj() {
        return obj;
    }
} public enum Status {
    OK, NOT_FOUND, CACHE_KEY_MISS, ILLEGAL_MODIFICATION, NON_CONFORMANT
} public class PageParameters {

    private int total;

    private String firstPageNumber;
    private String lastPageNumber;
    private String nextPageNumber;
    private int nextPage = 0;
    private int lastPage = 0;
    private String previousPageNumber;
    private String startIndexString;

    private int startIndex;

    public int getTotal() {
	return total;
    }
    public void setTotal(int total) {
	this.total = total;
    }

    public String getFirstPageNumber() {
	return firstPageNumber;
    }
    public void setFirstPageNumber(String firstPageNumber) {
	this.firstPageNumber = firstPageNumber;
    }
    public String getLastPageNumber() {
	return lastPageNumber;
    }
    public void setLastPageNumber(String lastPageNumber) {
	this.lastPageNumber = lastPageNumber;
    }
    public String getNextPageNumber() {
	return nextPageNumber;
    }
    public void setNextPageNumber(String nextPageNumber) {
	this.nextPageNumber = nextPageNumber;
    }
    public String getPreviousPageNumber() {
	return previousPageNumber;
    }
    public void setPreviousPageNumber(String previousPageNumber) {
	this.previousPageNumber = previousPageNumber;
    }
    public int getStartIndex() {
	return startIndex;
    }
    public void setStartIndex(int startIndex) {
	this.startIndex = startIndex;
    }
    public int getNextPage() {
	return nextPage;
    }
    public void setNextPage(int nextPage) {
	this.nextPage = nextPage;
    }
    public int getLastPage() {
	return lastPage;
    }
    public void setLastPage(int lastPage) {
	this.lastPage = lastPage;
    }
    public String getStartIndexString() {
	return startIndexString;
    }
    public void setStartIndexString(String startIndexString) {
	this.startIndexString = startIndexString;
    }


    @Override
    public String toString(){
	return "total:" +this.getTotal() + " startIndex:" +this.getStartIndex() + " nextPage:" + this.getNextPage() ;
    }

} public interface AnnotationAutocompleteService {


    public AnnotationUtils getAnnotationUtils();

    public ServiceResponse<Map<String, Object>> getTerms(String query, String motivation, String date, String user, String min, String queryString, boolean isW3c, String within);

    public ServiceResponse<Map<String, Object>> getTerms(Parameters parameters, String min, String queryString, boolean isW3c, String within);

    public ServiceResponse<Map<String, Object>> getTerms(String query, String min, String queryString, boolean isW3c, String within);

    public ServiceResponse<Map<String, Object>> getMixedTerms(String query, String min, String queryString, boolean isW3c, String within);

} public interface OAAnnotationSearchService {


   public ServiceResponse<Map<String, Object>> getAnnotationPage(Parameters parameters, String queryString, String page, String within, String type);


} @Service(AnnotationUtils.SERVICE_NAME)
public class AnnotationUtils extends CommonUtils {

    private static final Logger LOG = Logger.getLogger(AnnotationUtils.class);

    public static final String SERVICE_NAME = "AnnotationUtils";

    private static final String[] SCHEMES = new String[] { "http", "https", "ftp", "mailto", "file", "data" };

    public Map<String, Object> createAnnotationPage(String query, List<W3CAnnotation> annoList, boolean isW3c,
	    PageParameters pageParams, int totalHits, boolean isMixedSearch) {

	if (null == annoList || annoList.isEmpty()) {
	    return null;
	}

	Map<String, Object> root;

	if (isMixedSearch) {
	    root = new LinkedHashMap<>();
	    this.setResources(root, isW3c);
	} else {
	    if (AnnotationSearchServiceImpl.DEFAULT_PAGING_NUMBER <= totalHits) {
		root = this.buildAnnotationPageHead(query, isW3c, pageParams, false);
	    } else {
		root = this.buildAnnotationListHead(query, isW3c, false);
	    }
	}
	// this.setHits(root);

	List resources = this.getResources(root, isW3c);
	// List<Map<String, Object>> hitList = this.getHits(root);
	// Map<String, Object> hitMap = new LinkedHashMap<>();
	// List <String>annotationsList;
	// String[] beforeAfter = new String[]{"",""};
	// forEach result in the search get the annotation from the database and
	// populate resource element.
	for (W3CAnnotation w3CAnnotation : annoList) {
	    // annotationsList = new ArrayList<>(1);
	    // annotationsList.add(w3CAnnotation.getId());
	    // setHits(isW3c, hitMap, annotationsList,query, beforeAfter);
	    resources.add(w3CAnnotation.getJsonMap());
	}

	// LOG.info("Hit:" +hitMap.toString());
	// if(!hitMap.isEmpty()){
	// hitList.add(hitMap);
	// }

	return root;

    }

    /**
     * Method to create the terms urls will replace
     *
     * @param query
     * @param optionText
     * @return
     */
    private String getSearchQueryFromAutocompleteQuery(String query, String optionText) {

	String searchQuery = query;
	searchQuery = searchQuery.replace("autocomplete", "search");

	String tidyQuery = removeParametersAutocompleteQuery(searchQuery, AUTOCOMPLETE_IGNORE_PARAMETERS);
	String encodedOptionText = optionText;
	try {
	    encodedOptionText = URLEncoder.encode(optionText, "UTF-8");
	} catch (UnsupportedEncodingException e) {

	    LOG.error(String.format("Autocomplete: unable to encode [%s]", optionText), e);
	}

	return tidyQuery.replaceAll("q=[^&]+", "q=" + encodedOptionText);
    }

    public Map<String, Object> createAutocompleteList(List<SuggestOption> options, boolean isW3c, String queryString,
	    String motivation, String date, String user) {

	if (null == options || options.isEmpty()) {
	    return null;
	}
	try {

	    Map<String, Object> root = this.buildAutoCompleteHead(queryString, motivation, date, user);
	    List resources = (List) root.get(OA_TERMSLIST);

	    // forEach result in the search get the annotation from the database
	    // and populate resource element.
	    for (SuggestOption option : options) {
		Map<String, Object> optionRoot = new LinkedTreeMap<>();
		String optionText = option.getText();
		optionRoot.put("match", optionText);

		optionRoot.put("url", getSearchQueryFromAutocompleteQuery(queryString, optionText));

		if (!resources.contains(optionRoot)) {
		    resources.add(optionRoot);
		}
	    }

	    LOG.debug("resources are " + JsonUtils.toString(root));
	    return root;
	} catch (IOException ioe) {
	    LOG.error("Error getting default file " + ioe);
	    return null;
	}
    }

    public List<W3CAnnotation> getW3CAnnotations(String[] annotationArray) {
	if (null != annotationArray && annotationArray.length > 0) {
	    List<W3CAnnotation> annoList = new ArrayList<>();
	    for (String anno : annotationArray) {
		W3CAnnotation annotation = new W3CAnnotation();
		if (null != anno) {
		    Map<String, Object> javaRootMapObject = new Gson().fromJson(anno, Map.class);
		    annotation.setJsonMap(javaRootMapObject);
		    annoList.add(annotation);
		}
	    }
	    return annoList;
	}
	return new ArrayList<>();
    }

    public String convertSpecialCharacters(String input) {

	String returnInput = input;
	if (null != input && input.contains(":")) {

	    List<String> inputList = getListFromSpaceSeparatedTerms(input);
	    for (String replacement : inputList) {
		if (replacement.contains(":")) {
		    String start = replacement.toLowerCase().substring(0, replacement.indexOf(":"));

		    String tidyQuery;
		    if (ArrayUtils.contains(SCHEMES, start)) {
			tidyQuery = "(\"" + replacement + "\")";
		    } else {
			tidyQuery = replacement.replaceAll(":", "\\\\:");
		    }
		    returnInput = returnInput.replace(replacement, tidyQuery);
		}
	    }
	} else {
	    return input;
	}

	return returnInput;
    }

    public int[] getPageParams(Map<String, Object> root, boolean isW3c) {
	Map map;
	int total;

	List resources = getResources(root, isW3c);

	if (null != resources) {
	    LOG.info("resourcesSize in getPageParams" + resources.size());
	}

	Integer startIndex;
	int[] pageParams = new int[2];
	if (isW3c) {
	    map = (LinkedHashMap) root.get(W3C_WITHIN_IS_PART_OF);
	    total = (Integer) map.get(W3C_WITHIN_AS_TOTALITEMS);
	} else {
	    map = (LinkedHashMap) root.get(OA_WITHIN);
	    total = (Integer) map.get(OA_WITHIN_TOTAL);
	}
	pageParams[0] = total;

	if (isW3c) {
	    startIndex = (Integer) root.get(W3C_STARTINDEX);
	} else {
	    startIndex = (Integer) root.get(OA_STARTINDEX);
	}
	pageParams[1] = startIndex;

	return pageParams;
    }

}
import java.util.Map;
import javax.servlet.http.HttpServletRequest;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.CrossOrigin;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;
import com.digirati.themathmos.AnnotationSearchConstants;
import com.digirati.themathmos.exception.SearchException;
import com.digirati.themathmos.exception.SearchQueryException;
import com.digirati.themathmos.model.Parameters;
import com.digirati.themathmos.model.ServiceResponse;
import com.digirati.themathmos.model.ServiceResponse.Status;
import com.digirati.themathmos.model.annotation.page.PageParameters;
import com.digirati.themathmos.service.AnnotationAutocompleteService;
import com.digirati.themathmos.service.OAAnnotationSearchService;
import com.digirati.themathmos.service.impl.AnnotationUtils;
package com.digirati.themathmos.web.controller;





@RestController(OAAnnotationSearchController.CONTROLLER_NAME)
public class OAAnnotationSearchController {

	public static final String CONTROLLER_NAME = "oaAnnotationSearchController";

	private OAAnnotationSearchService oaAnnotationSearchService;
	private AnnotationAutocompleteService annotationAutocompleteService;
	private ControllerUtility controllerUtility;
	private AnnotationUtils annotationUtils;

	@Autowired
	public OAAnnotationSearchController(OAAnnotationSearchService oaAnnotationSearchService,
			AnnotationAutocompleteService annotationAutocompleteService) {
		this.oaAnnotationSearchService = oaAnnotationSearchService;
		this.annotationAutocompleteService = annotationAutocompleteService;
		this.controllerUtility = new ControllerUtility();
		this.annotationUtils = this.annotationAutocompleteService.getAnnotationUtils();
	}

	// autocomplete parameter defaults to 1 if not specified
	public static final String PARAM_MIN = "min";

	private static final String WITHIN_OA_SEARCH_REQUEST_PATH = "/{withinId}/oa/search";

	private static final String WITHIN_OA_AUTOCOMPLETE_REQUEST_PATH = "/{withinId}/oa/autocomplete";

	private static final String OA_SEARCH_REQUEST_PATH = "/oa/search";

	private static final String OA_AUTOCOMPLETE_REQUEST_PATH = "/oa/autocomplete";

	@CrossOrigin
	@RequestMapping(value = OA_SEARCH_REQUEST_PATH, method = RequestMethod.GET)
	public ResponseEntity<Map<String, Object>> searchOAGet(
			@RequestParam(value = AnnotationSearchConstants.PARAM_FIELD_QUERY, required = false) String query,
			@RequestParam(value = AnnotationSearchConstants.PARAM_FIELD_MOTIVATION, required = false) String motivation,
			@RequestParam(value = AnnotationSearchConstants.PARAM_FIELD_DATE, required = false) String date,
			@RequestParam(value = AnnotationSearchConstants.PARAM_FIELD_USER, required = false) String user,
			@RequestParam(value = AnnotationSearchConstants.PARAM_FIELD_PAGE, required = false) String page,
			HttpServletRequest request) {

		String queryString = controllerUtility.createQueryString(request);
		String type = null;

		if (!controllerUtility.validateParameters(query, motivation, date, user)) {
			throw new SearchQueryException(AnnotationSearchConstants.EMPTY_QUERY_MESSAGE);
		}

		ServiceResponse<Map<String, Object>> serviceResponse = oaAnnotationSearchService
				.getAnnotationPage(new Parameters(query, motivation, date, user), queryString, page, null, type);