/* * Copyright (C) 2010 Jan Pokorsky * * 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.editor.server; import cz.incad.kramerius.KrameriusModels; import cz.incad.kramerius.editor.share.rpc.GetSuggestionQuery; import cz.incad.kramerius.editor.share.rpc.GetSuggestionResult; import cz.incad.kramerius.editor.share.rpc.GetSuggestionResult.Suggestion; import cz.incad.kramerius.utils.XMLUtils; import cz.incad.kramerius.utils.conf.KConfiguration; import java.io.IOException; import java.io.InputStream; import java.net.HttpURLConnection; import java.net.MalformedURLException; import java.net.URI; import java.net.URISyntaxException; import java.net.URL; import java.util.ArrayList; import java.util.List; import java.util.logging.Level; import java.util.logging.Logger; import javax.xml.parsers.ParserConfigurationException; import javax.xml.xpath.XPath; import javax.xml.xpath.XPathConstants; import javax.xml.xpath.XPathExpressionException; import javax.xml.xpath.XPathFactory; import net.customware.gwt.dispatch.server.ActionHandler; import net.customware.gwt.dispatch.server.ExecutionContext; import net.customware.gwt.dispatch.shared.DispatchException; import org.w3c.dom.Document; import org.w3c.dom.Element; import org.w3c.dom.Node; import org.w3c.dom.NodeList; import org.xml.sax.SAXException; /** * Fetches Kramerius object suggestions according to passed filter and result limit. * * @author Jan Pokorsky */ public final class GetSuggestionQueryHandler implements ActionHandler<GetSuggestionQuery, GetSuggestionResult> { @Override public Class<GetSuggestionQuery> getActionType() { return GetSuggestionQuery.class; } @Override public GetSuggestionResult execute(GetSuggestionQuery action, ExecutionContext context) throws DispatchException { GetSuggestionResult result = new GetSuggestionResult(); try { List<Suggestion> suggestions = doExecute(action); result.setSuggestions(suggestions); } catch (Throwable ex) { result.setServerFailure(); Logger.getLogger(GetSuggestionQueryHandler.class.getName()).log(Level.SEVERE, null, ex); } return result; } private List<Suggestion> doExecute(GetSuggestionQuery action) throws Exception { InputStream istream = null; try { SolrSuggestionQuery query = new SolrSuggestionQuery(); istream = query.runQuery(action.getFilter(), action.getLimit()); List<Suggestion> suggestions = query.parseStream(istream); return suggestions; } finally { if (istream != null) { istream.close(); } } } @Override public void rollback(GetSuggestionQuery action, GetSuggestionResult result, ExecutionContext context) throws DispatchException { // ignore -> read only query } /** * Solr implementation. */ static final class SolrSuggestionQuery { private static final Logger LOG = Logger.getLogger(SolrSuggestionQuery.class.getName()); public static final int ROWS_THRESHOLD = 20; public InputStream runQuery(String filter, int limit) throws MalformedURLException, IOException, URISyntaxException { // XXX validate filter content? Lucene does not support e.g. '"' // check limit to prevent system overloading limit = Math.min(ROWS_THRESHOLD, limit); URL url = buildSolrQuery(filter, limit); InputStream stream = openStream(url); return stream; } InputStream openStream(URL url) throws IOException { HttpURLConnection conn = (HttpURLConnection) url.openConnection(); InputStream stream = url.openStream(); int responseCode = conn.getResponseCode(); if (responseCode != HttpURLConnection.HTTP_OK) { throw new IOException(conn.getResponseMessage()); } return stream; } URL buildSolrQuery(String filter, int limit) throws URISyntaxException, MalformedURLException { filter = filter.toLowerCase(); // XXX use new Locale("cs", "cz")? String solrHost = KConfiguration.getInstance().getSolrHost(); String solrQuery = String.format( // search dc.title field for <filter>* and skip all pages "q=dc.title:%s* -fedora.model:page" // select following fields for result + "&fl=PID,root_title,dc.title,fedora.model,score" + "&wt=xml" + "&omitHeader=true" // limit result records + "&rows=%s", filter, limit); LOG.log(Level.FINE, solrQuery); URI uri = URI.create(solrHost); // used to encode special characters of the query URI fullURI = new URI(uri.getScheme(), uri.getUserInfo(), uri.getHost(), uri.getPort(), uri.getPath() + "/select", solrQuery, null); URL url = fullURI.normalize().toURL(); LOG.log(Level.FINE, url.toExternalForm()); return url; } public List<Suggestion> parseStream(InputStream is) throws IOException, SAXException, ParserConfigurationException, XPathExpressionException { Document doc = XMLUtils.parseDocument(is); XPathFactory xpathFactory = XPathFactory.newInstance(); XPath paramPath = xpathFactory.newXPath(); List<Suggestion> suggestions = new ArrayList<Suggestion>(); NodeList nodes = findDocElements(doc, xpathFactory.newXPath()); for (int i = 0, length = nodes.getLength(); i < length; i++) { Element item = (Element) nodes.item(i); Suggestion suggestion = processDocNode(item, paramPath); if (suggestion != null) { suggestions.add(suggestion); } } return suggestions; } private Suggestion processDocNode(Node docNode, XPath xpath) throws XPathExpressionException { Suggestion suggestion = null; String pid = findDocChild("PID", docNode, xpath); String title = findDocChild("dc.title", docNode, xpath); String fmodel = findDocChild("fedora.model", docNode, xpath); KrameriusModels kmodel = KrameriusModels.parseString(fmodel); if (pid == null || pid.isEmpty()) { LOG.log(Level.WARNING, "missing pid: " + suggestion, new IllegalStateException()); } else { suggestion = new Suggestion(pid, title, EditorServerUtils.resolveKind(kmodel)); } return suggestion; } private NodeList findDocElements(Document doc, XPath xpath) throws XPathExpressionException { String expression = "/response/result/doc"; return (NodeList) xpath.evaluate(expression, doc, XPathConstants.NODESET); } private String findDocChild(String name, Node docNode, XPath xpath) throws XPathExpressionException { return xpath.evaluate(String.format("*[@name='%s']/text()", name), docNode).trim(); } } }