source: SRUAggregator/trunk/src/main/java/eu/clarin/sru/fcs/aggregator/sresult/SearchResultsController.java @ 2599

Last change on this file since 2599 was 2599, checked in by yana, 11 years ago

logger messages organized in one line for better 'grep'ing

File size: 15.7 KB
Line 
1package eu.clarin.sru.fcs.aggregator.sresult;
2
3import eu.clarin.sru.client.SRUClientException;
4import eu.clarin.sru.client.SRURecord;
5import eu.clarin.sru.client.SRUSearchRetrieveRequest;
6import eu.clarin.sru.client.SRUSearchRetrieveResponse;
7import eu.clarin.sru.client.SRUThreadedClient;
8import eu.clarin.sru.client.SRUVersion;
9import eu.clarin.sru.client.fcs.ClarinFCSRecordData;
10import eu.clarin.sru.client.fcs.ClarinFCSRecordParser;
11import eu.clarin.sru.client.fcs.DataViewKWIC;
12import eu.clarin.sru.fcs.aggregator.app.WebAppListener;
13import eu.clarin.sru.fcs.aggregator.data.Institution;
14import eu.clarin.sru.fcs.aggregator.data.SearchResult;
15import eu.clarin.sru.fcs.aggregator.sparam.CorpusTreeNodeRenderer;
16import eu.clarin.weblicht.wlfxb.io.WLDObjector;
17import eu.clarin.weblicht.wlfxb.io.WLFormatException;
18import eu.clarin.weblicht.wlfxb.md.xb.MetaData;
19import eu.clarin.weblicht.wlfxb.tc.xb.TextCorpusStored;
20import eu.clarin.weblicht.wlfxb.xb.WLData;
21import java.io.ByteArrayOutputStream;
22import java.util.ArrayList;
23import java.util.List;
24import java.util.Set;
25import java.util.concurrent.Future;
26import java.util.logging.Level;
27import java.util.logging.Logger;
28import org.zkoss.zhtml.Filedownload;
29import org.zkoss.zk.ui.Component;
30import org.zkoss.zk.ui.Executions;
31import org.zkoss.zk.ui.event.Event;
32import org.zkoss.zk.ui.event.EventListener;
33import org.zkoss.zul.Column;
34import org.zkoss.zul.Columns;
35import org.zkoss.zul.Grid;
36import org.zkoss.zul.Groupbox;
37import org.zkoss.zul.Label;
38import org.zkoss.zul.ListModel;
39import org.zkoss.zul.Menuitem;
40import org.zkoss.zul.Messagebox;
41import org.zkoss.zul.SimpleListModel;
42import org.zkoss.zul.Treeitem;
43
44/**
45 * Controls search execution: runs requests and displays results inside
46 * specified Component
47 *
48 * @author Yana Panchenko
49 */
50public class SearchResultsController {
51
52    private List<SearchResult> resultsUnprocessed;
53    private List<SearchResult> resultsProcessed;
54    private SRUThreadedClient searchClient;
55    private Component resultsArea;
56    private UpdateResultsThread resultsThread;
57    private int currentRequestId = 0;
58    private Label progress;
59   
60    private static final Logger logger = Logger.getLogger(SearchResultsController.class.getName());
61
62    public SearchResultsController(Component resultsArea, Label progress) {
63        this.resultsArea = resultsArea;
64        this.progress = progress;
65        Executions.getCurrent().getDesktop().enableServerPush(true);
66        searchClient = (SRUThreadedClient) Executions.getCurrent().getDesktop().getWebApp().getAttribute(WebAppListener.SHARED_SRU_CLIENT);
67    }
68
69    public void executeSearch(Set<Treeitem> selectedItems, int maxRecords, String searchString, SRUVersion version) {
70
71        // execute search only if a user selected at least one endpint/corpus
72        if (selectedItems.isEmpty()) {
73            Messagebox.show("Please select at least one corpus!", "CLARIN-D FCS Aggregator", 0, Messagebox.EXCLAMATION);
74            return;
75        }
76        // execute search only if a user entered a search query
77        if (searchString == null || searchString.isEmpty()) {
78            Messagebox.show("Please enter a query!", "CLARIN-D FCS Aggregator", 0, Messagebox.EXCLAMATION);
79            return;
80        }
81
82        // terminate previous search requests and corresponding response processing
83        terminateProcessingRequestsAndResponses();
84
85        // update current search request id
86        currentRequestId++;
87
88        // clear are where results are to be displayed
89        resultsArea.getChildren().clear();
90
91        // empty storage for unprocessed processed lists with recordsData
92        resultsProcessed = new ArrayList<SearchResult>();
93        resultsUnprocessed = new ArrayList<SearchResult>();
94
95        // finally, send search requests to all the selected by user
96        // endpoints/corpora and process the responses
97        sendRequests(selectedItems, maxRecords, searchString, version);
98        processResponses();
99    }
100
101    private void sendRequests(Set<Treeitem> selectedItems, int maxRecords, String searchString, SRUVersion version) {
102
103        logger.log(Level.INFO, "Executing query={0} maxRecords={1}", 
104                new Object[]{searchString, maxRecords});
105       
106        for (Treeitem selectedItem : selectedItems) {
107            Object nodeData = selectedItem.getAttribute(CorpusTreeNodeRenderer.ITEM_DATA);
108            if (selectedItem.getParentItem().isSelected() || (nodeData instanceof Institution)) {
109                // don't query institution, and don't query subcorpus separately
110                // if there whole parent corpus/endpoint will be queried
111            } else {
112                SearchResult resultsItem = executeRequest(nodeData, searchString, maxRecords, version);
113                resultsUnprocessed.add(resultsItem);
114            }
115        }
116    }
117
118    private SearchResult executeRequest(Object nodeData, String searchString, int maxRecords, SRUVersion version) {
119
120        SearchResult resultsItem = new SearchResult(nodeData);
121        logger.log(Level.FINE, "Executing search for {0} query={1} maxRecords={2}", 
122                new Object[]{nodeData.toString(), searchString, maxRecords});
123        SRUSearchRetrieveRequest searchRequest = new SRUSearchRetrieveRequest(resultsItem.getEndpoint().getUrl());
124        searchRequest.setVersion(version);
125        searchRequest.setMaximumRecords(maxRecords);
126        searchRequest.setRecordSchema(ClarinFCSRecordData.RECORD_SCHEMA);
127        searchRequest.setQuery(searchString);
128        if (resultsItem.hasCorpusHandler()) {
129            searchRequest.setExtraRequestData("x-context", resultsItem.getCorpus().getValue());
130        }
131        try {
132            Future<SRUSearchRetrieveResponse> futureResponse = searchClient.searchRetrieve(searchRequest);
133            resultsItem.setFutureResponse(futureResponse);
134        } catch (SRUClientException ex) {
135            logger.log(Level.SEVERE, "SearchRetrieve failed for {0} {1} {2}", 
136                    new String[]{resultsItem.getEndpoint().getUrl(), ex.getClass().getName(), ex.getMessage()});
137        }
138        return resultsItem;
139
140    }
141
142    private void processResponses() {
143        processResponsesWithAsyncResultsWindowUpdate();
144        //processResponsesWithSyncResultsWindowUpdate();
145
146    }
147
148//    private void processResponsesWithSyncResultsWindowUpdate() {
149//
150//        while (!resultsUnprocessed.isEmpty()) {
151//            SearchResult resultsItem = resultsUnprocessed.remove(0);
152//            if (!resultsItem.isWaitingForResponse()) {
153//                resultsItem.consumeResponse();
154//                // create groupbox with search results item
155//                Groupbox groupbox = createRecordsGroup(resultsItem);
156//                // appand this search result only
157//                resultsArea.appendChild(groupbox);
158//                resultsProcessed.add(resultsItem);
159//            } else {
160//                resultsUnprocessed.add(resultsItem);
161//            }
162//
163//        }
164//    }
165   
166    private void processResponsesWithAsyncResultsWindowUpdate() {
167        resultsThread = new UpdateResultsThread();
168        resultsThread.start();
169    }
170
171    private class UpdateResultsThread extends Thread {
172
173        @Override
174        public void run() {
175            while (!resultsUnprocessed.isEmpty() && !Thread.currentThread().isInterrupted()) {
176               
177                SearchResult resultsItem = resultsUnprocessed.remove(0);
178                if (!resultsItem.isWaitingForResponse()) {
179                    resultsItem.consumeResponse();
180                    Executions.schedule(resultsArea.getDesktop(), new ResponseListener(resultsItem, currentRequestId), new Event("onDummy"));
181                    // this alternative to Executions.schedule() - sinchronious update -
182                    // doesn't work: if interrupted here (exception thrown), then
183                    // the current thread seems to already get controll of the desktop,
184                    // but never gets it back and desktop (page) hangs....
185                    ////Obtain the control of UI
186                    //Executions.activate(resultsArea.getDesktop());
187                    //try {
188                    //    updateResultsArea();
189                    //} finally {
190                    //    //SDeactivate to return the control of UI back
191                    //    Executions.deactivate(resultsArea.getDesktop());
192                    //}
193                    resultsProcessed.add(resultsItem);
194                    //System.out.println("RECORDS ITEM ADDED");
195                   
196                } else {
197                    resultsUnprocessed.add(resultsItem);
198                }
199            }
200
201            if (Thread.currentThread().isInterrupted()) {
202                for (SearchResult resultsItem : resultsUnprocessed) {
203                    resultsItem.cancelWaitingForResponse();
204                }
205            }
206
207        }
208    }
209
210    private class ResponseListener implements EventListener {
211
212        int requestId;
213        SearchResult resultsItem;
214
215        public ResponseListener(SearchResult resultsItem, int requestId) {
216            this.resultsItem = resultsItem;
217            this.requestId = requestId;
218        }
219
220        @Override
221        public void onEvent(Event event) {
222
223            // create groupbox with search results item
224            Groupbox groupbox = createRecordsGroup(resultsItem);
225
226            // appand this search result only if it
227            // is a result of the current request
228            if (requestId == currentRequestId) {
229                resultsArea.appendChild(groupbox);
230            }
231
232            // if in the meanwhile there was a new request
233            // this search result is outdated, detach it:
234            if (requestId != currentRequestId) {
235                groupbox.detach();
236            }
237           
238            if (resultsUnprocessed.isEmpty()) {
239                progress.setValue("");
240            } else {
241                progress.setValue("waiting for " + resultsUnprocessed.size() + " responses...");
242            }
243
244        }
245    }
246
247    private Groupbox createRecordsGroup(SearchResult resultsItem) {
248
249        Groupbox recordsGroup = new Groupbox();
250
251        // style the box
252        recordsGroup.setMold("3d");
253        recordsGroup.setSclass("ccsLightBlue");
254        recordsGroup.setContentStyle("border:0;");
255        recordsGroup.setStyle("margin:10px;10px;10px;10px;");
256        recordsGroup.setClosable(true);
257        //recordsGroup.setOpen(false);
258
259        // create title
260        StringBuilder sb = new StringBuilder();
261        sb.append(resultsItem.getEndpoint().getInstitution().getName());
262        sb.append(" ");
263        sb.append(resultsItem.getEndpoint().getUrl());
264        if (resultsItem.hasCorpusHandler()) {
265            if (resultsItem.getCorpus().getDisplayTerm() != null) {
266                sb.append(" ");
267                sb.append(resultsItem.getCorpus().getDisplayTerm());
268            }
269            if (sb.append(resultsItem.getCorpus().getValue()) != null) {
270                sb.append(" ");
271                sb.append(resultsItem.getCorpus().getValue());
272            }
273        }
274        recordsGroup.setTitle(sb.toString());
275
276        // populate it with records grid or failure message
277        if (resultsItem.getResponse() == null) { // there was an error in response
278            recordsGroup.appendChild(new Label("Sorry, the search failed!"));
279        } else if (resultsItem.getResponse().hasRecords()) { // the response was fine and there >=1 records
280            Grid grid = new Grid();
281//            grid.setWidth("100%");
282//            grid.setMold("paging");
283//            grid.setPageSize(10);
284            Columns columns = new Columns();
285            Column c;
286            c = new Column();
287            //c.setLabel("Left");
288            columns.appendChild(c);
289            //c.setHflex("2");
290            c = new Column();
291            //c.setLabel("Hit");
292            c.setHflex("min");
293            //c.setHflex("1");
294            columns.appendChild(c);
295            c = new Column();
296            //c.setHflex("2");
297            //c.setLabel("Right");
298            columns.appendChild(c);
299            grid.appendChild(columns);
300
301            List<SRURecord> sruRecords = resultsItem.getResponse().getRecords();
302            ListModel lmodel = new SimpleListModel(sruRecords);
303            grid.setModel(lmodel);
304            grid.setRowRenderer(new SearchResultRecordRenderer(resultsItem));
305            recordsGroup.appendChild(grid);
306        } else { // the response was fine, but there are no records
307            recordsGroup.appendChild(new Label("Sorry, there were no results!"));
308        }
309
310        return recordsGroup;
311    }
312
313    public void exportCSV() {
314
315        boolean noResult = true;
316        StringBuilder csv = new StringBuilder();
317        if (resultsProcessed != null && !resultsProcessed.isEmpty()) {
318            for (SearchResult result : resultsProcessed) {
319                for (DataViewKWIC kwic : result.getDataKWIC()) {
320                    csv.append("\"");
321                    csv.append(kwic.getLeft().replace("\"", "QUOTE"));
322                    csv.append("\"");
323                    csv.append(",");
324                    csv.append("\"");
325                    csv.append(kwic.getKeyword().replace("\"", "QUOTE"));
326                    csv.append("\"");
327                    csv.append(",");
328                    csv.append("\"");
329                    csv.append(kwic.getRight().replace("\"", "QUOTE"));
330                    csv.append("\"");
331                    csv.append("\n");
332                    noResult = false;
333                }
334            }
335        }
336
337        if (noResult) {
338            Messagebox.show("Nothing to export!");
339        } else {
340            Filedownload.save(csv.toString(), "text/plain", "ClarinDFederatedContentSearch.csv");
341        }
342    }
343
344    public void exportTCF() {
345
346        boolean noResult = true;
347        StringBuilder text = new StringBuilder();
348
349        if (resultsProcessed != null && !resultsProcessed.isEmpty()) {
350            for (SearchResult result : resultsProcessed) {
351                for (DataViewKWIC kwic : result.getDataKWIC()) {
352                    text.append(kwic.getLeft());
353                    text.append(" ");
354                    text.append(kwic.getKeyword());
355                    text.append(" ");
356                    text.append(kwic.getRight());
357                    text.append("\n");
358                    noResult = false;
359                }
360            }
361
362        }
363
364        if (noResult) {
365            Messagebox.show("Nothing to export!");
366        } else {
367            WLData data;
368            MetaData md = new MetaData();
369            //data.metaData.source = "Tuebingen Uni";
370            //md.addMetaDataItem("title", "binding test");
371            //md.addMetaDataItem("author", "Yana");
372            TextCorpusStored tc = new TextCorpusStored("unknown");
373            tc.createTextLayer().addText(text.toString());
374            data = new WLData(md, tc);
375            ByteArrayOutputStream os = new ByteArrayOutputStream();
376            try {
377                WLDObjector.write(data, os);
378                Filedownload.save(os.toByteArray(), "text/tcf+xml", "ClarinDFederatedContentSearch.xml");
379            } catch (WLFormatException ex) {
380                logger.log(Level.SEVERE, "Error exporting TCF {0} {1}", new String[]{ex.getClass().getName(), ex.getMessage()});
381                Messagebox.show("Sorry, export error!");
382            }
383        }
384    }
385
386    public void shutdown() {
387        terminateProcessingRequestsAndResponses();
388    }
389
390    private void terminateProcessingRequestsAndResponses() {
391
392        if (resultsThread != null) {
393            resultsThread.interrupt();
394            try {
395                resultsThread.join();
396            } catch (InterruptedException ex) {
397                Logger.getLogger(SearchResultsController.class.getName()).log(Level.SEVERE, null, ex);
398            }
399        }
400
401        Logger.getLogger(SearchResultsController.class.getName()).log(Level.INFO, "Search terminated");
402    }
403}
Note: See TracBrowser for help on using the repository browser.