cz.incad.kramerius.rest.api.k5.client.search.SearchResource.java Source code

Java tutorial

Introduction

Here is the source code for cz.incad.kramerius.rest.api.k5.client.search.SearchResource.java

Source

/*
 * Copyright (C) 2013 Pavel Stastny
 * 
 * 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 cz.incad.kramerius.rest.api.k5.client.search;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.StringReader;
import java.io.StringWriter;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.Stack;
import java.util.logging.Level;
import java.util.logging.Logger;

import javax.ws.rs.GET;
import javax.ws.rs.Path;
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.MultivaluedMap;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.UriBuilder;
import javax.ws.rs.core.UriInfo;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.TransformerException;

import org.apache.commons.lang.StringUtils;
import org.apache.http.HttpResponse;
import org.apache.http.client.HttpResponseException;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.xml.sax.SAXException;

import com.google.inject.Inject;

import cz.incad.kramerius.SolrAccess;
import cz.incad.kramerius.rest.api.exceptions.BadRequestException;
import cz.incad.kramerius.rest.api.exceptions.GenericApplicationException;
import cz.incad.kramerius.rest.api.k5.client.JSONDecorator;
import cz.incad.kramerius.rest.api.k5.client.JSONDecoratorsAggregate;
import cz.incad.kramerius.rest.api.k5.client.utils.SOLRUtils;
import cz.incad.kramerius.utils.IOUtils;
import cz.incad.kramerius.utils.XMLUtils;
import cz.incad.kramerius.utils.conf.KConfiguration;

import static org.apache.http.HttpStatus.SC_BAD_REQUEST;

@Path("/v5.0/search")
public class SearchResource {

    private static Logger LOGGER = Logger.getLogger(SearchResource.class.getName());

    @Inject
    private SolrAccess solrAccess;

    @Inject
    private JSONDecoratorsAggregate jsonDecoratorAggregates;

    @GET
    @Produces({ MediaType.APPLICATION_XML + ";charset=utf-8" })
    public Response selectXML(@Context UriInfo uriInfo, @QueryParam("wt") String wt) {
        if ("json".equals(wt)) {
            return Response.ok().type(MediaType.APPLICATION_JSON + ";charset=utf-8")
                    .entity(getEntityJSON(uriInfo).toString()).build();
        } else {
            return Response.ok().entity(getEntityXML(uriInfo).toString()).build();
        }
    }

    private String getEntityXML(UriInfo uriInfo) {
        try {
            MultivaluedMap<String, String> queryParameters = uriInfo.getQueryParameters();
            StringBuilder builder = new StringBuilder();
            Set<String> keys = queryParameters.keySet();
            for (String k : keys) {
                for (String v : queryParameters.get(k)) {
                    if (k.equals("fl")) {
                        checkFieldSettings(v);
                    }
                    String value = URLEncoder.encode(v, "UTF-8");
                    value = checkHighlightValues(k, value);
                    builder.append(k + "=" + value);
                    builder.append("&");
                }
            }
            InputStream istream = this.solrAccess.request(builder.toString(), "xml");

            ByteArrayOutputStream bos = new ByteArrayOutputStream();
            IOUtils.copyStreams(istream, bos);
            String rawString = new String(bos.toByteArray(), "UTF-8");

            String uri = UriBuilder.fromResource(SearchResource.class).path("").build().toString();
            Document domObject = changeXMLResult(rawString, uri);

            StringWriter strWriter = new StringWriter();
            XMLUtils.print(domObject, strWriter);

            return strWriter.toString();
        } catch (HttpResponseException e) {
            if (e.getStatusCode() == SC_BAD_REQUEST) {
                LOGGER.log(Level.INFO, "SOLR Bad Request: " + uriInfo.getRequestUri());
                throw new BadRequestException(e.getMessage());
            } else {
                LOGGER.log(Level.INFO, e.getMessage(), e);
                throw new GenericApplicationException(e.getMessage());
            }
        } catch (IOException e) {
            LOGGER.log(Level.SEVERE, e.getMessage(), e);
            throw new GenericApplicationException(e.getMessage());
        } catch (TransformerException e) {
            LOGGER.log(Level.SEVERE, e.getMessage(), e);
            throw new GenericApplicationException(e.getMessage());
        } catch (ParserConfigurationException e) {
            LOGGER.log(Level.SEVERE, e.getMessage(), e);
            throw new GenericApplicationException(e.getMessage());
        } catch (SAXException e) {
            LOGGER.log(Level.SEVERE, e.getMessage(), e);
            throw new GenericApplicationException(e.getMessage());
        }
    }

    private void checkFieldSettings(String value) {
        List<String> filters = Arrays.asList(KConfiguration.getInstance().getAPISolrFilter());
        String[] vals = value.split(",");
        for (String v : vals) {
            // remove field alias
            v = StringUtils.substringAfterLast(v, ":");
            if (filters.contains(v))
                throw new BadRequestException("requesting filtering field");
        }
    }

    private String checkHighlightValues(String v, String value) {
        if (v.equals("hl.fragsize")) {
            try {
                int confVal = KConfiguration.getInstance().getConfiguration()
                        .getInt("api.search.highlight.defaultfragsize", 20);
                int maxVal = KConfiguration.getInstance().getConfiguration()
                        .getInt("api.search.highlight.maxfragsize", 120);
                int val = Integer.parseInt(value);
                if (val == 0) {
                    val = confVal;
                } else if (val > maxVal) {
                    val = confVal;
                }
                return "" + val;
            } catch (NumberFormatException e) {
                LOGGER.log(Level.SEVERE, e.getMessage(), e);
                return value;
            }
        } else {
            return value;
        }
    }

    @GET
    @Produces({ MediaType.APPLICATION_JSON + ";charset=utf-8" })
    public Response selectJSON(@Context UriInfo uriInfo, @QueryParam("wt") String wt) {
        if ("xml".equals(wt)) {
            return Response.ok().type(MediaType.APPLICATION_XML + ";charset=utf-8")
                    .entity(getEntityXML(uriInfo).toString()).build();
        } else {
            return Response.ok().entity(getEntityJSON(uriInfo).toString()).build();
        }
    }

    private String getEntityJSON(UriInfo uriInfo) {
        try {

            MultivaluedMap<String, String> queryParameters = uriInfo.getQueryParameters();
            StringBuilder builder = new StringBuilder();
            Set<String> keys = queryParameters.keySet();
            for (String k : keys) {
                for (String v : queryParameters.get(k)) {
                    if (k.equals("fl")) {
                        checkFieldSettings(v);
                    }
                    String value = URLEncoder.encode(v, "UTF-8");
                    value = checkHighlightValues(k, value);
                    builder.append(k + "=" + value);
                    builder.append("&");
                }
            }
            InputStream istream = this.solrAccess.request(builder.toString(), "json");

            ByteArrayOutputStream bos = new ByteArrayOutputStream();
            IOUtils.copyStreams(istream, bos);
            String rawString = new String(bos.toByteArray(), "UTF-8");

            String uri = UriBuilder.fromResource(SearchResource.class).path("").build().toString();
            JSONObject jsonObject = changeJSONResult(rawString, uri, this.jsonDecoratorAggregates.getDecorators());

            return jsonObject.toString();
        } catch (HttpResponseException e) {
            if (e.getStatusCode() == SC_BAD_REQUEST) {
                LOGGER.log(Level.INFO, "SOLR Bad Request: " + uriInfo.getRequestUri());
                throw new BadRequestException(e.getMessage());
            } else {
                LOGGER.log(Level.INFO, e.getMessage(), e);
                throw new GenericApplicationException(e.getMessage());
            }
        } catch (IOException e) {
            LOGGER.log(Level.SEVERE, e.getMessage(), e);
            throw new GenericApplicationException(e.getMessage());
        } catch (JSONException e) {
            LOGGER.log(Level.SEVERE, e.getMessage(), e);
            throw new GenericApplicationException(e.getMessage());
        }
    }

    /**
     * Change result in xml result
     * 
     * @param rawString
     *            XML result
     * @param context
     *            Calling context
     * @return Changed result
     * @throws ParserConfigurationException
     *             Parser error
     * @throws SAXException
     *             Parse error
     * @throws IOException
     *             IO error
     */
    public static Document changeXMLResult(String rawString, String context)
            throws ParserConfigurationException, SAXException, IOException {
        Document doc = XMLUtils.parseDocument(new StringReader(rawString));
        List<Element> elms = XMLUtils.getElementsRecursive(doc.getDocumentElement(), new XMLUtils.ElementsFilter() {
            @Override
            public boolean acceptElement(Element element) {
                return (element.getNodeName().equals("doc"));
            }
        });
        for (Element docE : elms) {
            changeMasterPidInDOM(docE);
            filterFieldsInDOM(docE);
            replacePidsInDOM(docE);
        }
        return doc;
    }

    public static void replacePidsInDOM(Element docE) {
        String[] apiReplace = KConfiguration.getInstance().getAPIPIDReplace();
        for (String k : apiReplace) {
            if (k.equals("PID"))
                continue; // already replaced
            Element foundElm = findSolrElement(docE, k);
            if (foundElm != null) {

                if (foundElm.getNodeName().equals("str")) {
                    String value = SOLRUtils.value(foundElm.getTextContent(), String.class);
                    if (value != null && (value.indexOf("/@") > 0)) {
                        value = value.replace("/@", "@");
                        foundElm.setTextContent(value);
                    }
                } else if (foundElm.getNodeName().equals("arr")) {
                    List<String> array = SOLRUtils.array(docE, k, String.class);
                    List<String> newArray = new ArrayList<String>();
                    for (String value : array) {
                        value = value.replace("/@", "@");
                        newArray.add(value);
                    }

                    docE.removeChild(foundElm);
                    Element newArrElm = SOLRUtils.arr(foundElm.getOwnerDocument(), k, newArray);
                    docE.appendChild(newArrElm);
                } else {
                    LOGGER.warning("skipping object type '" + foundElm.getNodeName() + "'");
                }

            }
        }

    }

    public static void filterFieldsInDOM(Element docE) {
        // filter
        String[] filters = KConfiguration.getInstance().getAPISolrFilter();
        for (final String name : filters) {
            Element found = findSolrElement(docE, name);
            if (found != null) {
                Node parentNode = found.getParentNode();
                Node removed = parentNode.removeChild(found);
            }
        }
    }

    public static Element findSolrElement(Element docE, final String name) {
        Element found = XMLUtils.findElement(docE, new XMLUtils.ElementsFilter() {
            @Override
            public boolean acceptElement(Element element) {
                return (element.hasAttribute("name") && element.getAttribute("name").equals(name));
            }
        });
        return found;
    }

    public static JSONObject changeJSONResult(String rawString, String context, List<JSONDecorator> decs)
            throws UnsupportedEncodingException, JSONException {

        //List<JSONDecorator> decs = this.jsonDecoratorAggregates.getDecorators();
        List<JSONArray> docsArrays = new ArrayList<JSONArray>();

        JSONObject resultJSONObject = new JSONObject(rawString);
        Stack<JSONObject> prcStack = new Stack<JSONObject>();
        prcStack.push(resultJSONObject);
        while (!prcStack.isEmpty()) {
            JSONObject popped = prcStack.pop();
            //Iterator keys = popped.keys();
            for (Iterator keys = popped.keys(); keys.hasNext();) {
                Object kobj = (Object) keys.next();
                String key = (String) kobj;
                Object obj = popped.get(key);
                boolean docsKey = key.equals("docs");
                if (docsKey && (obj instanceof JSONArray)) {
                    docsArrays.add((JSONArray) obj);
                }
                if (obj instanceof JSONObject) {
                    prcStack.push((JSONObject) obj);
                }
                if (obj instanceof JSONArray) {
                    JSONArray arr = (JSONArray) obj;
                    for (int i = 0, ll = arr.length(); i < ll; i++) {
                        Object arrObj = arr.get(i);
                        if (arrObj instanceof JSONObject) {
                            prcStack.push((JSONObject) arrObj);
                        }

                    }
                }

            }
        }

        for (JSONArray docs : docsArrays) {
            for (int i = 0, ll = docs.length(); i < ll; i++) {
                JSONObject docJSON = (JSONObject) docs.get(i);
                // check master pid
                changeMasterPidInJSON(docJSON);

                // fiter protected fields
                filterFieldsInJSON(docJSON);

                // decorators
                decorators(context, decs, docJSON);

                // replace pids
                replacePidsInJSON(docJSON);
            }
        }
        return resultJSONObject;
    }

    public static void decorators(String context, List<JSONDecorator> decs, JSONObject docJSON)
            throws JSONException {
        // decorators
        Map<String, Object> runtimeCtx = new HashMap<String, Object>();
        for (JSONDecorator d : decs) {
            d.before(runtimeCtx);
        }
        for (JSONDecorator jsonDec : decs) {
            if (jsonDec.apply(docJSON, context)) {
                jsonDec.decorate(docJSON, runtimeCtx);
            }
        }
        for (JSONDecorator d : decs) {
            d.after();
        }
    }

    public static void replacePidsInJSON(JSONObject jsonObj) throws JSONException {
        // repair results
        String[] apiReplace = KConfiguration.getInstance().getAPIPIDReplace();
        for (String k : apiReplace) {
            if (k.equals("PID"))
                continue; // already replaced
            if (jsonObj.has(k)) {
                Object object = jsonObj.get(k);
                if (object instanceof String) {
                    String s = jsonObj.getString(k);
                    if (s.indexOf("/@") > 0) {
                        s.replace("/@", "@"); // probable bug - not assigned, so it's ignored
                        jsonObj.put(k, s);
                    }
                } else if (object instanceof JSONArray) {
                    JSONArray jsonArr = (JSONArray) object;
                    JSONArray newJSONArray = new JSONArray();
                    int size = jsonArr.length();
                    for (int i = 0; i < size; i++) {
                        Object sObj = jsonArr.get(i);
                        if (sObj instanceof String) {
                            String s = (String) sObj;
                            s = s.replace("/@", "@");
                            newJSONArray.put(s);

                        } else {
                            LOGGER.warning("skipping object type '" + sObj.getClass().getName() + "'");
                        }
                    }
                    jsonObj.put(k, newJSONArray);
                } else {
                    LOGGER.warning("skipping object type '" + object.getClass().getName() + "'");
                }
            }
        }
    }

    public static void filterFieldsInJSON(JSONObject jsonObj) {
        // filter
        String[] filters = KConfiguration.getInstance().getAPISolrFilter();
        for (String filterKey : filters) {
            if (jsonObj.has(filterKey)) {
                jsonObj.remove(filterKey);
            }
        }
    }

    public static void changeMasterPidInJSON(JSONObject jsonObj) throws JSONException {
        if (jsonObj.has("PID")) {
            // pid contains '/' char
            String pid = jsonObj.getString("PID");
            if (pid.contains("/")) {
                pid = pid.replace("/", "");
                jsonObj.put("PID", pid);
            }

        }
    }

    public static void changeMasterPidInDOM(Element docElem) {
        // <str name="PID">uuid:2ad31d65-50ca-11e1-916e-001b63bd97ba</str>
        Element elm = XMLUtils.findElement(docElem, new XMLUtils.ElementsFilter() {

            @Override
            public boolean acceptElement(Element element) {
                if (element.getNodeName().equals("str")) {
                    if (element.hasAttribute("name") && (element.getAttribute("name").equals("PID"))) {
                        return true;
                    }
                }
                return false;
            }
        });
        if (elm != null) {
            String pid = elm.getTextContent();
            if (pid.contains("/")) {
                pid = pid.replace("/", "");
                elm.setTextContent(pid);
            }
        }
    }

    @GET
    @Path("terms")
    @Produces({ MediaType.APPLICATION_XML + ";charset=utf-8" })
    public Response termsXML(@Context UriInfo uriInfo) {
        try {
            MultivaluedMap<String, String> queryParameters = uriInfo.getQueryParameters();
            StringBuilder builder = new StringBuilder();
            Set<String> keys = queryParameters.keySet();
            for (String k : keys) {
                for (String v : queryParameters.get(k)) {
                    builder.append(k + "=" + URLEncoder.encode(v, "UTF-8"));
                    builder.append("&");
                }
            }
            InputStream istream = this.solrAccess.terms(builder.toString(), "xml");
            ByteArrayOutputStream bos = new ByteArrayOutputStream();
            IOUtils.copyStreams(istream, bos);

            String rawString = new String(bos.toByteArray(), "UTF-8");
            String uri = UriBuilder.fromResource(SearchResource.class).path("terms").build().toString();
            // Document domObject = changeXMLResult(rawString, uri);
            //
            // StringWriter strWriter = new StringWriter();
            // XMLUtils.print(domObject, strWriter);

            return Response.ok().entity(rawString).build();
        } catch (IOException e) {
            LOGGER.log(Level.SEVERE, e.getMessage());
            throw new GenericApplicationException(e.getMessage());
        }

    }

    @GET
    @Path("terms")
    @Produces({ MediaType.APPLICATION_JSON + ";charset=utf-8" })
    public Response termsJSON(@Context UriInfo uriInfo) {
        try {
            MultivaluedMap<String, String> queryParameters = uriInfo.getQueryParameters();
            StringBuilder builder = new StringBuilder();
            Set<String> keys = queryParameters.keySet();
            for (String k : keys) {
                for (String v : queryParameters.get(k)) {
                    builder.append(k + "=" + URLEncoder.encode(v, "UTF-8"));
                    builder.append("&");
                }
            }
            InputStream istream = this.solrAccess.terms(builder.toString(), "json");
            ByteArrayOutputStream bos = new ByteArrayOutputStream();
            IOUtils.copyStreams(istream, bos);

            String rawString = new String(bos.toByteArray(), "UTF-8");

            return Response.ok().entity(rawString).build();
        } catch (IOException e) {
            LOGGER.log(Level.SEVERE, e.getMessage());
            throw new GenericApplicationException(e.getMessage());
        }
    }

}