/**
* Search.java
* Author: Philip Kahle (philip.kahle@uibk.ac.at)
*
* This file is part of PrestoPRIME Preservation Platform (P4).
*
* Copyright (C) 2009-2012 University of Innsbruck, Austria
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package eu.prestoprime.ws;
import java.io.StringWriter;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.QueryParam;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.Response.ResponseBuilder;
import javax.xml.bind.JAXBException;
import javax.xml.bind.Marshaller;
import org.apache.log4j.Logger;
import com.sun.jersey.api.client.ClientResponse.Status;
import com.sun.jersey.api.core.HttpContext;
import com.sun.jersey.api.core.ResourceConfig;
import eu.prestoprime.datamanagement.P4DataManager;
import eu.prestoprime.model.ModelUtils;
import eu.prestoprime.model.ModelUtils.P4JAXBPackage;
import eu.prestoprime.model.oaipmh.OAIPMH;
import eu.prestoprime.model.search.SearchResults;
import eu.prestoprime.search.OAIPMHManager;
import eu.prestoprime.search.SearchConstants;
import eu.prestoprime.search.Searcher;
import eu.prestoprime.search.util.Schema;
import eu.prestoprime.search.util.Schema.P4FacetField;
import eu.prestoprime.search.util.Schema.P4SortField;
import eu.prestoprime.search.util.Schema.searchField;
import eu.prestoprime.search.util.SolrQueryUtils;
import eu.prestoprime.search.util.suggestion.P4Suggestions;
@Path("/search")
public class Search {
// @Context SecurityContext security;
@Context
HttpContext context;
@Context
ResourceConfig config;
private final static Logger LOGGER = Logger.getLogger(Search.class);
// private URI wsBaseUri; // URI of application server
private Searcher searcher;
private void init() {
try {
searcher = new Searcher();
// wsBaseUri = context.getRequest().getBaseUri();
} catch (Exception e) {
LOGGER.debug("Searcher could not be created!\n" + e.getMessage());
e.printStackTrace();
}
}
@GET
@Produces(MediaType.TEXT_PLAIN)
@Path("/checkCredentials")
public String checkCredentials() {
LOGGER.debug("Called checkCredentials --> User authenticated");
return "authenticated";
}
@GET
@Produces(MediaType.TEXT_PLAIN)
@Path("/")
public Response searchAIPByDC(@QueryParam("title") String title, @QueryParam("description") String description, @QueryParam("format") String format, @QueryParam("identifier") String identifier) {
ResponseBuilder rb;
try {
Map<String, String> elements = new HashMap<String, String>();
elements.put("title", title);
elements.put("description", description);
elements.put("format", format);
elements.put("identifier", identifier);
List<String> aipList = P4DataManager.getInstance().getAllAIP(elements);
StringBuffer sb = new StringBuffer();
for (String aipId : aipList)
sb.append(aipId + "\n");
rb = Response.status(Status.OK).entity(sb.toString());
} catch (Exception e) {
rb = Response.status(Status.INTERNAL_SERVER_ERROR);
}
return rb.build();
}
@GET
@Produces(MediaType.TEXT_PLAIN)
@Path("/identifier/{id}")
public Response searchAIPByDC(@PathParam("id") String identifier) {
ResponseBuilder rb;
try {
String aipId = P4DataManager.getInstance().getAIPByDCID(identifier);
StringBuffer sb = new StringBuffer();
if (aipId != null)
sb.append(aipId + "\n");
rb = Response.status(Status.OK).entity(sb.toString());
} catch (Exception e) {
rb = Response.status(Status.INTERNAL_SERVER_ERROR);
}
return rb.build();
}
/**
* Do a search on all fields, i.e. the ones that are stored to the
* catchall-field in Solr's schema. At the moment: dcTitle & dcCreator
*
* @param queryString
* the user input to search for
* @param from
* the first result to return depending on the order of the
* results. 0 gets results starting with the first. For the
* second page this number has to be increased by #resultCount.
* @param resultCount
* the number of results to return.
* @param sortAsc
* if true, then results are sorted ascending
* @param sortFieldName
* the field to sort on. see
* {@link eu.prestoprime.search.util.Schema.java} for allowed
* sortFields
* @param dateFacetValue
* the selected value for the date facet or just NULL in case
* there is none
* @param creatorFacetValue
* the selected value for the creator facet or just NULL in case
* there is none
* @param publisherFacetValue
* the selected value for the publisher facet or just NULL in
* case there is none
* @return a results XML according to the schema in
* search.conf.xsdSchema.searchResults.xsd
*/
@GET
@Produces(MediaType.APPLICATION_XML)
@Path("/quick")
public String quickSearch(@QueryParam("queryTerms") String queryString, @QueryParam("from") final String fromString, @QueryParam("resultCount") final String resultCountString, @QueryParam("sortAsc") final String sortAscString, @QueryParam("sortField") final String sortFieldName, @QueryParam("facetFilters") final String facetValues) {
this.init();
// process parameters
int from = validateFromParam(fromString);
int resultCount = validateResultCountParam(resultCountString);
boolean sortAsc = validateSortAscParam(sortAscString);
P4SortField sortField = getSortField(sortFieldName);
Map<String, String> paramMap = unwrap(facetValues);
Map<P4FacetField, String> facetParams = buildFacetParamMap(paramMap);
// do quick search
SearchResults results = searcher.simpleSearch(queryString, from, resultCount, sortField, sortAsc, facetParams);
// marshall the results Object
String resultString = "";
if (results != null) {
try {
resultString = marshallSearchResults(results);
} catch (JAXBException e) {
System.out.println(e.getMessage());
}
}
return resultString;
}
@GET
@Produces(MediaType.APPLICATION_XML)
@Path("/advanced")
public String advancedSearch(@QueryParam("queryTerms") final String wrappedQuery, @QueryParam("from") final String fromString, @QueryParam("resultCount") final String resultCountString, @QueryParam("sortAsc") final String sortAscString, @QueryParam("sortField") final String sortFieldName, @QueryParam("facetFilters") final String facetValues) {
this.init();
// process parameters
Map<String, String> paramMap = unwrap(wrappedQuery);
Map<Schema.searchField, String> fieldMap = buildQueryParamMap(paramMap);
// putIfSet(fieldMap, Schema.searchField.TITLE, dcTitle);
// putIfSet(fieldMap, Schema.searchField.CREATOR, dcCreator);
// putIfSet(fieldMap, Schema.searchField.DESC, dcDescription);
// putIfSet(fieldMap, Schema.searchField.FORMAT, dcFormat);
// putIfSet(fieldMap, Schema.searchField.IDENT, dcIdentifier);
// putIfSet(fieldMap, Schema.searchField.LANG, dcLanguage);
// putIfSet(fieldMap, Schema.searchField.PUBLISHER, dcPublisher);
// putIfSet(fieldMap, Schema.searchField.SOURCE, dcSource);
// putIfSet(fieldMap, Schema.searchField.SUBJECT, dcSubject);
// putIfSet(fieldMap, Schema.searchField.DATE, dcDate);
// putIfSet(fieldMap, Schema.searchField.CREATEDATE, createDate);
// putIfSet(fieldMap, Schema.searchField.WAISDA, userAnnot);
// putIfSet(fieldMap, Schema.searchField.ID, id);
int from = validateFromParam(fromString);
int resultCount = validateResultCountParam(resultCountString);
boolean sortAsc = validateSortAscParam(sortAscString);
P4SortField sortField = getSortField(sortFieldName);
paramMap = unwrap(facetValues);
Map<P4FacetField, String> facetParams = buildFacetParamMap(paramMap);
// do advanced search
SearchResults results = searcher.advancedSearch(fieldMap, from, resultCount, sortField, sortAsc, facetParams);
// marshall the results Object
String resultString = "";
if (results != null) {
try {
resultString = marshallSearchResults(results);
} catch (JAXBException e) {
System.out.println(e.getMessage());
}
}
return resultString;
}
/**
* Get Auto complete suggestions for an entered search string. Suggestions
* are produced per single token in the whole term (tokenized on whitespace)
* by Solr. As this feature will be accessed via AJAX in the frontend only,
* it produces JSON data for ease of access.
*
* @param term
* the search string
* @return JSON containing suggestions for each token in the term
*/
@GET
@Produces(MediaType.APPLICATION_JSON)
@Path("/suggest")
public P4Suggestions getSuggestions(@QueryParam("term") String term) {
this.init();
P4Suggestions results = searcher.getSuggestion(term);
return results;
}
private P4SortField getSortField(String sortFieldParamValue) {
// process sortParams
P4SortField sortField = P4SortField.TITLE;
if (sortFieldParamValue != null && !sortFieldParamValue.equals("")) {
for (P4SortField field : P4SortField.values()) {
if (field.getFieldName().equals(sortFieldParamValue))
sortField = field;
}
}
return sortField;
}
private Map<String, String> unwrap(String wrappedQuery) {
Map<String, String> paramMap = new HashMap<String, String>();
if (wrappedQuery != null && !wrappedQuery.isEmpty()) {
String[] kvPairs = wrappedQuery.split(SearchConstants.KV_SEP);
String[] kvPair;
for (int i = 0; i < kvPairs.length; i++) {
kvPair = kvPairs[i].split(SearchConstants.KV_CON);
if (kvPair != null && kvPair.length == 2) {
LOGGER.debug("Unwrapping KV Pair: " + kvPair[0] + " - " + kvPair[1]);
paramMap.put(kvPair[0], kvPair[1]);
} else {
LOGGER.error("Unwrapping the query failed! Query string was: " + wrappedQuery);
}
}
}
return paramMap;
}
private Map<P4FacetField, String> buildFacetParamMap(Map<String, String> kvMap) {
Map<P4FacetField, String> selectedFacets = SolrQueryUtils.getDefaultFacetMap();
for (P4FacetField ff : P4FacetField.values()) {
if (kvMap.containsKey(ff.getFieldName())) {
selectedFacets.put(P4FacetField.get(ff.getFieldName()), kvMap.get(ff.getFieldName()));
}
}
return selectedFacets;
}
private Map<searchField, String> buildQueryParamMap(Map<String, String> kvMap) {
Map<searchField, String> selectedFacets = new HashMap<searchField, String>();
for (searchField sf : searchField.values()) {
if (kvMap.containsKey(sf.getUrlParam())) {
selectedFacets.put(searchField.get(sf.getUrlParam()), kvMap.get(sf.getUrlParam()));
}
}
return selectedFacets;
}
private int validateFromParam(String fromString) {
int from = 0;
if (fromString != null && !fromString.equals("")) {
try {
from = Integer.parseInt(fromString);
} catch (NumberFormatException e) {
LOGGER.debug("Parameter 'from' value is not a number. Getting results with from = 0.");
}
}
return from;
}
private int validateResultCountParam(String resultCountString) {
int resultCount = 10;
if (resultCountString != null && !resultCountString.equals("")) {
try {
resultCount = Integer.parseInt(resultCountString);
} catch (NumberFormatException e) {
LOGGER.debug("Parameter 'resultCount' value is not a number. Getting 10 results.");
}
}
return resultCount;
}
private boolean validateSortAscParam(String sortAscString) {
boolean sortAsc = true;
if (sortAscString != null && !sortAscString.equals("")) {
sortAsc = Boolean.parseBoolean(sortAscString);
}
return sortAsc;
}
private String marshallSearchResults(SearchResults results) throws JAXBException {
Marshaller marshaller = ModelUtils.getMarshaller(P4JAXBPackage.CONF);
marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
// DocumentBuilderFactory docBuilderFactory =
// DocumentBuilderFactory.newInstance();
// DocumentBuilder docBuilder = docBuilderFactory.newDocumentBuilder();
// Document doc = docBuilder.newDocument();
// marshaller.marshal(results, doc);
StringWriter sw = new StringWriter();
marshaller.marshal(results, sw);
final String resultString = sw.toString();
LOGGER.debug(resultString);
return resultString;
}
@GET
@Produces(MediaType.APPLICATION_XML)
@Path("/oai")
public Response oaiRequest(@QueryParam("verb") String verb, @QueryParam("identifier") String identifier, @QueryParam("metadataPrefix") String metadataPrefix, @QueryParam("set") String set, @QueryParam("from") String from, @QueryParam("until") String until, @QueryParam("resumptionToken") String resumptionToken) {
LOGGER.debug("Called oaiRequest with verb: " + verb);
Map<String, String> params = new HashMap<String, String>();
params.put("verb", verb);
params.put("identifier", identifier);
params.put("metadataPrefix", metadataPrefix);
params.put("set", set);
params.put("from", from);
params.put("until", until);
params.put("resumptionToken", resumptionToken);
OAIPMH oaipmh = OAIPMHManager.search(params);
ResponseBuilder rb = null;
try {
StringWriter sw = new StringWriter();
Marshaller marshaller = ModelUtils.getMarshaller(P4JAXBPackage.OAI_PMH);
marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
marshaller.marshal(oaipmh, sw);
rb = Response.status(Status.OK).entity(sw.toString());
} catch (JAXBException e) {
rb = Response.status(Status.INTERNAL_SERVER_ERROR).entity("Unable to serialize OAIPMH repsonse");
}
return rb.build();
}
}