Changeset 6092 for SRUAggregator
- Timestamp:
- 03/10/15 14:16:28 (9 years ago)
- Location:
- SRUAggregator/trunk
- Files:
-
- 1 deleted
- 12 edited
Legend:
- Unmodified
- Added
- Removed
-
SRUAggregator/trunk/pom.xml
r6081 r6092 8 8 <groupId>eu.clarin.sru.fcs</groupId> 9 9 <artifactId>Aggregator2</artifactId> 10 <version>2.0.0- alpha-26</version>10 <version>2.0.0-beta-27</version> 11 11 <name>FCS Aggregator</name> 12 12 -
SRUAggregator/trunk/src/main/java/eu/clarin/sru/fcs/aggregator/app/Aggregator.java
r6081 r6092 107 107 private static final org.slf4j.Logger log = LoggerFactory.getLogger(Aggregator.class); 108 108 109 public static final String DE_TOK_MODEL = "tokenizer/de-tuebadz-8.0-token.bin"; 109 final int SEARCHES_SIZE_GC_THRESHOLD = 1000; 110 final int SEARCHES_AGE_GC_THRESHOLD = 60; 110 111 111 112 private static final ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1); … … 261 262 // this function should be thread-safe 262 263 public Search startSearch(SRUVersion version, List<Corpus> corpora, 263 String searchString, String searchLang, int maxRecords) throws Exception { 264 String searchString, String searchLang, 265 int firstRecord, int maxRecords) throws Exception { 264 266 if (corpora.isEmpty()) { 265 267 // No corpora … … 270 272 } else { 271 273 Search sr = new Search(sruSearchClient, version, searchStatsAtom.get(), 272 corpora, searchString, searchLang, 1,maxRecords);273 if ( (activeSearches.size() % 100) == 0) {274 corpora, searchString, searchLang, maxRecords); 275 if (activeSearches.size() > SEARCHES_SIZE_GC_THRESHOLD) { 274 276 List<Long> toBeRemoved = new ArrayList<Long>(); 275 277 long t0 = System.currentTimeMillis(); 276 278 for (Map.Entry<Long, Search> e : activeSearches.entrySet()) { 277 if (t0 - e.getValue().getCreatedAt() > 1800 * 1000) { 279 long dtmin = (t0 - e.getValue().getCreatedAt()) / 1000 / 60; 280 if (dtmin > SEARCHES_AGE_GC_THRESHOLD) { 281 log.info("removing search " + e.getKey() + ": " + dtmin + " minutes old"); 278 282 toBeRemoved.add(e.getKey()); 279 283 } -
SRUAggregator/trunk/src/main/java/eu/clarin/sru/fcs/aggregator/rest/RestService.java
r6081 r6092 1 1 package eu.clarin.sru.fcs.aggregator.rest; 2 2 3 import com.fasterxml.jackson.annotation.JsonProperty; 3 4 import com.fasterxml.jackson.core.JsonProcessingException; 4 5 import com.fasterxml.jackson.databind.ObjectMapper; … … 11 12 import eu.clarin.sru.fcs.aggregator.scan.Corpus; 12 13 import eu.clarin.sru.fcs.aggregator.scan.Statistics; 13 import eu.clarin.sru.fcs.aggregator.search.Request;14 14 import eu.clarin.sru.fcs.aggregator.search.Result; 15 15 import eu.clarin.sru.fcs.aggregator.search.Search; … … 98 98 public Response postSearch( 99 99 @FormParam("query") String query, 100 @FormParam("firstResultIndex") Integer firstResultIndex, 100 101 @FormParam("numberOfResults") Integer numberOfResults, 101 102 @FormParam("language") String language, 102 @FormParam("useLanguageGuesser") boolean useLanguageGuesser,103 103 @FormParam("corporaIds[]") List<String> corporaIds) throws Exception { 104 104 if (query == null || query.isEmpty()) { … … 113 113 return Response.status(503).entity("No corpora, please wait for the server to finish scanning").build(); 114 114 } 115 116 if (firstResultIndex == null || firstResultIndex < 1) { 117 firstResultIndex = 1; 118 } 119 if (firstResultIndex > 250) { 120 firstResultIndex = 250; 121 } 122 115 123 if (numberOfResults == null || numberOfResults < 10) { 116 124 numberOfResults = 10; … … 119 127 numberOfResults = 250; 120 128 } 121 Search search = Aggregator.getInstance().startSearch(SRUVersion.VERSION_1_2, corpora, query, language, numberOfResults); 129 130 Search search = Aggregator.getInstance().startSearch(SRUVersion.VERSION_1_2, 131 corpora, query, language, firstResultIndex, numberOfResults); 122 132 if (search == null) { 123 133 return Response.status(500).entity("Initiating search failed").build(); … … 129 139 public static class JsonSearch { 130 140 131 List<Request> requests; 141 @JsonProperty 142 int inProgress = 0; 143 @JsonProperty 132 144 List<Result> results; 133 145 134 public JsonSearch(List<Request> requests, List<Result> results) { 135 this.requests = requests; 146 public JsonSearch(List<Result> results) { 136 147 this.results = results; 137 }138 139 public List<Request> getRequests() {140 return requests;141 }142 143 public List<Result> getResults() {144 return results;145 148 } 146 149 } … … 155 158 } 156 159 157 JsonSearch js = new JsonSearch(search.getRequests(), search.getResults(corpusId)); 160 JsonSearch js = new JsonSearch(search.getResults(corpusId)); 161 for (Result r : js.results) { 162 if (r.getInProgress()) { 163 js.inProgress++; 164 } 165 } 158 166 return Response.ok(js).build(); 167 } 168 169 @POST 170 @Path("search/{id}") 171 public Response postSearchNextResults(@PathParam("id") Long searchId, 172 @FormParam("corpusId") String corpusId, 173 @FormParam("numberOfResults") Integer numberOfResults) throws Exception { 174 log.info("POST /search/{id}, corpusId: " + corpusId); 175 if (corpusId == null || corpusId.isEmpty()) { 176 return Response.status(400).entity("'corpusId' parameter expected").build(); 177 } 178 Search search = Aggregator.getInstance().getSearchById(searchId); 179 if (search == null) { 180 return Response.status(Response.Status.NOT_FOUND).entity("Search job not found").build(); 181 } 182 if (numberOfResults == null || numberOfResults < 10) { 183 numberOfResults = 10; 184 } 185 if (numberOfResults > 250) { 186 numberOfResults = 250; 187 } 188 189 boolean ret = search.searchForNextResults(corpusId, numberOfResults); 190 if (ret == false) { 191 return Response.status(500).entity("Initiating subSearch failed").build(); 192 } 193 URI uri = URI.create("" + search.getId()); 194 return Response.created(uri).entity(uri).build(); 159 195 } 160 196 -
SRUAggregator/trunk/src/main/java/eu/clarin/sru/fcs/aggregator/scan/Diagnostic.java
r5971 r6092 23 23 } 24 24 25 public String getUri() { 26 return uri; 27 } 28 29 public String getMessage() { 30 return message; 31 } 32 33 public String getDiagnostic() { 34 return diagnostic; 35 } 36 25 37 @Override 26 38 public int hashCode() { -
SRUAggregator/trunk/src/main/java/eu/clarin/sru/fcs/aggregator/scan/ScanCrawler.java
r6081 r6092 225 225 final Corpora corpora; 226 226 final int depth; 227 String fullRequestUrl;228 227 229 228 ScanTask(final Institution institution, final Endpoint endpoint, … … 248 247 scanRequest.setExtraRequestData(SRUCQL.SCAN_RESOURCE_INFO_PARAMETER, 249 248 SRUCQL.SCAN_RESOURCE_INFO_PARAMETER_DEFAULT_VALUE); 250 fullRequestUrl = scanRequest.getRequestedURI().toString();251 249 } catch (Throwable ex) { 252 250 log.error("Exception creating scan request for {}: {}", endpoint.getUrl(), ex.getMessage()); … … 285 283 for (SRUDiagnostic d : response.getDiagnostics()) { 286 284 Diagnostic diag = new Diagnostic(d.getURI(), d.getMessage(), d.getDetails()); 287 statistics.addEndpointDiagnostic(institution, endpoint, diag, fullRequestUrl);288 log.info("Diagnostic: {}: {}", fullRequestUrl, diag.message);285 statistics.addEndpointDiagnostic(institution, endpoint, diag, response.getRequest().getRequestedURI().toString()); 286 log.info("Diagnostic: {}: {}", response.getRequest().getRequestedURI().toString(), diag.message); 289 287 } 290 288 } … … 294 292 log.error("{} Exception in scan callback {}#{}", latch.get(), endpoint.getUrl(), normalizeHandle(parentCorpus)); 295 293 log.error("--> ", xc); 296 statistics.addErrorDatapoint(institution, endpoint, xc, fullRequestUrl);294 statistics.addErrorDatapoint(institution, endpoint, xc, response.getRequest().getRequestedURI().toString()); 297 295 } finally { 298 296 latch.decrement(); … … 305 303 log.error("{} Error while scanning {}#{}: {}", latch.get(), endpoint.getUrl(), normalizeHandle(parentCorpus), error.getMessage()); 306 304 statistics.addEndpointDatapoint(institution, endpoint, stats.getQueueTime(), stats.getExecutionTime()); 307 statistics.addErrorDatapoint(institution, endpoint, error, fullRequestUrl);305 statistics.addErrorDatapoint(institution, endpoint, error, request.getRequestedURI().toString()); 308 306 if (Throw.isCausedBy(error, SocketTimeoutException.class)) { 309 307 return; -
SRUAggregator/trunk/src/main/java/eu/clarin/sru/fcs/aggregator/search/Result.java
r5971 r6092 1 1 package eu.clarin.sru.fcs.aggregator.search; 2 2 3 import eu.clarin.sru.client.SRUClientException;4 3 import eu.clarin.sru.client.SRUDiagnostic; 5 4 import eu.clarin.sru.client.SRURecord; … … 19 18 import java.util.Collections; 20 19 import java.util.List; 20 import java.util.concurrent.atomic.AtomicBoolean; 21 import java.util.concurrent.atomic.AtomicInteger; 22 import java.util.concurrent.atomic.AtomicReference; 21 23 import org.w3c.dom.Node; 22 24 import org.slf4j.LoggerFactory; … … 34 36 private static final org.slf4j.Logger log = LoggerFactory.getLogger(Result.class); 35 37 36 private Request request; 37 private List<Kwic> kwics = new ArrayList<Kwic>(); 38 private JsonException exception; 39 private List<Diagnostic> diagnostics = new ArrayList<Diagnostic>(); 38 private final Corpus corpus; 39 private AtomicBoolean inProgress = new AtomicBoolean(true); 40 private AtomicInteger endpointReturnedRecords = new AtomicInteger(); 41 private AtomicReference<JsonException> exception = new AtomicReference<JsonException>(); 42 private List<Diagnostic> diagnostics = Collections.synchronizedList(new ArrayList<Diagnostic>()); 43 private List<Kwic> kwics = Collections.synchronizedList(new ArrayList<Kwic>()); 40 44 41 45 public List<Kwic> getKwics() { … … 43 47 } 44 48 45 public Result(Request request, SRUSearchRetrieveResponse response, 46 SRUClientException xc) { 47 this.request = request; 48 if (xc != null) { 49 exception = new JsonException(xc); 50 } 49 public Result(Corpus corpus) { 50 this.corpus = corpus; 51 } 52 53 public void setInProgress(boolean inProgress) { 54 this.inProgress.set(inProgress); 55 } 56 57 public boolean getInProgress() { 58 return inProgress.get(); 59 } 60 61 public void addResponse(SRUSearchRetrieveResponse response) { 51 62 if (response != null && response.hasRecords()) { 52 setResponse(response); 63 for (SRURecord record : response.getRecords()) { 64 addRecord(record); 65 } 53 66 } 54 67 if (response != null && response.hasDiagnostics()) { 55 setDiagnostics(response); 56 } 57 } 58 59 void setResponse(SRUSearchRetrieveResponse response) { 60 for (SRURecord record : response.getRecords()) { 61 if (record.isRecordSchema(ClarinFCSRecordData.RECORD_SCHEMA)) { 62 ClarinFCSRecordData rd = (ClarinFCSRecordData) record.getRecordData(); 63 Resource resource = rd.getResource(); 64 setClarinRecord(resource); 65 log.debug("Resource ref={0}, pid={1}, dataViews={2}", 66 new Object[]{resource.getRef(), resource.getPid(), resource.hasDataViews()}); 67 } else if (record.isRecordSchema(SRUSurrogateRecordData.RECORD_SCHEMA)) { 68 SRUSurrogateRecordData r = (SRUSurrogateRecordData) record.getRecordData(); 69 log.info("Surrogate diagnostic: uri={0}, message={1}, detail={2}", 70 new Object[]{r.getURI(), r.getMessage(), r.getDetails()}); 71 } else { 72 log.info("Unsupported schema: {0}", record.getRecordSchema()); 68 for (SRUDiagnostic d : response.getDiagnostics()) { 69 diagnostics.add(new Diagnostic(d.getURI(), d.getMessage(), d.getDetails())); 73 70 } 74 71 } 75 72 } 76 73 77 void setDiagnostics(SRUSearchRetrieveResponse response) { 78 for (SRUDiagnostic d : response.getDiagnostics()) { 79 SRUSearchRetrieveRequest srurequest = response.getRequest(); 80 diagnostics.add(new Diagnostic(d.getURI(), d.getMessage(), d.getDetails())); 74 void addRecord(SRURecord record) { 75 endpointReturnedRecords.getAndIncrement(); 76 if (record.isRecordSchema(ClarinFCSRecordData.RECORD_SCHEMA)) { 77 ClarinFCSRecordData rd = (ClarinFCSRecordData) record.getRecordData(); 78 Resource resource = rd.getResource(); 79 setClarinRecord(resource); 80 log.debug("Resource ref={0}, pid={1}, dataViews={2}", 81 new Object[]{resource.getRef(), resource.getPid(), resource.hasDataViews()}); 82 } else if (record.isRecordSchema(SRUSurrogateRecordData.RECORD_SCHEMA)) { 83 SRUSurrogateRecordData r = (SRUSurrogateRecordData) record.getRecordData(); 84 log.info("Surrogate diagnostic: uri={0}, message={1}, detail={2}", 85 new Object[]{r.getURI(), r.getMessage(), r.getDetails()}); 86 } else { 87 log.info("Unsupported schema: {0}", record.getRecordSchema()); 81 88 } 82 89 } … … 130 137 131 138 public JsonException getException() { 132 return exception ;139 return exception.get(); 133 140 } 134 141 135 public int getStartRecord() {136 return request.getStartRecord();142 public void setException(Exception xc) { 143 exception.set(new JsonException(xc)); 137 144 } 138 145 139 public int getEnd Record() {140 return request.getEndRecord();146 public int getEndpointReturnedRecords() { 147 return endpointReturnedRecords.get(); 141 148 } 142 149 143 150 public Corpus getCorpus() { 144 return request.getCorpus(); 145 } 146 147 public String getSearchString() { 148 return request.getSearchString(); 151 return corpus; 149 152 } 150 153 } -
SRUAggregator/trunk/src/main/java/eu/clarin/sru/fcs/aggregator/search/Search.java
r6081 r6092 33 33 private static final AtomicLong counter = new AtomicLong(Math.abs(new Random().nextInt())); 34 34 35 private final ThrottledClient searchClient; 36 private final SRUVersion version; 35 37 private final Long id; 36 38 private final String query; 37 39 private final long createdAt = System.currentTimeMillis(); 38 40 private final String searchLanguage; 39 private final List<Request> requests = Collections.synchronizedList(new ArrayList<Request>());40 41 private final List<Result> results = Collections.synchronizedList(new ArrayList<Result>()); 41 42 private final Statistics statistics; … … 43 44 public Search(ThrottledClient searchClient, SRUVersion version, 44 45 Statistics statistics, List<Corpus> corpora, String searchString, 45 String searchLanguage, int startRecord, intmaxRecords46 String searchLanguage, int maxRecords 46 47 ) { 48 this.searchClient = searchClient; 49 this.version = version; 47 50 this.id = counter.getAndIncrement(); 48 51 this.query = searchString; … … 50 53 this.statistics = statistics; 51 54 for (Corpus corpus : corpora) { 52 executeSearch(searchClient, version, corpus, searchString, startRecord, maxRecords); 55 Result result = new Result(corpus); 56 executeSearch(result, query, 1, maxRecords); 57 results.add(result); 53 58 } 54 59 } 55 60 56 private Request executeSearch(ThrottledClient searchClient, SRUVersion version, 57 final Corpus corpus, String searchString, 61 public boolean searchForNextResults(String corpusId, int maxRecords) { 62 for (Result r : results) { 63 if (r.getCorpus().getId().equals(corpusId)) { 64 executeSearch(r, query, r.getEndpointReturnedRecords() + 1, maxRecords); 65 return true; 66 } 67 } 68 return false; 69 } 70 71 private void executeSearch(final Result result, String searchString, 58 72 int startRecord, int maxRecords) { 59 final Request request = new Request(corpus, searchString, startRecord, startRecord + maxRecords - 1); 60 log.info("Executing search in '{}' query='{}' maxRecords='{}'", corpus, searchString, maxRecords); 73 final Corpus corpus = result.getCorpus(); 74 log.info("Executing search in '{}' query='{}' maxRecords='{}'", 75 corpus, searchString, maxRecords); 61 76 62 77 SRUSearchRetrieveRequest searchRequest = new SRUSearchRetrieveRequest(corpus.getEndpoint().getUrl()); 63 78 searchRequest.setVersion(version); 79 searchRequest.setStartRecord(startRecord); 64 80 searchRequest.setMaximumRecords(maxRecords); 65 81 boolean legacy = corpus.getEndpoint().getProtocol().equals(FCSProtocolVersion.LEGACY); … … 68 84 : ClarinFCSRecordData.RECORD_SCHEMA); 69 85 searchRequest.setQuery(searchString); 70 searchRequest.setStartRecord(startRecord);71 86 if (corpus.getHandle() != null) { 72 87 searchRequest.setExtraRequestData(legacy … … 75 90 corpus.getHandle()); 76 91 } 77 re quests.add(request);92 result.setInProgress(true); 78 93 79 94 try { … … 83 98 try { 84 99 statistics.addEndpointDatapoint(corpus.getInstitution(), corpus.getEndpoint(), stats.getQueueTime(), stats.getExecutionTime()); 85 Result result = new Result(request, response, null); 86 results.add(result); 87 requests.remove(request); 100 result.addResponse(response); 88 101 List<Diagnostic> diagnostics = result.getDiagnostics(); 89 102 if (diagnostics != null && !diagnostics.isEmpty()) { … … 96 109 } catch (Throwable xc) { 97 110 log.error("search.onSuccess exception:", xc); 111 } finally { 112 result.setInProgress(false); 98 113 } 99 114 } … … 104 119 statistics.addEndpointDatapoint(corpus.getInstitution(), corpus.getEndpoint(), stats.getQueueTime(), stats.getExecutionTime()); 105 120 statistics.addErrorDatapoint(corpus.getInstitution(), corpus.getEndpoint(), xc, srureq.getRequestedURI().toString()); 106 results.add(new Result(request, null, xc)); 107 requests.remove(request); 121 result.setException(xc); 108 122 log.error("search.onError: ", xc); 109 123 } catch (Throwable xxc) { 110 124 log.error("search.onError exception:", xxc); 125 } finally { 126 result.setInProgress(false); 111 127 } 112 128 } … … 115 131 log.error("SearchRetrieve error for " + corpus.getEndpoint().getUrl(), xc); 116 132 } 117 return request;118 133 } 119 134 120 135 public Long getId() { 121 136 return id; 122 }123 124 public List<Request> getRequests() {125 List<Request> copy = new ArrayList<>();126 synchronized (requests) {127 copy.addAll(requests);128 }129 return copy;130 137 } 131 138 -
SRUAggregator/trunk/src/main/resources/assets/base.css
r6081 r6092 513 513 } 514 514 515 .statistics .alert-warning.legacy { 516 color: #000000; 517 background-color: #FFFFFF; 518 border-color: #A00000; 519 } -
SRUAggregator/trunk/src/main/resources/assets/js/main.js
r6081 r6092 3 3 "use strict"; 4 4 5 var VERSION = "VERSION 2.0.0 .α26";5 var VERSION = "VERSION 2.0.0-beta-27"; 6 6 var URLROOT = "/Aggregator-testing"; 7 7 … … 137 137 React.createElement("div", {className: "container"}, 138 138 React.createElement("div", {className: "beta-tag"}, 139 React.createElement("span", null, " ALPHA")139 React.createElement("span", null, "BETA") 140 140 ) 141 141 ), … … 227 227 228 228 renderDiagnostic: function(d) { 229 var classes = "inline alert alert-warning " + (d.diagnostic.uri === 'LEGACY' ? "legacy" : ""); 229 230 return React.createElement("div", {key: d.diagnostic.uri}, 230 React.createElement("div", {className: "inline alert alert-warning"},231 React.createElement("div", {className: classes}, 231 232 React.createElement("div", null, 232 233 d.counter <= 1 ? false : -
SRUAggregator/trunk/src/main/resources/assets/js/main.jsx
r6081 r6092 3 3 "use strict"; 4 4 5 var VERSION = "VERSION 2.0.0 .α26";5 var VERSION = "VERSION 2.0.0-beta-27"; 6 6 var URLROOT = "/Aggregator-testing"; 7 7 … … 137 137 <div className="container"> 138 138 <div className="beta-tag"> 139 <span> ALPHA</span>139 <span>BETA</span> 140 140 </div> 141 141 </div> … … 227 227 228 228 renderDiagnostic: function(d) { 229 var classes = "inline alert alert-warning " + (d.diagnostic.uri === 'LEGACY' ? "legacy" : ""); 229 230 return <div key={d.diagnostic.uri}> 230 <div className= "inline alert alert-warning">231 <div className={classes} > 231 232 <div> 232 233 { d.counter <= 1 ? false : -
SRUAggregator/trunk/src/main/resources/assets/js/search.js
r6081 r6092 2 2 (function() { 3 3 "use strict"; 4 5 var NO_MORE_RECORDS_DIAGNOSTIC_URI = "info:srw/diagnostic/1/61"; 4 6 5 7 window.MyAggregator = window.MyAggregator || {}; … … 65 67 corpus.priority = 1; // used for ordering search results in corpus view 66 68 corpus.index = index; // original order, used for stable sort 67 });68 this.recurse(function(corpus, index) {69 if (corpus.institution.link > 1) {70 corpus.selected = false;71 }72 69 }); 73 70 } … … 180 177 181 178 nohits: { 182 requests: [], 183 results: [], 179 results: null, 184 180 }, 185 181 anyLanguage: [multipleLanguageCode, "Any Language"], … … 249 245 }); 250 246 }, 247 nextResults: function(corpusId) { 248 // console.log("searching next results in corpus:", corpusId); 249 this.props.ajax({ 250 url: 'rest/search/'+this.state.searchId, 251 type: "POST", 252 data: { 253 corpusId: corpusId, 254 numberOfResults: this.state.numberOfResults, 255 }, 256 success: function(searchId, textStatus, jqXHR) { 257 // console.log("search ["+query+"] ok: ", searchId, jqXHR); 258 var timeout = 250; 259 setTimeout(this.refreshSearchResults, timeout); 260 this.setState({ searchId: searchId, timeout: timeout }); 261 }.bind(this), 262 }); 263 }, 251 264 252 265 refreshSearchResults: function() { … … 258 271 success: function(json, textStatus, jqXHR) { 259 272 var timeout = this.state.timeout; 260 if (json. requests.length > 0) {273 if (json.inProgress) { 261 274 if (timeout < 10000) { 262 275 timeout = 1.5 * timeout; … … 267 280 console.log("search ended; hits:", json); 268 281 } 269 this.setState({ hits: json, timeout: timeout }); 282 var corpusHit = this.state.zoomedCorpusHit; 283 if (corpusHit) { 284 for (var resi = 0; resi < json.results.length; resi++) { 285 var res = json.results[resi]; 286 if (res.corpus.id === corpusHit.corpus.id) { 287 corpusHit = res; 288 break; 289 } 290 } 291 } 292 this.setState({ hits: json, timeout: timeout, zoomedCorpusHit: corpusHit}); 270 293 }.bind(this), 271 294 }); … … 293 316 this.state.corpora.setVisibility(this.state.searchLayerId, 294 317 languageFilter === 'byGuess' ? multipleLanguageCode : languageObj[0]); 295 this.setState({language: languageObj, languageFilter: languageFilter}); 296 this.state.corpora.update(); 318 this.setState({ 319 language: languageObj, 320 languageFilter: languageFilter, 321 corpora: this.state.corpora, // === this.state.corpora.update(); 322 }); 297 323 }, 298 324 299 325 setLayer: function(layerId) { 300 326 this.state.corpora.setVisibility(layerId, this.state.language[0]); 301 this.state.corpora.update(); 302 this.setState({searchLayerId: layerId}); 327 this.setState({ 328 searchLayerId: layerId, 329 hits: this.nohits, 330 searchId: null, 331 corpora: this.state.corpora, // === this.state.corpora.update(); 332 }); 303 333 }, 304 334 … … 319 349 var noLangFiltering = this.state.languageFilter === 'byMeta'; 320 350 var langCode = this.state.language[0]; 321 return this.state.hits.results.map(function(corpusHit) { 322 return { 323 corpus: corpusHit.corpus, 324 startRecord: corpusHit.startRecord, 325 endRecord: corpusHit.endRecord, 326 exception: corpusHit.exception, 327 diagnostics: corpusHit.diagnostics, 328 searchString: corpusHit.searchString, 329 kwics: noLangFiltering ? corpusHit.kwics : 330 corpusHit.kwics.filter(function(kwic) { 331 return kwic.language === langCode || langCode === multipleLanguageCode || langCode === null; 332 }), 333 }; 334 }); 351 var results = null, inProgress = 0, hits = 0; 352 if (this.state.hits.results) { 353 results = this.state.hits.results.map(function(corpusHit) { 354 return { 355 corpus: corpusHit.corpus, 356 inProgress: corpusHit.inProgress, 357 exception: corpusHit.exception, 358 diagnostics: corpusHit.diagnostics, 359 kwics: noLangFiltering ? corpusHit.kwics : 360 corpusHit.kwics.filter(function(kwic) { 361 return kwic.language === langCode || 362 langCode === multipleLanguageCode || 363 langCode === null; 364 }), 365 }; 366 }); 367 for (var i = 0; i < results.length; i++) { 368 var result = results[i]; 369 if (result.inProgress) { 370 inProgress++; 371 } 372 if (result.kwics.length > 0) { 373 hits ++; 374 } 375 } 376 } 377 return { 378 results: results, 379 hits: hits, 380 inProgress: inProgress, 381 }; 335 382 }, 336 383 … … 354 401 }, 355 402 356 handleChange: function(event) {403 onQuery: function(event) { 357 404 this.setState({query: event.target.value}); 358 405 }, … … 377 424 }, 378 425 379 render Aggregator: function() {426 render: function() { 380 427 var layer = layerMap[this.state.searchLayerId]; 381 428 return ( … … 390 437 React.createElement("input", {className: "form-control input-lg search", name: "query", type: "text", 391 438 value: this.state.query, placeholder: this.props.placeholder, 392 tabIndex: "1", onChange: this. handleChange, onKeyDown: this.handleKey}),439 tabIndex: "1", onChange: this.onQuery, onKeyDown: this.handleKey}), 393 440 React.createElement("div", {className: "input-group-btn"}, 394 441 React.createElement("button", {className: "btn btn-default input-lg", type: "button", onClick: this.search}, … … 467 514 React.createElement(Modal, {ref: "resultModal", title: this.renderZoomedResultTitle(this.state.zoomedCorpusHit)}, 468 515 React.createElement(ZoomedResult, {corpusHit: this.state.zoomedCorpusHit, 516 nextResults: this.nextResults, 469 517 getDownloadLink: this.getDownloadLink, 470 518 getToWeblichtLink: this.getToWeblichtLink, … … 475 523 476 524 React.createElement("div", {className: "top-gap"}, 477 React.createElement(Results, {requests: this.state.hits.requests, 478 results: this.filterResults(), 525 React.createElement(Results, {collhits: this.filterResults(), 479 526 toggleResultModal: this.toggleResultModal, 480 527 getDownloadLink: this.getDownloadLink, … … 485 532 ); 486 533 }, 487 render: function() {488 return this.renderAggregator();489 }490 534 }); 491 535 … … 622 666 }, 623 667 624 renderDiagnostic: function(d) { 625 return React.createElement("div", {className: "alert alert-warning", key: d.uri}, 668 renderDiagnostic: function(d, key) { 669 if (d.uri === NO_MORE_RECORDS_DIAGNOSTIC_URI) { 670 return false; 671 } 672 return React.createElement("div", {className: "alert alert-warning", key: key}, 626 673 React.createElement("div", null, "Diagnostic: ", d.message) 627 674 ); … … 632 679 return false; 633 680 } 634 635 681 return corpusHit.diagnostics.map(this.renderDiagnostic); 636 682 }, … … 714 760 React.createElement("li", null, 715 761 error ? 716 React.createElement("div", {className: "alert alert-danger", style: {margin:10 }}, error) :762 React.createElement("div", {className: "alert alert-danger", style: {margin:10, width:200}}, error) : 717 763 React.createElement("a", {href: this.props.getToWeblichtLink(corpusId), target: "_blank"}, " ", 718 764 "Send to Weblicht") … … 729 775 propTypes: { 730 776 corpusHit: PT.object, 777 nextResults: PT.func.isRequired, 731 778 languageMap: PT.object.isRequired, 732 779 weblichtLanguages: PT.array.isRequired, … … 737 784 mixins: [ResultMixin], 738 785 786 getInitialState: function() { 787 return { 788 inProgress: false, 789 }; 790 }, 791 792 componentWillReceiveProps: function() { 793 this.setState({inProgress: false}); 794 }, 795 796 nextResults: function(e) { 797 this.setState({inProgress: true}); 798 this.props.nextResults(this.props.corpusHit.corpus.id); 799 }, 800 739 801 renderLanguages: function(languages) { 740 802 return languages … … 742 804 .sort() 743 805 .join(", "); 806 }, 807 808 renderMoreResults:function(){ 809 if (this.state.inProgress || this.props.corpusHit.inProgress) 810 return React.createElement("span", {style: {fontStyle:'italic'}}, "Retrieving results, please wait..."); 811 812 var moreResults = true; 813 for (var i = 0; i < this.props.corpusHit.diagnostics.length; i++) { 814 var d = this.props.corpusHit.diagnostics[i]; 815 if (d.uri === NO_MORE_RECORDS_DIAGNOSTIC_URI) { 816 moreResults = false; 817 break; 818 } 819 } 820 if (!moreResults) 821 return React.createElement("span", {style: {fontStyle:'italic'}}, "No other results available for this query"); 822 return React.createElement("button", {className: "btn btn-default", onClick: this.nextResults}, 823 React.createElement("span", {className: "glyphicon glyphicon-option-horizontal", 'aria-hidden': "true"}), " More Results" 824 ); 744 825 }, 745 826 … … 777 858 778 859 React.createElement("div", {style: {textAlign:'center', marginTop:10}}, 779 React.createElement("button", {className: "btn btn-default"}, 780 React.createElement("span", {className: "glyphicon glyphicon-option-horizontal", 'aria-hidden': "true"}), " More Results" 781 ) 860 this.renderMoreResults() 782 861 ) 783 862 … … 789 868 var Results = React.createClass({displayName: 'Results', 790 869 propTypes: { 791 requests: PT.array.isRequired, 792 results: PT.array.isRequired, 870 collhits: PT.object.isRequired, 793 871 searchedLanguage: PT.array.isRequired, 794 872 toggleResultModal: PT.func.isRequired, … … 825 903 }, 826 904 827 renderProgressMessage: function(hits) { 828 var total = this.props.requests.length + this.props.results.length; 829 var msg = hits + " matching collections found in " + this.props.results.length + " searched collections"; 830 var percents = Math.round(100 * this.props.results.length / total); 905 renderProgressMessage: function() { 906 var collhits = this.props.collhits; 907 var done = collhits.results.length - collhits.inProgress; 908 var msg = collhits.hits + " matching collections found in " + done + " searched collections"; 909 var percents = Math.round(100 * collhits.hits / collhits.results.length); 831 910 var styleperc = {width: percents+"%"}; 832 911 return React.createElement("div", {style: {marginTop:10}}, 833 912 React.createElement("div", null, msg), 834 this.props.requests.length> 0 ?913 collhits.inProgress > 0 ? 835 914 React.createElement("div", {className: "progress", style: {marginBottom:10}}, 836 915 React.createElement("div", {className: "progress-bar progress-bar-striped active", role: "progressbar", … … 847 926 848 927 render: function() { 849 if (this.props.results.length + this.props.requests.length === 0) { 928 var collhits = this.props.collhits; 929 if (!collhits.results) { 850 930 return false; 851 931 } 852 var hits = this.props.results.filter(function(corpusHit) { return corpusHit.kwics.length > 0; }).length; 853 var showprogress = this.props.requests.length > 0; 932 var showprogress = collhits.inProgress > 0; 854 933 return React.createElement("div", null, 855 934 React.createElement(ReactCSSTransitionGroup, {transitionName: "fade"}, 856 showprogress ? this.renderProgressMessage( hits) : React.createElement("div", {style: {height:20}}),935 showprogress ? this.renderProgressMessage() : React.createElement("div", {style: {height:20}}), 857 936 React.createElement("div", {style: {marginBottom:2}}, 858 937 showprogress ? false : 859 React.createElement("div", {className: "float-left"}, " ", hits + " matching collections found", " "),938 React.createElement("div", {className: "float-left"}, " ", collhits.hits + " matching collections found", " "), 860 939 861 hits === 0 ? false :940 collhits.hits === 0 ? false : 862 941 React.createElement("div", {className: "float-right"}, 863 942 React.createElement("div", null, 864 943 this.renderDisplayKWIC(), 865 this.props.requests.length=== 0 ?944 collhits.inProgress === 0 ? 866 945 React.createElement("div", {className: "inline"}, " ", this.renderDownloadLinks(), " ") 867 946 :false … … 872 951 React.createElement("div", {style: {clear:'both'}}) 873 952 ), 874 this.props.results.map(this.renderResultPanel)953 collhits.results.map(this.renderResultPanel) 875 954 ) 876 955 ); -
SRUAggregator/trunk/src/main/resources/assets/js/search.jsx
r6081 r6092 2 2 (function() { 3 3 "use strict"; 4 5 var NO_MORE_RECORDS_DIAGNOSTIC_URI = "info:srw/diagnostic/1/61"; 4 6 5 7 window.MyAggregator = window.MyAggregator || {}; … … 65 67 corpus.priority = 1; // used for ordering search results in corpus view 66 68 corpus.index = index; // original order, used for stable sort 67 });68 this.recurse(function(corpus, index) {69 if (corpus.institution.link > 1) {70 corpus.selected = false;71 }72 69 }); 73 70 } … … 180 177 181 178 nohits: { 182 requests: [], 183 results: [], 179 results: null, 184 180 }, 185 181 anyLanguage: [multipleLanguageCode, "Any Language"], … … 249 245 }); 250 246 }, 247 nextResults: function(corpusId) { 248 // console.log("searching next results in corpus:", corpusId); 249 this.props.ajax({ 250 url: 'rest/search/'+this.state.searchId, 251 type: "POST", 252 data: { 253 corpusId: corpusId, 254 numberOfResults: this.state.numberOfResults, 255 }, 256 success: function(searchId, textStatus, jqXHR) { 257 // console.log("search ["+query+"] ok: ", searchId, jqXHR); 258 var timeout = 250; 259 setTimeout(this.refreshSearchResults, timeout); 260 this.setState({ searchId: searchId, timeout: timeout }); 261 }.bind(this), 262 }); 263 }, 251 264 252 265 refreshSearchResults: function() { … … 258 271 success: function(json, textStatus, jqXHR) { 259 272 var timeout = this.state.timeout; 260 if (json. requests.length > 0) {273 if (json.inProgress) { 261 274 if (timeout < 10000) { 262 275 timeout = 1.5 * timeout; … … 267 280 console.log("search ended; hits:", json); 268 281 } 269 this.setState({ hits: json, timeout: timeout }); 282 var corpusHit = this.state.zoomedCorpusHit; 283 if (corpusHit) { 284 for (var resi = 0; resi < json.results.length; resi++) { 285 var res = json.results[resi]; 286 if (res.corpus.id === corpusHit.corpus.id) { 287 corpusHit = res; 288 break; 289 } 290 } 291 } 292 this.setState({ hits: json, timeout: timeout, zoomedCorpusHit: corpusHit}); 270 293 }.bind(this), 271 294 }); … … 293 316 this.state.corpora.setVisibility(this.state.searchLayerId, 294 317 languageFilter === 'byGuess' ? multipleLanguageCode : languageObj[0]); 295 this.setState({language: languageObj, languageFilter: languageFilter}); 296 this.state.corpora.update(); 318 this.setState({ 319 language: languageObj, 320 languageFilter: languageFilter, 321 corpora: this.state.corpora, // === this.state.corpora.update(); 322 }); 297 323 }, 298 324 299 325 setLayer: function(layerId) { 300 326 this.state.corpora.setVisibility(layerId, this.state.language[0]); 301 this.state.corpora.update(); 302 this.setState({searchLayerId: layerId}); 327 this.setState({ 328 searchLayerId: layerId, 329 hits: this.nohits, 330 searchId: null, 331 corpora: this.state.corpora, // === this.state.corpora.update(); 332 }); 303 333 }, 304 334 … … 319 349 var noLangFiltering = this.state.languageFilter === 'byMeta'; 320 350 var langCode = this.state.language[0]; 321 return this.state.hits.results.map(function(corpusHit) { 322 return { 323 corpus: corpusHit.corpus, 324 startRecord: corpusHit.startRecord, 325 endRecord: corpusHit.endRecord, 326 exception: corpusHit.exception, 327 diagnostics: corpusHit.diagnostics, 328 searchString: corpusHit.searchString, 329 kwics: noLangFiltering ? corpusHit.kwics : 330 corpusHit.kwics.filter(function(kwic) { 331 return kwic.language === langCode || langCode === multipleLanguageCode || langCode === null; 332 }), 333 }; 334 }); 351 var results = null, inProgress = 0, hits = 0; 352 if (this.state.hits.results) { 353 results = this.state.hits.results.map(function(corpusHit) { 354 return { 355 corpus: corpusHit.corpus, 356 inProgress: corpusHit.inProgress, 357 exception: corpusHit.exception, 358 diagnostics: corpusHit.diagnostics, 359 kwics: noLangFiltering ? corpusHit.kwics : 360 corpusHit.kwics.filter(function(kwic) { 361 return kwic.language === langCode || 362 langCode === multipleLanguageCode || 363 langCode === null; 364 }), 365 }; 366 }); 367 for (var i = 0; i < results.length; i++) { 368 var result = results[i]; 369 if (result.inProgress) { 370 inProgress++; 371 } 372 if (result.kwics.length > 0) { 373 hits ++; 374 } 375 } 376 } 377 return { 378 results: results, 379 hits: hits, 380 inProgress: inProgress, 381 }; 335 382 }, 336 383 … … 354 401 }, 355 402 356 handleChange: function(event) {403 onQuery: function(event) { 357 404 this.setState({query: event.target.value}); 358 405 }, … … 377 424 }, 378 425 379 render Aggregator: function() {426 render: function() { 380 427 var layer = layerMap[this.state.searchLayerId]; 381 428 return ( … … 390 437 <input className="form-control input-lg search" name="query" type="text" 391 438 value={this.state.query} placeholder={this.props.placeholder} 392 tabIndex="1" onChange={this. handleChange} onKeyDown={this.handleKey} />439 tabIndex="1" onChange={this.onQuery} onKeyDown={this.handleKey} /> 393 440 <div className="input-group-btn"> 394 441 <button className="btn btn-default input-lg" type="button" onClick={this.search}> … … 467 514 <Modal ref="resultModal" title={this.renderZoomedResultTitle(this.state.zoomedCorpusHit)}> 468 515 <ZoomedResult corpusHit={this.state.zoomedCorpusHit} 516 nextResults={this.nextResults} 469 517 getDownloadLink={this.getDownloadLink} 470 518 getToWeblichtLink={this.getToWeblichtLink} … … 475 523 476 524 <div className="top-gap"> 477 <Results requests={this.state.hits.requests} 478 results={this.filterResults()} 525 <Results collhits={this.filterResults()} 479 526 toggleResultModal={this.toggleResultModal} 480 527 getDownloadLink={this.getDownloadLink} … … 485 532 ); 486 533 }, 487 render: function() {488 return this.renderAggregator();489 }490 534 }); 491 535 … … 622 666 }, 623 667 624 renderDiagnostic: function(d) { 625 return <div className="alert alert-warning" key={d.uri}> 668 renderDiagnostic: function(d, key) { 669 if (d.uri === NO_MORE_RECORDS_DIAGNOSTIC_URI) { 670 return false; 671 } 672 return <div className="alert alert-warning" key={key}> 626 673 <div>Diagnostic: {d.message}</div> 627 674 </div>; … … 632 679 return false; 633 680 } 634 635 681 return corpusHit.diagnostics.map(this.renderDiagnostic); 636 682 }, … … 714 760 <li> 715 761 {error ? 716 <div className="alert alert-danger" style={{margin:10 }}>{error}</div> :762 <div className="alert alert-danger" style={{margin:10, width:200}}>{error}</div> : 717 763 <a href={this.props.getToWeblichtLink(corpusId)} target="_blank">{" "} 718 764 Send to Weblicht</a> … … 729 775 propTypes: { 730 776 corpusHit: PT.object, 777 nextResults: PT.func.isRequired, 731 778 languageMap: PT.object.isRequired, 732 779 weblichtLanguages: PT.array.isRequired, … … 737 784 mixins: [ResultMixin], 738 785 786 getInitialState: function() { 787 return { 788 inProgress: false, 789 }; 790 }, 791 792 componentWillReceiveProps: function() { 793 this.setState({inProgress: false}); 794 }, 795 796 nextResults: function(e) { 797 this.setState({inProgress: true}); 798 this.props.nextResults(this.props.corpusHit.corpus.id); 799 }, 800 739 801 renderLanguages: function(languages) { 740 802 return languages … … 742 804 .sort() 743 805 .join(", "); 806 }, 807 808 renderMoreResults:function(){ 809 if (this.state.inProgress || this.props.corpusHit.inProgress) 810 return <span style={{fontStyle:'italic'}}>Retrieving results, please wait...</span>; 811 812 var moreResults = true; 813 for (var i = 0; i < this.props.corpusHit.diagnostics.length; i++) { 814 var d = this.props.corpusHit.diagnostics[i]; 815 if (d.uri === NO_MORE_RECORDS_DIAGNOSTIC_URI) { 816 moreResults = false; 817 break; 818 } 819 } 820 if (!moreResults) 821 return <span style={{fontStyle:'italic'}}>No other results available for this query</span>; 822 return <button className="btn btn-default" onClick={this.nextResults}> 823 <span className="glyphicon glyphicon-option-horizontal" aria-hidden="true"/> More Results 824 </button>; 744 825 }, 745 826 … … 777 858 778 859 <div style={{textAlign:'center', marginTop:10}}> 779 <button className="btn btn-default"> 780 <span className="glyphicon glyphicon-option-horizontal" aria-hidden="true"/> More Results 781 </button> 860 { this.renderMoreResults() } 782 861 </div> 783 862 … … 789 868 var Results = React.createClass({ 790 869 propTypes: { 791 requests: PT.array.isRequired, 792 results: PT.array.isRequired, 870 collhits: PT.object.isRequired, 793 871 searchedLanguage: PT.array.isRequired, 794 872 toggleResultModal: PT.func.isRequired, … … 825 903 }, 826 904 827 renderProgressMessage: function(hits) { 828 var total = this.props.requests.length + this.props.results.length; 829 var msg = hits + " matching collections found in " + this.props.results.length + " searched collections"; 830 var percents = Math.round(100 * this.props.results.length / total); 905 renderProgressMessage: function() { 906 var collhits = this.props.collhits; 907 var done = collhits.results.length - collhits.inProgress; 908 var msg = collhits.hits + " matching collections found in " + done + " searched collections"; 909 var percents = Math.round(100 * collhits.hits / collhits.results.length); 831 910 var styleperc = {width: percents+"%"}; 832 911 return <div style={{marginTop:10}}> 833 912 <div>{msg}</div> 834 { this.props.requests.length> 0 ?913 {collhits.inProgress > 0 ? 835 914 <div className="progress" style={{marginBottom:10}}> 836 915 <div className="progress-bar progress-bar-striped active" role="progressbar" … … 847 926 848 927 render: function() { 849 if (this.props.results.length + this.props.requests.length === 0) { 928 var collhits = this.props.collhits; 929 if (!collhits.results) { 850 930 return false; 851 931 } 852 var hits = this.props.results.filter(function(corpusHit) { return corpusHit.kwics.length > 0; }).length; 853 var showprogress = this.props.requests.length > 0; 932 var showprogress = collhits.inProgress > 0; 854 933 return <div> 855 934 <ReactCSSTransitionGroup transitionName="fade"> 856 { showprogress ? this.renderProgressMessage( hits) : <div style={{height:20}} />}935 { showprogress ? this.renderProgressMessage() : <div style={{height:20}} />} 857 936 <div style={{marginBottom:2}}> 858 937 { showprogress ? false : 859 <div className="float-left"> { hits + " matching collections found"} </div>938 <div className="float-left"> {collhits.hits + " matching collections found"} </div> 860 939 } 861 { hits === 0 ? false :940 { collhits.hits === 0 ? false : 862 941 <div className="float-right"> 863 942 <div> 864 943 { this.renderDisplayKWIC() } 865 { this.props.requests.length=== 0 ?944 { collhits.inProgress === 0 ? 866 945 <div className="inline"> {this.renderDownloadLinks()} </div> 867 946 :false … … 872 951 <div style={{clear:'both'}}/> 873 952 </div> 874 { this.props.results.map(this.renderResultPanel)}953 {collhits.results.map(this.renderResultPanel)} 875 954 </ReactCSSTransitionGroup> 876 955 </div>;
Note: See TracChangeset
for help on using the changeset viewer.