diff --git a/.travis.yml b/.travis.yml index 7263308f..5d8c04a8 100644 --- a/.travis.yml +++ b/.travis.yml @@ -7,6 +7,7 @@ branches: only: - master - develop + - hotfix - 1.4.2.1 before_cache: - rm -f $HOME/.gradle/caches/modules-2/modules-2.lock diff --git a/build.gradle b/build.gradle index cac18797..6a548b5d 100644 --- a/build.gradle +++ b/build.gradle @@ -11,7 +11,7 @@ buildscript { } } -version "1.4.13" +version "1.5" group "au.org.ala" apply plugin:"eclipse" @@ -60,15 +60,12 @@ dependencies { } compile 'net.sf.opencsv:opencsv:2.3' - compile "org.apache.solr:solr-solrj:6.6.5" + compile "org.apache.solr:solr-solrj:8.1.0" compile("org.gbif:dwca-io:1.24") { exclude group: 'com.google.guava', module: 'guava' } compile "com.google.guava:guava:19.0" - compile("au.org.ala:ala-name-matching:3.4") { - exclude group: 'org.slf4j', module: 'slf4j-log4j12' - exclude group: 'org.apache.bval', module: 'org.apache.bval.bundle' - } + compile group: "au.org.ala", name: "ala-name-matching-model", version:"4.0" compile "org.jsoup:jsoup:1.8.3" compile 'org.grails.plugins:external-config:1.1.1' diff --git a/grails-app/conf/application.yml b/grails-app/conf/application.yml index 40ee0a1f..f596c03d 100644 --- a/grails-app/conf/application.yml +++ b/grails-app/conf/application.yml @@ -205,16 +205,19 @@ solr: connection: http://localhost:8983/solr/bie queueSize: 10 threadCount: 4 + timeout: 120000 updatingLive: type: UPDATE connection: http://localhost:8983/solr/bie queueSize: 10 threadCount: 4 + timeout: 300000 offline: type: UPDATE connection: http://localhost:8983/solr/bie-offline queueSize: 10 threadCount: 4 + timeout: 300000 search: qf: - exact_text diff --git a/grails-app/conf/spring/resources.groovy b/grails-app/conf/spring/resources.groovy index e9dbd588..46ad521b 100755 --- a/grails-app/conf/spring/resources.groovy +++ b/grails-app/conf/spring/resources.groovy @@ -7,19 +7,22 @@ beans = { application.config.solr.live.type, application.config.solr.live.connection, application.config.solr.live.queueSize as Integer, - application.config.solr.live.threadCount as Integer + application.config.solr.live.threadCount as Integer, + application.config.solr.live.timeout as Integer ) offlineSolrClient(SolrClientBean, application.config.solr.offline.type, application.config.solr.offline.connection, application.config.solr.offline.queueSize as Integer, - application.config.solr.offline.threadCount as Integer + application.config.solr.offline.threadCount as Integer, + application.config.solr.offline.timeout as Integer ) updatingLiveSolrClient(SolrClientBean, application.config.solr.updatingLive.type, application.config.solr.updatingLive.connection, application.config.solr.updatingLive.queueSize as Integer, - application.config.solr.updatingLive.threadCount as Integer + application.config.solr.updatingLive.threadCount as Integer, + application.config.solr.updatingLive.timeout as Integer ) conservationListsSource(ConservationListsSource, application.config.conservationListsUrl) } diff --git a/grails-app/controllers/au/org/ala/bie/SearchController.groovy b/grails-app/controllers/au/org/ala/bie/SearchController.groovy index 808511df..d9b85e12 100755 --- a/grails-app/controllers/au/org/ala/bie/SearchController.groovy +++ b/grails-app/controllers/au/org/ala/bie/SearchController.groovy @@ -188,13 +188,13 @@ class SearchController implements GrailsConfigurationAware { if (!req) { response.sendError(400, "Body could not be parsed or was empty") } - boolean includeVernacular = req.optBoolean('vernacular') + boolean includeVernacular = req.optBoolean('vernacular', true) List names = req['names'] List result = [] names.eachWithIndex { name, idx -> log.debug "$idx. Looking up name: ${name}" - result.add(searchService.getLongProfileForName(name)) + result.add(searchService.getLongProfileForName(name, includeVernacular)) } render result as JSON diff --git a/grails-app/init/bie/index/Application.groovy b/grails-app/init/bie/index/Application.groovy index d8c73c30..9aea7931 100644 --- a/grails-app/init/bie/index/Application.groovy +++ b/grails-app/init/bie/index/Application.groovy @@ -2,7 +2,10 @@ package bie.index import grails.boot.GrailsApp import grails.boot.config.GrailsAutoConfiguration +import org.springframework.boot.autoconfigure.EnableAutoConfiguration +import org.springframework.boot.autoconfigure.solr.SolrAutoConfiguration +@EnableAutoConfiguration(exclude = [SolrAutoConfiguration]) class Application extends GrailsAutoConfiguration { static void main(String[] args) { GrailsApp.run(Application, args) diff --git a/grails-app/services/au/org/ala/bie/ImportService.groovy b/grails-app/services/au/org/ala/bie/ImportService.groovy index a64a36d9..40e0a86a 100644 --- a/grails-app/services/au/org/ala/bie/ImportService.groovy +++ b/grails-app/services/au/org/ala/bie/ImportService.groovy @@ -48,6 +48,7 @@ import java.text.SimpleDateFormat import java.util.concurrent.ConcurrentLinkedQueue import java.util.concurrent.ExecutorService import java.util.concurrent.Executors +import java.util.regex.Pattern import java.util.zip.GZIPInputStream /** @@ -106,7 +107,8 @@ class ImportService implements GrailsConfigurationAware { static ACCEPTED_STATUS = TaxonomicType.values().findAll({ it.accepted }).collect({ "taxonomicStatus:${it.term}" }).join(' OR ') // Synonym status static SYNONYM_STATUS = TaxonomicType.values().findAll({ it.synonym }).collect({ "taxonomicStatus:${it.term}" }).join(' OR ') - + // A pattern indicating that we have a URL embedded in an anchor. Yuk + static SOURCE_IN_ANCHOR = Pattern.compile(/<[Aa] [^>]*[Hh][Rr][Ee][Ff]\s*=\s*"([^"]+)"[^>]*>.*<\/[Aa]>/) def indexService, searchService, biocacheService def listService, layerService, collectoryService, wordpressService, knowledgeBaseService @@ -947,7 +949,11 @@ class ImportService implements GrailsConfigurationAware { private boolean addVernacularName(String taxonID, String name, String kingdom, String vernacularName, String nameId, Object status, String language, String source, String datasetID, String taxonRemarks, String provenance, Map additional, List buffer, Object defaultStatus) { def taxonDoc = null - + if (source) { // Extract URL from anchor if needed + def sia = SOURCE_IN_ANCHOR.matcher(source) + if (sia.matches()) + source = sia.group(1) + } if (taxonID) taxonDoc = searchService.lookupTaxon(taxonID, true) if (!taxonDoc && name) @@ -2129,17 +2135,16 @@ class ImportService implements GrailsConfigurationAware { def variants = searchService.lookupVariant(guid, !online) if (variants) { priority = variants.collect({ (double) it.priority ?: weightNorm }).max() - double boost = Math.min(weightMax, Math.max(weightMin, priority)) def names = (variants.collect { it.scientificName }) as Set names.addAll(variants.collect { it.nameComplete }) names.remove(null) names.remove(scientificName) names.remove(nameComplete) if (names) - update["nameVariant"] = [boost: boost, set: names] - update["scientificName"] = [boost: boost, set: scientificName] + update["nameVariant"] = [set: names] + update["scientificName"] = [set: scientificName] if (nameComplete) - update["nameComplete"] = [boost: boost, set: nameComplete] + update["nameComplete"] = [set: nameComplete] } update['priority'] = [set: (int) Math.round(priority)] def commonNames = searchService.lookupVernacular(guid, !online) diff --git a/grails-app/services/au/org/ala/bie/IndexService.groovy b/grails-app/services/au/org/ala/bie/IndexService.groovy index 72e932e2..8b6beac2 100644 --- a/grails-app/services/au/org/ala/bie/IndexService.groovy +++ b/grails-app/services/au/org/ala/bie/IndexService.groovy @@ -20,7 +20,6 @@ import org.apache.solr.common.params.SolrParams * The interface to SOLR based logic. */ class IndexService implements GrailsConfigurationAware { - def grailsApplication def liveSolrClient def offlineSolrClient def updatingLiveSolrClient @@ -30,18 +29,21 @@ class IndexService implements GrailsConfigurationAware { SolrQuery suggestTemplate List searchFq String searchDefType + String searchBoost List searchQf @Override void setConfiguration(Config config) { def search = config.solr.search + searchBoost = search.boost + searchDefType = search.defType searchTemplate = new SolrQuery() search.fq.each { searchTemplate.addFilterQuery(it) } - searchTemplate.set('defType', search.defType) + searchTemplate.set('defType', searchDefType) searchTemplate.set('qf', search.qf.join(' ')) search.bq.each { searchTemplate.add('bq', it) } searchTemplate.set('q.alt', search.qAlt) - searchTemplate.set('boost', search.boost) + searchTemplate.set('boost', searchBoost) if (search.hl.hl as Boolean) { searchTemplate.highlightFields = (search.hl.fl as String).split(',') searchTemplate.highlightSimplePre = search.hl.simple.pre @@ -84,17 +86,12 @@ class IndexService implements GrailsConfigurationAware { docsToIndex.each { map -> def solrDoc = new SolrInputDocument() map.each{ fieldName, fieldValue -> - def boost = 1.0f - if (fieldValue && Map.class.isAssignableFrom(fieldValue.getClass()) && fieldValue["boost"]) { - boost = fieldValue.boost - fieldValue.remove("boost") - } if(isList(fieldValue)){ fieldValue.each { - solrDoc.addField(fieldName, it, (float) boost) + solrDoc.addField(fieldName, it) } } else { - solrDoc.addField(fieldName, fieldValue, (float) boost) + solrDoc.addField(fieldName, fieldValue) } } buffer << solrDoc @@ -169,25 +166,38 @@ class IndexService implements GrailsConfigurationAware { * @param sort sort field * @param dir Sort direction * @param cursor Cursor mark if paginating + * @param useBoost Use the search boost functionality + * @param useDefType Use the default defType * * @return The query result */ - QueryResponse query(boolean online, String q, List fq = [], Integer rows = 10, Integer start = 0, String context = null, String sort = null, String dir = 'asc', String cursor = null) { + QueryResponse query(boolean online, String q, List fq = [], Integer rows = 10, Integer start = 0, String context = null, String sort = null, String dir = 'asc', String cursor = null, boolean useBoost = false, boolean useDefType = false) { def query = new SolrQuery(q) - if (context) + if (context) { query.add(context(context)) - if (fq) + } + if (fq) { fq.each { query.addFilterQuery(it) } - if (rows) + } + if (rows) { query.setRows(rows) - if (start) + } + if (start) { query.setStart(start) + } if (sort) { query.sort = new SolrQuery.SortClause(sort, dir == 'desc' ? SolrQuery.ORDER.desc : SolrQuery.ORDER.asc) } - if (cursor) + if (cursor) { query.set(CursorMarkParams.CURSOR_MARK_PARAM, cursor) + } + if (useBoost) { + query.set('boost', searchBoost) + } + if (useDefType) { + query.set("defType", searchDefType) + } return this.query(query, online) } diff --git a/grails-app/services/au/org/ala/bie/SearchService.groovy b/grails-app/services/au/org/ala/bie/SearchService.groovy index c6446878..dbb6f978 100644 --- a/grails-app/services/au/org/ala/bie/SearchService.groovy +++ b/grails-app/services/au/org/ala/bie/SearchService.groovy @@ -284,34 +284,40 @@ class SearchService { } } + private queryChildConcepts(q, fqs, queryString, children) { + def response = indexService.query(true, q, fqs, 1000, 0, queryString) + def taxa = response.results + taxa.each { taxon -> + children << [ + guid : taxon.guid, + parentGuid : taxon.parentGuid, + name : taxon.scientificName, + nameComplete : taxon.nameComplete ?: taxon.scientificName, + nameFormatted: taxon.nameFormatted, + author : taxon.scientificNameAuthorship, + rank : taxon.rank, + rankID : taxon.rankID + ] + } + } def getChildConcepts(taxonID, queryString, within, unranked){ def baseTaxon = lookupTaxon(taxonID) def baseRankID = baseTaxon?.rankID ?: -1 def baseFq = "idxtype:${IndexDocType.TAXON.name()}" def fqs = [ baseFq ] - if (baseRankID > 0 && within) { - fqs << "rankID:[${unranked ? -1 : baseRankID + 1} TO ${baseRankID + within}]" - } def q = "parentGuid:\"${ Encoder.escapeSolr(taxonID) }\"" - def response = indexService.query(true, q, fqs, 1000, 0, queryString) - if (response.results.numFound == 0) { - response = indexService.query(true, q, [ baseFq ], 1000, 0, queryString) - } def children = [] - def taxa = response.results - taxa.each { taxon -> - children << [ - guid:taxon.guid, - parentGuid: taxon.parentGuid, - name: taxon.scientificName, - nameComplete: taxon.nameComplete ?: taxon.scientificName, - nameFormatted: taxon.nameFormatted, - author: taxon.scientificNameAuthorship, - rank: taxon.rank, - rankID:taxon.rankID - ] + + if (baseRankID > 0 && within) { + fqs << "rankID:[${unranked ? -1 : baseRankID + 1} TO ${baseRankID + within}]" } + + this.queryChildConcepts(q, fqs, queryString, children) + if (unranked && baseRankID > 0 && within) // Long running bug in SOLR unable to handle (- +) subqueries + this.queryChildConcepts(q, [ baseFq, "-rankID:*"], queryString, children) + if (children.isEmpty() && fqs.size() > 1) + this.queryChildConcepts(q, [ baseFq ], queryString, children) children.sort { c1, c2 -> def r1 = c1.rankID def r2 = c2.rankID @@ -353,10 +359,22 @@ class SearchService { */ def lookupTaxonByName(String taxonName, String kingdom, Boolean useOfflineIndex = false){ taxonName = Encoder.escapeSolr(taxonName) - def q = "+commonNameExact:\"${taxonName}\" OR +scientificName:\"${taxonName}\" OR +nameComplete:\"${taxonName} OR +exact_text:\"${taxonName}\"" + def q = "scientificName:\"${taxonName}\" OR nameComplete:\"${taxonName}\" OR commonName:\"${taxonName}\"" if (kingdom) q = "(${q}) AND rk_kingdom:\"${ Encoder.escapeSolr(kingdom) }\"" - def response = indexService.search(!useOfflineIndex, q, [ "idxtype:${ IndexDocType.TAXON.name() }" ], [], 0, 1) + def response = indexService.query( + !useOfflineIndex, + q, + [ "idxtype:${IndexDocType.TAXON.name()}" ], + 1, + 0, + null, + null, + null, + null, + true, + true + ) if (response.results.isEmpty()) { q = "+scientificName:\"${taxonName}\" OR +nameComplete:\"${taxonName}\"" response = indexService.query(!useOfflineIndex, q, [ "idxtype:${ IndexDocType.TAXONVARIANT.name() }" ], 1, 0) @@ -463,7 +481,20 @@ class SearchService { */ def getProfileForName(String name){ name = Encoder.escapeSolr(name) - def response = indexService.search(true, '"' + name + '"', [ "idxtype:${IndexDocType.TAXON.name()}" ]) + def query = "scientificName:\"${name}\" OR nameComplete:\"${name}\" OR commonName:\"${name}\"" + def response = indexService.query( + true, + query, + [ "idxtype:${IndexDocType.TAXON.name()}" ], + 10, + 0, + null, + null, + null, + null, + true, + true + ) def model = [] if (response.results.numFound > 0) { @@ -480,9 +511,24 @@ class SearchService { model } - Map getLongProfileForName(String name){ + Map getLongProfileForName(String name, boolean includeVernacular){ name = Encoder.escapeSolr(name) - def response = indexService.search(true, '"' + name + '"', [ "idxtype:${IndexDocType.TAXON.name()}" ]) + def query = "scientificName:\"${name}\" OR nameComplete:\"${name}\"" + if (includeVernacular) + query = query + " OR commonName:\"${name}\"" + def response = indexService.query( + true, + query, + [ "idxtype:${IndexDocType.TAXON.name()}" ], + 1, + 0, + null, + null, + null, + null, + true, + true + ) def model = [:] if (response.results.numFound > 0) { def result = response.results.get(0) @@ -517,7 +563,6 @@ class SearchService { ] } - model } diff --git a/src/main/groovy/au/org/ala/bie/solr/SolrClientBean.groovy b/src/main/groovy/au/org/ala/bie/solr/SolrClientBean.groovy index daf933b4..f4ec0b60 100644 --- a/src/main/groovy/au/org/ala/bie/solr/SolrClientBean.groovy +++ b/src/main/groovy/au/org/ala/bie/solr/SolrClientBean.groovy @@ -1,6 +1,6 @@ package au.org.ala.bie.solr -import grails.util.CacheEntry + import org.apache.solr.client.solrj.SolrClient import org.apache.solr.client.solrj.impl.CloudSolrClient import org.apache.solr.client.solrj.impl.ConcurrentUpdateSolrClient @@ -29,6 +29,8 @@ class SolrClientBean { int queueSize = 10 /** The number of threads for updatable clients */ int threadCount = 4 + /** The socket timeout in milliseconds */ + int timeout = 300000; /** * Default constructor @@ -44,11 +46,12 @@ class SolrClientBean { * @param queueSize The queue size * @param threadCount The thread count */ - SolrClientBean(String clientType, String connection, int queueSize, int threadCount) { + SolrClientBean(String clientType, String connection, int queueSize, int threadCount, int timeout) { this.clientType = clientType this.connection = connection this.queueSize = queueSize this.threadCount = threadCount + this.timeout = timeout } /** @@ -57,21 +60,29 @@ class SolrClientBean { * @return */ SolrClient buildClient() { - def builder + def client = null switch (clientType) { case ClientType.UPDATE: - builder = new ConcurrentUpdateSolrClient.Builder(connection) + def builder = new ConcurrentUpdateSolrClient.Builder(connection) builder.withQueueSize(queueSize) builder.withThreadCount(threadCount) - break; + builder.withSocketTimeout(timeout) + client = builder.build() + // Required to get read timeout to be set + client.client.setSoTimeout(timeout) + break case ClientType.ZOOKEEPER: - builder = new CloudSolrClient.Builder() - builder.withZkHost(connection) - break; + def builder = new CloudSolrClient.Builder(connection.split(',') as List) + builder.withSocketTimeout(timeout) + client = builder.build(); + break default: - builder = new HttpSolrClient.Builder(connection) + def builder = new HttpSolrClient.Builder(connection) + builder.withSocketTimeout(timeout) + client = builder.build(); + break } - return builder.build() + return client } void setClientType(String name) { diff --git a/src/main/resources/public/openapi.yml b/src/main/resources/public/openapi.yml index 535fd28d..655e2651 100644 --- a/src/main/resources/public/openapi.yml +++ b/src/main/resources/public/openapi.yml @@ -3,7 +3,6 @@ info: title: "ALA BIE service" description: "Web services for the Biological Information Explorer (BIE)" version: "1.0" - termsOfServices: "http://www.ala.org.au/" contact: name: "ALA Support" url: "http://www.ala.org.au/" @@ -48,6 +47,7 @@ paths: required: false responses: 200: + description: 'Search results' content: application/json: schema: @@ -86,6 +86,7 @@ paths: required: false responses: 200: + description: 'Search results' content: application/json: schema: @@ -127,6 +128,100 @@ paths: /taxon/{id}: get: summary: Alternative form of /species/{id} + parameters: + - in: path + name: id + schema: + type: string + required: true + description: Numeric ID of the taxon + responses: + 404: + description: The identifier was not found + 200: + description: 'Species info in JSON' + content: + application/json: + schema: + type: object + properties: + taxonConcept: + description: Information about the base taxon concept + $ref: '#/components/schemas/TaxonConcept' + taxonName: + type: array + deprecated: true + description: Unused + items: + type: string + classification: + type: object + description: > + The higher-order classification for the base taxon concept. + Each higher-order rank has a name, given by the rank and a GUID, given by rankGuid. + eg. kingdom: 'Plantae', kingdomGuid: 'http://id.biodiversity.org.au/node/apni/9443092' + additionalProperties: + type: string + synonyms: + type: array + description: All synonyms that have this taxon as an accepted concept + items: + $ref: '#/components/schemas/TaxonConcept' + commonNames: + type: array + description: All the common names that a taxon is known by + items: + $ref: '#/components/schemas/CommonName' + imageIdentifier: + type: string + format: url + description: The identifier of a representative image + conservationStatuses: + type: object + description: Conservation status, keyed by the source of the conservation designation, eg. Qld + additionalProperties: + type: object + properties: + dr: + type: string + description: The data resource identifier for the list that is the source of the status + status: + type: string + description: The status term + extantStatuses: + type: array + deprecated: true + description: Unused + items: + type: string + habitats: + type: array + deprecated: true + description: Unused + items: + type: string + simpleProperties: + type: array + deprecated: true + description: Unused + items: + type: string + images: + type: array + deprecated: true + description: Unused + items: + type: string + identifiers: + type: array + description: All the identifiers that can be used to refer to this taxon + items: + $ref: '#/components/schemas/Identifier' + variants: + type: array + description: Alternative sources for the base taxon concept (including itself) + items: + $ref: '#/components/schemas/TaxonConcept' tags: - lookup /species/{id}: @@ -141,6 +236,7 @@ paths: 404: description: The identifier was not found 200: + description: 'Species info in JSON' content: application/json: schema: @@ -154,7 +250,7 @@ paths: deprecated: true description: Unused items: - type: null + type: string classification: type: object description: > @@ -194,25 +290,25 @@ paths: deprecated: true description: Unused items: - type: null + type: string habitats: type: array deprecated: true description: Unused items: - type: null + type: string simpleProperties: type: array deprecated: true description: Unused items: - type: null + type: string images: type: array deprecated: true description: Unused items: - type: null + type: string identifiers: type: array description: All the identifiers that can be used to refer to this taxon @@ -235,6 +331,7 @@ paths: 404: description: The identifier was not found 200: + description: 'Classification info in JSON' content: application/json: schema: @@ -277,6 +374,7 @@ paths: 404: description: The identifier was not found 200: + description: 'Child concepts list in JSON' content: application/json: schema: @@ -325,6 +423,7 @@ paths: type: string responses: 200: + description: 'Image seearch list in JSON' content: application/json: schema: @@ -347,6 +446,7 @@ paths: 404: description: If the name is not found 200: + description: 'GUID name lookup results in JSON' content: application/json: schema: @@ -388,6 +488,7 @@ paths: 404: description: If there is no image for the supplied id and showNoImage=false 302: + description: Redirect to image location headers: Location: description: The location of the image @@ -406,6 +507,7 @@ paths: 404: description: If the taxon identifier is not found 200: + description: Short profile for taxon content: application/json: schema: @@ -429,6 +531,7 @@ paths: type: string responses: 200: + description: List of short profile for taxa content: application/json: schema: @@ -458,6 +561,7 @@ paths: 404: description: If a guid is not found 200: + description: List of short profile for taxa content: application/json: schema: @@ -519,7 +623,8 @@ paths: summary: Bulk search for species by name description: > Retrieve taxon information for a list of vernacular or scientific names. - This operation can be used to retrieve large lists of taxa + This operation can be used to retrieve large lists of taxa. + By default, the operation searches for both vernacular names and scientific names. tags: - bulk requestBody: @@ -531,7 +636,7 @@ paths: vernacular: type: boolean description: Look up via vernacular names as well as scientific names - default: false + default: true names: type: array description: The list of names to search for @@ -541,6 +646,7 @@ paths: 400: description: On an invalid request 200: + description: List of long profiles for taxa content: application/json: schema: @@ -559,15 +665,15 @@ paths: - name: fields description: A list of fields to download in: query - style: simple + style: form schema: type: array items: type: string required: false - default: guid,rank,scientificName,rk_genus,rk_family,rk_order,rk_class,rk_phylum,rk_kingdom,datasetName responses: 200: + description: Zipped file containing CSV content: text/csv: schema: @@ -583,6 +689,7 @@ paths: 404: description: If the species group configuration is not found 200: + description: JSON for species groups and subgroups content: application/json: schema: @@ -620,6 +727,7 @@ paths: 500: description: If unable to connect to the solr index 200: + description: List of indexed fields in JSON content: application/json: schema: @@ -637,7 +745,7 @@ paths: type: string description: The name of the field numberDistinctValues: - type: null + type: integer description: Always null stored: type: boolean @@ -650,6 +758,7 @@ paths: - metadata responses: 200: + description: A list of ranks in JSON content: application/json: schema: @@ -694,6 +803,7 @@ paths: 500: description: If unable to connect to the solr index 200: + description: A list of habitats in JSON content: application/json: schema: @@ -702,7 +812,7 @@ paths: searchResults: type: object items: - type: null + type: string /habitats/tree: get: summary: Get a tree of habitats @@ -714,6 +824,7 @@ paths: 500: description: If unable to connect to the solr index 200: + description: A tree structure of habitats in JSON content: application/json: schema: @@ -721,17 +832,20 @@ paths: properties: searchResults: type: object - /habitat/{guid}: + /habitat/{id}: get: summary: Get a habitat description description: Gets a description of a habitat. Currently, this will just return an empty value deprecated: true tags: - metadata + parameters: + - $ref: '#/components/parameters/idParam' responses: 500: description: If unable to connect to the solr index 200: + description: A habitat description in JSON content: application/json: schema: @@ -740,18 +854,21 @@ paths: searchResults: type: object items: - type: null - /habitat/ids/{guid}: + type: string + /habitat/ids/{id}: get: summary: Get a the identifiers associated with a habitat description: Gets a set of habitat ids. Currently, this will just return an empty value deprecated: true tags: - metadata + parameters: + - $ref: '#/components/parameters/idParam' responses: 500: description: If unable to connect to the solr index 200: + description: A habitat description in JSON content: application/json: schema: @@ -760,7 +877,7 @@ paths: searchResults: type: object items: - type: null + type: string /admin/import/importAll: get: summary: Import all features @@ -775,6 +892,7 @@ paths: - cas: [] responses: 200: + description: JSON response indicating job status content: application/json: schema: @@ -789,6 +907,7 @@ paths: - apiKey: [] responses: 200: + description: JSON response indicating job status content: application/json: schema: @@ -811,6 +930,7 @@ paths: - cas: [] responses: 200: + description: JSON response indicating job status content: application/json: schema: @@ -826,6 +946,7 @@ paths: - cas: [] responses: 200: + description: JSON response indicating job status content: application/json: schema: @@ -843,6 +964,7 @@ paths: - cas: [] responses: 200: + description: JSON response indicating job status content: application/json: schema: @@ -860,6 +982,7 @@ paths: - cas: [] responses: 200: + description: JSON response indicating job status content: application/json: schema: @@ -877,6 +1000,7 @@ paths: - cas: [] responses: 200: + description: JSON response indicating job status content: application/json: schema: @@ -894,6 +1018,7 @@ paths: - cas: [] responses: 200: + description: JSON response indicating job status content: application/json: schema: @@ -909,6 +1034,7 @@ paths: - cas: [] responses: 200: + description: JSON response indicating job status content: application/json: schema: @@ -926,6 +1052,7 @@ paths: - cas: [] responses: 200: + description: JSON response indicating job status content: application/json: schema: @@ -943,6 +1070,7 @@ paths: - cas: [] responses: 200: + description: JSON response indicating job status content: application/json: schema: @@ -960,6 +1088,7 @@ paths: - cas: [] responses: 200: + description: JSON response indicating job status content: application/json: schema: @@ -977,6 +1106,7 @@ paths: - cas: [] responses: 200: + description: JSON response indicating job status content: application/json: schema: @@ -994,6 +1124,7 @@ paths: - cas: [] responses: 200: + description: JSON response indicating job status content: application/json: schema: @@ -1014,6 +1145,7 @@ paths: - cas: [] responses: 200: + description: JSON response indicating job status content: application/json: schema: @@ -1031,6 +1163,7 @@ paths: - cas: [] responses: 200: + description: JSON response indicating job status content: application/json: schema: @@ -1048,6 +1181,7 @@ paths: - cas: [] responses: 200: + description: JSON response indicating job status content: application/json: schema: @@ -1066,6 +1200,7 @@ paths: - cas: [] responses: 200: + description: JSON response indicating job status content: application/json: schema: @@ -1085,6 +1220,7 @@ paths: - cas: [] responses: 200: + description: JSON response indicating job status content: application/json: schema: @@ -1102,6 +1238,7 @@ paths: - cas: [] responses: 200: + description: JSON response indicating job status content: application/json: schema: @@ -1119,6 +1256,7 @@ paths: - cas: [] responses: 200: + description: JSON response indicating job status content: application/json: schema: @@ -1139,13 +1277,13 @@ paths: - name: clear_index in: query description: Clear the offline index before loading - default: false schema: type: boolean security: - cas: [] responses: 200: + description: JSON response indicating job status content: application/json: schema: @@ -1162,6 +1300,7 @@ paths: - cas: [] responses: 200: + description: JSON response indicating job status content: application/json: schema: @@ -1175,16 +1314,13 @@ paths: tags: - admin parameters: - - name: id - description: The job identifier - schema: - type: string - format: uuid + - $ref: '#/components/parameters/idParam' - $ref: '#/components/parameters/formatParam' security: - cas: [] responses: 200: + description: JSON response indicating job status content: application/json: schema: @@ -1198,16 +1334,13 @@ paths: tags: - admin parameters: - - name: id - description: The job identifier - schema: - type: string - format: uuid + - $ref: '#/components/parameters/idParam' - $ref: '#/components/parameters/formatParam' security: - cas: [] responses: 200: + description: JSON response indicating job status content: application/json: schema: @@ -1221,16 +1354,13 @@ paths: tags: - admin parameters: - - name: id - description: The job identifier - schema: - type: string - format: uuid + - $ref: '#/components/parameters/idParam' - $ref: '#/components/parameters/formatParam' security: - cas: [] responses: 200: + description: JSON response indicating job status content: application/json: schema: @@ -1303,7 +1433,6 @@ components: properties: id: type: string - style: uuid description: An internal, index-specific identifier. Best ignored guid: type: string @@ -1495,12 +1624,10 @@ components: type: string format: url description: A link to a representative thumnail image, if available - required: false imageUrl: type: string format: url description: A link to a representative image, if available - required: false LongProfile: type: object description: A description of essential information about a taxon @@ -1893,7 +2020,7 @@ components: name: format in: path description: Optional response format - required: false + required: true schema: type: string enum: @@ -1933,7 +2060,7 @@ components: type: array items: type: string - style: simple + style: form idParam: name: 'id' in: path diff --git a/src/test/groovy/au/org/ala/bie/SearchControllerSpec.groovy b/src/test/groovy/au/org/ala/bie/SearchControllerSpec.groovy index 05cbe7db..46f5d0a3 100755 --- a/src/test/groovy/au/org/ala/bie/SearchControllerSpec.groovy +++ b/src/test/groovy/au/org/ala/bie/SearchControllerSpec.groovy @@ -98,8 +98,8 @@ class SearchControllerSpec extends Specification { // String guid, filter, start, limit, field, order, exact, includeVernacular -> // return new SearchResultsDTO(totalRecords: result.size(), searchResults: [result[guid]]) // } - names.size() * searchService.getLongProfileForName(_) >> { - String name -> + names.size() * searchService.getLongProfileForName(_, _) >> { + String name, boolean vernacular -> return [totalRecords: result.size(), searchResults: [result[name]]] } diff --git a/src/test/groovy/au/org/ala/bie/solr/SolrClientBeanSpec.groovy b/src/test/groovy/au/org/ala/bie/solr/SolrClientBeanSpec.groovy index 7fae7c46..e3be3efa 100644 --- a/src/test/groovy/au/org/ala/bie/solr/SolrClientBeanSpec.groovy +++ b/src/test/groovy/au/org/ala/bie/solr/SolrClientBeanSpec.groovy @@ -3,6 +3,7 @@ package au.org.ala.bie.solr import org.apache.solr.client.solrj.impl.CloudSolrClient import org.apache.solr.client.solrj.impl.ConcurrentUpdateSolrClient import org.apache.solr.client.solrj.impl.HttpSolrClient +import spock.lang.Ignore import spock.lang.Specification /** @@ -42,11 +43,12 @@ class SolrClientBeanSpec extends Specification { client instanceof ConcurrentUpdateSolrClient } + @Ignore // Until we get a zookeeper setup def "create zookeeper client"() { given: def bean = new SolrClientBean() bean.clientType = 'ZOOKEEPER' - bean.connection = 'localhost:10228' + bean.connection = 'http://localhost:10228' when: def client = bean.client